From 293913568e6a7a86fd1479e1cff8e2ecb58d6568 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 15:44:03 +0200 Subject: Adding upstream version 16.2. Signed-off-by: Daniel Baumann --- contrib/Makefile | 97 + contrib/README | 28 + contrib/adminpack/.gitignore | 4 + contrib/adminpack/Makefile | 24 + contrib/adminpack/adminpack--1.0--1.1.sql | 6 + contrib/adminpack/adminpack--1.0.sql | 53 + contrib/adminpack/adminpack--1.1--2.0.sql | 51 + contrib/adminpack/adminpack--2.0--2.1.sql | 17 + contrib/adminpack/adminpack.c | 591 + contrib/adminpack/adminpack.control | 6 + contrib/adminpack/expected/adminpack.out | 172 + contrib/adminpack/meson.build | 35 + contrib/adminpack/sql/adminpack.sql | 76 + contrib/amcheck/.gitignore | 4 + contrib/amcheck/Makefile | 27 + contrib/amcheck/amcheck--1.0--1.1.sql | 29 + contrib/amcheck/amcheck--1.0.sql | 24 + contrib/amcheck/amcheck--1.1--1.2.sql | 19 + contrib/amcheck/amcheck--1.2--1.3.sql | 30 + contrib/amcheck/amcheck.control | 5 + contrib/amcheck/expected/check.out | 1 + contrib/amcheck/expected/check_btree.out | 210 + contrib/amcheck/expected/check_heap.out | 240 + contrib/amcheck/meson.build | 48 + contrib/amcheck/sql/check.sql | 1 + contrib/amcheck/sql/check_btree.sql | 146 + contrib/amcheck/sql/check_heap.sql | 147 + contrib/amcheck/t/001_verify_heapam.pl | 286 + contrib/amcheck/t/002_cic.pl | 64 + contrib/amcheck/t/003_cic_2pc.pl | 170 + contrib/amcheck/t/005_pitr.pl | 82 + contrib/amcheck/verify_heapam.c | 2082 ++++ contrib/amcheck/verify_nbtree.c | 3347 ++++++ contrib/auth_delay/Makefile | 15 + contrib/auth_delay/auth_delay.c | 74 + contrib/auth_delay/meson.build | 17 + contrib/auto_explain/.gitignore | 4 + contrib/auto_explain/Makefile | 20 + contrib/auto_explain/auto_explain.c | 442 + contrib/auto_explain/meson.build | 28 + contrib/auto_explain/t/001_auto_explain.pl | 215 + contrib/basebackup_to_shell/.gitignore | 4 + contrib/basebackup_to_shell/Makefile | 24 + contrib/basebackup_to_shell/basebackup_to_shell.c | 373 + contrib/basebackup_to_shell/meson.build | 30 + contrib/basebackup_to_shell/t/001_basic.pl | 142 + contrib/basic_archive/.gitignore | 4 + contrib/basic_archive/Makefile | 21 + contrib/basic_archive/basic_archive.c | 428 + contrib/basic_archive/basic_archive.conf | 4 + contrib/basic_archive/expected/basic_archive.out | 29 + contrib/basic_archive/meson.build | 34 + contrib/basic_archive/sql/basic_archive.sql | 22 + contrib/bloom/.gitignore | 4 + contrib/bloom/Makefile | 30 + contrib/bloom/blcost.c | 42 + contrib/bloom/blinsert.c | 341 + contrib/bloom/bloom--1.0.sql | 25 + contrib/bloom/bloom.control | 5 + contrib/bloom/bloom.h | 215 + contrib/bloom/blscan.c | 172 + contrib/bloom/blutils.c | 491 + contrib/bloom/blvacuum.c | 217 + contrib/bloom/blvalidate.c | 225 + contrib/bloom/expected/bloom.out | 230 + contrib/bloom/meson.build | 45 + contrib/bloom/sql/bloom.sql | 95 + contrib/bloom/t/001_wal.pl | 84 + contrib/bool_plperl/.gitignore | 4 + contrib/bool_plperl/Makefile | 39 + contrib/bool_plperl/bool_plperl--1.0.sql | 19 + contrib/bool_plperl/bool_plperl.c | 30 + contrib/bool_plperl/bool_plperl.control | 7 + contrib/bool_plperl/bool_plperlu--1.0.sql | 19 + contrib/bool_plperl/bool_plperlu.control | 6 + contrib/bool_plperl/expected/bool_plperl.out | 112 + contrib/bool_plperl/expected/bool_plperlu.out | 112 + contrib/bool_plperl/meson.build | 50 + contrib/bool_plperl/sql/bool_plperl.sql | 70 + contrib/bool_plperl/sql/bool_plperlu.sql | 70 + contrib/btree_gin/.gitignore | 4 + contrib/btree_gin/Makefile | 27 + contrib/btree_gin/btree_gin--1.0--1.1.sql | 35 + contrib/btree_gin/btree_gin--1.0.sql | 689 ++ contrib/btree_gin/btree_gin--1.1--1.2.sql | 47 + contrib/btree_gin/btree_gin--1.2--1.3.sql | 128 + contrib/btree_gin/btree_gin.c | 513 + contrib/btree_gin/btree_gin.control | 6 + contrib/btree_gin/expected/bit.out | 44 + contrib/btree_gin/expected/bool.out | 133 + contrib/btree_gin/expected/bpchar.out | 109 + contrib/btree_gin/expected/bytea.out | 46 + contrib/btree_gin/expected/char.out | 44 + contrib/btree_gin/expected/cidr.out | 51 + contrib/btree_gin/expected/date.out | 51 + contrib/btree_gin/expected/enum.out | 63 + contrib/btree_gin/expected/float4.out | 44 + contrib/btree_gin/expected/float8.out | 44 + contrib/btree_gin/expected/inet.out | 51 + contrib/btree_gin/expected/install_btree_gin.out | 9 + contrib/btree_gin/expected/int2.out | 44 + contrib/btree_gin/expected/int4.out | 44 + contrib/btree_gin/expected/int8.out | 44 + contrib/btree_gin/expected/interval.out | 57 + contrib/btree_gin/expected/macaddr.out | 51 + contrib/btree_gin/expected/macaddr8.out | 51 + contrib/btree_gin/expected/money.out | 44 + contrib/btree_gin/expected/name.out | 97 + contrib/btree_gin/expected/numeric.out | 44 + contrib/btree_gin/expected/oid.out | 44 + contrib/btree_gin/expected/text.out | 44 + contrib/btree_gin/expected/time.out | 51 + contrib/btree_gin/expected/timestamp.out | 51 + contrib/btree_gin/expected/timestamptz.out | 51 + contrib/btree_gin/expected/timetz.out | 51 + contrib/btree_gin/expected/uuid.out | 104 + contrib/btree_gin/expected/varbit.out | 44 + contrib/btree_gin/expected/varchar.out | 44 + contrib/btree_gin/meson.build | 66 + contrib/btree_gin/sql/bit.sql | 15 + contrib/btree_gin/sql/bool.sql | 29 + contrib/btree_gin/sql/bpchar.sql | 22 + contrib/btree_gin/sql/bytea.sql | 17 + contrib/btree_gin/sql/char.sql | 15 + contrib/btree_gin/sql/cidr.sql | 22 + contrib/btree_gin/sql/date.sql | 22 + contrib/btree_gin/sql/enum.sql | 26 + contrib/btree_gin/sql/float4.sql | 15 + contrib/btree_gin/sql/float8.sql | 15 + contrib/btree_gin/sql/inet.sql | 22 + contrib/btree_gin/sql/install_btree_gin.sql | 6 + contrib/btree_gin/sql/int2.sql | 15 + contrib/btree_gin/sql/int4.sql | 15 + contrib/btree_gin/sql/int8.sql | 15 + contrib/btree_gin/sql/interval.sql | 24 + contrib/btree_gin/sql/macaddr.sql | 22 + contrib/btree_gin/sql/macaddr8.sql | 22 + contrib/btree_gin/sql/money.sql | 15 + contrib/btree_gin/sql/name.sql | 21 + contrib/btree_gin/sql/numeric.sql | 15 + contrib/btree_gin/sql/oid.sql | 15 + contrib/btree_gin/sql/text.sql | 15 + contrib/btree_gin/sql/time.sql | 22 + contrib/btree_gin/sql/timestamp.sql | 22 + contrib/btree_gin/sql/timestamptz.sql | 22 + contrib/btree_gin/sql/timetz.sql | 22 + contrib/btree_gin/sql/uuid.sql | 28 + contrib/btree_gin/sql/varbit.sql | 15 + contrib/btree_gin/sql/varchar.sql | 15 + contrib/btree_gist/.gitignore | 4 + contrib/btree_gist/Makefile | 54 + contrib/btree_gist/btree_bit.c | 210 + contrib/btree_gist/btree_bool.c | 169 + contrib/btree_gist/btree_bytea.c | 170 + contrib/btree_gist/btree_cash.c | 217 + contrib/btree_gist/btree_date.c | 259 + contrib/btree_gist/btree_enum.c | 185 + contrib/btree_gist/btree_float4.c | 212 + contrib/btree_gist/btree_float8.c | 219 + contrib/btree_gist/btree_gist--1.0--1.1.sql | 127 + contrib/btree_gist/btree_gist--1.1--1.2.sql | 79 + contrib/btree_gist/btree_gist--1.2--1.3.sql | 65 + contrib/btree_gist/btree_gist--1.2.sql | 1570 +++ contrib/btree_gist/btree_gist--1.3--1.4.sql | 64 + contrib/btree_gist/btree_gist--1.4--1.5.sql | 69 + contrib/btree_gist/btree_gist--1.5--1.6.sql | 191 + contrib/btree_gist/btree_gist--1.6--1.7.sql | 77 + contrib/btree_gist/btree_gist.c | 53 + contrib/btree_gist/btree_gist.control | 6 + contrib/btree_gist/btree_gist.h | 41 + contrib/btree_gist/btree_inet.c | 187 + contrib/btree_gist/btree_int2.c | 216 + contrib/btree_gist/btree_int4.c | 217 + contrib/btree_gist/btree_int8.c | 217 + contrib/btree_gist/btree_interval.c | 297 + contrib/btree_gist/btree_macaddr.c | 196 + contrib/btree_gist/btree_macaddr8.c | 196 + contrib/btree_gist/btree_numeric.c | 229 + contrib/btree_gist/btree_oid.c | 217 + contrib/btree_gist/btree_text.c | 289 + contrib/btree_gist/btree_time.c | 334 + contrib/btree_gist/btree_ts.c | 400 + contrib/btree_gist/btree_utils_num.c | 384 + contrib/btree_gist/btree_utils_num.h | 118 + contrib/btree_gist/btree_utils_var.c | 612 + contrib/btree_gist/btree_utils_var.h | 72 + contrib/btree_gist/btree_uuid.c | 235 + contrib/btree_gist/data/bit.data | 612 + contrib/btree_gist/data/cash.data | 600 + contrib/btree_gist/data/char.data | 1000 ++ contrib/btree_gist/data/date.data | 600 + contrib/btree_gist/data/enum.data | 595 + contrib/btree_gist/data/float4.data | 600 + contrib/btree_gist/data/float8.data | 600 + contrib/btree_gist/data/inet.data | 675 ++ contrib/btree_gist/data/int2.data | 600 + contrib/btree_gist/data/int4.data | 600 + contrib/btree_gist/data/int8.data | 600 + contrib/btree_gist/data/interval.data | 612 + contrib/btree_gist/data/macaddr.data | 644 + contrib/btree_gist/data/numeric.data | 11 + contrib/btree_gist/data/text.data | 1 + contrib/btree_gist/data/time.data | 599 + contrib/btree_gist/data/timestamp.data | 624 + contrib/btree_gist/data/timestamptz.data | 618 + contrib/btree_gist/data/timetz.data | 599 + contrib/btree_gist/data/uuid.data | 703 ++ contrib/btree_gist/data/varbit.data | 621 + contrib/btree_gist/expected/bit.out | 76 + contrib/btree_gist/expected/bool.out | 96 + contrib/btree_gist/expected/bytea.out | 90 + contrib/btree_gist/expected/cash.out | 91 + contrib/btree_gist/expected/char.out | 82 + contrib/btree_gist/expected/char_1.out | 82 + contrib/btree_gist/expected/cidr.out | 66 + contrib/btree_gist/expected/date.out | 91 + contrib/btree_gist/expected/enum.out | 91 + contrib/btree_gist/expected/float4.out | 91 + contrib/btree_gist/expected/float8.out | 91 + contrib/btree_gist/expected/inet.out | 101 + contrib/btree_gist/expected/init.out | 9 + contrib/btree_gist/expected/int2.out | 91 + contrib/btree_gist/expected/int4.out | 91 + contrib/btree_gist/expected/int8.out | 91 + contrib/btree_gist/expected/interval.out | 109 + contrib/btree_gist/expected/macaddr.out | 89 + contrib/btree_gist/expected/macaddr8.out | 89 + contrib/btree_gist/expected/not_equal.out | 41 + contrib/btree_gist/expected/numeric.out | 207 + contrib/btree_gist/expected/oid.out | 66 + contrib/btree_gist/expected/text.out | 89 + contrib/btree_gist/expected/text_1.out | 89 + contrib/btree_gist/expected/time.out | 91 + contrib/btree_gist/expected/timestamp.out | 91 + contrib/btree_gist/expected/timestamptz.out | 211 + contrib/btree_gist/expected/timetz.out | 43 + contrib/btree_gist/expected/uuid.out | 66 + contrib/btree_gist/expected/varbit.out | 76 + contrib/btree_gist/expected/varchar.out | 66 + contrib/btree_gist/expected/varchar_1.out | 66 + contrib/btree_gist/meson.build | 93 + contrib/btree_gist/sql/bit.sql | 36 + contrib/btree_gist/sql/bool.sql | 42 + contrib/btree_gist/sql/bytea.sql | 40 + contrib/btree_gist/sql/cash.sql | 37 + contrib/btree_gist/sql/char.sql | 37 + contrib/btree_gist/sql/cidr.sql | 30 + contrib/btree_gist/sql/date.sql | 37 + contrib/btree_gist/sql/enum.sql | 38 + contrib/btree_gist/sql/float4.sql | 37 + contrib/btree_gist/sql/float8.sql | 37 + contrib/btree_gist/sql/inet.sql | 49 + contrib/btree_gist/sql/init.sql | 6 + contrib/btree_gist/sql/int2.sql | 37 + contrib/btree_gist/sql/int4.sql | 37 + contrib/btree_gist/sql/int8.sql | 37 + contrib/btree_gist/sql/interval.sql | 43 + contrib/btree_gist/sql/macaddr.sql | 37 + contrib/btree_gist/sql/macaddr8.sql | 37 + contrib/btree_gist/sql/not_equal.sql | 36 + contrib/btree_gist/sql/numeric.sql | 83 + contrib/btree_gist/sql/oid.sql | 30 + contrib/btree_gist/sql/text.sql | 40 + contrib/btree_gist/sql/time.sql | 37 + contrib/btree_gist/sql/timestamp.sql | 37 + contrib/btree_gist/sql/timestamptz.sql | 80 + contrib/btree_gist/sql/timetz.sql | 82 + contrib/btree_gist/sql/uuid.sql | 31 + contrib/btree_gist/sql/varbit.sql | 36 + contrib/btree_gist/sql/varchar.sql | 31 + contrib/citext/.gitignore | 4 + contrib/citext/Makefile | 25 + contrib/citext/citext--1.0--1.1.sql | 21 + contrib/citext/citext--1.1--1.2.sql | 68 + contrib/citext/citext--1.2--1.3.sql | 21 + contrib/citext/citext--1.3--1.4.sql | 12 + contrib/citext/citext--1.4--1.5.sql | 88 + contrib/citext/citext--1.4.sql | 501 + contrib/citext/citext--1.5--1.6.sql | 12 + contrib/citext/citext.c | 409 + contrib/citext/citext.control | 6 + contrib/citext/expected/citext.out | 2693 +++++ contrib/citext/expected/citext_1.out | 2693 +++++ contrib/citext/expected/citext_utf8.out | 153 + contrib/citext/expected/citext_utf8_1.out | 16 + contrib/citext/expected/create_index_acl.out | 86 + contrib/citext/meson.build | 42 + contrib/citext/sql/citext.sql | 810 ++ contrib/citext/sql/citext_utf8.sql | 58 + contrib/citext/sql/create_index_acl.sql | 88 + contrib/contrib-global.mk | 4 + contrib/cube/.gitignore | 7 + contrib/cube/CHANGES | 130 + contrib/cube/Makefile | 44 + contrib/cube/cube--1.0--1.1.sql | 59 + contrib/cube/cube--1.1--1.2.sql | 75 + contrib/cube/cube--1.2--1.3.sql | 12 + contrib/cube/cube--1.2.sql | 378 + contrib/cube/cube--1.3--1.4.sql | 58 + contrib/cube/cube--1.4--1.5.sql | 21 + contrib/cube/cube.c | 1909 +++ contrib/cube/cube.control | 6 + contrib/cube/cubedata.h | 72 + contrib/cube/cubeparse.c | 1575 +++ contrib/cube/cubeparse.h | 79 + contrib/cube/cubeparse.y | 291 + contrib/cube/cubescan.c | 2125 ++++ contrib/cube/cubescan.l | 133 + contrib/cube/data/test_cube.data | 3100 +++++ contrib/cube/expected/cube.out | 1975 +++ contrib/cube/expected/cube_sci.out | 106 + contrib/cube/meson.build | 61 + contrib/cube/sql/cube.sql | 438 + contrib/cube/sql/cube_sci.sql | 22 + contrib/dblink/.gitignore | 4 + contrib/dblink/Makefile | 27 + contrib/dblink/dblink--1.0--1.1.sql | 14 + contrib/dblink/dblink--1.1--1.2.sql | 46 + contrib/dblink/dblink--1.2.sql | 235 + contrib/dblink/dblink.c | 3077 +++++ contrib/dblink/dblink.control | 5 + contrib/dblink/expected/dblink.out | 1221 ++ contrib/dblink/meson.build | 39 + contrib/dblink/pg_service.conf | 7 + contrib/dblink/sql/dblink.sql | 634 + contrib/dict_int/.gitignore | 4 + contrib/dict_int/Makefile | 23 + contrib/dict_int/dict_int--1.0.sql | 25 + contrib/dict_int/dict_int.c | 116 + contrib/dict_int/dict_int.control | 6 + contrib/dict_int/expected/dict_int.out | 356 + contrib/dict_int/meson.build | 34 + contrib/dict_int/sql/dict_int.sql | 69 + contrib/dict_xsyn/.gitignore | 4 + contrib/dict_xsyn/Makefile | 24 + contrib/dict_xsyn/dict_xsyn--1.0.sql | 25 + contrib/dict_xsyn/dict_xsyn.c | 259 + contrib/dict_xsyn/dict_xsyn.control | 5 + contrib/dict_xsyn/expected/dict_xsyn.out | 142 + contrib/dict_xsyn/meson.build | 41 + contrib/dict_xsyn/sql/dict_xsyn.sql | 45 + contrib/dict_xsyn/xsyn_sample.rules | 6 + contrib/earthdistance/.gitignore | 4 + contrib/earthdistance/Makefile | 23 + contrib/earthdistance/earthdistance--1.0--1.1.sql | 14 + contrib/earthdistance/earthdistance--1.1.sql | 98 + contrib/earthdistance/earthdistance.c | 105 + contrib/earthdistance/earthdistance.control | 6 + contrib/earthdistance/expected/earthdistance.out | 1098 ++ contrib/earthdistance/meson.build | 35 + contrib/earthdistance/sql/earthdistance.sql | 359 + contrib/file_fdw/.gitignore | 4 + contrib/file_fdw/Makefile | 20 + contrib/file_fdw/data/agg.bad | 4 + contrib/file_fdw/data/agg.csv | 4 + contrib/file_fdw/data/agg.data | 4 + contrib/file_fdw/data/copy_default.csv | 3 + contrib/file_fdw/data/list1.csv | 2 + contrib/file_fdw/data/list2.bad | 2 + contrib/file_fdw/data/list2.csv | 2 + contrib/file_fdw/data/text.csv | 5 + contrib/file_fdw/expected/file_fdw.out | 510 + contrib/file_fdw/file_fdw--1.0.sql | 18 + contrib/file_fdw/file_fdw.c | 1252 ++ contrib/file_fdw/file_fdw.control | 5 + contrib/file_fdw/meson.build | 34 + contrib/file_fdw/sql/file_fdw.sql | 273 + contrib/fuzzystrmatch/.gitignore | 6 + contrib/fuzzystrmatch/Makefile | 40 + contrib/fuzzystrmatch/daitch_mokotoff.c | 571 + contrib/fuzzystrmatch/daitch_mokotoff.h | 949 ++ contrib/fuzzystrmatch/daitch_mokotoff_header.pl | 219 + contrib/fuzzystrmatch/dmetaphone.c | 1438 +++ contrib/fuzzystrmatch/expected/fuzzystrmatch.out | 244 + .../fuzzystrmatch/expected/fuzzystrmatch_utf8.out | 61 + .../expected/fuzzystrmatch_utf8_1.out | 8 + contrib/fuzzystrmatch/fuzzystrmatch--1.0--1.1.sql | 15 + contrib/fuzzystrmatch/fuzzystrmatch--1.1--1.2.sql | 8 + contrib/fuzzystrmatch/fuzzystrmatch--1.1.sql | 44 + contrib/fuzzystrmatch/fuzzystrmatch.c | 794 ++ contrib/fuzzystrmatch/fuzzystrmatch.control | 6 + contrib/fuzzystrmatch/meson.build | 48 + contrib/fuzzystrmatch/sql/fuzzystrmatch.sql | 67 + contrib/fuzzystrmatch/sql/fuzzystrmatch_utf8.sql | 26 + contrib/hstore/.gitignore | 4 + contrib/hstore/Makefile | 36 + contrib/hstore/data/hstore.data | 1001 ++ contrib/hstore/expected/hstore.out | 1635 +++ contrib/hstore/expected/hstore_utf8.out | 36 + contrib/hstore/expected/hstore_utf8_1.out | 8 + contrib/hstore/hstore--1.1--1.2.sql | 53 + contrib/hstore/hstore--1.2--1.3.sql | 17 + contrib/hstore/hstore--1.3--1.4.sql | 99 + contrib/hstore/hstore--1.4--1.5.sql | 14 + contrib/hstore/hstore--1.4.sql | 550 + contrib/hstore/hstore--1.5--1.6.sql | 12 + contrib/hstore/hstore--1.6--1.7.sql | 27 + contrib/hstore/hstore--1.7--1.8.sql | 17 + contrib/hstore/hstore.control | 6 + contrib/hstore/hstore.h | 205 + contrib/hstore/hstore_compat.c | 359 + contrib/hstore/hstore_gin.c | 210 + contrib/hstore/hstore_gist.c | 623 + contrib/hstore/hstore_io.c | 1554 +++ contrib/hstore/hstore_op.c | 1273 ++ contrib/hstore/hstore_subs.c | 297 + contrib/hstore/meson.build | 56 + contrib/hstore/sql/hstore.sql | 397 + contrib/hstore/sql/hstore_utf8.sql | 19 + contrib/hstore_plperl/.gitignore | 4 + contrib/hstore_plperl/Makefile | 41 + .../hstore_plperl/expected/create_transform.out | 75 + contrib/hstore_plperl/expected/hstore_plperl.out | 67 + contrib/hstore_plperl/expected/hstore_plperlu.out | 151 + contrib/hstore_plperl/hstore_plperl--1.0.sql | 17 + contrib/hstore_plperl/hstore_plperl.c | 152 + contrib/hstore_plperl/hstore_plperl.control | 6 + contrib/hstore_plperl/hstore_plperlu--1.0.sql | 17 + contrib/hstore_plperl/hstore_plperlu.control | 6 + contrib/hstore_plperl/meson.build | 51 + contrib/hstore_plperl/sql/create_transform.sql | 49 + contrib/hstore_plperl/sql/hstore_plperl.sql | 60 + contrib/hstore_plperl/sql/hstore_plperlu.sql | 125 + contrib/hstore_plpython/.gitignore | 4 + contrib/hstore_plpython/Makefile | 39 + .../hstore_plpython/expected/hstore_plpython.out | 161 + contrib/hstore_plpython/hstore_plpython.c | 190 + contrib/hstore_plpython/hstore_plpython3u--1.0.sql | 19 + contrib/hstore_plpython/hstore_plpython3u.control | 6 + contrib/hstore_plpython/meson.build | 45 + contrib/hstore_plpython/sql/hstore_plpython.sql | 130 + contrib/intagg/Makefile | 15 + contrib/intagg/intagg--1.0--1.1.sql | 23 + contrib/intagg/intagg--1.1.sql | 37 + contrib/intagg/intagg.control | 4 + contrib/intagg/meson.build | 8 + contrib/intarray/.gitignore | 4 + contrib/intarray/Makefile | 31 + contrib/intarray/_int.h | 191 + contrib/intarray/_int_bool.c | 670 ++ contrib/intarray/_int_gin.c | 180 + contrib/intarray/_int_gist.c | 637 + contrib/intarray/_int_op.c | 432 + contrib/intarray/_int_selfuncs.c | 333 + contrib/intarray/_int_tool.c | 410 + contrib/intarray/_intbig_gist.c | 596 + contrib/intarray/bench/bench.pl | 140 + contrib/intarray/bench/create_test.pl | 91 + contrib/intarray/data/test__int.data | 7001 +++++++++++ contrib/intarray/expected/_int.out | 971 ++ contrib/intarray/intarray--1.0--1.1.sql | 49 + contrib/intarray/intarray--1.1--1.2.sql | 94 + contrib/intarray/intarray--1.2--1.3.sql | 20 + contrib/intarray/intarray--1.2.sql | 520 + contrib/intarray/intarray--1.3--1.4.sql | 21 + contrib/intarray/intarray--1.4--1.5.sql | 8 + contrib/intarray/intarray.control | 6 + contrib/intarray/meson.build | 45 + contrib/intarray/sql/_int.sql | 234 + contrib/isn/.gitignore | 4 + contrib/isn/EAN13.h | 148 + contrib/isn/ISBN.h | 990 ++ contrib/isn/ISMN.h | 52 + contrib/isn/ISSN.h | 49 + contrib/isn/Makefile | 24 + contrib/isn/UPC.h | 28 + contrib/isn/expected/isn.out | 285 + contrib/isn/isn--1.0--1.1.sql | 250 + contrib/isn/isn--1.1--1.2.sql | 228 + contrib/isn/isn--1.1.sql | 3434 ++++++ contrib/isn/isn.c | 1132 ++ contrib/isn/isn.control | 6 + contrib/isn/isn.h | 35 + contrib/isn/meson.build | 41 + contrib/isn/sql/isn.sql | 126 + contrib/jsonb_plperl/.gitignore | 4 + contrib/jsonb_plperl/Makefile | 41 + contrib/jsonb_plperl/expected/jsonb_plperl.out | 253 + contrib/jsonb_plperl/expected/jsonb_plperlu.out | 280 + contrib/jsonb_plperl/jsonb_plperl--1.0.sql | 19 + contrib/jsonb_plperl/jsonb_plperl.c | 295 + contrib/jsonb_plperl/jsonb_plperl.control | 7 + contrib/jsonb_plperl/jsonb_plperlu--1.0.sql | 19 + contrib/jsonb_plperl/jsonb_plperlu.control | 6 + contrib/jsonb_plperl/meson.build | 51 + contrib/jsonb_plperl/sql/jsonb_plperl.sql | 117 + contrib/jsonb_plperl/sql/jsonb_plperlu.sql | 121 + contrib/jsonb_plpython/.gitignore | 4 + contrib/jsonb_plpython/Makefile | 34 + contrib/jsonb_plpython/expected/jsonb_plpython.out | 306 + contrib/jsonb_plpython/jsonb_plpython.c | 502 + contrib/jsonb_plpython/jsonb_plpython3u--1.0.sql | 19 + contrib/jsonb_plpython/jsonb_plpython3u.control | 6 + contrib/jsonb_plpython/meson.build | 44 + contrib/jsonb_plpython/sql/jsonb_plpython.sql | 183 + contrib/lo/.gitignore | 4 + contrib/lo/Makefile | 20 + contrib/lo/expected/lo.out | 50 + contrib/lo/lo--1.0--1.1.sql | 6 + contrib/lo/lo--1.1.sql | 25 + contrib/lo/lo.c | 111 + contrib/lo/lo.control | 6 + contrib/lo/lo_test.sql | 79 + contrib/lo/meson.build | 35 + contrib/lo/sql/lo.sql | 30 + contrib/ltree/.gitignore | 4 + contrib/ltree/Makefile | 33 + contrib/ltree/_ltree_gist.c | 558 + contrib/ltree/_ltree_op.c | 326 + contrib/ltree/crc32.c | 40 + contrib/ltree/crc32.h | 12 + contrib/ltree/data/_ltree.data | 1000 ++ contrib/ltree/data/ltree.data | 1006 ++ contrib/ltree/expected/ltree.out | 8134 +++++++++++++ contrib/ltree/lquery_op.c | 281 + contrib/ltree/ltree--1.0--1.1.sql | 115 + contrib/ltree/ltree--1.1--1.2.sql | 139 + contrib/ltree/ltree--1.1.sql | 872 ++ contrib/ltree/ltree.control | 6 + contrib/ltree/ltree.h | 317 + contrib/ltree/ltree_gist.c | 749 ++ contrib/ltree/ltree_io.c | 816 ++ contrib/ltree/ltree_op.c | 590 + contrib/ltree/ltreetest.sql | 21 + contrib/ltree/ltxtquery_io.c | 629 + contrib/ltree/ltxtquery_op.c | 111 + contrib/ltree/meson.build | 52 + contrib/ltree/sql/ltree.sql | 411 + contrib/ltree_plpython/.gitignore | 4 + contrib/ltree_plpython/Makefile | 39 + contrib/ltree_plpython/expected/ltree_plpython.out | 43 + contrib/ltree_plpython/ltree_plpython.c | 58 + contrib/ltree_plpython/ltree_plpython3u--1.0.sql | 12 + contrib/ltree_plpython/ltree_plpython3u.control | 6 + contrib/ltree_plpython/meson.build | 45 + contrib/ltree_plpython/sql/ltree_plpython.sql | 36 + contrib/meson.build | 68 + contrib/oid2name/.gitignore | 3 + contrib/oid2name/Makefile | 25 + contrib/oid2name/meson.build | 29 + contrib/oid2name/oid2name.c | 650 + contrib/oid2name/t/001_basic.pl | 17 + contrib/old_snapshot/Makefile | 21 + contrib/old_snapshot/meson.build | 23 + contrib/old_snapshot/old_snapshot--1.0.sql | 14 + contrib/old_snapshot/old_snapshot.control | 5 + contrib/old_snapshot/time_mapping.c | 142 + contrib/pageinspect/.gitignore | 4 + contrib/pageinspect/Makefile | 36 + contrib/pageinspect/brinfuncs.c | 422 + contrib/pageinspect/btreefuncs.c | 939 ++ contrib/pageinspect/expected/brin.out | 92 + contrib/pageinspect/expected/btree.out | 221 + contrib/pageinspect/expected/checksum.out | 40 + contrib/pageinspect/expected/checksum_1.out | 40 + contrib/pageinspect/expected/gin.out | 71 + contrib/pageinspect/expected/gist.out | 133 + contrib/pageinspect/expected/hash.out | 210 + contrib/pageinspect/expected/oldextversions.out | 56 + contrib/pageinspect/expected/page.out | 241 + contrib/pageinspect/fsmfuncs.c | 65 + contrib/pageinspect/ginfuncs.c | 285 + contrib/pageinspect/gistfuncs.c | 369 + contrib/pageinspect/hashfuncs.c | 570 + contrib/pageinspect/heapfuncs.c | 618 + contrib/pageinspect/meson.build | 60 + contrib/pageinspect/pageinspect--1.0--1.1.sql | 18 + contrib/pageinspect/pageinspect--1.1--1.2.sql | 18 + contrib/pageinspect/pageinspect--1.10--1.11.sql | 28 + contrib/pageinspect/pageinspect--1.11--1.12.sql | 40 + contrib/pageinspect/pageinspect--1.2--1.3.sql | 82 + contrib/pageinspect/pageinspect--1.3--1.4.sql | 118 + contrib/pageinspect/pageinspect--1.4--1.5.sql | 24 + contrib/pageinspect/pageinspect--1.5--1.6.sql | 99 + contrib/pageinspect/pageinspect--1.5.sql | 279 + contrib/pageinspect/pageinspect--1.6--1.7.sql | 26 + contrib/pageinspect/pageinspect--1.7--1.8.sql | 69 + contrib/pageinspect/pageinspect--1.8--1.9.sql | 137 + contrib/pageinspect/pageinspect--1.9--1.10.sql | 21 + contrib/pageinspect/pageinspect.control | 5 + contrib/pageinspect/pageinspect.h | 30 + contrib/pageinspect/rawpage.c | 376 + contrib/pageinspect/sql/brin.sql | 39 + contrib/pageinspect/sql/btree.sql | 62 + contrib/pageinspect/sql/checksum.sql | 31 + contrib/pageinspect/sql/gin.sql | 41 + contrib/pageinspect/sql/gist.sql | 69 + contrib/pageinspect/sql/hash.sql | 113 + contrib/pageinspect/sql/oldextversions.sql | 26 + contrib/pageinspect/sql/page.sql | 100 + contrib/passwordcheck/.gitignore | 4 + contrib/passwordcheck/Makefile | 24 + contrib/passwordcheck/expected/passwordcheck.out | 19 + contrib/passwordcheck/meson.build | 38 + contrib/passwordcheck/passwordcheck.c | 148 + contrib/passwordcheck/sql/passwordcheck.sql | 23 + contrib/pg_buffercache/.gitignore | 4 + contrib/pg_buffercache/Makefile | 25 + contrib/pg_buffercache/expected/pg_buffercache.out | 57 + contrib/pg_buffercache/meson.build | 38 + .../pg_buffercache/pg_buffercache--1.0--1.1.sql | 11 + .../pg_buffercache/pg_buffercache--1.1--1.2.sql | 6 + .../pg_buffercache/pg_buffercache--1.2--1.3.sql | 7 + contrib/pg_buffercache/pg_buffercache--1.2.sql | 21 + .../pg_buffercache/pg_buffercache--1.3--1.4.sql | 28 + contrib/pg_buffercache/pg_buffercache.control | 5 + contrib/pg_buffercache/pg_buffercache_pages.c | 349 + contrib/pg_buffercache/sql/pg_buffercache.sql | 28 + contrib/pg_freespacemap/.gitignore | 4 + contrib/pg_freespacemap/Makefile | 29 + .../pg_freespacemap/expected/pg_freespacemap.out | 85 + contrib/pg_freespacemap/meson.build | 42 + .../pg_freespacemap/pg_freespacemap--1.0--1.1.sql | 7 + .../pg_freespacemap/pg_freespacemap--1.1--1.2.sql | 7 + contrib/pg_freespacemap/pg_freespacemap--1.1.sql | 25 + contrib/pg_freespacemap/pg_freespacemap.c | 42 + contrib/pg_freespacemap/pg_freespacemap.conf | 1 + contrib/pg_freespacemap/pg_freespacemap.control | 5 + contrib/pg_freespacemap/sql/pg_freespacemap.sql | 32 + contrib/pg_prewarm/.gitignore | 4 + contrib/pg_prewarm/Makefile | 24 + contrib/pg_prewarm/autoprewarm.c | 920 ++ contrib/pg_prewarm/meson.build | 37 + contrib/pg_prewarm/pg_prewarm--1.0--1.1.sql | 6 + contrib/pg_prewarm/pg_prewarm--1.1--1.2.sql | 14 + contrib/pg_prewarm/pg_prewarm--1.1.sql | 14 + contrib/pg_prewarm/pg_prewarm.c | 204 + contrib/pg_prewarm/pg_prewarm.control | 5 + contrib/pg_prewarm/t/001_basic.pl | 58 + contrib/pg_stat_statements/.gitignore | 4 + contrib/pg_stat_statements/Makefile | 35 + contrib/pg_stat_statements/expected/cleanup.out | 1 + contrib/pg_stat_statements/expected/cursors.out | 70 + contrib/pg_stat_statements/expected/dml.out | 174 + .../pg_stat_statements/expected/level_tracking.out | 210 + .../pg_stat_statements/expected/oldextversions.out | 253 + contrib/pg_stat_statements/expected/planning.out | 88 + contrib/pg_stat_statements/expected/select.out | 414 + .../pg_stat_statements/expected/user_activity.out | 205 + contrib/pg_stat_statements/expected/utility.out | 535 + contrib/pg_stat_statements/expected/wal.out | 30 + contrib/pg_stat_statements/meson.build | 60 + .../pg_stat_statements--1.0--1.1.sql | 42 + .../pg_stat_statements--1.1--1.2.sql | 43 + .../pg_stat_statements--1.2--1.3.sql | 47 + .../pg_stat_statements--1.3--1.4.sql | 7 + .../pg_stat_statements--1.4--1.5.sql | 6 + .../pg_stat_statements/pg_stat_statements--1.4.sql | 48 + .../pg_stat_statements--1.5--1.6.sql | 7 + .../pg_stat_statements--1.6--1.7.sql | 22 + .../pg_stat_statements--1.7--1.8.sql | 56 + .../pg_stat_statements--1.8--1.9.sql | 71 + .../pg_stat_statements--1.9--1.10.sql | 67 + contrib/pg_stat_statements/pg_stat_statements.c | 2877 +++++ contrib/pg_stat_statements/pg_stat_statements.conf | 1 + .../pg_stat_statements/pg_stat_statements.control | 5 + contrib/pg_stat_statements/sql/cleanup.sql | 1 + contrib/pg_stat_statements/sql/cursors.sql | 30 + contrib/pg_stat_statements/sql/dml.sql | 95 + contrib/pg_stat_statements/sql/level_tracking.sql | 100 + contrib/pg_stat_statements/sql/oldextversions.sql | 51 + contrib/pg_stat_statements/sql/planning.sql | 31 + contrib/pg_stat_statements/sql/select.sql | 149 + contrib/pg_stat_statements/sql/user_activity.sql | 66 + contrib/pg_stat_statements/sql/utility.sql | 266 + contrib/pg_stat_statements/sql/wal.sql | 20 + contrib/pg_surgery/.gitignore | 4 + contrib/pg_surgery/Makefile | 23 + contrib/pg_surgery/expected/heap_surgery.out | 180 + contrib/pg_surgery/heap_surgery.c | 418 + contrib/pg_surgery/meson.build | 35 + contrib/pg_surgery/pg_surgery--1.0.sql | 18 + contrib/pg_surgery/pg_surgery.control | 5 + contrib/pg_surgery/sql/heap_surgery.sql | 88 + contrib/pg_trgm/.gitignore | 4 + contrib/pg_trgm/Makefile | 28 + contrib/pg_trgm/data/trgm.data | 1000 ++ contrib/pg_trgm/data/trgm2.data | 696 ++ contrib/pg_trgm/expected/pg_strict_word_trgm.out | 1027 ++ contrib/pg_trgm/expected/pg_trgm.out | 5404 +++++++++ contrib/pg_trgm/expected/pg_word_trgm.out | 1052 ++ contrib/pg_trgm/meson.build | 46 + contrib/pg_trgm/pg_trgm--1.0--1.1.sql | 12 + contrib/pg_trgm/pg_trgm--1.1--1.2.sql | 74 + contrib/pg_trgm/pg_trgm--1.2--1.3.sql | 65 + contrib/pg_trgm/pg_trgm--1.3--1.4.sql | 68 + contrib/pg_trgm/pg_trgm--1.3.sql | 254 + contrib/pg_trgm/pg_trgm--1.4--1.5.sql | 23 + contrib/pg_trgm/pg_trgm--1.5--1.6.sql | 10 + contrib/pg_trgm/pg_trgm.control | 6 + contrib/pg_trgm/sql/pg_strict_word_trgm.sql | 45 + contrib/pg_trgm/sql/pg_trgm.sql | 238 + contrib/pg_trgm/sql/pg_word_trgm.sql | 48 + contrib/pg_trgm/trgm.h | 140 + contrib/pg_trgm/trgm_gin.c | 360 + contrib/pg_trgm/trgm_gist.c | 971 ++ contrib/pg_trgm/trgm_op.c | 1323 ++ contrib/pg_trgm/trgm_regexp.c | 2358 ++++ contrib/pg_visibility/.gitignore | 4 + contrib/pg_visibility/Makefile | 24 + contrib/pg_visibility/expected/pg_visibility.out | 279 + contrib/pg_visibility/meson.build | 36 + contrib/pg_visibility/pg_visibility--1.0--1.1.sql | 24 + contrib/pg_visibility/pg_visibility--1.1--1.2.sql | 13 + contrib/pg_visibility/pg_visibility--1.1.sql | 75 + contrib/pg_visibility/pg_visibility.c | 779 ++ contrib/pg_visibility/pg_visibility.control | 5 + contrib/pg_visibility/sql/pg_visibility.sql | 182 + contrib/pg_walinspect/.gitignore | 4 + contrib/pg_walinspect/Makefile | 29 + contrib/pg_walinspect/expected/oldextversions.out | 69 + contrib/pg_walinspect/expected/pg_walinspect.out | 262 + contrib/pg_walinspect/meson.build | 40 + contrib/pg_walinspect/pg_walinspect--1.0--1.1.sql | 42 + contrib/pg_walinspect/pg_walinspect--1.0.sql | 118 + contrib/pg_walinspect/pg_walinspect.c | 851 ++ contrib/pg_walinspect/pg_walinspect.control | 5 + contrib/pg_walinspect/sql/oldextversions.sql | 39 + contrib/pg_walinspect/sql/pg_walinspect.sql | 157 + contrib/pg_walinspect/walinspect.conf | 2 + contrib/pgcrypto/.gitignore | 4 + contrib/pgcrypto/Makefile | 69 + contrib/pgcrypto/crypt-blowfish.c | 756 ++ contrib/pgcrypto/crypt-des.c | 791 ++ contrib/pgcrypto/crypt-gensalt.c | 187 + contrib/pgcrypto/crypt-md5.c | 169 + contrib/pgcrypto/expected/3des.out | 65 + contrib/pgcrypto/expected/blowfish.out | 142 + contrib/pgcrypto/expected/blowfish_1.out | 62 + contrib/pgcrypto/expected/cast5.out | 73 + contrib/pgcrypto/expected/cast5_1.out | 33 + contrib/pgcrypto/expected/crypt-blowfish.out | 36 + contrib/pgcrypto/expected/crypt-des.out | 31 + contrib/pgcrypto/expected/crypt-md5.out | 27 + contrib/pgcrypto/expected/crypt-xdes.out | 51 + contrib/pgcrypto/expected/des.out | 58 + contrib/pgcrypto/expected/des_1.out | 26 + contrib/pgcrypto/expected/hmac-md5.out | 72 + contrib/pgcrypto/expected/hmac-sha1.out | 72 + contrib/pgcrypto/expected/init.out | 13 + contrib/pgcrypto/expected/md5.out | 45 + contrib/pgcrypto/expected/pgp-armor.out | 370 + contrib/pgcrypto/expected/pgp-compression.out | 80 + contrib/pgcrypto/expected/pgp-decrypt.out | 419 + contrib/pgcrypto/expected/pgp-decrypt_1.out | 415 + contrib/pgcrypto/expected/pgp-encrypt.out | 208 + contrib/pgcrypto/expected/pgp-info.out | 79 + contrib/pgcrypto/expected/pgp-pubkey-decrypt.out | 656 + contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out | 652 + contrib/pgcrypto/expected/pgp-pubkey-encrypt.out | 68 + contrib/pgcrypto/expected/pgp-zlib-DISABLED.out | 1 + contrib/pgcrypto/expected/rijndael.out | 137 + contrib/pgcrypto/expected/sha1.out | 45 + contrib/pgcrypto/expected/sha2.out | 139 + contrib/pgcrypto/mbuf.c | 547 + contrib/pgcrypto/mbuf.h | 122 + contrib/pgcrypto/meson.build | 109 + contrib/pgcrypto/openssl.c | 829 ++ contrib/pgcrypto/pgcrypto--1.0--1.1.sql | 9 + contrib/pgcrypto/pgcrypto--1.1--1.2.sql | 14 + contrib/pgcrypto/pgcrypto--1.2--1.3.sql | 41 + contrib/pgcrypto/pgcrypto--1.3.sql | 217 + contrib/pgcrypto/pgcrypto.c | 477 + contrib/pgcrypto/pgcrypto.control | 6 + contrib/pgcrypto/pgcrypto.h | 37 + contrib/pgcrypto/pgp-armor.c | 488 + contrib/pgcrypto/pgp-cfb.c | 265 + contrib/pgcrypto/pgp-compress.c | 346 + contrib/pgcrypto/pgp-decrypt.c | 1213 ++ contrib/pgcrypto/pgp-encrypt.c | 704 ++ contrib/pgcrypto/pgp-info.c | 235 + contrib/pgcrypto/pgp-mpi-openssl.c | 284 + contrib/pgcrypto/pgp-mpi.c | 142 + contrib/pgcrypto/pgp-pgsql.c | 1001 ++ contrib/pgcrypto/pgp-pubdec.c | 235 + contrib/pgcrypto/pgp-pubenc.c | 244 + contrib/pgcrypto/pgp-pubkey.c | 583 + contrib/pgcrypto/pgp-s2k.c | 308 + contrib/pgcrypto/pgp.c | 360 + contrib/pgcrypto/pgp.h | 326 + contrib/pgcrypto/px-crypt.c | 164 + contrib/pgcrypto/px-crypt.h | 82 + contrib/pgcrypto/px-hmac.c | 176 + contrib/pgcrypto/px.c | 340 + contrib/pgcrypto/px.h | 228 + contrib/pgcrypto/sql/3des.sql | 25 + contrib/pgcrypto/sql/blowfish.sql | 53 + contrib/pgcrypto/sql/cast5.sql | 32 + contrib/pgcrypto/sql/crypt-blowfish.sql | 26 + contrib/pgcrypto/sql/crypt-des.sql | 21 + contrib/pgcrypto/sql/crypt-md5.sql | 17 + contrib/pgcrypto/sql/crypt-xdes.sql | 33 + contrib/pgcrypto/sql/des.sql | 24 + contrib/pgcrypto/sql/hmac-md5.sql | 44 + contrib/pgcrypto/sql/hmac-sha1.sql | 44 + contrib/pgcrypto/sql/init.sql | 11 + contrib/pgcrypto/sql/md5.sql | 11 + contrib/pgcrypto/sql/pgp-armor.sql | 214 + contrib/pgcrypto/sql/pgp-compression.sql | 51 + contrib/pgcrypto/sql/pgp-decrypt.sql | 309 + contrib/pgcrypto/sql/pgp-encrypt.sql | 104 + contrib/pgcrypto/sql/pgp-info.sql | 22 + contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql | 647 + contrib/pgcrypto/sql/pgp-pubkey-encrypt.sql | 48 + contrib/pgcrypto/sql/pgp-zlib-DISABLED.sql | 1 + contrib/pgcrypto/sql/rijndael.sql | 72 + contrib/pgcrypto/sql/sha1.sql | 11 + contrib/pgcrypto/sql/sha2.sql | 33 + contrib/pgrowlocks/.gitignore | 6 + contrib/pgrowlocks/Makefile | 24 + contrib/pgrowlocks/expected/pgrowlocks.out | 233 + contrib/pgrowlocks/meson.build | 37 + contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql | 17 + contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql | 6 + contrib/pgrowlocks/pgrowlocks--1.2.sql | 15 + contrib/pgrowlocks/pgrowlocks.c | 276 + contrib/pgrowlocks/pgrowlocks.control | 5 + contrib/pgrowlocks/specs/pgrowlocks.spec | 39 + contrib/pgstattuple/.gitignore | 4 + contrib/pgstattuple/Makefile | 27 + contrib/pgstattuple/expected/pgstattuple.out | 280 + contrib/pgstattuple/meson.build | 42 + contrib/pgstattuple/pgstatapprox.c | 319 + contrib/pgstattuple/pgstatindex.c | 761 ++ contrib/pgstattuple/pgstattuple--1.0--1.1.sql | 11 + contrib/pgstattuple/pgstattuple--1.1--1.2.sql | 39 + contrib/pgstattuple/pgstattuple--1.2--1.3.sql | 18 + contrib/pgstattuple/pgstattuple--1.3--1.4.sql | 13 + contrib/pgstattuple/pgstattuple--1.4--1.5.sql | 136 + contrib/pgstattuple/pgstattuple--1.4.sql | 95 + contrib/pgstattuple/pgstattuple.c | 585 + contrib/pgstattuple/pgstattuple.control | 5 + contrib/pgstattuple/sql/pgstattuple.sql | 126 + contrib/postgres_fdw/.gitignore | 4 + contrib/postgres_fdw/Makefile | 31 + contrib/postgres_fdw/connection.c | 2226 ++++ contrib/postgres_fdw/deparse.c | 4049 +++++++ contrib/postgres_fdw/expected/postgres_fdw.out | 11923 +++++++++++++++++++ contrib/postgres_fdw/meson.build | 42 + contrib/postgres_fdw/option.c | 590 + contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql | 20 + contrib/postgres_fdw/postgres_fdw--1.0.sql | 18 + contrib/postgres_fdw/postgres_fdw.c | 7811 ++++++++++++ contrib/postgres_fdw/postgres_fdw.control | 5 + contrib/postgres_fdw/postgres_fdw.h | 255 + contrib/postgres_fdw/shippable.c | 205 + contrib/postgres_fdw/sql/postgres_fdw.sql | 4050 +++++++ contrib/seg/.gitignore | 7 + contrib/seg/Makefile | 45 + contrib/seg/data/test_seg.data | 2578 ++++ contrib/seg/expected/security.out | 32 + contrib/seg/expected/seg.out | 1299 ++ contrib/seg/meson.build | 60 + contrib/seg/seg--1.0--1.1.sql | 63 + contrib/seg/seg--1.1--1.2.sql | 14 + contrib/seg/seg--1.1.sql | 395 + contrib/seg/seg--1.2--1.3.sql | 58 + contrib/seg/seg--1.3--1.4.sql | 8 + contrib/seg/seg-validate.pl | 56 + contrib/seg/seg.c | 1095 ++ contrib/seg/seg.control | 6 + contrib/seg/segdata.h | 25 + contrib/seg/segparse.c | 1446 +++ contrib/seg/segparse.h | 92 + contrib/seg/segparse.y | 183 + contrib/seg/segscan.c | 2107 ++++ contrib/seg/segscan.l | 129 + contrib/seg/sort-segments.pl | 30 + contrib/seg/sql/security.sql | 32 + contrib/seg/sql/seg.sql | 257 + contrib/sepgsql/.gitignore | 7 + contrib/sepgsql/Makefile | 33 + contrib/sepgsql/database.c | 216 + contrib/sepgsql/dml.c | 359 + contrib/sepgsql/expected/alter.out | 316 + contrib/sepgsql/expected/ddl.out | 538 + contrib/sepgsql/expected/dml.out | 399 + contrib/sepgsql/expected/label.out | 611 + contrib/sepgsql/expected/misc.out | 231 + contrib/sepgsql/expected/truncate.out | 46 + contrib/sepgsql/hooks.c | 483 + contrib/sepgsql/label.c | 916 ++ contrib/sepgsql/launcher | 52 + contrib/sepgsql/meson.build | 43 + contrib/sepgsql/proc.c | 333 + contrib/sepgsql/relation.c | 773 ++ contrib/sepgsql/schema.c | 235 + contrib/sepgsql/selinux.c | 888 ++ contrib/sepgsql/sepgsql-regtest.te | 261 + contrib/sepgsql/sepgsql.h | 324 + contrib/sepgsql/sepgsql.sql.in | 37 + contrib/sepgsql/sql/alter.sql | 197 + contrib/sepgsql/sql/ddl.sql | 125 + contrib/sepgsql/sql/dml.sql | 257 + contrib/sepgsql/sql/label.sql | 294 + contrib/sepgsql/sql/misc.sql | 45 + contrib/sepgsql/sql/truncate.sql | 24 + contrib/sepgsql/test_sepgsql | 306 + contrib/sepgsql/uavc.c | 521 + contrib/spi/Makefile | 28 + contrib/spi/autoinc--1.0.sql | 9 + contrib/spi/autoinc.c | 127 + contrib/spi/autoinc.control | 5 + contrib/spi/autoinc.example | 35 + contrib/spi/insert_username--1.0.sql | 9 + contrib/spi/insert_username.c | 92 + contrib/spi/insert_username.control | 5 + contrib/spi/insert_username.example | 20 + contrib/spi/meson.build | 92 + contrib/spi/moddatetime--1.0.sql | 9 + contrib/spi/moddatetime.c | 130 + contrib/spi/moddatetime.control | 5 + contrib/spi/moddatetime.example | 27 + contrib/spi/refint--1.0.sql | 14 + contrib/spi/refint.c | 654 + contrib/spi/refint.control | 5 + contrib/spi/refint.example | 82 + contrib/sslinfo/Makefile | 23 + contrib/sslinfo/meson.build | 31 + contrib/sslinfo/sslinfo--1.0--1.1.sql | 12 + contrib/sslinfo/sslinfo--1.1--1.2.sql | 15 + contrib/sslinfo/sslinfo--1.2.sql | 48 + contrib/sslinfo/sslinfo.c | 484 + contrib/sslinfo/sslinfo.control | 5 + contrib/start-scripts/freebsd | 66 + contrib/start-scripts/linux | 124 + contrib/start-scripts/macos/README | 24 + .../macos/org.postgresql.postgres.plist | 17 + contrib/start-scripts/macos/postgres-wrapper.sh | 25 + contrib/tablefunc/.gitignore | 4 + contrib/tablefunc/Makefile | 22 + contrib/tablefunc/data/connectby_int.data | 9 + contrib/tablefunc/data/connectby_text.data | 9 + contrib/tablefunc/data/ct.data | 18 + contrib/tablefunc/expected/tablefunc.out | 430 + contrib/tablefunc/meson.build | 34 + contrib/tablefunc/sql/tablefunc.sql | 208 + contrib/tablefunc/tablefunc--1.0.sql | 88 + contrib/tablefunc/tablefunc.c | 1591 +++ contrib/tablefunc/tablefunc.control | 6 + contrib/tablefunc/tablefunc.h | 39 + contrib/tcn/.gitignore | 6 + contrib/tcn/Makefile | 21 + contrib/tcn/expected/tcn.out | 13 + contrib/tcn/meson.build | 35 + contrib/tcn/specs/tcn.spec | 28 + contrib/tcn/tcn--1.0.sql | 9 + contrib/tcn/tcn.c | 180 + contrib/tcn/tcn.control | 6 + contrib/test_decoding/.gitignore | 6 + contrib/test_decoding/Makefile | 37 + contrib/test_decoding/expected/binary.out | 35 + .../expected/catalog_change_snapshot.out | 143 + .../test_decoding/expected/concurrent_ddl_dml.out | 799 ++ .../test_decoding/expected/concurrent_stream.out | 24 + contrib/test_decoding/expected/ddl.out | 845 ++ .../test_decoding/expected/decoding_in_xact.out | 87 + .../test_decoding/expected/decoding_into_rel.out | 111 + contrib/test_decoding/expected/delayed_startup.out | 50 + contrib/test_decoding/expected/messages.out | 99 + contrib/test_decoding/expected/mxact.out | 90 + contrib/test_decoding/expected/oldest_xmin.out | 40 + contrib/test_decoding/expected/ondisk_startup.out | 66 + contrib/test_decoding/expected/permissions.out | 134 + contrib/test_decoding/expected/prepared.out | 74 + contrib/test_decoding/expected/replorigin.out | 325 + contrib/test_decoding/expected/rewrite.out | 164 + contrib/test_decoding/expected/slot.out | 408 + .../test_decoding/expected/slot_creation_error.out | 114 + .../test_decoding/expected/snapshot_transfer.out | 61 + contrib/test_decoding/expected/spill.out | 256 + contrib/test_decoding/expected/stats.out | 149 + contrib/test_decoding/expected/stream.out | 115 + .../test_decoding/expected/subxact_without_top.out | 49 + contrib/test_decoding/expected/time.out | 40 + contrib/test_decoding/expected/toast.out | 390 + contrib/test_decoding/expected/truncate.out | 33 + contrib/test_decoding/expected/twophase.out | 223 + .../test_decoding/expected/twophase_snapshot.out | 53 + contrib/test_decoding/expected/twophase_stream.out | 125 + contrib/test_decoding/expected/xact.out | 64 + contrib/test_decoding/logical.conf | 4 + contrib/test_decoding/meson.build | 77 + .../specs/catalog_change_snapshot.spec | 75 + .../test_decoding/specs/concurrent_ddl_dml.spec | 88 + contrib/test_decoding/specs/concurrent_stream.spec | 43 + contrib/test_decoding/specs/delayed_startup.spec | 24 + contrib/test_decoding/specs/mxact.spec | 38 + contrib/test_decoding/specs/oldest_xmin.spec | 42 + contrib/test_decoding/specs/ondisk_startup.spec | 45 + .../test_decoding/specs/slot_creation_error.spec | 41 + contrib/test_decoding/specs/snapshot_transfer.spec | 43 + .../test_decoding/specs/subxact_without_top.spec | 63 + contrib/test_decoding/specs/twophase_snapshot.spec | 53 + contrib/test_decoding/sql/binary.sql | 14 + contrib/test_decoding/sql/ddl.sql | 445 + contrib/test_decoding/sql/decoding_in_xact.sql | 41 + contrib/test_decoding/sql/decoding_into_rel.sql | 42 + contrib/test_decoding/sql/messages.sql | 34 + contrib/test_decoding/sql/permissions.sql | 69 + contrib/test_decoding/sql/prepared.sql | 50 + contrib/test_decoding/sql/replorigin.sql | 148 + contrib/test_decoding/sql/rewrite.sql | 107 + contrib/test_decoding/sql/slot.sql | 178 + contrib/test_decoding/sql/spill.sql | 179 + contrib/test_decoding/sql/stats.sql | 56 + contrib/test_decoding/sql/stream.sql | 48 + contrib/test_decoding/sql/time.sql | 22 + contrib/test_decoding/sql/toast.sql | 327 + contrib/test_decoding/sql/truncate.sql | 14 + contrib/test_decoding/sql/twophase.sql | 114 + contrib/test_decoding/sql/twophase_stream.sql | 45 + contrib/test_decoding/sql/xact.sql | 33 + contrib/test_decoding/t/001_repl_stats.pl | 121 + contrib/test_decoding/test_decoding.c | 976 ++ contrib/tsm_system_rows/.gitignore | 4 + contrib/tsm_system_rows/Makefile | 23 + .../tsm_system_rows/expected/tsm_system_rows.out | 83 + contrib/tsm_system_rows/meson.build | 34 + contrib/tsm_system_rows/sql/tsm_system_rows.sql | 39 + contrib/tsm_system_rows/tsm_system_rows--1.0.sql | 9 + contrib/tsm_system_rows/tsm_system_rows.c | 340 + contrib/tsm_system_rows/tsm_system_rows.control | 6 + contrib/tsm_system_time/.gitignore | 4 + contrib/tsm_system_time/Makefile | 25 + .../tsm_system_time/expected/tsm_system_time.out | 100 + contrib/tsm_system_time/meson.build | 34 + contrib/tsm_system_time/sql/tsm_system_time.sql | 51 + contrib/tsm_system_time/tsm_system_time--1.0.sql | 9 + contrib/tsm_system_time/tsm_system_time.c | 353 + contrib/tsm_system_time/tsm_system_time.control | 6 + contrib/unaccent/.gitignore | 7 + contrib/unaccent/Makefile | 49 + contrib/unaccent/expected/unaccent.out | 143 + contrib/unaccent/generate_unaccent_rules.py | 285 + contrib/unaccent/meson.build | 42 + contrib/unaccent/sql/unaccent.sql | 34 + contrib/unaccent/unaccent--1.0--1.1.sql | 9 + contrib/unaccent/unaccent--1.1.sql | 34 + contrib/unaccent/unaccent.c | 434 + contrib/unaccent/unaccent.control | 6 + contrib/unaccent/unaccent.rules | 1650 +++ contrib/uuid-ossp/.gitignore | 4 + contrib/uuid-ossp/Makefile | 25 + contrib/uuid-ossp/expected/uuid_ossp.out | 139 + contrib/uuid-ossp/meson.build | 41 + contrib/uuid-ossp/sql/uuid_ossp.sql | 75 + contrib/uuid-ossp/uuid-ossp--1.0--1.1.sql | 15 + contrib/uuid-ossp/uuid-ossp--1.1.sql | 54 + contrib/uuid-ossp/uuid-ossp.c | 552 + contrib/uuid-ossp/uuid-ossp.control | 6 + contrib/vacuumlo/.gitignore | 3 + contrib/vacuumlo/Makefile | 25 + contrib/vacuumlo/meson.build | 29 + contrib/vacuumlo/t/001_basic.pl | 14 + contrib/vacuumlo/vacuumlo.c | 544 + contrib/xml2/.gitignore | 4 + contrib/xml2/Makefile | 26 + contrib/xml2/expected/xml2.out | 224 + contrib/xml2/expected/xml2_1.out | 168 + contrib/xml2/meson.build | 43 + contrib/xml2/sql/xml2.sql | 139 + contrib/xml2/xml2--1.0--1.1.sql | 18 + contrib/xml2/xml2--1.1.sql | 73 + contrib/xml2/xml2.control | 6 + contrib/xml2/xpath.c | 745 ++ contrib/xml2/xslt_proc.c | 256 + 1067 files changed, 252443 insertions(+) create mode 100644 contrib/Makefile create mode 100644 contrib/README create mode 100644 contrib/adminpack/.gitignore create mode 100644 contrib/adminpack/Makefile create mode 100644 contrib/adminpack/adminpack--1.0--1.1.sql create mode 100644 contrib/adminpack/adminpack--1.0.sql create mode 100644 contrib/adminpack/adminpack--1.1--2.0.sql create mode 100644 contrib/adminpack/adminpack--2.0--2.1.sql create mode 100644 contrib/adminpack/adminpack.c create mode 100644 contrib/adminpack/adminpack.control create mode 100644 contrib/adminpack/expected/adminpack.out create mode 100644 contrib/adminpack/meson.build create mode 100644 contrib/adminpack/sql/adminpack.sql create mode 100644 contrib/amcheck/.gitignore create mode 100644 contrib/amcheck/Makefile create mode 100644 contrib/amcheck/amcheck--1.0--1.1.sql create mode 100644 contrib/amcheck/amcheck--1.0.sql create mode 100644 contrib/amcheck/amcheck--1.1--1.2.sql create mode 100644 contrib/amcheck/amcheck--1.2--1.3.sql create mode 100644 contrib/amcheck/amcheck.control create mode 100644 contrib/amcheck/expected/check.out create mode 100644 contrib/amcheck/expected/check_btree.out create mode 100644 contrib/amcheck/expected/check_heap.out create mode 100644 contrib/amcheck/meson.build create mode 100644 contrib/amcheck/sql/check.sql create mode 100644 contrib/amcheck/sql/check_btree.sql create mode 100644 contrib/amcheck/sql/check_heap.sql create mode 100644 contrib/amcheck/t/001_verify_heapam.pl create mode 100644 contrib/amcheck/t/002_cic.pl create mode 100644 contrib/amcheck/t/003_cic_2pc.pl create mode 100644 contrib/amcheck/t/005_pitr.pl create mode 100644 contrib/amcheck/verify_heapam.c create mode 100644 contrib/amcheck/verify_nbtree.c create mode 100644 contrib/auth_delay/Makefile create mode 100644 contrib/auth_delay/auth_delay.c create mode 100644 contrib/auth_delay/meson.build create mode 100644 contrib/auto_explain/.gitignore create mode 100644 contrib/auto_explain/Makefile create mode 100644 contrib/auto_explain/auto_explain.c create mode 100644 contrib/auto_explain/meson.build create mode 100644 contrib/auto_explain/t/001_auto_explain.pl create mode 100644 contrib/basebackup_to_shell/.gitignore create mode 100644 contrib/basebackup_to_shell/Makefile create mode 100644 contrib/basebackup_to_shell/basebackup_to_shell.c create mode 100644 contrib/basebackup_to_shell/meson.build create mode 100644 contrib/basebackup_to_shell/t/001_basic.pl create mode 100644 contrib/basic_archive/.gitignore create mode 100644 contrib/basic_archive/Makefile create mode 100644 contrib/basic_archive/basic_archive.c create mode 100644 contrib/basic_archive/basic_archive.conf create mode 100644 contrib/basic_archive/expected/basic_archive.out create mode 100644 contrib/basic_archive/meson.build create mode 100644 contrib/basic_archive/sql/basic_archive.sql create mode 100644 contrib/bloom/.gitignore create mode 100644 contrib/bloom/Makefile create mode 100644 contrib/bloom/blcost.c create mode 100644 contrib/bloom/blinsert.c create mode 100644 contrib/bloom/bloom--1.0.sql create mode 100644 contrib/bloom/bloom.control create mode 100644 contrib/bloom/bloom.h create mode 100644 contrib/bloom/blscan.c create mode 100644 contrib/bloom/blutils.c create mode 100644 contrib/bloom/blvacuum.c create mode 100644 contrib/bloom/blvalidate.c create mode 100644 contrib/bloom/expected/bloom.out create mode 100644 contrib/bloom/meson.build create mode 100644 contrib/bloom/sql/bloom.sql create mode 100644 contrib/bloom/t/001_wal.pl create mode 100644 contrib/bool_plperl/.gitignore create mode 100644 contrib/bool_plperl/Makefile create mode 100644 contrib/bool_plperl/bool_plperl--1.0.sql create mode 100644 contrib/bool_plperl/bool_plperl.c create mode 100644 contrib/bool_plperl/bool_plperl.control create mode 100644 contrib/bool_plperl/bool_plperlu--1.0.sql create mode 100644 contrib/bool_plperl/bool_plperlu.control create mode 100644 contrib/bool_plperl/expected/bool_plperl.out create mode 100644 contrib/bool_plperl/expected/bool_plperlu.out create mode 100644 contrib/bool_plperl/meson.build create mode 100644 contrib/bool_plperl/sql/bool_plperl.sql create mode 100644 contrib/bool_plperl/sql/bool_plperlu.sql create mode 100644 contrib/btree_gin/.gitignore create mode 100644 contrib/btree_gin/Makefile create mode 100644 contrib/btree_gin/btree_gin--1.0--1.1.sql create mode 100644 contrib/btree_gin/btree_gin--1.0.sql create mode 100644 contrib/btree_gin/btree_gin--1.1--1.2.sql create mode 100644 contrib/btree_gin/btree_gin--1.2--1.3.sql create mode 100644 contrib/btree_gin/btree_gin.c create mode 100644 contrib/btree_gin/btree_gin.control create mode 100644 contrib/btree_gin/expected/bit.out create mode 100644 contrib/btree_gin/expected/bool.out create mode 100644 contrib/btree_gin/expected/bpchar.out create mode 100644 contrib/btree_gin/expected/bytea.out create mode 100644 contrib/btree_gin/expected/char.out create mode 100644 contrib/btree_gin/expected/cidr.out create mode 100644 contrib/btree_gin/expected/date.out create mode 100644 contrib/btree_gin/expected/enum.out create mode 100644 contrib/btree_gin/expected/float4.out create mode 100644 contrib/btree_gin/expected/float8.out create mode 100644 contrib/btree_gin/expected/inet.out create mode 100644 contrib/btree_gin/expected/install_btree_gin.out create mode 100644 contrib/btree_gin/expected/int2.out create mode 100644 contrib/btree_gin/expected/int4.out create mode 100644 contrib/btree_gin/expected/int8.out create mode 100644 contrib/btree_gin/expected/interval.out create mode 100644 contrib/btree_gin/expected/macaddr.out create mode 100644 contrib/btree_gin/expected/macaddr8.out create mode 100644 contrib/btree_gin/expected/money.out create mode 100644 contrib/btree_gin/expected/name.out create mode 100644 contrib/btree_gin/expected/numeric.out create mode 100644 contrib/btree_gin/expected/oid.out create mode 100644 contrib/btree_gin/expected/text.out create mode 100644 contrib/btree_gin/expected/time.out create mode 100644 contrib/btree_gin/expected/timestamp.out create mode 100644 contrib/btree_gin/expected/timestamptz.out create mode 100644 contrib/btree_gin/expected/timetz.out create mode 100644 contrib/btree_gin/expected/uuid.out create mode 100644 contrib/btree_gin/expected/varbit.out create mode 100644 contrib/btree_gin/expected/varchar.out create mode 100644 contrib/btree_gin/meson.build create mode 100644 contrib/btree_gin/sql/bit.sql create mode 100644 contrib/btree_gin/sql/bool.sql create mode 100644 contrib/btree_gin/sql/bpchar.sql create mode 100644 contrib/btree_gin/sql/bytea.sql create mode 100644 contrib/btree_gin/sql/char.sql create mode 100644 contrib/btree_gin/sql/cidr.sql create mode 100644 contrib/btree_gin/sql/date.sql create mode 100644 contrib/btree_gin/sql/enum.sql create mode 100644 contrib/btree_gin/sql/float4.sql create mode 100644 contrib/btree_gin/sql/float8.sql create mode 100644 contrib/btree_gin/sql/inet.sql create mode 100644 contrib/btree_gin/sql/install_btree_gin.sql create mode 100644 contrib/btree_gin/sql/int2.sql create mode 100644 contrib/btree_gin/sql/int4.sql create mode 100644 contrib/btree_gin/sql/int8.sql create mode 100644 contrib/btree_gin/sql/interval.sql create mode 100644 contrib/btree_gin/sql/macaddr.sql create mode 100644 contrib/btree_gin/sql/macaddr8.sql create mode 100644 contrib/btree_gin/sql/money.sql create mode 100644 contrib/btree_gin/sql/name.sql create mode 100644 contrib/btree_gin/sql/numeric.sql create mode 100644 contrib/btree_gin/sql/oid.sql create mode 100644 contrib/btree_gin/sql/text.sql create mode 100644 contrib/btree_gin/sql/time.sql create mode 100644 contrib/btree_gin/sql/timestamp.sql create mode 100644 contrib/btree_gin/sql/timestamptz.sql create mode 100644 contrib/btree_gin/sql/timetz.sql create mode 100644 contrib/btree_gin/sql/uuid.sql create mode 100644 contrib/btree_gin/sql/varbit.sql create mode 100644 contrib/btree_gin/sql/varchar.sql create mode 100644 contrib/btree_gist/.gitignore create mode 100644 contrib/btree_gist/Makefile create mode 100644 contrib/btree_gist/btree_bit.c create mode 100644 contrib/btree_gist/btree_bool.c create mode 100644 contrib/btree_gist/btree_bytea.c create mode 100644 contrib/btree_gist/btree_cash.c create mode 100644 contrib/btree_gist/btree_date.c create mode 100644 contrib/btree_gist/btree_enum.c create mode 100644 contrib/btree_gist/btree_float4.c create mode 100644 contrib/btree_gist/btree_float8.c create mode 100644 contrib/btree_gist/btree_gist--1.0--1.1.sql create mode 100644 contrib/btree_gist/btree_gist--1.1--1.2.sql create mode 100644 contrib/btree_gist/btree_gist--1.2--1.3.sql create mode 100644 contrib/btree_gist/btree_gist--1.2.sql create mode 100644 contrib/btree_gist/btree_gist--1.3--1.4.sql create mode 100644 contrib/btree_gist/btree_gist--1.4--1.5.sql create mode 100644 contrib/btree_gist/btree_gist--1.5--1.6.sql create mode 100644 contrib/btree_gist/btree_gist--1.6--1.7.sql create mode 100644 contrib/btree_gist/btree_gist.c create mode 100644 contrib/btree_gist/btree_gist.control create mode 100644 contrib/btree_gist/btree_gist.h create mode 100644 contrib/btree_gist/btree_inet.c create mode 100644 contrib/btree_gist/btree_int2.c create mode 100644 contrib/btree_gist/btree_int4.c create mode 100644 contrib/btree_gist/btree_int8.c create mode 100644 contrib/btree_gist/btree_interval.c create mode 100644 contrib/btree_gist/btree_macaddr.c create mode 100644 contrib/btree_gist/btree_macaddr8.c create mode 100644 contrib/btree_gist/btree_numeric.c create mode 100644 contrib/btree_gist/btree_oid.c create mode 100644 contrib/btree_gist/btree_text.c create mode 100644 contrib/btree_gist/btree_time.c create mode 100644 contrib/btree_gist/btree_ts.c create mode 100644 contrib/btree_gist/btree_utils_num.c create mode 100644 contrib/btree_gist/btree_utils_num.h create mode 100644 contrib/btree_gist/btree_utils_var.c create mode 100644 contrib/btree_gist/btree_utils_var.h create mode 100644 contrib/btree_gist/btree_uuid.c create mode 100644 contrib/btree_gist/data/bit.data create mode 100644 contrib/btree_gist/data/cash.data create mode 100644 contrib/btree_gist/data/char.data create mode 100644 contrib/btree_gist/data/date.data create mode 100644 contrib/btree_gist/data/enum.data create mode 100644 contrib/btree_gist/data/float4.data create mode 100644 contrib/btree_gist/data/float8.data create mode 100644 contrib/btree_gist/data/inet.data create mode 100644 contrib/btree_gist/data/int2.data create mode 100644 contrib/btree_gist/data/int4.data create mode 100644 contrib/btree_gist/data/int8.data create mode 100644 contrib/btree_gist/data/interval.data create mode 100644 contrib/btree_gist/data/macaddr.data create mode 100644 contrib/btree_gist/data/numeric.data create mode 100644 contrib/btree_gist/data/text.data create mode 100644 contrib/btree_gist/data/time.data create mode 100644 contrib/btree_gist/data/timestamp.data create mode 100644 contrib/btree_gist/data/timestamptz.data create mode 100644 contrib/btree_gist/data/timetz.data create mode 100644 contrib/btree_gist/data/uuid.data create mode 100644 contrib/btree_gist/data/varbit.data create mode 100644 contrib/btree_gist/expected/bit.out create mode 100644 contrib/btree_gist/expected/bool.out create mode 100644 contrib/btree_gist/expected/bytea.out create mode 100644 contrib/btree_gist/expected/cash.out create mode 100644 contrib/btree_gist/expected/char.out create mode 100644 contrib/btree_gist/expected/char_1.out create mode 100644 contrib/btree_gist/expected/cidr.out create mode 100644 contrib/btree_gist/expected/date.out create mode 100644 contrib/btree_gist/expected/enum.out create mode 100644 contrib/btree_gist/expected/float4.out create mode 100644 contrib/btree_gist/expected/float8.out create mode 100644 contrib/btree_gist/expected/inet.out create mode 100644 contrib/btree_gist/expected/init.out create mode 100644 contrib/btree_gist/expected/int2.out create mode 100644 contrib/btree_gist/expected/int4.out create mode 100644 contrib/btree_gist/expected/int8.out create mode 100644 contrib/btree_gist/expected/interval.out create mode 100644 contrib/btree_gist/expected/macaddr.out create mode 100644 contrib/btree_gist/expected/macaddr8.out create mode 100644 contrib/btree_gist/expected/not_equal.out create mode 100644 contrib/btree_gist/expected/numeric.out create mode 100644 contrib/btree_gist/expected/oid.out create mode 100644 contrib/btree_gist/expected/text.out create mode 100644 contrib/btree_gist/expected/text_1.out create mode 100644 contrib/btree_gist/expected/time.out create mode 100644 contrib/btree_gist/expected/timestamp.out create mode 100644 contrib/btree_gist/expected/timestamptz.out create mode 100644 contrib/btree_gist/expected/timetz.out create mode 100644 contrib/btree_gist/expected/uuid.out create mode 100644 contrib/btree_gist/expected/varbit.out create mode 100644 contrib/btree_gist/expected/varchar.out create mode 100644 contrib/btree_gist/expected/varchar_1.out create mode 100644 contrib/btree_gist/meson.build create mode 100644 contrib/btree_gist/sql/bit.sql create mode 100644 contrib/btree_gist/sql/bool.sql create mode 100644 contrib/btree_gist/sql/bytea.sql create mode 100644 contrib/btree_gist/sql/cash.sql create mode 100644 contrib/btree_gist/sql/char.sql create mode 100644 contrib/btree_gist/sql/cidr.sql create mode 100644 contrib/btree_gist/sql/date.sql create mode 100644 contrib/btree_gist/sql/enum.sql create mode 100644 contrib/btree_gist/sql/float4.sql create mode 100644 contrib/btree_gist/sql/float8.sql create mode 100644 contrib/btree_gist/sql/inet.sql create mode 100644 contrib/btree_gist/sql/init.sql create mode 100644 contrib/btree_gist/sql/int2.sql create mode 100644 contrib/btree_gist/sql/int4.sql create mode 100644 contrib/btree_gist/sql/int8.sql create mode 100644 contrib/btree_gist/sql/interval.sql create mode 100644 contrib/btree_gist/sql/macaddr.sql create mode 100644 contrib/btree_gist/sql/macaddr8.sql create mode 100644 contrib/btree_gist/sql/not_equal.sql create mode 100644 contrib/btree_gist/sql/numeric.sql create mode 100644 contrib/btree_gist/sql/oid.sql create mode 100644 contrib/btree_gist/sql/text.sql create mode 100644 contrib/btree_gist/sql/time.sql create mode 100644 contrib/btree_gist/sql/timestamp.sql create mode 100644 contrib/btree_gist/sql/timestamptz.sql create mode 100644 contrib/btree_gist/sql/timetz.sql create mode 100644 contrib/btree_gist/sql/uuid.sql create mode 100644 contrib/btree_gist/sql/varbit.sql create mode 100644 contrib/btree_gist/sql/varchar.sql create mode 100644 contrib/citext/.gitignore create mode 100644 contrib/citext/Makefile create mode 100644 contrib/citext/citext--1.0--1.1.sql create mode 100644 contrib/citext/citext--1.1--1.2.sql create mode 100644 contrib/citext/citext--1.2--1.3.sql create mode 100644 contrib/citext/citext--1.3--1.4.sql create mode 100644 contrib/citext/citext--1.4--1.5.sql create mode 100644 contrib/citext/citext--1.4.sql create mode 100644 contrib/citext/citext--1.5--1.6.sql create mode 100644 contrib/citext/citext.c create mode 100644 contrib/citext/citext.control create mode 100644 contrib/citext/expected/citext.out create mode 100644 contrib/citext/expected/citext_1.out create mode 100644 contrib/citext/expected/citext_utf8.out create mode 100644 contrib/citext/expected/citext_utf8_1.out create mode 100644 contrib/citext/expected/create_index_acl.out create mode 100644 contrib/citext/meson.build create mode 100644 contrib/citext/sql/citext.sql create mode 100644 contrib/citext/sql/citext_utf8.sql create mode 100644 contrib/citext/sql/create_index_acl.sql create mode 100644 contrib/contrib-global.mk create mode 100644 contrib/cube/.gitignore create mode 100644 contrib/cube/CHANGES create mode 100644 contrib/cube/Makefile create mode 100644 contrib/cube/cube--1.0--1.1.sql create mode 100644 contrib/cube/cube--1.1--1.2.sql create mode 100644 contrib/cube/cube--1.2--1.3.sql create mode 100644 contrib/cube/cube--1.2.sql create mode 100644 contrib/cube/cube--1.3--1.4.sql create mode 100644 contrib/cube/cube--1.4--1.5.sql create mode 100644 contrib/cube/cube.c create mode 100644 contrib/cube/cube.control create mode 100644 contrib/cube/cubedata.h create mode 100644 contrib/cube/cubeparse.c create mode 100644 contrib/cube/cubeparse.h create mode 100644 contrib/cube/cubeparse.y create mode 100644 contrib/cube/cubescan.c create mode 100644 contrib/cube/cubescan.l create mode 100644 contrib/cube/data/test_cube.data create mode 100644 contrib/cube/expected/cube.out create mode 100644 contrib/cube/expected/cube_sci.out create mode 100644 contrib/cube/meson.build create mode 100644 contrib/cube/sql/cube.sql create mode 100644 contrib/cube/sql/cube_sci.sql create mode 100644 contrib/dblink/.gitignore create mode 100644 contrib/dblink/Makefile create mode 100644 contrib/dblink/dblink--1.0--1.1.sql create mode 100644 contrib/dblink/dblink--1.1--1.2.sql create mode 100644 contrib/dblink/dblink--1.2.sql create mode 100644 contrib/dblink/dblink.c create mode 100644 contrib/dblink/dblink.control create mode 100644 contrib/dblink/expected/dblink.out create mode 100644 contrib/dblink/meson.build create mode 100644 contrib/dblink/pg_service.conf create mode 100644 contrib/dblink/sql/dblink.sql create mode 100644 contrib/dict_int/.gitignore create mode 100644 contrib/dict_int/Makefile create mode 100644 contrib/dict_int/dict_int--1.0.sql create mode 100644 contrib/dict_int/dict_int.c create mode 100644 contrib/dict_int/dict_int.control create mode 100644 contrib/dict_int/expected/dict_int.out create mode 100644 contrib/dict_int/meson.build create mode 100644 contrib/dict_int/sql/dict_int.sql create mode 100644 contrib/dict_xsyn/.gitignore create mode 100644 contrib/dict_xsyn/Makefile create mode 100644 contrib/dict_xsyn/dict_xsyn--1.0.sql create mode 100644 contrib/dict_xsyn/dict_xsyn.c create mode 100644 contrib/dict_xsyn/dict_xsyn.control create mode 100644 contrib/dict_xsyn/expected/dict_xsyn.out create mode 100644 contrib/dict_xsyn/meson.build create mode 100644 contrib/dict_xsyn/sql/dict_xsyn.sql create mode 100644 contrib/dict_xsyn/xsyn_sample.rules create mode 100644 contrib/earthdistance/.gitignore create mode 100644 contrib/earthdistance/Makefile create mode 100644 contrib/earthdistance/earthdistance--1.0--1.1.sql create mode 100644 contrib/earthdistance/earthdistance--1.1.sql create mode 100644 contrib/earthdistance/earthdistance.c create mode 100644 contrib/earthdistance/earthdistance.control create mode 100644 contrib/earthdistance/expected/earthdistance.out create mode 100644 contrib/earthdistance/meson.build create mode 100644 contrib/earthdistance/sql/earthdistance.sql create mode 100644 contrib/file_fdw/.gitignore create mode 100644 contrib/file_fdw/Makefile create mode 100644 contrib/file_fdw/data/agg.bad create mode 100644 contrib/file_fdw/data/agg.csv create mode 100644 contrib/file_fdw/data/agg.data create mode 100644 contrib/file_fdw/data/copy_default.csv create mode 100644 contrib/file_fdw/data/list1.csv create mode 100644 contrib/file_fdw/data/list2.bad create mode 100644 contrib/file_fdw/data/list2.csv create mode 100644 contrib/file_fdw/data/text.csv create mode 100644 contrib/file_fdw/expected/file_fdw.out create mode 100644 contrib/file_fdw/file_fdw--1.0.sql create mode 100644 contrib/file_fdw/file_fdw.c create mode 100644 contrib/file_fdw/file_fdw.control create mode 100644 contrib/file_fdw/meson.build create mode 100644 contrib/file_fdw/sql/file_fdw.sql create mode 100644 contrib/fuzzystrmatch/.gitignore create mode 100644 contrib/fuzzystrmatch/Makefile create mode 100644 contrib/fuzzystrmatch/daitch_mokotoff.c create mode 100644 contrib/fuzzystrmatch/daitch_mokotoff.h create mode 100755 contrib/fuzzystrmatch/daitch_mokotoff_header.pl create mode 100644 contrib/fuzzystrmatch/dmetaphone.c create mode 100644 contrib/fuzzystrmatch/expected/fuzzystrmatch.out create mode 100644 contrib/fuzzystrmatch/expected/fuzzystrmatch_utf8.out create mode 100644 contrib/fuzzystrmatch/expected/fuzzystrmatch_utf8_1.out create mode 100644 contrib/fuzzystrmatch/fuzzystrmatch--1.0--1.1.sql create mode 100644 contrib/fuzzystrmatch/fuzzystrmatch--1.1--1.2.sql create mode 100644 contrib/fuzzystrmatch/fuzzystrmatch--1.1.sql create mode 100644 contrib/fuzzystrmatch/fuzzystrmatch.c create mode 100644 contrib/fuzzystrmatch/fuzzystrmatch.control create mode 100644 contrib/fuzzystrmatch/meson.build create mode 100644 contrib/fuzzystrmatch/sql/fuzzystrmatch.sql create mode 100644 contrib/fuzzystrmatch/sql/fuzzystrmatch_utf8.sql create mode 100644 contrib/hstore/.gitignore create mode 100644 contrib/hstore/Makefile create mode 100644 contrib/hstore/data/hstore.data create mode 100644 contrib/hstore/expected/hstore.out create mode 100644 contrib/hstore/expected/hstore_utf8.out create mode 100644 contrib/hstore/expected/hstore_utf8_1.out create mode 100644 contrib/hstore/hstore--1.1--1.2.sql create mode 100644 contrib/hstore/hstore--1.2--1.3.sql create mode 100644 contrib/hstore/hstore--1.3--1.4.sql create mode 100644 contrib/hstore/hstore--1.4--1.5.sql create mode 100644 contrib/hstore/hstore--1.4.sql create mode 100644 contrib/hstore/hstore--1.5--1.6.sql create mode 100644 contrib/hstore/hstore--1.6--1.7.sql create mode 100644 contrib/hstore/hstore--1.7--1.8.sql create mode 100644 contrib/hstore/hstore.control create mode 100644 contrib/hstore/hstore.h create mode 100644 contrib/hstore/hstore_compat.c create mode 100644 contrib/hstore/hstore_gin.c create mode 100644 contrib/hstore/hstore_gist.c create mode 100644 contrib/hstore/hstore_io.c create mode 100644 contrib/hstore/hstore_op.c create mode 100644 contrib/hstore/hstore_subs.c create mode 100644 contrib/hstore/meson.build create mode 100644 contrib/hstore/sql/hstore.sql create mode 100644 contrib/hstore/sql/hstore_utf8.sql create mode 100644 contrib/hstore_plperl/.gitignore create mode 100644 contrib/hstore_plperl/Makefile create mode 100644 contrib/hstore_plperl/expected/create_transform.out create mode 100644 contrib/hstore_plperl/expected/hstore_plperl.out create mode 100644 contrib/hstore_plperl/expected/hstore_plperlu.out create mode 100644 contrib/hstore_plperl/hstore_plperl--1.0.sql create mode 100644 contrib/hstore_plperl/hstore_plperl.c create mode 100644 contrib/hstore_plperl/hstore_plperl.control create mode 100644 contrib/hstore_plperl/hstore_plperlu--1.0.sql create mode 100644 contrib/hstore_plperl/hstore_plperlu.control create mode 100644 contrib/hstore_plperl/meson.build create mode 100644 contrib/hstore_plperl/sql/create_transform.sql create mode 100644 contrib/hstore_plperl/sql/hstore_plperl.sql create mode 100644 contrib/hstore_plperl/sql/hstore_plperlu.sql create mode 100644 contrib/hstore_plpython/.gitignore create mode 100644 contrib/hstore_plpython/Makefile create mode 100644 contrib/hstore_plpython/expected/hstore_plpython.out create mode 100644 contrib/hstore_plpython/hstore_plpython.c create mode 100644 contrib/hstore_plpython/hstore_plpython3u--1.0.sql create mode 100644 contrib/hstore_plpython/hstore_plpython3u.control create mode 100644 contrib/hstore_plpython/meson.build create mode 100644 contrib/hstore_plpython/sql/hstore_plpython.sql create mode 100644 contrib/intagg/Makefile create mode 100644 contrib/intagg/intagg--1.0--1.1.sql create mode 100644 contrib/intagg/intagg--1.1.sql create mode 100644 contrib/intagg/intagg.control create mode 100644 contrib/intagg/meson.build create mode 100644 contrib/intarray/.gitignore create mode 100644 contrib/intarray/Makefile create mode 100644 contrib/intarray/_int.h create mode 100644 contrib/intarray/_int_bool.c create mode 100644 contrib/intarray/_int_gin.c create mode 100644 contrib/intarray/_int_gist.c create mode 100644 contrib/intarray/_int_op.c create mode 100644 contrib/intarray/_int_selfuncs.c create mode 100644 contrib/intarray/_int_tool.c create mode 100644 contrib/intarray/_intbig_gist.c create mode 100755 contrib/intarray/bench/bench.pl create mode 100755 contrib/intarray/bench/create_test.pl create mode 100644 contrib/intarray/data/test__int.data create mode 100644 contrib/intarray/expected/_int.out create mode 100644 contrib/intarray/intarray--1.0--1.1.sql create mode 100644 contrib/intarray/intarray--1.1--1.2.sql create mode 100644 contrib/intarray/intarray--1.2--1.3.sql create mode 100644 contrib/intarray/intarray--1.2.sql create mode 100644 contrib/intarray/intarray--1.3--1.4.sql create mode 100644 contrib/intarray/intarray--1.4--1.5.sql create mode 100644 contrib/intarray/intarray.control create mode 100644 contrib/intarray/meson.build create mode 100644 contrib/intarray/sql/_int.sql create mode 100644 contrib/isn/.gitignore create mode 100644 contrib/isn/EAN13.h create mode 100644 contrib/isn/ISBN.h create mode 100644 contrib/isn/ISMN.h create mode 100644 contrib/isn/ISSN.h create mode 100644 contrib/isn/Makefile create mode 100644 contrib/isn/UPC.h create mode 100644 contrib/isn/expected/isn.out create mode 100644 contrib/isn/isn--1.0--1.1.sql create mode 100644 contrib/isn/isn--1.1--1.2.sql create mode 100644 contrib/isn/isn--1.1.sql create mode 100644 contrib/isn/isn.c create mode 100644 contrib/isn/isn.control create mode 100644 contrib/isn/isn.h create mode 100644 contrib/isn/meson.build create mode 100644 contrib/isn/sql/isn.sql create mode 100644 contrib/jsonb_plperl/.gitignore create mode 100644 contrib/jsonb_plperl/Makefile create mode 100644 contrib/jsonb_plperl/expected/jsonb_plperl.out create mode 100644 contrib/jsonb_plperl/expected/jsonb_plperlu.out create mode 100644 contrib/jsonb_plperl/jsonb_plperl--1.0.sql create mode 100644 contrib/jsonb_plperl/jsonb_plperl.c create mode 100644 contrib/jsonb_plperl/jsonb_plperl.control create mode 100644 contrib/jsonb_plperl/jsonb_plperlu--1.0.sql create mode 100644 contrib/jsonb_plperl/jsonb_plperlu.control create mode 100644 contrib/jsonb_plperl/meson.build create mode 100644 contrib/jsonb_plperl/sql/jsonb_plperl.sql create mode 100644 contrib/jsonb_plperl/sql/jsonb_plperlu.sql create mode 100644 contrib/jsonb_plpython/.gitignore create mode 100644 contrib/jsonb_plpython/Makefile create mode 100644 contrib/jsonb_plpython/expected/jsonb_plpython.out create mode 100644 contrib/jsonb_plpython/jsonb_plpython.c create mode 100644 contrib/jsonb_plpython/jsonb_plpython3u--1.0.sql create mode 100644 contrib/jsonb_plpython/jsonb_plpython3u.control create mode 100644 contrib/jsonb_plpython/meson.build create mode 100644 contrib/jsonb_plpython/sql/jsonb_plpython.sql create mode 100644 contrib/lo/.gitignore create mode 100644 contrib/lo/Makefile create mode 100644 contrib/lo/expected/lo.out create mode 100644 contrib/lo/lo--1.0--1.1.sql create mode 100644 contrib/lo/lo--1.1.sql create mode 100644 contrib/lo/lo.c create mode 100644 contrib/lo/lo.control create mode 100644 contrib/lo/lo_test.sql create mode 100644 contrib/lo/meson.build create mode 100644 contrib/lo/sql/lo.sql create mode 100644 contrib/ltree/.gitignore create mode 100644 contrib/ltree/Makefile create mode 100644 contrib/ltree/_ltree_gist.c create mode 100644 contrib/ltree/_ltree_op.c create mode 100644 contrib/ltree/crc32.c create mode 100644 contrib/ltree/crc32.h create mode 100644 contrib/ltree/data/_ltree.data create mode 100644 contrib/ltree/data/ltree.data create mode 100644 contrib/ltree/expected/ltree.out create mode 100644 contrib/ltree/lquery_op.c create mode 100644 contrib/ltree/ltree--1.0--1.1.sql create mode 100644 contrib/ltree/ltree--1.1--1.2.sql create mode 100644 contrib/ltree/ltree--1.1.sql create mode 100644 contrib/ltree/ltree.control create mode 100644 contrib/ltree/ltree.h create mode 100644 contrib/ltree/ltree_gist.c create mode 100644 contrib/ltree/ltree_io.c create mode 100644 contrib/ltree/ltree_op.c create mode 100644 contrib/ltree/ltreetest.sql create mode 100644 contrib/ltree/ltxtquery_io.c create mode 100644 contrib/ltree/ltxtquery_op.c create mode 100644 contrib/ltree/meson.build create mode 100644 contrib/ltree/sql/ltree.sql create mode 100644 contrib/ltree_plpython/.gitignore create mode 100644 contrib/ltree_plpython/Makefile create mode 100644 contrib/ltree_plpython/expected/ltree_plpython.out create mode 100644 contrib/ltree_plpython/ltree_plpython.c create mode 100644 contrib/ltree_plpython/ltree_plpython3u--1.0.sql create mode 100644 contrib/ltree_plpython/ltree_plpython3u.control create mode 100644 contrib/ltree_plpython/meson.build create mode 100644 contrib/ltree_plpython/sql/ltree_plpython.sql create mode 100644 contrib/meson.build create mode 100644 contrib/oid2name/.gitignore create mode 100644 contrib/oid2name/Makefile create mode 100644 contrib/oid2name/meson.build create mode 100644 contrib/oid2name/oid2name.c create mode 100644 contrib/oid2name/t/001_basic.pl create mode 100644 contrib/old_snapshot/Makefile create mode 100644 contrib/old_snapshot/meson.build create mode 100644 contrib/old_snapshot/old_snapshot--1.0.sql create mode 100644 contrib/old_snapshot/old_snapshot.control create mode 100644 contrib/old_snapshot/time_mapping.c create mode 100644 contrib/pageinspect/.gitignore create mode 100644 contrib/pageinspect/Makefile create mode 100644 contrib/pageinspect/brinfuncs.c create mode 100644 contrib/pageinspect/btreefuncs.c create mode 100644 contrib/pageinspect/expected/brin.out create mode 100644 contrib/pageinspect/expected/btree.out create mode 100644 contrib/pageinspect/expected/checksum.out create mode 100644 contrib/pageinspect/expected/checksum_1.out create mode 100644 contrib/pageinspect/expected/gin.out create mode 100644 contrib/pageinspect/expected/gist.out create mode 100644 contrib/pageinspect/expected/hash.out create mode 100644 contrib/pageinspect/expected/oldextversions.out create mode 100644 contrib/pageinspect/expected/page.out create mode 100644 contrib/pageinspect/fsmfuncs.c create mode 100644 contrib/pageinspect/ginfuncs.c create mode 100644 contrib/pageinspect/gistfuncs.c create mode 100644 contrib/pageinspect/hashfuncs.c create mode 100644 contrib/pageinspect/heapfuncs.c create mode 100644 contrib/pageinspect/meson.build create mode 100644 contrib/pageinspect/pageinspect--1.0--1.1.sql create mode 100644 contrib/pageinspect/pageinspect--1.1--1.2.sql create mode 100644 contrib/pageinspect/pageinspect--1.10--1.11.sql create mode 100644 contrib/pageinspect/pageinspect--1.11--1.12.sql create mode 100644 contrib/pageinspect/pageinspect--1.2--1.3.sql create mode 100644 contrib/pageinspect/pageinspect--1.3--1.4.sql create mode 100644 contrib/pageinspect/pageinspect--1.4--1.5.sql create mode 100644 contrib/pageinspect/pageinspect--1.5--1.6.sql create mode 100644 contrib/pageinspect/pageinspect--1.5.sql create mode 100644 contrib/pageinspect/pageinspect--1.6--1.7.sql create mode 100644 contrib/pageinspect/pageinspect--1.7--1.8.sql create mode 100644 contrib/pageinspect/pageinspect--1.8--1.9.sql create mode 100644 contrib/pageinspect/pageinspect--1.9--1.10.sql create mode 100644 contrib/pageinspect/pageinspect.control create mode 100644 contrib/pageinspect/pageinspect.h create mode 100644 contrib/pageinspect/rawpage.c create mode 100644 contrib/pageinspect/sql/brin.sql create mode 100644 contrib/pageinspect/sql/btree.sql create mode 100644 contrib/pageinspect/sql/checksum.sql create mode 100644 contrib/pageinspect/sql/gin.sql create mode 100644 contrib/pageinspect/sql/gist.sql create mode 100644 contrib/pageinspect/sql/hash.sql create mode 100644 contrib/pageinspect/sql/oldextversions.sql create mode 100644 contrib/pageinspect/sql/page.sql create mode 100644 contrib/passwordcheck/.gitignore create mode 100644 contrib/passwordcheck/Makefile create mode 100644 contrib/passwordcheck/expected/passwordcheck.out create mode 100644 contrib/passwordcheck/meson.build create mode 100644 contrib/passwordcheck/passwordcheck.c create mode 100644 contrib/passwordcheck/sql/passwordcheck.sql create mode 100644 contrib/pg_buffercache/.gitignore create mode 100644 contrib/pg_buffercache/Makefile create mode 100644 contrib/pg_buffercache/expected/pg_buffercache.out create mode 100644 contrib/pg_buffercache/meson.build create mode 100644 contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql create mode 100644 contrib/pg_buffercache/pg_buffercache--1.1--1.2.sql create mode 100644 contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql create mode 100644 contrib/pg_buffercache/pg_buffercache--1.2.sql create mode 100644 contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql create mode 100644 contrib/pg_buffercache/pg_buffercache.control create mode 100644 contrib/pg_buffercache/pg_buffercache_pages.c create mode 100644 contrib/pg_buffercache/sql/pg_buffercache.sql create mode 100644 contrib/pg_freespacemap/.gitignore create mode 100644 contrib/pg_freespacemap/Makefile create mode 100644 contrib/pg_freespacemap/expected/pg_freespacemap.out create mode 100644 contrib/pg_freespacemap/meson.build create mode 100644 contrib/pg_freespacemap/pg_freespacemap--1.0--1.1.sql create mode 100644 contrib/pg_freespacemap/pg_freespacemap--1.1--1.2.sql create mode 100644 contrib/pg_freespacemap/pg_freespacemap--1.1.sql create mode 100644 contrib/pg_freespacemap/pg_freespacemap.c create mode 100644 contrib/pg_freespacemap/pg_freespacemap.conf create mode 100644 contrib/pg_freespacemap/pg_freespacemap.control create mode 100644 contrib/pg_freespacemap/sql/pg_freespacemap.sql create mode 100644 contrib/pg_prewarm/.gitignore create mode 100644 contrib/pg_prewarm/Makefile create mode 100644 contrib/pg_prewarm/autoprewarm.c create mode 100644 contrib/pg_prewarm/meson.build create mode 100644 contrib/pg_prewarm/pg_prewarm--1.0--1.1.sql create mode 100644 contrib/pg_prewarm/pg_prewarm--1.1--1.2.sql create mode 100644 contrib/pg_prewarm/pg_prewarm--1.1.sql create mode 100644 contrib/pg_prewarm/pg_prewarm.c create mode 100644 contrib/pg_prewarm/pg_prewarm.control create mode 100644 contrib/pg_prewarm/t/001_basic.pl create mode 100644 contrib/pg_stat_statements/.gitignore create mode 100644 contrib/pg_stat_statements/Makefile create mode 100644 contrib/pg_stat_statements/expected/cleanup.out create mode 100644 contrib/pg_stat_statements/expected/cursors.out create mode 100644 contrib/pg_stat_statements/expected/dml.out create mode 100644 contrib/pg_stat_statements/expected/level_tracking.out create mode 100644 contrib/pg_stat_statements/expected/oldextversions.out create mode 100644 contrib/pg_stat_statements/expected/planning.out create mode 100644 contrib/pg_stat_statements/expected/select.out create mode 100644 contrib/pg_stat_statements/expected/user_activity.out create mode 100644 contrib/pg_stat_statements/expected/utility.out create mode 100644 contrib/pg_stat_statements/expected/wal.out create mode 100644 contrib/pg_stat_statements/meson.build create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.0--1.1.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.1--1.2.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.2--1.3.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.3--1.4.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.4--1.5.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.4.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.5--1.6.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.6--1.7.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements--1.9--1.10.sql create mode 100644 contrib/pg_stat_statements/pg_stat_statements.c create mode 100644 contrib/pg_stat_statements/pg_stat_statements.conf create mode 100644 contrib/pg_stat_statements/pg_stat_statements.control create mode 100644 contrib/pg_stat_statements/sql/cleanup.sql create mode 100644 contrib/pg_stat_statements/sql/cursors.sql create mode 100644 contrib/pg_stat_statements/sql/dml.sql create mode 100644 contrib/pg_stat_statements/sql/level_tracking.sql create mode 100644 contrib/pg_stat_statements/sql/oldextversions.sql create mode 100644 contrib/pg_stat_statements/sql/planning.sql create mode 100644 contrib/pg_stat_statements/sql/select.sql create mode 100644 contrib/pg_stat_statements/sql/user_activity.sql create mode 100644 contrib/pg_stat_statements/sql/utility.sql create mode 100644 contrib/pg_stat_statements/sql/wal.sql create mode 100644 contrib/pg_surgery/.gitignore create mode 100644 contrib/pg_surgery/Makefile create mode 100644 contrib/pg_surgery/expected/heap_surgery.out create mode 100644 contrib/pg_surgery/heap_surgery.c create mode 100644 contrib/pg_surgery/meson.build create mode 100644 contrib/pg_surgery/pg_surgery--1.0.sql create mode 100644 contrib/pg_surgery/pg_surgery.control create mode 100644 contrib/pg_surgery/sql/heap_surgery.sql create mode 100644 contrib/pg_trgm/.gitignore create mode 100644 contrib/pg_trgm/Makefile create mode 100644 contrib/pg_trgm/data/trgm.data create mode 100644 contrib/pg_trgm/data/trgm2.data create mode 100644 contrib/pg_trgm/expected/pg_strict_word_trgm.out create mode 100644 contrib/pg_trgm/expected/pg_trgm.out create mode 100644 contrib/pg_trgm/expected/pg_word_trgm.out create mode 100644 contrib/pg_trgm/meson.build create mode 100644 contrib/pg_trgm/pg_trgm--1.0--1.1.sql create mode 100644 contrib/pg_trgm/pg_trgm--1.1--1.2.sql create mode 100644 contrib/pg_trgm/pg_trgm--1.2--1.3.sql create mode 100644 contrib/pg_trgm/pg_trgm--1.3--1.4.sql create mode 100644 contrib/pg_trgm/pg_trgm--1.3.sql create mode 100644 contrib/pg_trgm/pg_trgm--1.4--1.5.sql create mode 100644 contrib/pg_trgm/pg_trgm--1.5--1.6.sql create mode 100644 contrib/pg_trgm/pg_trgm.control create mode 100644 contrib/pg_trgm/sql/pg_strict_word_trgm.sql create mode 100644 contrib/pg_trgm/sql/pg_trgm.sql create mode 100644 contrib/pg_trgm/sql/pg_word_trgm.sql create mode 100644 contrib/pg_trgm/trgm.h create mode 100644 contrib/pg_trgm/trgm_gin.c create mode 100644 contrib/pg_trgm/trgm_gist.c create mode 100644 contrib/pg_trgm/trgm_op.c create mode 100644 contrib/pg_trgm/trgm_regexp.c create mode 100644 contrib/pg_visibility/.gitignore create mode 100644 contrib/pg_visibility/Makefile create mode 100644 contrib/pg_visibility/expected/pg_visibility.out create mode 100644 contrib/pg_visibility/meson.build create mode 100644 contrib/pg_visibility/pg_visibility--1.0--1.1.sql create mode 100644 contrib/pg_visibility/pg_visibility--1.1--1.2.sql create mode 100644 contrib/pg_visibility/pg_visibility--1.1.sql create mode 100644 contrib/pg_visibility/pg_visibility.c create mode 100644 contrib/pg_visibility/pg_visibility.control create mode 100644 contrib/pg_visibility/sql/pg_visibility.sql create mode 100644 contrib/pg_walinspect/.gitignore create mode 100644 contrib/pg_walinspect/Makefile create mode 100644 contrib/pg_walinspect/expected/oldextversions.out create mode 100644 contrib/pg_walinspect/expected/pg_walinspect.out create mode 100644 contrib/pg_walinspect/meson.build create mode 100644 contrib/pg_walinspect/pg_walinspect--1.0--1.1.sql create mode 100644 contrib/pg_walinspect/pg_walinspect--1.0.sql create mode 100644 contrib/pg_walinspect/pg_walinspect.c create mode 100644 contrib/pg_walinspect/pg_walinspect.control create mode 100644 contrib/pg_walinspect/sql/oldextversions.sql create mode 100644 contrib/pg_walinspect/sql/pg_walinspect.sql create mode 100644 contrib/pg_walinspect/walinspect.conf create mode 100644 contrib/pgcrypto/.gitignore create mode 100644 contrib/pgcrypto/Makefile create mode 100644 contrib/pgcrypto/crypt-blowfish.c create mode 100644 contrib/pgcrypto/crypt-des.c create mode 100644 contrib/pgcrypto/crypt-gensalt.c create mode 100644 contrib/pgcrypto/crypt-md5.c create mode 100644 contrib/pgcrypto/expected/3des.out create mode 100644 contrib/pgcrypto/expected/blowfish.out create mode 100644 contrib/pgcrypto/expected/blowfish_1.out create mode 100644 contrib/pgcrypto/expected/cast5.out create mode 100644 contrib/pgcrypto/expected/cast5_1.out create mode 100644 contrib/pgcrypto/expected/crypt-blowfish.out create mode 100644 contrib/pgcrypto/expected/crypt-des.out create mode 100644 contrib/pgcrypto/expected/crypt-md5.out create mode 100644 contrib/pgcrypto/expected/crypt-xdes.out create mode 100644 contrib/pgcrypto/expected/des.out create mode 100644 contrib/pgcrypto/expected/des_1.out create mode 100644 contrib/pgcrypto/expected/hmac-md5.out create mode 100644 contrib/pgcrypto/expected/hmac-sha1.out create mode 100644 contrib/pgcrypto/expected/init.out create mode 100644 contrib/pgcrypto/expected/md5.out create mode 100644 contrib/pgcrypto/expected/pgp-armor.out create mode 100644 contrib/pgcrypto/expected/pgp-compression.out create mode 100644 contrib/pgcrypto/expected/pgp-decrypt.out create mode 100644 contrib/pgcrypto/expected/pgp-decrypt_1.out create mode 100644 contrib/pgcrypto/expected/pgp-encrypt.out create mode 100644 contrib/pgcrypto/expected/pgp-info.out create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-decrypt.out create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out create mode 100644 contrib/pgcrypto/expected/pgp-pubkey-encrypt.out create mode 100644 contrib/pgcrypto/expected/pgp-zlib-DISABLED.out create mode 100644 contrib/pgcrypto/expected/rijndael.out create mode 100644 contrib/pgcrypto/expected/sha1.out create mode 100644 contrib/pgcrypto/expected/sha2.out create mode 100644 contrib/pgcrypto/mbuf.c create mode 100644 contrib/pgcrypto/mbuf.h create mode 100644 contrib/pgcrypto/meson.build create mode 100644 contrib/pgcrypto/openssl.c create mode 100644 contrib/pgcrypto/pgcrypto--1.0--1.1.sql create mode 100644 contrib/pgcrypto/pgcrypto--1.1--1.2.sql create mode 100644 contrib/pgcrypto/pgcrypto--1.2--1.3.sql create mode 100644 contrib/pgcrypto/pgcrypto--1.3.sql create mode 100644 contrib/pgcrypto/pgcrypto.c create mode 100644 contrib/pgcrypto/pgcrypto.control create mode 100644 contrib/pgcrypto/pgcrypto.h create mode 100644 contrib/pgcrypto/pgp-armor.c create mode 100644 contrib/pgcrypto/pgp-cfb.c create mode 100644 contrib/pgcrypto/pgp-compress.c create mode 100644 contrib/pgcrypto/pgp-decrypt.c create mode 100644 contrib/pgcrypto/pgp-encrypt.c create mode 100644 contrib/pgcrypto/pgp-info.c create mode 100644 contrib/pgcrypto/pgp-mpi-openssl.c create mode 100644 contrib/pgcrypto/pgp-mpi.c create mode 100644 contrib/pgcrypto/pgp-pgsql.c create mode 100644 contrib/pgcrypto/pgp-pubdec.c create mode 100644 contrib/pgcrypto/pgp-pubenc.c create mode 100644 contrib/pgcrypto/pgp-pubkey.c create mode 100644 contrib/pgcrypto/pgp-s2k.c create mode 100644 contrib/pgcrypto/pgp.c create mode 100644 contrib/pgcrypto/pgp.h create mode 100644 contrib/pgcrypto/px-crypt.c create mode 100644 contrib/pgcrypto/px-crypt.h create mode 100644 contrib/pgcrypto/px-hmac.c create mode 100644 contrib/pgcrypto/px.c create mode 100644 contrib/pgcrypto/px.h create mode 100644 contrib/pgcrypto/sql/3des.sql create mode 100644 contrib/pgcrypto/sql/blowfish.sql create mode 100644 contrib/pgcrypto/sql/cast5.sql create mode 100644 contrib/pgcrypto/sql/crypt-blowfish.sql create mode 100644 contrib/pgcrypto/sql/crypt-des.sql create mode 100644 contrib/pgcrypto/sql/crypt-md5.sql create mode 100644 contrib/pgcrypto/sql/crypt-xdes.sql create mode 100644 contrib/pgcrypto/sql/des.sql create mode 100644 contrib/pgcrypto/sql/hmac-md5.sql create mode 100644 contrib/pgcrypto/sql/hmac-sha1.sql create mode 100644 contrib/pgcrypto/sql/init.sql create mode 100644 contrib/pgcrypto/sql/md5.sql create mode 100644 contrib/pgcrypto/sql/pgp-armor.sql create mode 100644 contrib/pgcrypto/sql/pgp-compression.sql create mode 100644 contrib/pgcrypto/sql/pgp-decrypt.sql create mode 100644 contrib/pgcrypto/sql/pgp-encrypt.sql create mode 100644 contrib/pgcrypto/sql/pgp-info.sql create mode 100644 contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql create mode 100644 contrib/pgcrypto/sql/pgp-pubkey-encrypt.sql create mode 100644 contrib/pgcrypto/sql/pgp-zlib-DISABLED.sql create mode 100644 contrib/pgcrypto/sql/rijndael.sql create mode 100644 contrib/pgcrypto/sql/sha1.sql create mode 100644 contrib/pgcrypto/sql/sha2.sql create mode 100644 contrib/pgrowlocks/.gitignore create mode 100644 contrib/pgrowlocks/Makefile create mode 100644 contrib/pgrowlocks/expected/pgrowlocks.out create mode 100644 contrib/pgrowlocks/meson.build create mode 100644 contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql create mode 100644 contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql create mode 100644 contrib/pgrowlocks/pgrowlocks--1.2.sql create mode 100644 contrib/pgrowlocks/pgrowlocks.c create mode 100644 contrib/pgrowlocks/pgrowlocks.control create mode 100644 contrib/pgrowlocks/specs/pgrowlocks.spec create mode 100644 contrib/pgstattuple/.gitignore create mode 100644 contrib/pgstattuple/Makefile create mode 100644 contrib/pgstattuple/expected/pgstattuple.out create mode 100644 contrib/pgstattuple/meson.build create mode 100644 contrib/pgstattuple/pgstatapprox.c create mode 100644 contrib/pgstattuple/pgstatindex.c create mode 100644 contrib/pgstattuple/pgstattuple--1.0--1.1.sql create mode 100644 contrib/pgstattuple/pgstattuple--1.1--1.2.sql create mode 100644 contrib/pgstattuple/pgstattuple--1.2--1.3.sql create mode 100644 contrib/pgstattuple/pgstattuple--1.3--1.4.sql create mode 100644 contrib/pgstattuple/pgstattuple--1.4--1.5.sql create mode 100644 contrib/pgstattuple/pgstattuple--1.4.sql create mode 100644 contrib/pgstattuple/pgstattuple.c create mode 100644 contrib/pgstattuple/pgstattuple.control create mode 100644 contrib/pgstattuple/sql/pgstattuple.sql create mode 100644 contrib/postgres_fdw/.gitignore create mode 100644 contrib/postgres_fdw/Makefile create mode 100644 contrib/postgres_fdw/connection.c create mode 100644 contrib/postgres_fdw/deparse.c create mode 100644 contrib/postgres_fdw/expected/postgres_fdw.out create mode 100644 contrib/postgres_fdw/meson.build create mode 100644 contrib/postgres_fdw/option.c create mode 100644 contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql create mode 100644 contrib/postgres_fdw/postgres_fdw--1.0.sql create mode 100644 contrib/postgres_fdw/postgres_fdw.c create mode 100644 contrib/postgres_fdw/postgres_fdw.control create mode 100644 contrib/postgres_fdw/postgres_fdw.h create mode 100644 contrib/postgres_fdw/shippable.c create mode 100644 contrib/postgres_fdw/sql/postgres_fdw.sql create mode 100644 contrib/seg/.gitignore create mode 100644 contrib/seg/Makefile create mode 100644 contrib/seg/data/test_seg.data create mode 100644 contrib/seg/expected/security.out create mode 100644 contrib/seg/expected/seg.out create mode 100644 contrib/seg/meson.build create mode 100644 contrib/seg/seg--1.0--1.1.sql create mode 100644 contrib/seg/seg--1.1--1.2.sql create mode 100644 contrib/seg/seg--1.1.sql create mode 100644 contrib/seg/seg--1.2--1.3.sql create mode 100644 contrib/seg/seg--1.3--1.4.sql create mode 100755 contrib/seg/seg-validate.pl create mode 100644 contrib/seg/seg.c create mode 100644 contrib/seg/seg.control create mode 100644 contrib/seg/segdata.h create mode 100644 contrib/seg/segparse.c create mode 100644 contrib/seg/segparse.h create mode 100644 contrib/seg/segparse.y create mode 100644 contrib/seg/segscan.c create mode 100644 contrib/seg/segscan.l create mode 100755 contrib/seg/sort-segments.pl create mode 100644 contrib/seg/sql/security.sql create mode 100644 contrib/seg/sql/seg.sql create mode 100644 contrib/sepgsql/.gitignore create mode 100644 contrib/sepgsql/Makefile create mode 100644 contrib/sepgsql/database.c create mode 100644 contrib/sepgsql/dml.c create mode 100644 contrib/sepgsql/expected/alter.out create mode 100644 contrib/sepgsql/expected/ddl.out create mode 100644 contrib/sepgsql/expected/dml.out create mode 100644 contrib/sepgsql/expected/label.out create mode 100644 contrib/sepgsql/expected/misc.out create mode 100644 contrib/sepgsql/expected/truncate.out create mode 100644 contrib/sepgsql/hooks.c create mode 100644 contrib/sepgsql/label.c create mode 100755 contrib/sepgsql/launcher create mode 100644 contrib/sepgsql/meson.build create mode 100644 contrib/sepgsql/proc.c create mode 100644 contrib/sepgsql/relation.c create mode 100644 contrib/sepgsql/schema.c create mode 100644 contrib/sepgsql/selinux.c create mode 100644 contrib/sepgsql/sepgsql-regtest.te create mode 100644 contrib/sepgsql/sepgsql.h create mode 100644 contrib/sepgsql/sepgsql.sql.in create mode 100644 contrib/sepgsql/sql/alter.sql create mode 100644 contrib/sepgsql/sql/ddl.sql create mode 100644 contrib/sepgsql/sql/dml.sql create mode 100644 contrib/sepgsql/sql/label.sql create mode 100644 contrib/sepgsql/sql/misc.sql create mode 100644 contrib/sepgsql/sql/truncate.sql create mode 100755 contrib/sepgsql/test_sepgsql create mode 100644 contrib/sepgsql/uavc.c create mode 100644 contrib/spi/Makefile create mode 100644 contrib/spi/autoinc--1.0.sql create mode 100644 contrib/spi/autoinc.c create mode 100644 contrib/spi/autoinc.control create mode 100644 contrib/spi/autoinc.example create mode 100644 contrib/spi/insert_username--1.0.sql create mode 100644 contrib/spi/insert_username.c create mode 100644 contrib/spi/insert_username.control create mode 100644 contrib/spi/insert_username.example create mode 100644 contrib/spi/meson.build create mode 100644 contrib/spi/moddatetime--1.0.sql create mode 100644 contrib/spi/moddatetime.c create mode 100644 contrib/spi/moddatetime.control create mode 100644 contrib/spi/moddatetime.example create mode 100644 contrib/spi/refint--1.0.sql create mode 100644 contrib/spi/refint.c create mode 100644 contrib/spi/refint.control create mode 100644 contrib/spi/refint.example create mode 100644 contrib/sslinfo/Makefile create mode 100644 contrib/sslinfo/meson.build create mode 100644 contrib/sslinfo/sslinfo--1.0--1.1.sql create mode 100644 contrib/sslinfo/sslinfo--1.1--1.2.sql create mode 100644 contrib/sslinfo/sslinfo--1.2.sql create mode 100644 contrib/sslinfo/sslinfo.c create mode 100644 contrib/sslinfo/sslinfo.control create mode 100644 contrib/start-scripts/freebsd create mode 100644 contrib/start-scripts/linux create mode 100644 contrib/start-scripts/macos/README create mode 100644 contrib/start-scripts/macos/org.postgresql.postgres.plist create mode 100644 contrib/start-scripts/macos/postgres-wrapper.sh create mode 100644 contrib/tablefunc/.gitignore create mode 100644 contrib/tablefunc/Makefile create mode 100644 contrib/tablefunc/data/connectby_int.data create mode 100644 contrib/tablefunc/data/connectby_text.data create mode 100644 contrib/tablefunc/data/ct.data create mode 100644 contrib/tablefunc/expected/tablefunc.out create mode 100644 contrib/tablefunc/meson.build create mode 100644 contrib/tablefunc/sql/tablefunc.sql create mode 100644 contrib/tablefunc/tablefunc--1.0.sql create mode 100644 contrib/tablefunc/tablefunc.c create mode 100644 contrib/tablefunc/tablefunc.control create mode 100644 contrib/tablefunc/tablefunc.h create mode 100644 contrib/tcn/.gitignore create mode 100644 contrib/tcn/Makefile create mode 100644 contrib/tcn/expected/tcn.out create mode 100644 contrib/tcn/meson.build create mode 100644 contrib/tcn/specs/tcn.spec create mode 100644 contrib/tcn/tcn--1.0.sql create mode 100644 contrib/tcn/tcn.c create mode 100644 contrib/tcn/tcn.control create mode 100644 contrib/test_decoding/.gitignore create mode 100644 contrib/test_decoding/Makefile create mode 100644 contrib/test_decoding/expected/binary.out create mode 100644 contrib/test_decoding/expected/catalog_change_snapshot.out create mode 100644 contrib/test_decoding/expected/concurrent_ddl_dml.out create mode 100644 contrib/test_decoding/expected/concurrent_stream.out create mode 100644 contrib/test_decoding/expected/ddl.out create mode 100644 contrib/test_decoding/expected/decoding_in_xact.out create mode 100644 contrib/test_decoding/expected/decoding_into_rel.out create mode 100644 contrib/test_decoding/expected/delayed_startup.out create mode 100644 contrib/test_decoding/expected/messages.out create mode 100644 contrib/test_decoding/expected/mxact.out create mode 100644 contrib/test_decoding/expected/oldest_xmin.out create mode 100644 contrib/test_decoding/expected/ondisk_startup.out create mode 100644 contrib/test_decoding/expected/permissions.out create mode 100644 contrib/test_decoding/expected/prepared.out create mode 100644 contrib/test_decoding/expected/replorigin.out create mode 100644 contrib/test_decoding/expected/rewrite.out create mode 100644 contrib/test_decoding/expected/slot.out create mode 100644 contrib/test_decoding/expected/slot_creation_error.out create mode 100644 contrib/test_decoding/expected/snapshot_transfer.out create mode 100644 contrib/test_decoding/expected/spill.out create mode 100644 contrib/test_decoding/expected/stats.out create mode 100644 contrib/test_decoding/expected/stream.out create mode 100644 contrib/test_decoding/expected/subxact_without_top.out create mode 100644 contrib/test_decoding/expected/time.out create mode 100644 contrib/test_decoding/expected/toast.out create mode 100644 contrib/test_decoding/expected/truncate.out create mode 100644 contrib/test_decoding/expected/twophase.out create mode 100644 contrib/test_decoding/expected/twophase_snapshot.out create mode 100644 contrib/test_decoding/expected/twophase_stream.out create mode 100644 contrib/test_decoding/expected/xact.out create mode 100644 contrib/test_decoding/logical.conf create mode 100644 contrib/test_decoding/meson.build create mode 100644 contrib/test_decoding/specs/catalog_change_snapshot.spec create mode 100644 contrib/test_decoding/specs/concurrent_ddl_dml.spec create mode 100644 contrib/test_decoding/specs/concurrent_stream.spec create mode 100644 contrib/test_decoding/specs/delayed_startup.spec create mode 100644 contrib/test_decoding/specs/mxact.spec create mode 100644 contrib/test_decoding/specs/oldest_xmin.spec create mode 100644 contrib/test_decoding/specs/ondisk_startup.spec create mode 100644 contrib/test_decoding/specs/slot_creation_error.spec create mode 100644 contrib/test_decoding/specs/snapshot_transfer.spec create mode 100644 contrib/test_decoding/specs/subxact_without_top.spec create mode 100644 contrib/test_decoding/specs/twophase_snapshot.spec create mode 100644 contrib/test_decoding/sql/binary.sql create mode 100644 contrib/test_decoding/sql/ddl.sql create mode 100644 contrib/test_decoding/sql/decoding_in_xact.sql create mode 100644 contrib/test_decoding/sql/decoding_into_rel.sql create mode 100644 contrib/test_decoding/sql/messages.sql create mode 100644 contrib/test_decoding/sql/permissions.sql create mode 100644 contrib/test_decoding/sql/prepared.sql create mode 100644 contrib/test_decoding/sql/replorigin.sql create mode 100644 contrib/test_decoding/sql/rewrite.sql create mode 100644 contrib/test_decoding/sql/slot.sql create mode 100644 contrib/test_decoding/sql/spill.sql create mode 100644 contrib/test_decoding/sql/stats.sql create mode 100644 contrib/test_decoding/sql/stream.sql create mode 100644 contrib/test_decoding/sql/time.sql create mode 100644 contrib/test_decoding/sql/toast.sql create mode 100644 contrib/test_decoding/sql/truncate.sql create mode 100644 contrib/test_decoding/sql/twophase.sql create mode 100644 contrib/test_decoding/sql/twophase_stream.sql create mode 100644 contrib/test_decoding/sql/xact.sql create mode 100644 contrib/test_decoding/t/001_repl_stats.pl create mode 100644 contrib/test_decoding/test_decoding.c create mode 100644 contrib/tsm_system_rows/.gitignore create mode 100644 contrib/tsm_system_rows/Makefile create mode 100644 contrib/tsm_system_rows/expected/tsm_system_rows.out create mode 100644 contrib/tsm_system_rows/meson.build create mode 100644 contrib/tsm_system_rows/sql/tsm_system_rows.sql create mode 100644 contrib/tsm_system_rows/tsm_system_rows--1.0.sql create mode 100644 contrib/tsm_system_rows/tsm_system_rows.c create mode 100644 contrib/tsm_system_rows/tsm_system_rows.control create mode 100644 contrib/tsm_system_time/.gitignore create mode 100644 contrib/tsm_system_time/Makefile create mode 100644 contrib/tsm_system_time/expected/tsm_system_time.out create mode 100644 contrib/tsm_system_time/meson.build create mode 100644 contrib/tsm_system_time/sql/tsm_system_time.sql create mode 100644 contrib/tsm_system_time/tsm_system_time--1.0.sql create mode 100644 contrib/tsm_system_time/tsm_system_time.c create mode 100644 contrib/tsm_system_time/tsm_system_time.control create mode 100644 contrib/unaccent/.gitignore create mode 100644 contrib/unaccent/Makefile create mode 100644 contrib/unaccent/expected/unaccent.out create mode 100644 contrib/unaccent/generate_unaccent_rules.py create mode 100644 contrib/unaccent/meson.build create mode 100644 contrib/unaccent/sql/unaccent.sql create mode 100644 contrib/unaccent/unaccent--1.0--1.1.sql create mode 100644 contrib/unaccent/unaccent--1.1.sql create mode 100644 contrib/unaccent/unaccent.c create mode 100644 contrib/unaccent/unaccent.control create mode 100644 contrib/unaccent/unaccent.rules create mode 100644 contrib/uuid-ossp/.gitignore create mode 100644 contrib/uuid-ossp/Makefile create mode 100644 contrib/uuid-ossp/expected/uuid_ossp.out create mode 100644 contrib/uuid-ossp/meson.build create mode 100644 contrib/uuid-ossp/sql/uuid_ossp.sql create mode 100644 contrib/uuid-ossp/uuid-ossp--1.0--1.1.sql create mode 100644 contrib/uuid-ossp/uuid-ossp--1.1.sql create mode 100644 contrib/uuid-ossp/uuid-ossp.c create mode 100644 contrib/uuid-ossp/uuid-ossp.control create mode 100644 contrib/vacuumlo/.gitignore create mode 100644 contrib/vacuumlo/Makefile create mode 100644 contrib/vacuumlo/meson.build create mode 100644 contrib/vacuumlo/t/001_basic.pl create mode 100644 contrib/vacuumlo/vacuumlo.c create mode 100644 contrib/xml2/.gitignore create mode 100644 contrib/xml2/Makefile create mode 100644 contrib/xml2/expected/xml2.out create mode 100644 contrib/xml2/expected/xml2_1.out create mode 100644 contrib/xml2/meson.build create mode 100644 contrib/xml2/sql/xml2.sql create mode 100644 contrib/xml2/xml2--1.0--1.1.sql create mode 100644 contrib/xml2/xml2--1.1.sql create mode 100644 contrib/xml2/xml2.control create mode 100644 contrib/xml2/xpath.c create mode 100644 contrib/xml2/xslt_proc.c (limited to 'contrib') diff --git a/contrib/Makefile b/contrib/Makefile new file mode 100644 index 0000000..bbf2204 --- /dev/null +++ b/contrib/Makefile @@ -0,0 +1,97 @@ +# contrib/Makefile + +subdir = contrib +top_builddir = .. +include $(top_builddir)/src/Makefile.global + +SUBDIRS = \ + adminpack \ + amcheck \ + auth_delay \ + auto_explain \ + basic_archive \ + basebackup_to_shell \ + bloom \ + btree_gin \ + btree_gist \ + citext \ + cube \ + dblink \ + dict_int \ + dict_xsyn \ + earthdistance \ + file_fdw \ + fuzzystrmatch \ + hstore \ + intagg \ + intarray \ + isn \ + lo \ + ltree \ + oid2name \ + old_snapshot \ + pageinspect \ + passwordcheck \ + pg_buffercache \ + pg_freespacemap \ + pg_prewarm \ + pg_stat_statements \ + pg_surgery \ + pg_trgm \ + pgrowlocks \ + pgstattuple \ + pg_visibility \ + pg_walinspect \ + postgres_fdw \ + seg \ + spi \ + tablefunc \ + tcn \ + test_decoding \ + tsm_system_rows \ + tsm_system_time \ + unaccent \ + vacuumlo + +ifeq ($(with_ssl),openssl) +SUBDIRS += pgcrypto sslinfo +else +ALWAYS_SUBDIRS += pgcrypto sslinfo +endif + +ifneq ($(with_uuid),no) +SUBDIRS += uuid-ossp +else +ALWAYS_SUBDIRS += uuid-ossp +endif + +ifeq ($(with_libxml),yes) +SUBDIRS += xml2 +else +ALWAYS_SUBDIRS += xml2 +endif + +ifeq ($(with_selinux),yes) +SUBDIRS += sepgsql +else +ALWAYS_SUBDIRS += sepgsql +endif + +ifeq ($(with_perl),yes) +SUBDIRS += bool_plperl hstore_plperl jsonb_plperl +else +ALWAYS_SUBDIRS += bool_plperl hstore_plperl jsonb_plperl +endif + +ifeq ($(with_python),yes) +SUBDIRS += hstore_plpython jsonb_plpython ltree_plpython +else +ALWAYS_SUBDIRS += hstore_plpython jsonb_plpython ltree_plpython +endif + +# Missing: +# start-scripts \ (does not have a makefile) + + +$(recurse) +$(recurse_always) diff --git a/contrib/README b/contrib/README new file mode 100644 index 0000000..5eaeb24 --- /dev/null +++ b/contrib/README @@ -0,0 +1,28 @@ +The PostgreSQL contrib tree +--------------------------- + +This subtree contains porting tools, analysis utilities, and plug-in +features that are not part of the core PostgreSQL system, mainly +because they address a limited audience or are too experimental to be +part of the main source tree. This does not preclude their +usefulness. + +User documentation for each module appears in the main SGML +documentation. + +When building from the source distribution, these modules are not +built automatically, unless you build the "world" target. You can +also build and install them all by running "make all" and "make +install" in this directory; or to build and install just one selected +module, do the same in that module's subdirectory. + +Some directories supply new user-defined functions, operators, or +types. To make use of one of these modules, after you have installed +the code you need to register the new SQL objects in the database +system by executing a CREATE EXTENSION command. In a fresh database, +you can simply do + + CREATE EXTENSION module_name; + +See the PostgreSQL documentation for more information about this +procedure. diff --git a/contrib/adminpack/.gitignore b/contrib/adminpack/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/adminpack/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/adminpack/Makefile b/contrib/adminpack/Makefile new file mode 100644 index 0000000..851504f --- /dev/null +++ b/contrib/adminpack/Makefile @@ -0,0 +1,24 @@ +# contrib/adminpack/Makefile + +MODULE_big = adminpack +OBJS = \ + $(WIN32RES) \ + adminpack.o + +EXTENSION = adminpack +DATA = adminpack--1.0.sql adminpack--1.0--1.1.sql adminpack--1.1--2.0.sql\ + adminpack--2.0--2.1.sql +PGFILEDESC = "adminpack - support functions for pgAdmin" + +REGRESS = adminpack + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/adminpack +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/adminpack/adminpack--1.0--1.1.sql b/contrib/adminpack/adminpack--1.0--1.1.sql new file mode 100644 index 0000000..bb58165 --- /dev/null +++ b/contrib/adminpack/adminpack--1.0--1.1.sql @@ -0,0 +1,6 @@ +/* contrib/adminpack/adminpack--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION adminpack UPDATE TO '1.1'" to load this file. \quit + +REVOKE EXECUTE ON FUNCTION pg_catalog.pg_logfile_rotate() FROM PUBLIC; diff --git a/contrib/adminpack/adminpack--1.0.sql b/contrib/adminpack/adminpack--1.0.sql new file mode 100644 index 0000000..f76f5c3 --- /dev/null +++ b/contrib/adminpack/adminpack--1.0.sql @@ -0,0 +1,53 @@ +/* contrib/adminpack/adminpack--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION adminpack" to load this file. \quit + +/* *********************************************** + * Administrative functions for PostgreSQL + * *********************************************** */ + +/* generic file access functions */ + +CREATE FUNCTION pg_catalog.pg_file_write(text, text, bool) +RETURNS bigint +AS 'MODULE_PATHNAME', 'pg_file_write' +LANGUAGE C VOLATILE STRICT; + +CREATE FUNCTION pg_catalog.pg_file_rename(text, text, text) +RETURNS bool +AS 'MODULE_PATHNAME', 'pg_file_rename' +LANGUAGE C VOLATILE; + +CREATE FUNCTION pg_catalog.pg_file_rename(text, text) +RETURNS bool +AS 'SELECT pg_catalog.pg_file_rename($1, $2, NULL::pg_catalog.text);' +LANGUAGE SQL VOLATILE STRICT; + +CREATE FUNCTION pg_catalog.pg_file_unlink(text) +RETURNS bool +AS 'MODULE_PATHNAME', 'pg_file_unlink' +LANGUAGE C VOLATILE STRICT; + +CREATE FUNCTION pg_catalog.pg_logdir_ls() +RETURNS setof record +AS 'MODULE_PATHNAME', 'pg_logdir_ls' +LANGUAGE C VOLATILE STRICT; + + +/* Renaming of existing backend functions for pgAdmin compatibility */ + +CREATE FUNCTION pg_catalog.pg_file_read(text, bigint, bigint) +RETURNS text +AS 'pg_read_file' +LANGUAGE INTERNAL VOLATILE STRICT; + +CREATE FUNCTION pg_catalog.pg_file_length(text) +RETURNS bigint +AS 'SELECT size FROM pg_catalog.pg_stat_file($1)' +LANGUAGE SQL VOLATILE STRICT; + +CREATE FUNCTION pg_catalog.pg_logfile_rotate() +RETURNS int4 +AS 'pg_rotate_logfile' +LANGUAGE INTERNAL VOLATILE STRICT; diff --git a/contrib/adminpack/adminpack--1.1--2.0.sql b/contrib/adminpack/adminpack--1.1--2.0.sql new file mode 100644 index 0000000..ceaeafa --- /dev/null +++ b/contrib/adminpack/adminpack--1.1--2.0.sql @@ -0,0 +1,51 @@ +/* contrib/adminpack/adminpack--1.1--2.0.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION adminpack UPDATE TO '2.0'" to load this file. \quit + +/* *********************************************** + * Administrative functions for PostgreSQL + * *********************************************** */ + +/* generic file access functions */ + +CREATE OR REPLACE FUNCTION pg_catalog.pg_file_write(text, text, bool) +RETURNS bigint +AS 'MODULE_PATHNAME', 'pg_file_write_v1_1' +LANGUAGE C VOLATILE STRICT; + +REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_write(text, text, bool) FROM PUBLIC; + +CREATE OR REPLACE FUNCTION pg_catalog.pg_file_rename(text, text, text) +RETURNS bool +AS 'MODULE_PATHNAME', 'pg_file_rename_v1_1' +LANGUAGE C VOLATILE; + +REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_rename(text, text, text) FROM PUBLIC; + +CREATE OR REPLACE FUNCTION pg_catalog.pg_file_rename(text, text) +RETURNS bool +AS 'SELECT pg_catalog.pg_file_rename($1, $2, NULL::pg_catalog.text);' +LANGUAGE SQL VOLATILE STRICT; + +CREATE OR REPLACE FUNCTION pg_catalog.pg_file_unlink(text) +RETURNS bool +AS 'MODULE_PATHNAME', 'pg_file_unlink_v1_1' +LANGUAGE C VOLATILE STRICT; + +REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_unlink(text) FROM PUBLIC; + +CREATE OR REPLACE FUNCTION pg_catalog.pg_logdir_ls() +RETURNS setof record +AS 'MODULE_PATHNAME', 'pg_logdir_ls_v1_1' +LANGUAGE C VOLATILE STRICT; + +REVOKE EXECUTE ON FUNCTION pg_catalog.pg_logdir_ls() FROM PUBLIC; + +/* These functions are now in the backend and callers should update to use those */ + +DROP FUNCTION pg_file_read(text, bigint, bigint); + +DROP FUNCTION pg_file_length(text); + +DROP FUNCTION pg_logfile_rotate(); diff --git a/contrib/adminpack/adminpack--2.0--2.1.sql b/contrib/adminpack/adminpack--2.0--2.1.sql new file mode 100644 index 0000000..1c6712e --- /dev/null +++ b/contrib/adminpack/adminpack--2.0--2.1.sql @@ -0,0 +1,17 @@ +/* contrib/adminpack/adminpack--2.0--2.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION adminpack UPDATE TO '2.1'" to load this file. \quit + +/* *********************************************** + * Administrative functions for PostgreSQL + * *********************************************** */ + +/* generic file access functions */ + +CREATE OR REPLACE FUNCTION pg_catalog.pg_file_sync(text) +RETURNS void +AS 'MODULE_PATHNAME', 'pg_file_sync' +LANGUAGE C VOLATILE STRICT; + +REVOKE EXECUTE ON FUNCTION pg_catalog.pg_file_sync(text) FROM PUBLIC; diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c new file mode 100644 index 0000000..d3aec7b --- /dev/null +++ b/contrib/adminpack/adminpack.c @@ -0,0 +1,591 @@ +/*------------------------------------------------------------------------- + * + * adminpack.c + * + * + * Copyright (c) 2002-2023, PostgreSQL Global Development Group + * + * Author: Andreas Pflug + * + * IDENTIFICATION + * contrib/adminpack/adminpack.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include +#include +#include + +#include "catalog/pg_authid.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "postmaster/syslogger.h" +#include "storage/fd.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/datetime.h" + + +#ifdef WIN32 + +#ifdef rename +#undef rename +#endif + +#ifdef unlink +#undef unlink +#endif +#endif + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(pg_file_write); +PG_FUNCTION_INFO_V1(pg_file_write_v1_1); +PG_FUNCTION_INFO_V1(pg_file_sync); +PG_FUNCTION_INFO_V1(pg_file_rename); +PG_FUNCTION_INFO_V1(pg_file_rename_v1_1); +PG_FUNCTION_INFO_V1(pg_file_unlink); +PG_FUNCTION_INFO_V1(pg_file_unlink_v1_1); +PG_FUNCTION_INFO_V1(pg_logdir_ls); +PG_FUNCTION_INFO_V1(pg_logdir_ls_v1_1); + +static int64 pg_file_write_internal(text *file, text *data, bool replace); +static bool pg_file_rename_internal(text *file1, text *file2, text *file3); +static Datum pg_logdir_ls_internal(FunctionCallInfo fcinfo); + + +/*----------------------- + * some helper functions + */ + +/* + * Convert a "text" filename argument to C string, and check it's allowable. + * + * Filename may be absolute or relative to the DataDir, but we only allow + * absolute paths that match DataDir. + */ +static char * +convert_and_check_filename(text *arg) +{ + char *filename = text_to_cstring(arg); + + canonicalize_path(filename); /* filename can change length here */ + + /* + * Members of the 'pg_write_server_files' role are allowed to access any + * files on the server as the PG user, so no need to do any further checks + * here. + */ + if (has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES)) + return filename; + + /* + * User isn't a member of the pg_write_server_files role, so check if it's + * allowable + */ + if (is_absolute_path(filename)) + { + /* Allow absolute paths if within DataDir */ + if (!path_is_prefix_of_path(DataDir, filename)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("absolute path not allowed"))); + } + else if (!path_is_relative_and_below_cwd(filename)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("path must be in or below the data directory"))); + + return filename; +} + + +/* + * check for superuser, bark if not. + */ +static void +requireSuperuser(void) +{ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("only superuser may access generic file functions"))); +} + + + +/* ------------------------------------ + * pg_file_write - old version + * + * The superuser() check here must be kept as the library might be upgraded + * without the extension being upgraded, meaning that in pre-1.1 installations + * these functions could be called by any user. + */ +Datum +pg_file_write(PG_FUNCTION_ARGS) +{ + text *file = PG_GETARG_TEXT_PP(0); + text *data = PG_GETARG_TEXT_PP(1); + bool replace = PG_GETARG_BOOL(2); + int64 count = 0; + + requireSuperuser(); + + count = pg_file_write_internal(file, data, replace); + + PG_RETURN_INT64(count); +} + +/* ------------------------------------ + * pg_file_write_v1_1 - Version 1.1 + * + * As of adminpack version 1.1, we no longer need to check if the user + * is a superuser because we REVOKE EXECUTE on the function from PUBLIC. + * Users can then grant access to it based on their policies. + * + * Otherwise identical to pg_file_write (above). + */ +Datum +pg_file_write_v1_1(PG_FUNCTION_ARGS) +{ + text *file = PG_GETARG_TEXT_PP(0); + text *data = PG_GETARG_TEXT_PP(1); + bool replace = PG_GETARG_BOOL(2); + int64 count = 0; + + count = pg_file_write_internal(file, data, replace); + + PG_RETURN_INT64(count); +} + +/* ------------------------------------ + * pg_file_write_internal - Workhorse for pg_file_write functions. + * + * This handles the actual work for pg_file_write. + */ +static int64 +pg_file_write_internal(text *file, text *data, bool replace) +{ + FILE *f; + char *filename; + int64 count = 0; + + filename = convert_and_check_filename(file); + + if (!replace) + { + struct stat fst; + + if (stat(filename, &fst) >= 0) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_FILE), + errmsg("file \"%s\" exists", filename))); + + f = AllocateFile(filename, "wb"); + } + else + f = AllocateFile(filename, "ab"); + + if (!f) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\" for writing: %m", + filename))); + + count = fwrite(VARDATA_ANY(data), 1, VARSIZE_ANY_EXHDR(data), f); + if (count != VARSIZE_ANY_EXHDR(data) || FreeFile(f)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write file \"%s\": %m", filename))); + + return (count); +} + +/* ------------------------------------ + * pg_file_sync + * + * We REVOKE EXECUTE on the function from PUBLIC. + * Users can then grant access to it based on their policies. + */ +Datum +pg_file_sync(PG_FUNCTION_ARGS) +{ + char *filename; + struct stat fst; + + filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0)); + + if (stat(filename, &fst) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + + fsync_fname_ext(filename, S_ISDIR(fst.st_mode), false, ERROR); + + PG_RETURN_VOID(); +} + +/* ------------------------------------ + * pg_file_rename - old version + * + * The superuser() check here must be kept as the library might be upgraded + * without the extension being upgraded, meaning that in pre-1.1 installations + * these functions could be called by any user. + */ +Datum +pg_file_rename(PG_FUNCTION_ARGS) +{ + text *file1; + text *file2; + text *file3; + bool result; + + requireSuperuser(); + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + file1 = PG_GETARG_TEXT_PP(0); + file2 = PG_GETARG_TEXT_PP(1); + + if (PG_ARGISNULL(2)) + file3 = NULL; + else + file3 = PG_GETARG_TEXT_PP(2); + + result = pg_file_rename_internal(file1, file2, file3); + + PG_RETURN_BOOL(result); +} + +/* ------------------------------------ + * pg_file_rename_v1_1 - Version 1.1 + * + * As of adminpack version 1.1, we no longer need to check if the user + * is a superuser because we REVOKE EXECUTE on the function from PUBLIC. + * Users can then grant access to it based on their policies. + * + * Otherwise identical to pg_file_write (above). + */ +Datum +pg_file_rename_v1_1(PG_FUNCTION_ARGS) +{ + text *file1; + text *file2; + text *file3; + bool result; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + file1 = PG_GETARG_TEXT_PP(0); + file2 = PG_GETARG_TEXT_PP(1); + + if (PG_ARGISNULL(2)) + file3 = NULL; + else + file3 = PG_GETARG_TEXT_PP(2); + + result = pg_file_rename_internal(file1, file2, file3); + + PG_RETURN_BOOL(result); +} + +/* ------------------------------------ + * pg_file_rename_internal - Workhorse for pg_file_rename functions. + * + * This handles the actual work for pg_file_rename. + */ +static bool +pg_file_rename_internal(text *file1, text *file2, text *file3) +{ + char *fn1, + *fn2, + *fn3; + int rc; + + fn1 = convert_and_check_filename(file1); + fn2 = convert_and_check_filename(file2); + + if (file3 == NULL) + fn3 = NULL; + else + fn3 = convert_and_check_filename(file3); + + if (access(fn1, W_OK) < 0) + { + ereport(WARNING, + (errcode_for_file_access(), + errmsg("file \"%s\" is not accessible: %m", fn1))); + + return false; + } + + if (fn3 && access(fn2, W_OK) < 0) + { + ereport(WARNING, + (errcode_for_file_access(), + errmsg("file \"%s\" is not accessible: %m", fn2))); + + return false; + } + + rc = access(fn3 ? fn3 : fn2, W_OK); + if (rc >= 0 || errno != ENOENT) + { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_FILE), + errmsg("cannot rename to target file \"%s\"", + fn3 ? fn3 : fn2))); + } + + if (fn3) + { + if (rename(fn2, fn3) != 0) + { + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not rename \"%s\" to \"%s\": %m", + fn2, fn3))); + } + if (rename(fn1, fn2) != 0) + { + ereport(WARNING, + (errcode_for_file_access(), + errmsg("could not rename \"%s\" to \"%s\": %m", + fn1, fn2))); + + if (rename(fn3, fn2) != 0) + { + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not rename \"%s\" back to \"%s\": %m", + fn3, fn2))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FILE), + errmsg("renaming \"%s\" to \"%s\" was reverted", + fn2, fn3))); + } + } + } + else if (rename(fn1, fn2) != 0) + { + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not rename \"%s\" to \"%s\": %m", fn1, fn2))); + } + + return true; +} + + +/* ------------------------------------ + * pg_file_unlink - old version + * + * The superuser() check here must be kept as the library might be upgraded + * without the extension being upgraded, meaning that in pre-1.1 installations + * these functions could be called by any user. + */ +Datum +pg_file_unlink(PG_FUNCTION_ARGS) +{ + char *filename; + + requireSuperuser(); + + filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0)); + + if (access(filename, W_OK) < 0) + { + if (errno == ENOENT) + PG_RETURN_BOOL(false); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("file \"%s\" is not accessible: %m", filename))); + } + + if (unlink(filename) < 0) + { + ereport(WARNING, + (errcode_for_file_access(), + errmsg("could not unlink file \"%s\": %m", filename))); + + PG_RETURN_BOOL(false); + } + PG_RETURN_BOOL(true); +} + + +/* ------------------------------------ + * pg_file_unlink_v1_1 - Version 1.1 + * + * As of adminpack version 1.1, we no longer need to check if the user + * is a superuser because we REVOKE EXECUTE on the function from PUBLIC. + * Users can then grant access to it based on their policies. + * + * Otherwise identical to pg_file_unlink (above). + */ +Datum +pg_file_unlink_v1_1(PG_FUNCTION_ARGS) +{ + char *filename; + + filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0)); + + if (access(filename, W_OK) < 0) + { + if (errno == ENOENT) + PG_RETURN_BOOL(false); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("file \"%s\" is not accessible: %m", filename))); + } + + if (unlink(filename) < 0) + { + ereport(WARNING, + (errcode_for_file_access(), + errmsg("could not unlink file \"%s\": %m", filename))); + + PG_RETURN_BOOL(false); + } + PG_RETURN_BOOL(true); +} + +/* ------------------------------------ + * pg_logdir_ls - Old version + * + * The superuser() check here must be kept as the library might be upgraded + * without the extension being upgraded, meaning that in pre-1.1 installations + * these functions could be called by any user. + */ +Datum +pg_logdir_ls(PG_FUNCTION_ARGS) +{ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("only superuser can list the log directory"))); + + return (pg_logdir_ls_internal(fcinfo)); +} + +/* ------------------------------------ + * pg_logdir_ls_v1_1 - Version 1.1 + * + * As of adminpack version 1.1, we no longer need to check if the user + * is a superuser because we REVOKE EXECUTE on the function from PUBLIC. + * Users can then grant access to it based on their policies. + * + * Otherwise identical to pg_logdir_ls (above). + */ +Datum +pg_logdir_ls_v1_1(PG_FUNCTION_ARGS) +{ + return (pg_logdir_ls_internal(fcinfo)); +} + +static Datum +pg_logdir_ls_internal(FunctionCallInfo fcinfo) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + bool randomAccess; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + AttInMetadata *attinmeta; + DIR *dirdesc; + struct dirent *de; + MemoryContext oldcontext; + + if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'"))); + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */ + oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); + + tupdesc = CreateTemplateTupleDesc(2); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime", + TIMESTAMPOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename", + TEXTOID, -1, 0); + + randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0; + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + attinmeta = TupleDescGetAttInMetadata(tupdesc); + + dirdesc = AllocateDir(Log_directory); + while ((de = ReadDir(dirdesc, Log_directory)) != NULL) + { + char *values[2]; + HeapTuple tuple; + char timestampbuf[32]; + char *field[MAXDATEFIELDS]; + char lowstr[MAXDATELEN + 1]; + int dtype; + int nf, + ftype[MAXDATEFIELDS]; + fsec_t fsec; + int tz = 0; + struct pg_tm date; + DateTimeErrorExtra extra; + + /* + * Default format: postgresql-YYYY-MM-DD_HHMMSS.log + */ + if (strlen(de->d_name) != 32 + || strncmp(de->d_name, "postgresql-", 11) != 0 + || de->d_name[21] != '_' + || strcmp(de->d_name + 28, ".log") != 0) + continue; + + /* extract timestamp portion of filename */ + strcpy(timestampbuf, de->d_name + 11); + timestampbuf[17] = '\0'; + + /* parse and decode expected timestamp to verify it's OK format */ + if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf)) + continue; + + if (DecodeDateTime(field, ftype, nf, + &dtype, &date, &fsec, &tz, &extra)) + continue; + + /* Seems the timestamp is OK; prepare and return tuple */ + + values[0] = timestampbuf; + values[1] = psprintf("%s/%s", Log_directory, de->d_name); + + tuple = BuildTupleFromCStrings(attinmeta, values); + + tuplestore_puttuple(tupstore, tuple); + } + + FreeDir(dirdesc); + return (Datum) 0; +} diff --git a/contrib/adminpack/adminpack.control b/contrib/adminpack/adminpack.control new file mode 100644 index 0000000..ae35d22 --- /dev/null +++ b/contrib/adminpack/adminpack.control @@ -0,0 +1,6 @@ +# adminpack extension +comment = 'administrative functions for PostgreSQL' +default_version = '2.1' +module_pathname = '$libdir/adminpack' +relocatable = false +schema = pg_catalog diff --git a/contrib/adminpack/expected/adminpack.out b/contrib/adminpack/expected/adminpack.out new file mode 100644 index 0000000..f419a5e --- /dev/null +++ b/contrib/adminpack/expected/adminpack.out @@ -0,0 +1,172 @@ +CREATE EXTENSION adminpack; +-- create new file +SELECT pg_file_write('test_file1', 'test1', false); + pg_file_write +--------------- + 5 +(1 row) + +SELECT pg_read_file('test_file1'); + pg_read_file +-------------- + test1 +(1 row) + +-- append +SELECT pg_file_write('test_file1', 'test1', true); + pg_file_write +--------------- + 5 +(1 row) + +SELECT pg_read_file('test_file1'); + pg_read_file +-------------- + test1test1 +(1 row) + +-- error, already exists +SELECT pg_file_write('test_file1', 'test1', false); +ERROR: file "test_file1" exists +SELECT pg_read_file('test_file1'); + pg_read_file +-------------- + test1test1 +(1 row) + +-- disallowed file paths for non-superusers and users who are +-- not members of pg_write_server_files +CREATE ROLE regress_adminpack_user1; +GRANT pg_read_all_settings TO regress_adminpack_user1; +GRANT EXECUTE ON FUNCTION pg_file_write(text,text,bool) TO regress_adminpack_user1; +SET ROLE regress_adminpack_user1; +SELECT pg_file_write('../test_file0', 'test0', false); +ERROR: path must be in or below the data directory +SELECT pg_file_write('/tmp/test_file0', 'test0', false); +ERROR: absolute path not allowed +SELECT pg_file_write(current_setting('data_directory') || '/test_file4', 'test4', false); + pg_file_write +--------------- + 5 +(1 row) + +SELECT pg_file_write(current_setting('data_directory') || '/../test_file4', 'test4', false); +ERROR: absolute path not allowed +RESET ROLE; +REVOKE EXECUTE ON FUNCTION pg_file_write(text,text,bool) FROM regress_adminpack_user1; +REVOKE pg_read_all_settings FROM regress_adminpack_user1; +DROP ROLE regress_adminpack_user1; +-- sync +SELECT pg_file_sync('test_file1'); -- sync file + pg_file_sync +-------------- + +(1 row) + +SELECT pg_file_sync('pg_stat'); -- sync directory + pg_file_sync +-------------- + +(1 row) + +SELECT pg_file_sync('test_file2'); -- not there +ERROR: could not stat file "test_file2": No such file or directory +-- rename file +SELECT pg_file_rename('test_file1', 'test_file2'); + pg_file_rename +---------------- + t +(1 row) + +SELECT pg_read_file('test_file1'); -- not there +ERROR: could not open file "test_file1" for reading: No such file or directory +SELECT pg_read_file('test_file2'); + pg_read_file +-------------- + test1test1 +(1 row) + +-- error +SELECT pg_file_rename('test_file1', 'test_file2'); +WARNING: file "test_file1" is not accessible: No such file or directory + pg_file_rename +---------------- + f +(1 row) + +-- rename file and archive +SELECT pg_file_write('test_file3', 'test3', false); + pg_file_write +--------------- + 5 +(1 row) + +SELECT pg_file_rename('test_file2', 'test_file3', 'test_file3_archive'); + pg_file_rename +---------------- + t +(1 row) + +SELECT pg_read_file('test_file2'); -- not there +ERROR: could not open file "test_file2" for reading: No such file or directory +SELECT pg_read_file('test_file3'); + pg_read_file +-------------- + test1test1 +(1 row) + +SELECT pg_read_file('test_file3_archive'); + pg_read_file +-------------- + test3 +(1 row) + +-- unlink +SELECT pg_file_unlink('test_file1'); -- does not exist + pg_file_unlink +---------------- + f +(1 row) + +SELECT pg_file_unlink('test_file2'); -- does not exist + pg_file_unlink +---------------- + f +(1 row) + +SELECT pg_file_unlink('test_file3'); + pg_file_unlink +---------------- + t +(1 row) + +SELECT pg_file_unlink('test_file3_archive'); + pg_file_unlink +---------------- + t +(1 row) + +SELECT pg_file_unlink('test_file4'); + pg_file_unlink +---------------- + t +(1 row) + +-- superuser checks +CREATE USER regress_adminpack_user1; +SET ROLE regress_adminpack_user1; +SELECT pg_file_write('test_file0', 'test0', false); +ERROR: permission denied for function pg_file_write +SELECT pg_file_sync('test_file0'); +ERROR: permission denied for function pg_file_sync +SELECT pg_file_rename('test_file0', 'test_file0'); +ERROR: permission denied for function pg_file_rename +CONTEXT: SQL function "pg_file_rename" statement 1 +SELECT pg_file_unlink('test_file0'); +ERROR: permission denied for function pg_file_unlink +SELECT pg_logdir_ls(); +ERROR: permission denied for function pg_logdir_ls +RESET ROLE; +DROP USER regress_adminpack_user1; +-- no further tests for pg_logdir_ls() because it depends on the +-- server's logging setup diff --git a/contrib/adminpack/meson.build b/contrib/adminpack/meson.build new file mode 100644 index 0000000..7658732 --- /dev/null +++ b/contrib/adminpack/meson.build @@ -0,0 +1,35 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +adminpack_sources = files( + 'adminpack.c', +) + +if host_system == 'windows' + adminpack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'adminpack', + '--FILEDESC', 'adminpack - support functions for pgAdmin',]) +endif + +adminpack = shared_module('adminpack', + adminpack_sources, + kwargs: contrib_mod_args, +) +contrib_targets += adminpack + +install_data( + 'adminpack.control', + 'adminpack--1.0.sql', + 'adminpack--1.0--1.1.sql', + 'adminpack--1.1--2.0.sql', + 'adminpack--2.0--2.1.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'adminpack', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': ['adminpack'], + }, +} diff --git a/contrib/adminpack/sql/adminpack.sql b/contrib/adminpack/sql/adminpack.sql new file mode 100644 index 0000000..5776c9a --- /dev/null +++ b/contrib/adminpack/sql/adminpack.sql @@ -0,0 +1,76 @@ +CREATE EXTENSION adminpack; + +-- create new file +SELECT pg_file_write('test_file1', 'test1', false); +SELECT pg_read_file('test_file1'); + +-- append +SELECT pg_file_write('test_file1', 'test1', true); +SELECT pg_read_file('test_file1'); + +-- error, already exists +SELECT pg_file_write('test_file1', 'test1', false); +SELECT pg_read_file('test_file1'); + +-- disallowed file paths for non-superusers and users who are +-- not members of pg_write_server_files +CREATE ROLE regress_adminpack_user1; + +GRANT pg_read_all_settings TO regress_adminpack_user1; +GRANT EXECUTE ON FUNCTION pg_file_write(text,text,bool) TO regress_adminpack_user1; + +SET ROLE regress_adminpack_user1; +SELECT pg_file_write('../test_file0', 'test0', false); +SELECT pg_file_write('/tmp/test_file0', 'test0', false); +SELECT pg_file_write(current_setting('data_directory') || '/test_file4', 'test4', false); +SELECT pg_file_write(current_setting('data_directory') || '/../test_file4', 'test4', false); +RESET ROLE; +REVOKE EXECUTE ON FUNCTION pg_file_write(text,text,bool) FROM regress_adminpack_user1; +REVOKE pg_read_all_settings FROM regress_adminpack_user1; +DROP ROLE regress_adminpack_user1; + +-- sync +SELECT pg_file_sync('test_file1'); -- sync file +SELECT pg_file_sync('pg_stat'); -- sync directory +SELECT pg_file_sync('test_file2'); -- not there + +-- rename file +SELECT pg_file_rename('test_file1', 'test_file2'); +SELECT pg_read_file('test_file1'); -- not there +SELECT pg_read_file('test_file2'); + +-- error +SELECT pg_file_rename('test_file1', 'test_file2'); + +-- rename file and archive +SELECT pg_file_write('test_file3', 'test3', false); +SELECT pg_file_rename('test_file2', 'test_file3', 'test_file3_archive'); +SELECT pg_read_file('test_file2'); -- not there +SELECT pg_read_file('test_file3'); +SELECT pg_read_file('test_file3_archive'); + + +-- unlink +SELECT pg_file_unlink('test_file1'); -- does not exist +SELECT pg_file_unlink('test_file2'); -- does not exist +SELECT pg_file_unlink('test_file3'); +SELECT pg_file_unlink('test_file3_archive'); +SELECT pg_file_unlink('test_file4'); + + +-- superuser checks +CREATE USER regress_adminpack_user1; +SET ROLE regress_adminpack_user1; + +SELECT pg_file_write('test_file0', 'test0', false); +SELECT pg_file_sync('test_file0'); +SELECT pg_file_rename('test_file0', 'test_file0'); +SELECT pg_file_unlink('test_file0'); +SELECT pg_logdir_ls(); + +RESET ROLE; +DROP USER regress_adminpack_user1; + + +-- no further tests for pg_logdir_ls() because it depends on the +-- server's logging setup diff --git a/contrib/amcheck/.gitignore b/contrib/amcheck/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/amcheck/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/amcheck/Makefile b/contrib/amcheck/Makefile new file mode 100644 index 0000000..f830bd3 --- /dev/null +++ b/contrib/amcheck/Makefile @@ -0,0 +1,27 @@ +# contrib/amcheck/Makefile + +MODULE_big = amcheck +OBJS = \ + $(WIN32RES) \ + verify_heapam.o \ + verify_nbtree.o + +EXTENSION = amcheck +DATA = amcheck--1.2--1.3.sql amcheck--1.1--1.2.sql amcheck--1.0--1.1.sql amcheck--1.0.sql +PGFILEDESC = "amcheck - function for verifying relation integrity" + +REGRESS = check check_btree check_heap + +EXTRA_INSTALL = contrib/pg_walinspect +TAP_TESTS = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/amcheck +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/amcheck/amcheck--1.0--1.1.sql b/contrib/amcheck/amcheck--1.0--1.1.sql new file mode 100644 index 0000000..088416e --- /dev/null +++ b/contrib/amcheck/amcheck--1.0--1.1.sql @@ -0,0 +1,29 @@ +/* contrib/amcheck/amcheck--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION amcheck UPDATE TO '1.1'" to load this file. \quit + +-- In order to avoid issues with dependencies when updating amcheck to 1.1, +-- create new, overloaded versions of the 1.0 functions + +-- +-- bt_index_check() +-- +CREATE FUNCTION bt_index_check(index regclass, + heapallindexed boolean) +RETURNS VOID +AS 'MODULE_PATHNAME', 'bt_index_check' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +-- +-- bt_index_parent_check() +-- +CREATE FUNCTION bt_index_parent_check(index regclass, + heapallindexed boolean) +RETURNS VOID +AS 'MODULE_PATHNAME', 'bt_index_parent_check' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +-- Don't want these to be available to public +REVOKE ALL ON FUNCTION bt_index_check(regclass, boolean) FROM PUBLIC; +REVOKE ALL ON FUNCTION bt_index_parent_check(regclass, boolean) FROM PUBLIC; diff --git a/contrib/amcheck/amcheck--1.0.sql b/contrib/amcheck/amcheck--1.0.sql new file mode 100644 index 0000000..a6612d1 --- /dev/null +++ b/contrib/amcheck/amcheck--1.0.sql @@ -0,0 +1,24 @@ +/* contrib/amcheck/amcheck--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION amcheck" to load this file. \quit + +-- +-- bt_index_check() +-- +CREATE FUNCTION bt_index_check(index regclass) +RETURNS VOID +AS 'MODULE_PATHNAME', 'bt_index_check' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +-- +-- bt_index_parent_check() +-- +CREATE FUNCTION bt_index_parent_check(index regclass) +RETURNS VOID +AS 'MODULE_PATHNAME', 'bt_index_parent_check' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +-- Don't want these to be available to public +REVOKE ALL ON FUNCTION bt_index_check(regclass) FROM PUBLIC; +REVOKE ALL ON FUNCTION bt_index_parent_check(regclass) FROM PUBLIC; diff --git a/contrib/amcheck/amcheck--1.1--1.2.sql b/contrib/amcheck/amcheck--1.1--1.2.sql new file mode 100644 index 0000000..883530d --- /dev/null +++ b/contrib/amcheck/amcheck--1.1--1.2.sql @@ -0,0 +1,19 @@ +/* contrib/amcheck/amcheck--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION amcheck UPDATE TO '1.2'" to load this file. \quit + +-- In order to avoid issues with dependencies when updating amcheck to 1.2, +-- create new, overloaded version of the 1.1 function signature + +-- +-- bt_index_parent_check() +-- +CREATE FUNCTION bt_index_parent_check(index regclass, + heapallindexed boolean, rootdescend boolean) +RETURNS VOID +AS 'MODULE_PATHNAME', 'bt_index_parent_check' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +-- Don't want this to be available to public +REVOKE ALL ON FUNCTION bt_index_parent_check(regclass, boolean, boolean) FROM PUBLIC; diff --git a/contrib/amcheck/amcheck--1.2--1.3.sql b/contrib/amcheck/amcheck--1.2--1.3.sql new file mode 100644 index 0000000..7237ab7 --- /dev/null +++ b/contrib/amcheck/amcheck--1.2--1.3.sql @@ -0,0 +1,30 @@ +/* contrib/amcheck/amcheck--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION amcheck UPDATE TO '1.3'" to load this file. \quit + +-- +-- verify_heapam() +-- +CREATE FUNCTION verify_heapam(relation regclass, + on_error_stop boolean default false, + check_toast boolean default false, + skip text default 'none', + startblock bigint default null, + endblock bigint default null, + blkno OUT bigint, + offnum OUT integer, + attnum OUT integer, + msg OUT text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'verify_heapam' +LANGUAGE C; + +-- Don't want this to be available to public +REVOKE ALL ON FUNCTION verify_heapam(regclass, + boolean, + boolean, + text, + bigint, + bigint) +FROM PUBLIC; diff --git a/contrib/amcheck/amcheck.control b/contrib/amcheck/amcheck.control new file mode 100644 index 0000000..ab50931 --- /dev/null +++ b/contrib/amcheck/amcheck.control @@ -0,0 +1,5 @@ +# amcheck extension +comment = 'functions for verifying relation integrity' +default_version = '1.3' +module_pathname = '$libdir/amcheck' +relocatable = true diff --git a/contrib/amcheck/expected/check.out b/contrib/amcheck/expected/check.out new file mode 100644 index 0000000..5b3e6d5 --- /dev/null +++ b/contrib/amcheck/expected/check.out @@ -0,0 +1 @@ +CREATE EXTENSION amcheck; diff --git a/contrib/amcheck/expected/check_btree.out b/contrib/amcheck/expected/check_btree.out new file mode 100644 index 0000000..38791bb --- /dev/null +++ b/contrib/amcheck/expected/check_btree.out @@ -0,0 +1,210 @@ +CREATE TABLE bttest_a(id int8); +CREATE TABLE bttest_b(id int8); +CREATE TABLE bttest_multi(id int8, data int8); +CREATE TABLE delete_test_table (a bigint, b bigint, c bigint, d bigint); +-- Stabalize tests +ALTER TABLE bttest_a SET (autovacuum_enabled = false); +ALTER TABLE bttest_b SET (autovacuum_enabled = false); +ALTER TABLE bttest_multi SET (autovacuum_enabled = false); +ALTER TABLE delete_test_table SET (autovacuum_enabled = false); +INSERT INTO bttest_a SELECT * FROM generate_series(1, 100000); +INSERT INTO bttest_b SELECT * FROM generate_series(100000, 1, -1); +INSERT INTO bttest_multi SELECT i, i%2 FROM generate_series(1, 100000) as i; +CREATE INDEX bttest_a_idx ON bttest_a USING btree (id) WITH (deduplicate_items = ON); +CREATE INDEX bttest_b_idx ON bttest_b USING btree (id); +CREATE UNIQUE INDEX bttest_multi_idx ON bttest_multi +USING btree (id) INCLUDE (data); +CREATE ROLE regress_bttest_role; +-- verify permissions are checked (error due to function not callable) +SET ROLE regress_bttest_role; +SELECT bt_index_check('bttest_a_idx'::regclass); +ERROR: permission denied for function bt_index_check +SELECT bt_index_parent_check('bttest_a_idx'::regclass); +ERROR: permission denied for function bt_index_parent_check +RESET ROLE; +-- we, intentionally, don't check relation permissions - it's useful +-- to run this cluster-wide with a restricted account, and as tested +-- above explicit permission has to be granted for that. +GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO regress_bttest_role; +GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass) TO regress_bttest_role; +GRANT EXECUTE ON FUNCTION bt_index_check(regclass, boolean) TO regress_bttest_role; +GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass, boolean) TO regress_bttest_role; +SET ROLE regress_bttest_role; +SELECT bt_index_check('bttest_a_idx'); + bt_index_check +---------------- + +(1 row) + +SELECT bt_index_parent_check('bttest_a_idx'); + bt_index_parent_check +----------------------- + +(1 row) + +RESET ROLE; +-- verify plain tables are rejected (error) +SELECT bt_index_check('bttest_a'); +ERROR: "bttest_a" is not an index +SELECT bt_index_parent_check('bttest_a'); +ERROR: "bttest_a" is not an index +-- verify non-existing indexes are rejected (error) +SELECT bt_index_check(17); +ERROR: could not open relation with OID 17 +SELECT bt_index_parent_check(17); +ERROR: could not open relation with OID 17 +-- verify wrong index types are rejected (error) +BEGIN; +CREATE INDEX bttest_a_brin_idx ON bttest_a USING brin(id); +SELECT bt_index_parent_check('bttest_a_brin_idx'); +ERROR: only B-Tree indexes are supported as targets for verification +DETAIL: Relation "bttest_a_brin_idx" is not a B-Tree index. +ROLLBACK; +-- normal check outside of xact +SELECT bt_index_check('bttest_a_idx'); + bt_index_check +---------------- + +(1 row) + +-- more expansive tests +SELECT bt_index_check('bttest_a_idx', true); + bt_index_check +---------------- + +(1 row) + +SELECT bt_index_parent_check('bttest_b_idx', true); + bt_index_parent_check +----------------------- + +(1 row) + +BEGIN; +SELECT bt_index_check('bttest_a_idx'); + bt_index_check +---------------- + +(1 row) + +SELECT bt_index_parent_check('bttest_b_idx'); + bt_index_parent_check +----------------------- + +(1 row) + +-- make sure we don't have any leftover locks +SELECT * FROM pg_locks +WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx']::regclass[]) + AND pid = pg_backend_pid(); + locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | mode | granted | fastpath | waitstart +----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----+------+---------+----------+----------- +(0 rows) + +COMMIT; +-- Deduplication +TRUNCATE bttest_a; +INSERT INTO bttest_a SELECT 42 FROM generate_series(1, 2000); +SELECT bt_index_check('bttest_a_idx', true); + bt_index_check +---------------- + +(1 row) + +-- normal check outside of xact for index with included columns +SELECT bt_index_check('bttest_multi_idx'); + bt_index_check +---------------- + +(1 row) + +-- more expansive tests for index with included columns +SELECT bt_index_parent_check('bttest_multi_idx', true, true); + bt_index_parent_check +----------------------- + +(1 row) + +-- repeat expansive tests for index built using insertions +TRUNCATE bttest_multi; +INSERT INTO bttest_multi SELECT i, i%2 FROM generate_series(1, 100000) as i; +SELECT bt_index_parent_check('bttest_multi_idx', true, true); + bt_index_parent_check +----------------------- + +(1 row) + +-- +-- Test for multilevel page deletion/downlink present checks, and rootdescend +-- checks +-- +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; +SELECT bt_index_parent_check('delete_test_table_pkey', true); + bt_index_parent_check +----------------------- + +(1 row) + +-- +-- BUG #15597: must not assume consistent input toasting state when forming +-- tuple. Bloom filter must fingerprint normalized index tuple representation. +-- +CREATE TABLE toast_bug(buggy text); +ALTER TABLE toast_bug ALTER COLUMN buggy SET STORAGE extended; +CREATE INDEX toasty ON toast_bug(buggy); +-- pg_attribute entry for toasty.buggy (the index) will have plain storage: +UPDATE pg_attribute SET attstorage = 'p' +WHERE attrelid = 'toasty'::regclass AND attname = 'buggy'; +-- Whereas pg_attribute entry for toast_bug.buggy (the table) still has extended storage: +SELECT attstorage FROM pg_attribute +WHERE attrelid = 'toast_bug'::regclass AND attname = 'buggy'; + attstorage +------------ + x +(1 row) + +-- Insert compressible heap tuple (comfortably exceeds TOAST_TUPLE_THRESHOLD): +INSERT INTO toast_bug SELECT repeat('a', 2200); +-- Should not get false positive report of corruption: +SELECT bt_index_check('toasty', true); + bt_index_check +---------------- + +(1 row) + +-- +-- Check that index expressions and predicates are run as the table's owner +-- +TRUNCATE bttest_a; +INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000); +ALTER TABLE bttest_a OWNER TO regress_bttest_role; +-- A dummy index function checking current_user +CREATE FUNCTION ifun(int8) RETURNS int8 AS $$ +BEGIN + ASSERT current_user = 'regress_bttest_role', + format('ifun(%s) called by %s', $1, current_user); + RETURN $1; +END; +$$ LANGUAGE plpgsql IMMUTABLE; +CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0))) + WHERE ifun(id + 10) > ifun(10); +SELECT bt_index_check('bttest_a_expr_idx', true); + bt_index_check +---------------- + +(1 row) + +-- cleanup +DROP TABLE bttest_a; +DROP TABLE bttest_b; +DROP TABLE bttest_multi; +DROP TABLE delete_test_table; +DROP TABLE toast_bug; +DROP FUNCTION ifun(int8); +DROP OWNED BY regress_bttest_role; -- permissions +DROP ROLE regress_bttest_role; diff --git a/contrib/amcheck/expected/check_heap.out b/contrib/amcheck/expected/check_heap.out new file mode 100644 index 0000000..979e5e8 --- /dev/null +++ b/contrib/amcheck/expected/check_heap.out @@ -0,0 +1,240 @@ +CREATE TABLE heaptest (a integer, b text); +REVOKE ALL ON heaptest FROM PUBLIC; +-- Check that invalid skip option is rejected +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'rope'); +ERROR: invalid skip option +HINT: Valid skip options are "all-visible", "all-frozen", and "none". +-- Check specifying invalid block ranges when verifying an empty table +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 5, endblock := 8); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Check that valid options are not rejected nor corruption reported +-- for an empty table, and that skip enum-like parameter is case-insensitive +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'None'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Frozen'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Visible'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'NONE'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-FROZEN'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-VISIBLE'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Add some data so subsequent tests are not entirely trivial +INSERT INTO heaptest (a, b) + (SELECT gs, repeat('x', gs) + FROM generate_series(1,50) gs); +-- pg_stat_io test: +-- verify_heapam always uses a BAS_BULKREAD BufferAccessStrategy, whereas a +-- sequential scan does so only if the table is large enough when compared to +-- shared buffers (see initscan()). CREATE DATABASE ... also unconditionally +-- uses a BAS_BULKREAD strategy, but we have chosen to use a tablespace and +-- verify_heapam to provide coverage instead of adding another expensive +-- operation to the main regression test suite. +-- +-- Create an alternative tablespace and move the heaptest table to it, causing +-- it to be rewritten and all the blocks to reliably evicted from shared +-- buffers -- guaranteeing actual reads when we next select from it in the +-- same transaction. The heaptest table is smaller than the default +-- wal_skip_threshold, so a wal_level=minimal commit reads the table into +-- shared_buffers. A transaction delays that and excludes any autovacuum. +SET allow_in_place_tablespaces = true; +CREATE TABLESPACE regress_test_stats_tblspc LOCATION ''; +SELECT sum(reads) AS stats_bulkreads_before + FROM pg_stat_io WHERE context = 'bulkread' \gset +BEGIN; +ALTER TABLE heaptest SET TABLESPACE regress_test_stats_tblspc; +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty table +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +COMMIT; +-- verify_heapam should have read in the page written out by +-- ALTER TABLE ... SET TABLESPACE ... +-- causing an additional bulkread, which should be reflected in pg_stat_io. +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT sum(reads) AS stats_bulkreads_after + FROM pg_stat_io WHERE context = 'bulkread' \gset +SELECT :stats_bulkreads_after > :stats_bulkreads_before; + ?column? +---------- + t +(1 row) + +CREATE ROLE regress_heaptest_role; +-- verify permissions are checked (error due to function not callable) +SET ROLE regress_heaptest_role; +SELECT * FROM verify_heapam(relation := 'heaptest'); +ERROR: permission denied for function verify_heapam +RESET ROLE; +GRANT EXECUTE ON FUNCTION verify_heapam(regclass, boolean, boolean, text, bigint, bigint) TO regress_heaptest_role; +-- verify permissions are now sufficient +SET ROLE regress_heaptest_role; +SELECT * FROM verify_heapam(relation := 'heaptest'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +RESET ROLE; +-- Check specifying invalid block ranges when verifying a non-empty table. +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 10000); +ERROR: ending block number must be between 0 and 0 +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 10000, endblock := 11000); +ERROR: starting block number must be between 0 and 0 +-- Vacuum freeze to change the xids encountered in subsequent tests +VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) heaptest; +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty frozen table +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Check that partitioned tables (the parent ones) which don't have visibility +-- maps are rejected +CREATE TABLE test_partitioned (a int, b text default repeat('x', 5000)) + PARTITION BY list (a); +SELECT * FROM verify_heapam('test_partitioned', + startblock := NULL, + endblock := NULL); +ERROR: cannot check relation "test_partitioned" +DETAIL: This operation is not supported for partitioned tables. +-- Check that valid options are not rejected nor corruption reported +-- for an empty partition table (the child one) +CREATE TABLE test_partition partition OF test_partitioned FOR VALUES IN (1); +SELECT * FROM verify_heapam('test_partition', + startblock := NULL, + endblock := NULL); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty partition table (the child one) +INSERT INTO test_partitioned (a) (SELECT 1 FROM generate_series(1,1000) gs); +SELECT * FROM verify_heapam('test_partition', + startblock := NULL, + endblock := NULL); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Check that indexes are rejected +CREATE INDEX test_index ON test_partition (a); +SELECT * FROM verify_heapam('test_index', + startblock := NULL, + endblock := NULL); +ERROR: cannot check relation "test_index" +DETAIL: This operation is not supported for indexes. +-- Check that views are rejected +CREATE VIEW test_view AS SELECT 1; +SELECT * FROM verify_heapam('test_view', + startblock := NULL, + endblock := NULL); +ERROR: cannot check relation "test_view" +DETAIL: This operation is not supported for views. +-- Check that sequences are rejected +CREATE SEQUENCE test_sequence; +SELECT * FROM verify_heapam('test_sequence', + startblock := NULL, + endblock := NULL); + blkno | offnum | attnum | msg +-------+--------+--------+----- +(0 rows) + +-- Check that foreign tables are rejected +CREATE FOREIGN DATA WRAPPER dummy; +CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy; +CREATE FOREIGN TABLE test_foreign_table () SERVER dummy_server; +SELECT * FROM verify_heapam('test_foreign_table', + startblock := NULL, + endblock := NULL); +ERROR: cannot check relation "test_foreign_table" +DETAIL: This operation is not supported for foreign tables. +-- cleanup +DROP TABLE heaptest; +DROP TABLESPACE regress_test_stats_tblspc; +DROP TABLE test_partition; +DROP TABLE test_partitioned; +DROP OWNED BY regress_heaptest_role; -- permissions +DROP ROLE regress_heaptest_role; diff --git a/contrib/amcheck/meson.build b/contrib/amcheck/meson.build new file mode 100644 index 0000000..78a46bf --- /dev/null +++ b/contrib/amcheck/meson.build @@ -0,0 +1,48 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +amcheck_sources = files( + 'verify_heapam.c', + 'verify_nbtree.c', +) + +if host_system == 'windows' + amcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'amcheck', + '--FILEDESC', 'amcheck - function for verifying relation integrity',]) +endif + +amcheck = shared_module('amcheck', + amcheck_sources, + kwargs: contrib_mod_args, +) +contrib_targets += amcheck + +install_data( + 'amcheck.control', + 'amcheck--1.0.sql', + 'amcheck--1.0--1.1.sql', + 'amcheck--1.1--1.2.sql', + 'amcheck--1.2--1.3.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'amcheck', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'check', + 'check_btree', + 'check_heap', + ], + }, + 'tap': { + 'tests': [ + 't/001_verify_heapam.pl', + 't/002_cic.pl', + 't/003_cic_2pc.pl', + 't/005_pitr.pl', + ], + }, +} diff --git a/contrib/amcheck/sql/check.sql b/contrib/amcheck/sql/check.sql new file mode 100644 index 0000000..5b3e6d5 --- /dev/null +++ b/contrib/amcheck/sql/check.sql @@ -0,0 +1 @@ +CREATE EXTENSION amcheck; diff --git a/contrib/amcheck/sql/check_btree.sql b/contrib/amcheck/sql/check_btree.sql new file mode 100644 index 0000000..033c04b --- /dev/null +++ b/contrib/amcheck/sql/check_btree.sql @@ -0,0 +1,146 @@ +CREATE TABLE bttest_a(id int8); +CREATE TABLE bttest_b(id int8); +CREATE TABLE bttest_multi(id int8, data int8); +CREATE TABLE delete_test_table (a bigint, b bigint, c bigint, d bigint); + +-- Stabalize tests +ALTER TABLE bttest_a SET (autovacuum_enabled = false); +ALTER TABLE bttest_b SET (autovacuum_enabled = false); +ALTER TABLE bttest_multi SET (autovacuum_enabled = false); +ALTER TABLE delete_test_table SET (autovacuum_enabled = false); + +INSERT INTO bttest_a SELECT * FROM generate_series(1, 100000); +INSERT INTO bttest_b SELECT * FROM generate_series(100000, 1, -1); +INSERT INTO bttest_multi SELECT i, i%2 FROM generate_series(1, 100000) as i; + +CREATE INDEX bttest_a_idx ON bttest_a USING btree (id) WITH (deduplicate_items = ON); +CREATE INDEX bttest_b_idx ON bttest_b USING btree (id); +CREATE UNIQUE INDEX bttest_multi_idx ON bttest_multi +USING btree (id) INCLUDE (data); + +CREATE ROLE regress_bttest_role; + +-- verify permissions are checked (error due to function not callable) +SET ROLE regress_bttest_role; +SELECT bt_index_check('bttest_a_idx'::regclass); +SELECT bt_index_parent_check('bttest_a_idx'::regclass); +RESET ROLE; + +-- we, intentionally, don't check relation permissions - it's useful +-- to run this cluster-wide with a restricted account, and as tested +-- above explicit permission has to be granted for that. +GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO regress_bttest_role; +GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass) TO regress_bttest_role; +GRANT EXECUTE ON FUNCTION bt_index_check(regclass, boolean) TO regress_bttest_role; +GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass, boolean) TO regress_bttest_role; +SET ROLE regress_bttest_role; +SELECT bt_index_check('bttest_a_idx'); +SELECT bt_index_parent_check('bttest_a_idx'); +RESET ROLE; + +-- verify plain tables are rejected (error) +SELECT bt_index_check('bttest_a'); +SELECT bt_index_parent_check('bttest_a'); + +-- verify non-existing indexes are rejected (error) +SELECT bt_index_check(17); +SELECT bt_index_parent_check(17); + +-- verify wrong index types are rejected (error) +BEGIN; +CREATE INDEX bttest_a_brin_idx ON bttest_a USING brin(id); +SELECT bt_index_parent_check('bttest_a_brin_idx'); +ROLLBACK; + +-- normal check outside of xact +SELECT bt_index_check('bttest_a_idx'); +-- more expansive tests +SELECT bt_index_check('bttest_a_idx', true); +SELECT bt_index_parent_check('bttest_b_idx', true); + +BEGIN; +SELECT bt_index_check('bttest_a_idx'); +SELECT bt_index_parent_check('bttest_b_idx'); +-- make sure we don't have any leftover locks +SELECT * FROM pg_locks +WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx']::regclass[]) + AND pid = pg_backend_pid(); +COMMIT; + +-- Deduplication +TRUNCATE bttest_a; +INSERT INTO bttest_a SELECT 42 FROM generate_series(1, 2000); +SELECT bt_index_check('bttest_a_idx', true); + +-- normal check outside of xact for index with included columns +SELECT bt_index_check('bttest_multi_idx'); +-- more expansive tests for index with included columns +SELECT bt_index_parent_check('bttest_multi_idx', true, true); + +-- repeat expansive tests for index built using insertions +TRUNCATE bttest_multi; +INSERT INTO bttest_multi SELECT i, i%2 FROM generate_series(1, 100000) as i; +SELECT bt_index_parent_check('bttest_multi_idx', true, true); + +-- +-- Test for multilevel page deletion/downlink present checks, and rootdescend +-- checks +-- +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; +SELECT bt_index_parent_check('delete_test_table_pkey', true); + +-- +-- BUG #15597: must not assume consistent input toasting state when forming +-- tuple. Bloom filter must fingerprint normalized index tuple representation. +-- +CREATE TABLE toast_bug(buggy text); +ALTER TABLE toast_bug ALTER COLUMN buggy SET STORAGE extended; +CREATE INDEX toasty ON toast_bug(buggy); + +-- pg_attribute entry for toasty.buggy (the index) will have plain storage: +UPDATE pg_attribute SET attstorage = 'p' +WHERE attrelid = 'toasty'::regclass AND attname = 'buggy'; + +-- Whereas pg_attribute entry for toast_bug.buggy (the table) still has extended storage: +SELECT attstorage FROM pg_attribute +WHERE attrelid = 'toast_bug'::regclass AND attname = 'buggy'; + +-- Insert compressible heap tuple (comfortably exceeds TOAST_TUPLE_THRESHOLD): +INSERT INTO toast_bug SELECT repeat('a', 2200); +-- Should not get false positive report of corruption: +SELECT bt_index_check('toasty', true); + +-- +-- Check that index expressions and predicates are run as the table's owner +-- +TRUNCATE bttest_a; +INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000); +ALTER TABLE bttest_a OWNER TO regress_bttest_role; +-- A dummy index function checking current_user +CREATE FUNCTION ifun(int8) RETURNS int8 AS $$ +BEGIN + ASSERT current_user = 'regress_bttest_role', + format('ifun(%s) called by %s', $1, current_user); + RETURN $1; +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0))) + WHERE ifun(id + 10) > ifun(10); + +SELECT bt_index_check('bttest_a_expr_idx', true); + +-- cleanup +DROP TABLE bttest_a; +DROP TABLE bttest_b; +DROP TABLE bttest_multi; +DROP TABLE delete_test_table; +DROP TABLE toast_bug; +DROP FUNCTION ifun(int8); +DROP OWNED BY regress_bttest_role; -- permissions +DROP ROLE regress_bttest_role; diff --git a/contrib/amcheck/sql/check_heap.sql b/contrib/amcheck/sql/check_heap.sql new file mode 100644 index 0000000..1745bae --- /dev/null +++ b/contrib/amcheck/sql/check_heap.sql @@ -0,0 +1,147 @@ +CREATE TABLE heaptest (a integer, b text); +REVOKE ALL ON heaptest FROM PUBLIC; + +-- Check that invalid skip option is rejected +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'rope'); + +-- Check specifying invalid block ranges when verifying an empty table +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 5, endblock := 8); + +-- Check that valid options are not rejected nor corruption reported +-- for an empty table, and that skip enum-like parameter is case-insensitive +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'None'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Frozen'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'All-Visible'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'NONE'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-FROZEN'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'ALL-VISIBLE'); + + +-- Add some data so subsequent tests are not entirely trivial +INSERT INTO heaptest (a, b) + (SELECT gs, repeat('x', gs) + FROM generate_series(1,50) gs); + +-- pg_stat_io test: +-- verify_heapam always uses a BAS_BULKREAD BufferAccessStrategy, whereas a +-- sequential scan does so only if the table is large enough when compared to +-- shared buffers (see initscan()). CREATE DATABASE ... also unconditionally +-- uses a BAS_BULKREAD strategy, but we have chosen to use a tablespace and +-- verify_heapam to provide coverage instead of adding another expensive +-- operation to the main regression test suite. +-- +-- Create an alternative tablespace and move the heaptest table to it, causing +-- it to be rewritten and all the blocks to reliably evicted from shared +-- buffers -- guaranteeing actual reads when we next select from it in the +-- same transaction. The heaptest table is smaller than the default +-- wal_skip_threshold, so a wal_level=minimal commit reads the table into +-- shared_buffers. A transaction delays that and excludes any autovacuum. +SET allow_in_place_tablespaces = true; +CREATE TABLESPACE regress_test_stats_tblspc LOCATION ''; +SELECT sum(reads) AS stats_bulkreads_before + FROM pg_stat_io WHERE context = 'bulkread' \gset +BEGIN; +ALTER TABLE heaptest SET TABLESPACE regress_test_stats_tblspc; +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty table +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); +COMMIT; + +-- verify_heapam should have read in the page written out by +-- ALTER TABLE ... SET TABLESPACE ... +-- causing an additional bulkread, which should be reflected in pg_stat_io. +SELECT pg_stat_force_next_flush(); +SELECT sum(reads) AS stats_bulkreads_after + FROM pg_stat_io WHERE context = 'bulkread' \gset +SELECT :stats_bulkreads_after > :stats_bulkreads_before; + +CREATE ROLE regress_heaptest_role; + +-- verify permissions are checked (error due to function not callable) +SET ROLE regress_heaptest_role; +SELECT * FROM verify_heapam(relation := 'heaptest'); +RESET ROLE; + +GRANT EXECUTE ON FUNCTION verify_heapam(regclass, boolean, boolean, text, bigint, bigint) TO regress_heaptest_role; + +-- verify permissions are now sufficient +SET ROLE regress_heaptest_role; +SELECT * FROM verify_heapam(relation := 'heaptest'); +RESET ROLE; + +-- Check specifying invalid block ranges when verifying a non-empty table. +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 10000); +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 10000, endblock := 11000); + +-- Vacuum freeze to change the xids encountered in subsequent tests +VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) heaptest; + +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty frozen table +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'none'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-frozen'); +SELECT * FROM verify_heapam(relation := 'heaptest', skip := 'all-visible'); +SELECT * FROM verify_heapam(relation := 'heaptest', startblock := 0, endblock := 0); + +-- Check that partitioned tables (the parent ones) which don't have visibility +-- maps are rejected +CREATE TABLE test_partitioned (a int, b text default repeat('x', 5000)) + PARTITION BY list (a); +SELECT * FROM verify_heapam('test_partitioned', + startblock := NULL, + endblock := NULL); + +-- Check that valid options are not rejected nor corruption reported +-- for an empty partition table (the child one) +CREATE TABLE test_partition partition OF test_partitioned FOR VALUES IN (1); +SELECT * FROM verify_heapam('test_partition', + startblock := NULL, + endblock := NULL); + +-- Check that valid options are not rejected nor corruption reported +-- for a non-empty partition table (the child one) +INSERT INTO test_partitioned (a) (SELECT 1 FROM generate_series(1,1000) gs); +SELECT * FROM verify_heapam('test_partition', + startblock := NULL, + endblock := NULL); + +-- Check that indexes are rejected +CREATE INDEX test_index ON test_partition (a); +SELECT * FROM verify_heapam('test_index', + startblock := NULL, + endblock := NULL); + +-- Check that views are rejected +CREATE VIEW test_view AS SELECT 1; +SELECT * FROM verify_heapam('test_view', + startblock := NULL, + endblock := NULL); + +-- Check that sequences are rejected +CREATE SEQUENCE test_sequence; +SELECT * FROM verify_heapam('test_sequence', + startblock := NULL, + endblock := NULL); + +-- Check that foreign tables are rejected +CREATE FOREIGN DATA WRAPPER dummy; +CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy; +CREATE FOREIGN TABLE test_foreign_table () SERVER dummy_server; +SELECT * FROM verify_heapam('test_foreign_table', + startblock := NULL, + endblock := NULL); + +-- cleanup +DROP TABLE heaptest; +DROP TABLESPACE regress_test_stats_tblspc; +DROP TABLE test_partition; +DROP TABLE test_partitioned; +DROP OWNED BY regress_heaptest_role; -- permissions +DROP ROLE regress_heaptest_role; diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl new file mode 100644 index 0000000..46d5b53 --- /dev/null +++ b/contrib/amcheck/t/001_verify_heapam.pl @@ -0,0 +1,286 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; + +use Test::More; + +my ($node, $result); + +# +# Test set-up +# +$node = PostgreSQL::Test::Cluster->new('test'); +$node->init; +$node->append_conf('postgresql.conf', 'autovacuum=off'); +$node->start; +$node->safe_psql('postgres', q(CREATE EXTENSION amcheck)); + +# +# Check a table with data loaded but no corruption, freezing, etc. +# +fresh_test_table('test'); +check_all_options_uncorrupted('test', 'plain'); + +# +# Check a corrupt table +# +fresh_test_table('test'); +corrupt_first_page('test'); +detects_heap_corruption("verify_heapam('test')", "plain corrupted table"); +detects_heap_corruption( + "verify_heapam('test', skip := 'all-visible')", + "plain corrupted table skipping all-visible"); +detects_heap_corruption( + "verify_heapam('test', skip := 'all-frozen')", + "plain corrupted table skipping all-frozen"); +detects_heap_corruption( + "verify_heapam('test', check_toast := false)", + "plain corrupted table skipping toast"); +detects_heap_corruption( + "verify_heapam('test', startblock := 0, endblock := 0)", + "plain corrupted table checking only block zero"); + +# +# Check a corrupt table with all-frozen data +# +fresh_test_table('test'); +$node->safe_psql('postgres', q(VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test)); +detects_no_corruption("verify_heapam('test')", + "all-frozen not corrupted table"); +corrupt_first_page('test'); +detects_heap_corruption("verify_heapam('test')", + "all-frozen corrupted table"); +detects_no_corruption( + "verify_heapam('test', skip := 'all-frozen')", + "all-frozen corrupted table skipping all-frozen"); + +# +# Check a sequence with no corruption. The current implementation of sequences +# doesn't require its own test setup, since sequences are really just heap +# tables under-the-hood. To guard against future implementation changes made +# without remembering to update verify_heapam, we create and exercise a +# sequence, checking along the way that it passes corruption checks. +# +fresh_test_sequence('test_seq'); +check_all_options_uncorrupted('test_seq', 'plain'); +advance_test_sequence('test_seq'); +check_all_options_uncorrupted('test_seq', 'plain'); +set_test_sequence('test_seq'); +check_all_options_uncorrupted('test_seq', 'plain'); +reset_test_sequence('test_seq'); +check_all_options_uncorrupted('test_seq', 'plain'); + +# Returns the filesystem path for the named relation. +sub relation_filepath +{ + my ($relname) = @_; + + my $pgdata = $node->data_dir; + my $rel = $node->safe_psql('postgres', + qq(SELECT pg_relation_filepath('$relname'))); + die "path not found for relation $relname" unless defined $rel; + return "$pgdata/$rel"; +} + +# Returns the fully qualified name of the toast table for the named relation +sub get_toast_for +{ + my ($relname) = @_; + + return $node->safe_psql( + 'postgres', qq( + SELECT 'pg_toast.' || t.relname + FROM pg_catalog.pg_class c, pg_catalog.pg_class t + WHERE c.relname = '$relname' + AND c.reltoastrelid = t.oid)); +} + +# (Re)create and populate a test table of the given name. +sub fresh_test_table +{ + my ($relname) = @_; + + return $node->safe_psql( + 'postgres', qq( + DROP TABLE IF EXISTS $relname CASCADE; + CREATE TABLE $relname (a integer, b text); + ALTER TABLE $relname SET (autovacuum_enabled=false); + ALTER TABLE $relname ALTER b SET STORAGE external; + INSERT INTO $relname (a, b) + (SELECT gs, repeat('b',gs*10) FROM generate_series(1,1000) gs); + BEGIN; + SAVEPOINT s1; + SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE; + UPDATE $relname SET b = b WHERE a = 42; + RELEASE s1; + SAVEPOINT s1; + SELECT 1 FROM $relname WHERE a = 42 FOR UPDATE; + UPDATE $relname SET b = b WHERE a = 42; + COMMIT; + )); +} + +# Create a test sequence of the given name. +sub fresh_test_sequence +{ + my ($seqname) = @_; + + return $node->safe_psql( + 'postgres', qq( + DROP SEQUENCE IF EXISTS $seqname CASCADE; + CREATE SEQUENCE $seqname + INCREMENT BY 13 + MINVALUE 17 + START WITH 23; + SELECT nextval('$seqname'); + SELECT setval('$seqname', currval('$seqname') + nextval('$seqname')); + )); +} + +# Call SQL functions to increment the sequence +sub advance_test_sequence +{ + my ($seqname) = @_; + + return $node->safe_psql( + 'postgres', qq( + SELECT nextval('$seqname'); + )); +} + +# Call SQL functions to set the sequence +sub set_test_sequence +{ + my ($seqname) = @_; + + return $node->safe_psql( + 'postgres', qq( + SELECT setval('$seqname', 102); + )); +} + +# Call SQL functions to reset the sequence +sub reset_test_sequence +{ + my ($seqname) = @_; + + return $node->safe_psql( + 'postgres', qq( + ALTER SEQUENCE $seqname RESTART WITH 51 + )); +} + +# Stops the test node, corrupts the first page of the named relation, and +# restarts the node. +sub corrupt_first_page +{ + my ($relname) = @_; + my $relpath = relation_filepath($relname); + + $node->stop; + + my $fh; + open($fh, '+<', $relpath) + or BAIL_OUT("open failed: $!"); + binmode $fh; + + # Corrupt some line pointers. The values are chosen to hit the + # various line-pointer-corruption checks in verify_heapam.c + # on both little-endian and big-endian architectures. + sysseek($fh, 32, 0) + or BAIL_OUT("sysseek failed: $!"); + syswrite( + $fh, + pack("L*", + 0xAAA15550, 0xAAA0D550, 0x00010000, + 0x00008000, 0x0000800F, 0x001e8000) + ) or BAIL_OUT("syswrite failed: $!"); + close($fh) + or BAIL_OUT("close failed: $!"); + + $node->start; +} + +sub detects_heap_corruption +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($function, $testname) = @_; + + detects_corruption( + $function, + $testname, + qr/line pointer redirection to item at offset \d+ precedes minimum offset \d+/, + qr/line pointer redirection to item at offset \d+ exceeds maximum offset \d+/, + qr/line pointer to page offset \d+ is not maximally aligned/, + qr/line pointer length \d+ is less than the minimum tuple header size \d+/, + qr/line pointer to page offset \d+ with length \d+ ends beyond maximum page offset \d+/, + ); +} + +sub detects_corruption +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($function, $testname, @re) = @_; + + my $result = $node->safe_psql('postgres', qq(SELECT * FROM $function)); + like($result, $_, $testname) for (@re); +} + +sub detects_no_corruption +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($function, $testname) = @_; + + my $result = $node->safe_psql('postgres', qq(SELECT * FROM $function)); + is($result, '', $testname); +} + +# Check various options are stable (don't abort) and do not report corruption +# when running verify_heapam on an uncorrupted test table. +# +# The relname *must* be an uncorrupted table, or this will fail. +# +# The prefix is used to identify the test, along with the options, +# and should be unique. +sub check_all_options_uncorrupted +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($relname, $prefix) = @_; + + for my $stop (qw(true false)) + { + for my $check_toast (qw(true false)) + { + for my $skip ("'none'", "'all-frozen'", "'all-visible'") + { + for my $startblock (qw(NULL 0)) + { + for my $endblock (qw(NULL 0)) + { + my $opts = + "on_error_stop := $stop, " + . "check_toast := $check_toast, " + . "skip := $skip, " + . "startblock := $startblock, " + . "endblock := $endblock"; + + detects_no_corruption( + "verify_heapam('$relname', $opts)", + "$prefix: $opts"); + } + } + } + } + } +} + +done_testing(); diff --git a/contrib/amcheck/t/002_cic.pl b/contrib/amcheck/t/002_cic.pl new file mode 100644 index 0000000..42a047a --- /dev/null +++ b/contrib/amcheck/t/002_cic.pl @@ -0,0 +1,64 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# Test CREATE INDEX CONCURRENTLY with concurrent modifications +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; + +use Test::More; + +my ($node, $result); + +# +# Test set-up +# +$node = PostgreSQL::Test::Cluster->new('CIC_test'); +$node->init; +$node->append_conf('postgresql.conf', + 'lock_timeout = ' . (1000 * $PostgreSQL::Test::Utils::timeout_default)); +$node->start; +$node->safe_psql('postgres', q(CREATE EXTENSION amcheck)); +$node->safe_psql('postgres', q(CREATE TABLE tbl(i int))); +$node->safe_psql('postgres', q(CREATE INDEX idx ON tbl(i))); + +# +# Stress CIC with pgbench. +# +# pgbench might try to launch more than one instance of the CIC +# transaction concurrently. That would deadlock, so use an advisory +# lock to ensure only one CIC runs at a time. +# +$node->pgbench( + '--no-vacuum --client=5 --transactions=100', + 0, + [qr{actually processed}], + [qr{^$}], + 'concurrent INSERTs and CIC', + { + '002_pgbench_concurrent_transaction' => q( + BEGIN; + INSERT INTO tbl VALUES(0); + COMMIT; + ), + '002_pgbench_concurrent_transaction_savepoints' => q( + BEGIN; + SAVEPOINT s1; + INSERT INTO tbl VALUES(0); + COMMIT; + ), + '002_pgbench_concurrent_cic' => q( + SELECT pg_try_advisory_lock(42)::integer AS gotlock \gset + \if :gotlock + DROP INDEX CONCURRENTLY idx; + CREATE INDEX CONCURRENTLY idx ON tbl(i); + SELECT bt_index_check('idx',true); + SELECT pg_advisory_unlock(42); + \endif + ) + }); + +$node->stop; +done_testing(); diff --git a/contrib/amcheck/t/003_cic_2pc.pl b/contrib/amcheck/t/003_cic_2pc.pl new file mode 100644 index 0000000..3279a25 --- /dev/null +++ b/contrib/amcheck/t/003_cic_2pc.pl @@ -0,0 +1,170 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# Test CREATE INDEX CONCURRENTLY with concurrent prepared-xact modifications +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; + +use Test::More; + +Test::More->builder->todo_start('filesystem bug') + if PostgreSQL::Test::Utils::has_wal_read_bug; + +my ($node, $result); + +# +# Test set-up +# +$node = PostgreSQL::Test::Cluster->new('CIC_2PC_test'); +$node->init; +$node->append_conf('postgresql.conf', 'max_prepared_transactions = 10'); +$node->append_conf('postgresql.conf', + 'lock_timeout = ' . (1000 * $PostgreSQL::Test::Utils::timeout_default)); +$node->start; +$node->safe_psql('postgres', q(CREATE EXTENSION amcheck)); +$node->safe_psql('postgres', q(CREATE TABLE tbl(i int))); + + +# +# Run 3 overlapping 2PC transactions with CIC +# +# We have two concurrent background psql processes: $main_h for INSERTs and +# $cic_h for CIC. Also, we use non-background psql for some COMMIT PREPARED +# statements. +# + +my $main_h = $node->background_psql('postgres'); + +$main_h->query_safe( + q( +BEGIN; +INSERT INTO tbl VALUES(0); +)); + +my $cic_h = $node->background_psql('postgres'); + +$cic_h->query_until( + qr/start/, q( +\echo start +CREATE INDEX CONCURRENTLY idx ON tbl(i); +)); + +$main_h->query_safe( + q( +PREPARE TRANSACTION 'a'; +)); + +$main_h->query_safe( + q( +BEGIN; +INSERT INTO tbl VALUES(0); +)); + +$node->safe_psql('postgres', q(COMMIT PREPARED 'a';)); + +$main_h->query_safe( + q( +PREPARE TRANSACTION 'b'; +BEGIN; +INSERT INTO tbl VALUES(0); +)); + +$node->safe_psql('postgres', q(COMMIT PREPARED 'b';)); + +$main_h->query_safe( + q( +PREPARE TRANSACTION 'c'; +COMMIT PREPARED 'c'; +)); + +$main_h->quit; +$cic_h->quit; + +$result = $node->psql('postgres', q(SELECT bt_index_check('idx',true))); +is($result, '0', 'bt_index_check after overlapping 2PC'); + + +# +# Server restart shall not change whether prepared xact blocks CIC +# + +$node->safe_psql( + 'postgres', q( +BEGIN; +INSERT INTO tbl VALUES(0); +PREPARE TRANSACTION 'spans_restart'; +BEGIN; +CREATE TABLE unused (); +PREPARE TRANSACTION 'persists_forever'; +)); +$node->restart; + +my $reindex_h = $node->background_psql('postgres'); +$reindex_h->query_until( + qr/start/, q( +\echo start +DROP INDEX CONCURRENTLY idx; +CREATE INDEX CONCURRENTLY idx ON tbl(i); +)); + +$node->safe_psql('postgres', "COMMIT PREPARED 'spans_restart'"); +$reindex_h->quit; +$result = $node->psql('postgres', q(SELECT bt_index_check('idx',true))); +is($result, '0', 'bt_index_check after 2PC and restart'); + + +# +# Stress CIC+2PC with pgbench +# +# pgbench might try to launch more than one instance of the CIC +# transaction concurrently. That would deadlock, so use an advisory +# lock to ensure only one CIC runs at a time. + +# Fix broken index first +$node->safe_psql('postgres', q(REINDEX TABLE tbl;)); + +# Run pgbench. +$node->pgbench( + '--no-vacuum --client=5 --transactions=100', + 0, + [qr{actually processed}], + [qr{^$}], + 'concurrent INSERTs w/ 2PC and CIC', + { + '003_pgbench_concurrent_2pc' => q( + BEGIN; + INSERT INTO tbl VALUES(0); + PREPARE TRANSACTION 'c:client_id'; + COMMIT PREPARED 'c:client_id'; + ), + '003_pgbench_concurrent_2pc_savepoint' => q( + BEGIN; + SAVEPOINT s1; + INSERT INTO tbl VALUES(0); + PREPARE TRANSACTION 'c:client_id'; + COMMIT PREPARED 'c:client_id'; + ), + '003_pgbench_concurrent_cic' => q( + SELECT pg_try_advisory_lock(42)::integer AS gotlock \gset + \if :gotlock + DROP INDEX CONCURRENTLY idx; + CREATE INDEX CONCURRENTLY idx ON tbl(i); + SELECT bt_index_check('idx',true); + SELECT pg_advisory_unlock(42); + \endif + ), + '004_pgbench_concurrent_ric' => q( + SELECT pg_try_advisory_lock(42)::integer AS gotlock \gset + \if :gotlock + REINDEX INDEX CONCURRENTLY idx; + SELECT bt_index_check('idx',true); + SELECT pg_advisory_unlock(42); + \endif + ) + }); + +$node->stop; +done_testing(); diff --git a/contrib/amcheck/t/005_pitr.pl b/contrib/amcheck/t/005_pitr.pl new file mode 100644 index 0000000..6bcc159 --- /dev/null +++ b/contrib/amcheck/t/005_pitr.pl @@ -0,0 +1,82 @@ +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# Test integrity of intermediate states by PITR to those states +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# origin node: generate WAL records of interest. +my $origin = PostgreSQL::Test::Cluster->new('origin'); +$origin->init(has_archiving => 1, allows_streaming => 1); +$origin->append_conf('postgresql.conf', 'autovacuum = off'); +$origin->start; +$origin->backup('my_backup'); +# Create a table with each of 6 PK values spanning 1/4 of a block. Delete the +# first four, so one index leaf is eligible for deletion. Make a replication +# slot just so pg_walinspect will always have access to later WAL. +my $setup = <safe_psql('postgres', $setup); +my $before_vacuum_lsn = + $origin->safe_psql('postgres', "SELECT pg_current_wal_lsn()"); +# VACUUM to delete the aforementioned leaf page. Force an XLogFlush() by +# dropping a permanent table. That way, the XLogReader infrastructure can +# always see VACUUM's records, even under synchronous_commit=off. Finally, +# find the LSN of that VACUUM's last UNLINK_PAGE record. +my $vacuum = <safe_psql('postgres', $vacuum); +$origin->stop; +die "did not find UNLINK_PAGE record" unless $unlink_lsn; + +# replica node: amcheck at notable points in the WAL stream +my $replica = PostgreSQL::Test::Cluster->new('replica'); +$replica->init_from_backup($origin, 'my_backup', has_restoring => 1); +$replica->append_conf('postgresql.conf', + "recovery_target_lsn = '$unlink_lsn'"); +$replica->append_conf('postgresql.conf', 'recovery_target_inclusive = off'); +$replica->append_conf('postgresql.conf', 'recovery_target_action = promote'); +$replica->start; +$replica->poll_query_until('postgres', "SELECT pg_is_in_recovery() = 'f';") + or die "Timed out while waiting for PITR promotion"; +# recovery done; run amcheck +my $debug = "SET client_min_messages = 'debug1'"; +my ($rc, $stderr); +$rc = $replica->psql( + 'postgres', + "$debug; SELECT bt_index_parent_check('not_leftmost_pk', true)", + stderr => \$stderr); +print STDERR $stderr, "\n"; +is($rc, 0, "bt_index_parent_check passes"); +like( + $stderr, + qr/interrupted page deletion detected/, + "bt_index_parent_check: interrupted page deletion detected"); +$rc = $replica->psql( + 'postgres', + "$debug; SELECT bt_index_check('not_leftmost_pk', true)", + stderr => \$stderr); +print STDERR $stderr, "\n"; +is($rc, 0, "bt_index_check passes"); + +done_testing(); diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c new file mode 100644 index 0000000..97f3253 --- /dev/null +++ b/contrib/amcheck/verify_heapam.c @@ -0,0 +1,2082 @@ +/*------------------------------------------------------------------------- + * + * verify_heapam.c + * Functions to check postgresql heap relations for corruption + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * contrib/amcheck/verify_heapam.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/detoast.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/heaptoast.h" +#include "access/multixact.h" +#include "access/toast_internals.h" +#include "access/visibilitymap.h" +#include "catalog/pg_am.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/procarray.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" + +PG_FUNCTION_INFO_V1(verify_heapam); + +/* The number of columns in tuples returned by verify_heapam */ +#define HEAPCHECK_RELATION_COLS 4 + +/* The largest valid toast va_rawsize */ +#define VARLENA_SIZE_LIMIT 0x3FFFFFFF + +/* + * Despite the name, we use this for reporting problems with both XIDs and + * MXIDs. + */ +typedef enum XidBoundsViolation +{ + XID_INVALID, + XID_IN_FUTURE, + XID_PRECEDES_CLUSTERMIN, + XID_PRECEDES_RELMIN, + XID_BOUNDS_OK +} XidBoundsViolation; + +typedef enum XidCommitStatus +{ + XID_COMMITTED, + XID_IS_CURRENT_XID, + XID_IN_PROGRESS, + XID_ABORTED +} XidCommitStatus; + +typedef enum SkipPages +{ + SKIP_PAGES_ALL_FROZEN, + SKIP_PAGES_ALL_VISIBLE, + SKIP_PAGES_NONE +} SkipPages; + +/* + * Struct holding information about a toasted attribute sufficient to both + * check the toasted attribute and, if found to be corrupt, to report where it + * was encountered in the main table. + */ +typedef struct ToastedAttribute +{ + struct varatt_external toast_pointer; + BlockNumber blkno; /* block in main table */ + OffsetNumber offnum; /* offset in main table */ + AttrNumber attnum; /* attribute in main table */ +} ToastedAttribute; + +/* + * Struct holding the running context information during + * a lifetime of a verify_heapam execution. + */ +typedef struct HeapCheckContext +{ + /* + * Cached copies of values from ShmemVariableCache and computed values + * from them. + */ + FullTransactionId next_fxid; /* ShmemVariableCache->nextXid */ + TransactionId next_xid; /* 32-bit version of next_fxid */ + TransactionId oldest_xid; /* ShmemVariableCache->oldestXid */ + FullTransactionId oldest_fxid; /* 64-bit version of oldest_xid, computed + * relative to next_fxid */ + TransactionId safe_xmin; /* this XID and newer ones can't become + * all-visible while we're running */ + + /* + * Cached copy of value from MultiXactState + */ + MultiXactId next_mxact; /* MultiXactState->nextMXact */ + MultiXactId oldest_mxact; /* MultiXactState->oldestMultiXactId */ + + /* + * Cached copies of the most recently checked xid and its status. + */ + TransactionId cached_xid; + XidCommitStatus cached_status; + + /* Values concerning the heap relation being checked */ + Relation rel; + TransactionId relfrozenxid; + FullTransactionId relfrozenfxid; + TransactionId relminmxid; + Relation toast_rel; + Relation *toast_indexes; + Relation valid_toast_index; + int num_toast_indexes; + + /* Values for iterating over pages in the relation */ + BlockNumber blkno; + BufferAccessStrategy bstrategy; + Buffer buffer; + Page page; + + /* Values for iterating over tuples within a page */ + OffsetNumber offnum; + ItemId itemid; + uint16 lp_len; + uint16 lp_off; + HeapTupleHeader tuphdr; + int natts; + + /* Values for iterating over attributes within the tuple */ + uint32 offset; /* offset in tuple data */ + AttrNumber attnum; + + /* True if tuple's xmax makes it eligible for pruning */ + bool tuple_could_be_pruned; + + /* + * List of ToastedAttribute structs for toasted attributes which are not + * eligible for pruning and should be checked + */ + List *toasted_attributes; + + /* Whether verify_heapam has yet encountered any corrupt tuples */ + bool is_corrupt; + + /* The descriptor and tuplestore for verify_heapam's result tuples */ + TupleDesc tupdesc; + Tuplestorestate *tupstore; +} HeapCheckContext; + +/* Internal implementation */ +static void check_tuple(HeapCheckContext *ctx, + bool *xmin_commit_status_ok, + XidCommitStatus *xmin_commit_status); +static void check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx, + ToastedAttribute *ta, int32 *expected_chunk_seq, + uint32 extsize); + +static bool check_tuple_attribute(HeapCheckContext *ctx); +static void check_toasted_attribute(HeapCheckContext *ctx, + ToastedAttribute *ta); + +static bool check_tuple_header(HeapCheckContext *ctx); +static bool check_tuple_visibility(HeapCheckContext *ctx, + bool *xmin_commit_status_ok, + XidCommitStatus *xmin_commit_status); + +static void report_corruption(HeapCheckContext *ctx, char *msg); +static void report_toast_corruption(HeapCheckContext *ctx, + ToastedAttribute *ta, char *msg); +static FullTransactionId FullTransactionIdFromXidAndCtx(TransactionId xid, + const HeapCheckContext *ctx); +static void update_cached_xid_range(HeapCheckContext *ctx); +static void update_cached_mxid_range(HeapCheckContext *ctx); +static XidBoundsViolation check_mxid_in_range(MultiXactId mxid, + HeapCheckContext *ctx); +static XidBoundsViolation check_mxid_valid_in_rel(MultiXactId mxid, + HeapCheckContext *ctx); +static XidBoundsViolation get_xid_status(TransactionId xid, + HeapCheckContext *ctx, + XidCommitStatus *status); + +/* + * Scan and report corruption in heap pages, optionally reconciling toasted + * attributes with entries in the associated toast table. Intended to be + * called from SQL with the following parameters: + * + * relation: + * The Oid of the heap relation to be checked. + * + * on_error_stop: + * Whether to stop at the end of the first page for which errors are + * detected. Note that multiple rows may be returned. + * + * check_toast: + * Whether to check each toasted attribute against the toast table to + * verify that it can be found there. + * + * skip: + * What kinds of pages in the heap relation should be skipped. Valid + * options are "all-visible", "all-frozen", and "none". + * + * Returns to the SQL caller a set of tuples, each containing the location + * and a description of a corruption found in the heap. + * + * This code goes to some trouble to avoid crashing the server even if the + * table pages are badly corrupted, but it's probably not perfect. If + * check_toast is true, we'll use regular index lookups to try to fetch TOAST + * tuples, which can certainly cause crashes if the right kind of corruption + * exists in the toast table or index. No matter what parameters you pass, + * we can't protect against crashes that might occur trying to look up the + * commit status of transaction IDs (though we avoid trying to do such lookups + * for transaction IDs that can't legally appear in the table). + */ +Datum +verify_heapam(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + HeapCheckContext ctx; + Buffer vmbuffer = InvalidBuffer; + Oid relid; + bool on_error_stop; + bool check_toast; + SkipPages skip_option = SKIP_PAGES_NONE; + BlockNumber first_block; + BlockNumber last_block; + BlockNumber nblocks; + const char *skip; + + /* Check supplied arguments */ + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("relation cannot be null"))); + relid = PG_GETARG_OID(0); + + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("on_error_stop cannot be null"))); + on_error_stop = PG_GETARG_BOOL(1); + + if (PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("check_toast cannot be null"))); + check_toast = PG_GETARG_BOOL(2); + + if (PG_ARGISNULL(3)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("skip cannot be null"))); + skip = text_to_cstring(PG_GETARG_TEXT_PP(3)); + if (pg_strcasecmp(skip, "all-visible") == 0) + skip_option = SKIP_PAGES_ALL_VISIBLE; + else if (pg_strcasecmp(skip, "all-frozen") == 0) + skip_option = SKIP_PAGES_ALL_FROZEN; + else if (pg_strcasecmp(skip, "none") == 0) + skip_option = SKIP_PAGES_NONE; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid skip option"), + errhint("Valid skip options are \"all-visible\", \"all-frozen\", and \"none\"."))); + + memset(&ctx, 0, sizeof(HeapCheckContext)); + ctx.cached_xid = InvalidTransactionId; + ctx.toasted_attributes = NIL; + + /* + * Any xmin newer than the xmin of our snapshot can't become all-visible + * while we're running. + */ + ctx.safe_xmin = GetTransactionSnapshot()->xmin; + + /* + * If we report corruption when not examining some individual attribute, + * we need attnum to be reported as NULL. Set that up before any + * corruption reporting might happen. + */ + ctx.attnum = -1; + + /* Construct the tuplestore and tuple descriptor */ + InitMaterializedSRF(fcinfo, 0); + ctx.tupdesc = rsinfo->setDesc; + ctx.tupstore = rsinfo->setResult; + + /* Open relation, check relkind and access method */ + ctx.rel = relation_open(relid, AccessShareLock); + + /* + * Check that a relation's relkind and access method are both supported. + */ + if (!RELKIND_HAS_TABLE_AM(ctx.rel->rd_rel->relkind) && + ctx.rel->rd_rel->relkind != RELKIND_SEQUENCE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot check relation \"%s\"", + RelationGetRelationName(ctx.rel)), + errdetail_relkind_not_supported(ctx.rel->rd_rel->relkind))); + + /* + * Sequences always use heap AM, but they don't show that in the catalogs. + * Other relkinds might be using a different AM, so check. + */ + if (ctx.rel->rd_rel->relkind != RELKIND_SEQUENCE && + ctx.rel->rd_rel->relam != HEAP_TABLE_AM_OID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only heap AM is supported"))); + + /* + * Early exit for unlogged relations during recovery. These will have no + * relation fork, so there won't be anything to check. We behave as if + * the relation is empty. + */ + if (ctx.rel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED && + RecoveryInProgress()) + { + ereport(DEBUG1, + (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION), + errmsg("cannot verify unlogged relation \"%s\" during recovery, skipping", + RelationGetRelationName(ctx.rel)))); + relation_close(ctx.rel, AccessShareLock); + PG_RETURN_NULL(); + } + + /* Early exit if the relation is empty */ + nblocks = RelationGetNumberOfBlocks(ctx.rel); + if (!nblocks) + { + relation_close(ctx.rel, AccessShareLock); + PG_RETURN_NULL(); + } + + ctx.bstrategy = GetAccessStrategy(BAS_BULKREAD); + ctx.buffer = InvalidBuffer; + ctx.page = NULL; + + /* Validate block numbers, or handle nulls. */ + if (PG_ARGISNULL(4)) + first_block = 0; + else + { + int64 fb = PG_GETARG_INT64(4); + + if (fb < 0 || fb >= nblocks) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("starting block number must be between 0 and %u", + nblocks - 1))); + first_block = (BlockNumber) fb; + } + if (PG_ARGISNULL(5)) + last_block = nblocks - 1; + else + { + int64 lb = PG_GETARG_INT64(5); + + if (lb < 0 || lb >= nblocks) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ending block number must be between 0 and %u", + nblocks - 1))); + last_block = (BlockNumber) lb; + } + + /* Optionally open the toast relation, if any. */ + if (ctx.rel->rd_rel->reltoastrelid && check_toast) + { + int offset; + + /* Main relation has associated toast relation */ + ctx.toast_rel = table_open(ctx.rel->rd_rel->reltoastrelid, + AccessShareLock); + offset = toast_open_indexes(ctx.toast_rel, + AccessShareLock, + &(ctx.toast_indexes), + &(ctx.num_toast_indexes)); + ctx.valid_toast_index = ctx.toast_indexes[offset]; + } + else + { + /* + * Main relation has no associated toast relation, or we're + * intentionally skipping it. + */ + ctx.toast_rel = NULL; + ctx.toast_indexes = NULL; + ctx.num_toast_indexes = 0; + } + + update_cached_xid_range(&ctx); + update_cached_mxid_range(&ctx); + ctx.relfrozenxid = ctx.rel->rd_rel->relfrozenxid; + ctx.relfrozenfxid = FullTransactionIdFromXidAndCtx(ctx.relfrozenxid, &ctx); + ctx.relminmxid = ctx.rel->rd_rel->relminmxid; + + if (TransactionIdIsNormal(ctx.relfrozenxid)) + ctx.oldest_xid = ctx.relfrozenxid; + + for (ctx.blkno = first_block; ctx.blkno <= last_block; ctx.blkno++) + { + OffsetNumber maxoff; + OffsetNumber predecessor[MaxOffsetNumber]; + OffsetNumber successor[MaxOffsetNumber]; + bool lp_valid[MaxOffsetNumber]; + bool xmin_commit_status_ok[MaxOffsetNumber]; + XidCommitStatus xmin_commit_status[MaxOffsetNumber]; + + CHECK_FOR_INTERRUPTS(); + + memset(predecessor, 0, sizeof(OffsetNumber) * MaxOffsetNumber); + + /* Optionally skip over all-frozen or all-visible blocks */ + if (skip_option != SKIP_PAGES_NONE) + { + int32 mapbits; + + mapbits = (int32) visibilitymap_get_status(ctx.rel, ctx.blkno, + &vmbuffer); + if (skip_option == SKIP_PAGES_ALL_FROZEN) + { + if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0) + continue; + } + + if (skip_option == SKIP_PAGES_ALL_VISIBLE) + { + if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0) + continue; + } + } + + /* Read and lock the next page. */ + ctx.buffer = ReadBufferExtended(ctx.rel, MAIN_FORKNUM, ctx.blkno, + RBM_NORMAL, ctx.bstrategy); + LockBuffer(ctx.buffer, BUFFER_LOCK_SHARE); + ctx.page = BufferGetPage(ctx.buffer); + + /* Perform tuple checks */ + maxoff = PageGetMaxOffsetNumber(ctx.page); + for (ctx.offnum = FirstOffsetNumber; ctx.offnum <= maxoff; + ctx.offnum = OffsetNumberNext(ctx.offnum)) + { + BlockNumber nextblkno; + OffsetNumber nextoffnum; + + successor[ctx.offnum] = InvalidOffsetNumber; + lp_valid[ctx.offnum] = false; + xmin_commit_status_ok[ctx.offnum] = false; + ctx.itemid = PageGetItemId(ctx.page, ctx.offnum); + + /* Skip over unused/dead line pointers */ + if (!ItemIdIsUsed(ctx.itemid) || ItemIdIsDead(ctx.itemid)) + continue; + + /* + * If this line pointer has been redirected, check that it + * redirects to a valid offset within the line pointer array + */ + if (ItemIdIsRedirected(ctx.itemid)) + { + OffsetNumber rdoffnum = ItemIdGetRedirect(ctx.itemid); + ItemId rditem; + + if (rdoffnum < FirstOffsetNumber) + { + report_corruption(&ctx, + psprintf("line pointer redirection to item at offset %u precedes minimum offset %u", + (unsigned) rdoffnum, + (unsigned) FirstOffsetNumber)); + continue; + } + if (rdoffnum > maxoff) + { + report_corruption(&ctx, + psprintf("line pointer redirection to item at offset %u exceeds maximum offset %u", + (unsigned) rdoffnum, + (unsigned) maxoff)); + continue; + } + + /* + * Since we've checked that this redirect points to a line + * pointer between FirstOffsetNumber and maxoff, it should now + * be safe to fetch the referenced line pointer. We expect it + * to be LP_NORMAL; if not, that's corruption. + */ + rditem = PageGetItemId(ctx.page, rdoffnum); + if (!ItemIdIsUsed(rditem)) + { + report_corruption(&ctx, + psprintf("redirected line pointer points to an unused item at offset %u", + (unsigned) rdoffnum)); + continue; + } + else if (ItemIdIsDead(rditem)) + { + report_corruption(&ctx, + psprintf("redirected line pointer points to a dead item at offset %u", + (unsigned) rdoffnum)); + continue; + } + else if (ItemIdIsRedirected(rditem)) + { + report_corruption(&ctx, + psprintf("redirected line pointer points to another redirected line pointer at offset %u", + (unsigned) rdoffnum)); + continue; + } + + /* + * Record the fact that this line pointer has passed basic + * sanity checking, and also the offset number to which it + * points. + */ + lp_valid[ctx.offnum] = true; + successor[ctx.offnum] = rdoffnum; + continue; + } + + /* Sanity-check the line pointer's offset and length values */ + ctx.lp_len = ItemIdGetLength(ctx.itemid); + ctx.lp_off = ItemIdGetOffset(ctx.itemid); + + if (ctx.lp_off != MAXALIGN(ctx.lp_off)) + { + report_corruption(&ctx, + psprintf("line pointer to page offset %u is not maximally aligned", + ctx.lp_off)); + continue; + } + if (ctx.lp_len < MAXALIGN(SizeofHeapTupleHeader)) + { + report_corruption(&ctx, + psprintf("line pointer length %u is less than the minimum tuple header size %u", + ctx.lp_len, + (unsigned) MAXALIGN(SizeofHeapTupleHeader))); + continue; + } + if (ctx.lp_off + ctx.lp_len > BLCKSZ) + { + report_corruption(&ctx, + psprintf("line pointer to page offset %u with length %u ends beyond maximum page offset %u", + ctx.lp_off, + ctx.lp_len, + (unsigned) BLCKSZ)); + continue; + } + + /* It should be safe to examine the tuple's header, at least */ + lp_valid[ctx.offnum] = true; + ctx.tuphdr = (HeapTupleHeader) PageGetItem(ctx.page, ctx.itemid); + ctx.natts = HeapTupleHeaderGetNatts(ctx.tuphdr); + + /* Ok, ready to check this next tuple */ + check_tuple(&ctx, + &xmin_commit_status_ok[ctx.offnum], + &xmin_commit_status[ctx.offnum]); + + /* + * If the CTID field of this tuple seems to point to another tuple + * on the same page, record that tuple as the successor of this + * one. + */ + nextblkno = ItemPointerGetBlockNumber(&(ctx.tuphdr)->t_ctid); + nextoffnum = ItemPointerGetOffsetNumber(&(ctx.tuphdr)->t_ctid); + if (nextblkno == ctx.blkno && nextoffnum != ctx.offnum && + nextoffnum >= FirstOffsetNumber && nextoffnum <= maxoff) + successor[ctx.offnum] = nextoffnum; + } + + /* + * Update chain validation. Check each line pointer that's got a valid + * successor against that successor. + */ + ctx.attnum = -1; + for (ctx.offnum = FirstOffsetNumber; ctx.offnum <= maxoff; + ctx.offnum = OffsetNumberNext(ctx.offnum)) + { + ItemId curr_lp; + ItemId next_lp; + HeapTupleHeader curr_htup; + HeapTupleHeader next_htup; + TransactionId curr_xmin; + TransactionId curr_xmax; + TransactionId next_xmin; + OffsetNumber nextoffnum = successor[ctx.offnum]; + + /* + * The current line pointer may not have a successor, either + * because it's not valid or because it didn't point to anything. + * In either case, we have to give up. + * + * If the current line pointer does point to something, it's + * possible that the target line pointer isn't valid. We have to + * give up in that case, too. + */ + if (nextoffnum == InvalidOffsetNumber || !lp_valid[nextoffnum]) + continue; + + /* We have two valid line pointers that we can examine. */ + curr_lp = PageGetItemId(ctx.page, ctx.offnum); + next_lp = PageGetItemId(ctx.page, nextoffnum); + + /* Handle the cases where the current line pointer is a redirect. */ + if (ItemIdIsRedirected(curr_lp)) + { + /* + * We should not have set successor[ctx.offnum] to a value + * other than InvalidOffsetNumber unless that line pointer is + * LP_NORMAL. + */ + Assert(ItemIdIsNormal(next_lp)); + + /* Can only redirect to a HOT tuple. */ + next_htup = (HeapTupleHeader) PageGetItem(ctx.page, next_lp); + if (!HeapTupleHeaderIsHeapOnly(next_htup)) + { + report_corruption(&ctx, + psprintf("redirected line pointer points to a non-heap-only tuple at offset %u", + (unsigned) nextoffnum)); + } + + /* HOT chains should not intersect. */ + if (predecessor[nextoffnum] != InvalidOffsetNumber) + { + report_corruption(&ctx, + psprintf("redirect line pointer points to offset %u, but offset %u also points there", + (unsigned) nextoffnum, (unsigned) predecessor[nextoffnum])); + continue; + } + + /* + * This redirect and the tuple to which it points seem to be + * part of an update chain. + */ + predecessor[nextoffnum] = ctx.offnum; + continue; + } + + /* + * If the next line pointer is a redirect, or if it's a tuple but + * the XMAX of this tuple doesn't match the XMIN of the next + * tuple, then the two aren't part of the same update chain and + * there is nothing more to do. + */ + if (ItemIdIsRedirected(next_lp)) + continue; + curr_htup = (HeapTupleHeader) PageGetItem(ctx.page, curr_lp); + curr_xmax = HeapTupleHeaderGetUpdateXid(curr_htup); + next_htup = (HeapTupleHeader) PageGetItem(ctx.page, next_lp); + next_xmin = HeapTupleHeaderGetXmin(next_htup); + if (!TransactionIdIsValid(curr_xmax) || + !TransactionIdEquals(curr_xmax, next_xmin)) + continue; + + /* HOT chains should not intersect. */ + if (predecessor[nextoffnum] != InvalidOffsetNumber) + { + report_corruption(&ctx, + psprintf("tuple points to new version at offset %u, but offset %u also points there", + (unsigned) nextoffnum, (unsigned) predecessor[nextoffnum])); + continue; + } + + /* + * This tuple and the tuple to which it points seem to be part of + * an update chain. + */ + predecessor[nextoffnum] = ctx.offnum; + + /* + * If the current tuple is marked as HOT-updated, then the next + * tuple should be marked as a heap-only tuple. Conversely, if the + * current tuple isn't marked as HOT-updated, then the next tuple + * shouldn't be marked as a heap-only tuple. + * + * NB: Can't use HeapTupleHeaderIsHotUpdated() as it checks if + * hint bits indicate xmin/xmax aborted. + */ + if (!(curr_htup->t_infomask2 & HEAP_HOT_UPDATED) && + HeapTupleHeaderIsHeapOnly(next_htup)) + { + report_corruption(&ctx, + psprintf("non-heap-only update produced a heap-only tuple at offset %u", + (unsigned) nextoffnum)); + } + if ((curr_htup->t_infomask2 & HEAP_HOT_UPDATED) && + !HeapTupleHeaderIsHeapOnly(next_htup)) + { + report_corruption(&ctx, + psprintf("heap-only update produced a non-heap only tuple at offset %u", + (unsigned) nextoffnum)); + } + + /* + * If the current tuple's xmin is still in progress but the + * successor tuple's xmin is committed, that's corruption. + * + * NB: We recheck the commit status of the current tuple's xmin + * here, because it might have committed after we checked it and + * before we checked the commit status of the successor tuple's + * xmin. This should be safe because the xmin itself can't have + * changed, only its commit status. + */ + curr_xmin = HeapTupleHeaderGetXmin(curr_htup); + if (xmin_commit_status_ok[ctx.offnum] && + xmin_commit_status[ctx.offnum] == XID_IN_PROGRESS && + xmin_commit_status_ok[nextoffnum] && + xmin_commit_status[nextoffnum] == XID_COMMITTED && + TransactionIdIsInProgress(curr_xmin)) + { + report_corruption(&ctx, + psprintf("tuple with in-progress xmin %u was updated to produce a tuple at offset %u with committed xmin %u", + (unsigned) curr_xmin, + (unsigned) ctx.offnum, + (unsigned) next_xmin)); + } + + /* + * If the current tuple's xmin is aborted but the successor + * tuple's xmin is in-progress or committed, that's corruption. + */ + if (xmin_commit_status_ok[ctx.offnum] && + xmin_commit_status[ctx.offnum] == XID_ABORTED && + xmin_commit_status_ok[nextoffnum]) + { + if (xmin_commit_status[nextoffnum] == XID_IN_PROGRESS) + report_corruption(&ctx, + psprintf("tuple with aborted xmin %u was updated to produce a tuple at offset %u with in-progress xmin %u", + (unsigned) curr_xmin, + (unsigned) ctx.offnum, + (unsigned) next_xmin)); + else if (xmin_commit_status[nextoffnum] == XID_COMMITTED) + report_corruption(&ctx, + psprintf("tuple with aborted xmin %u was updated to produce a tuple at offset %u with committed xmin %u", + (unsigned) curr_xmin, + (unsigned) ctx.offnum, + (unsigned) next_xmin)); + } + } + + /* + * An update chain can start either with a non-heap-only tuple or with + * a redirect line pointer, but not with a heap-only tuple. + * + * (This check is in a separate loop because we need the predecessor + * array to be fully populated before we can perform it.) + */ + for (ctx.offnum = FirstOffsetNumber; + ctx.offnum <= maxoff; + ctx.offnum = OffsetNumberNext(ctx.offnum)) + { + if (xmin_commit_status_ok[ctx.offnum] && + (xmin_commit_status[ctx.offnum] == XID_COMMITTED || + xmin_commit_status[ctx.offnum] == XID_IN_PROGRESS) && + predecessor[ctx.offnum] == InvalidOffsetNumber) + { + ItemId curr_lp; + + curr_lp = PageGetItemId(ctx.page, ctx.offnum); + if (!ItemIdIsRedirected(curr_lp)) + { + HeapTupleHeader curr_htup; + + curr_htup = (HeapTupleHeader) + PageGetItem(ctx.page, curr_lp); + if (HeapTupleHeaderIsHeapOnly(curr_htup)) + report_corruption(&ctx, + psprintf("tuple is root of chain but is marked as heap-only tuple")); + } + } + } + + /* clean up */ + UnlockReleaseBuffer(ctx.buffer); + + /* + * Check any toast pointers from the page whose lock we just released + */ + if (ctx.toasted_attributes != NIL) + { + ListCell *cell; + + foreach(cell, ctx.toasted_attributes) + check_toasted_attribute(&ctx, lfirst(cell)); + list_free_deep(ctx.toasted_attributes); + ctx.toasted_attributes = NIL; + } + + if (on_error_stop && ctx.is_corrupt) + break; + } + + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + + /* Close the associated toast table and indexes, if any. */ + if (ctx.toast_indexes) + toast_close_indexes(ctx.toast_indexes, ctx.num_toast_indexes, + AccessShareLock); + if (ctx.toast_rel) + table_close(ctx.toast_rel, AccessShareLock); + + /* Close the main relation */ + relation_close(ctx.rel, AccessShareLock); + + PG_RETURN_NULL(); +} + +/* + * Shared internal implementation for report_corruption and + * report_toast_corruption. + */ +static void +report_corruption_internal(Tuplestorestate *tupstore, TupleDesc tupdesc, + BlockNumber blkno, OffsetNumber offnum, + AttrNumber attnum, char *msg) +{ + Datum values[HEAPCHECK_RELATION_COLS] = {0}; + bool nulls[HEAPCHECK_RELATION_COLS] = {0}; + HeapTuple tuple; + + values[0] = Int64GetDatum(blkno); + values[1] = Int32GetDatum(offnum); + values[2] = Int32GetDatum(attnum); + nulls[2] = (attnum < 0); + values[3] = CStringGetTextDatum(msg); + + /* + * In principle, there is nothing to prevent a scan over a large, highly + * corrupted table from using work_mem worth of memory building up the + * tuplestore. That's ok, but if we also leak the msg argument memory + * until the end of the query, we could exceed work_mem by more than a + * trivial amount. Therefore, free the msg argument each time we are + * called rather than waiting for our current memory context to be freed. + */ + pfree(msg); + + tuple = heap_form_tuple(tupdesc, values, nulls); + tuplestore_puttuple(tupstore, tuple); +} + +/* + * Record a single corruption found in the main table. The values in ctx should + * indicate the location of the corruption, and the msg argument should contain + * a human-readable description of the corruption. + * + * The msg argument is pfree'd by this function. + */ +static void +report_corruption(HeapCheckContext *ctx, char *msg) +{ + report_corruption_internal(ctx->tupstore, ctx->tupdesc, ctx->blkno, + ctx->offnum, ctx->attnum, msg); + ctx->is_corrupt = true; +} + +/* + * Record corruption found in the toast table. The values in ta should + * indicate the location in the main table where the toast pointer was + * encountered, and the msg argument should contain a human-readable + * description of the toast table corruption. + * + * As above, the msg argument is pfree'd by this function. + */ +static void +report_toast_corruption(HeapCheckContext *ctx, ToastedAttribute *ta, + char *msg) +{ + report_corruption_internal(ctx->tupstore, ctx->tupdesc, ta->blkno, + ta->offnum, ta->attnum, msg); + ctx->is_corrupt = true; +} + +/* + * Check for tuple header corruption. + * + * Some kinds of corruption make it unsafe to check the tuple attributes, for + * example when the line pointer refers to a range of bytes outside the page. + * In such cases, we return false (not checkable) after recording appropriate + * corruption messages. + * + * Some other kinds of tuple header corruption confuse the question of where + * the tuple attributes begin, or how long the nulls bitmap is, etc., making it + * unreasonable to attempt to check attributes, even if all candidate answers + * to those questions would not result in reading past the end of the line + * pointer or page. In such cases, like above, we record corruption messages + * about the header and then return false. + * + * Other kinds of tuple header corruption do not bear on the question of + * whether the tuple attributes can be checked, so we record corruption + * messages for them but we do not return false merely because we detected + * them. + * + * Returns whether the tuple is sufficiently sensible to undergo visibility and + * attribute checks. + */ +static bool +check_tuple_header(HeapCheckContext *ctx) +{ + HeapTupleHeader tuphdr = ctx->tuphdr; + uint16 infomask = tuphdr->t_infomask; + TransactionId curr_xmax = HeapTupleHeaderGetUpdateXid(tuphdr); + bool result = true; + unsigned expected_hoff; + + if (ctx->tuphdr->t_hoff > ctx->lp_len) + { + report_corruption(ctx, + psprintf("data begins at offset %u beyond the tuple length %u", + ctx->tuphdr->t_hoff, ctx->lp_len)); + result = false; + } + + if ((ctx->tuphdr->t_infomask & HEAP_XMAX_COMMITTED) && + (ctx->tuphdr->t_infomask & HEAP_XMAX_IS_MULTI)) + { + report_corruption(ctx, + pstrdup("multixact should not be marked committed")); + + /* + * This condition is clearly wrong, but it's not enough to justify + * skipping further checks, because we don't rely on this to determine + * whether the tuple is visible or to interpret other relevant header + * fields. + */ + } + + if (!TransactionIdIsValid(curr_xmax) && + HeapTupleHeaderIsHotUpdated(tuphdr)) + { + report_corruption(ctx, + psprintf("tuple has been HOT updated, but xmax is 0")); + + /* + * As above, even though this shouldn't happen, it's not sufficient + * justification for skipping further checks, we should still be able + * to perform sensibly. + */ + } + + if (HeapTupleHeaderIsHeapOnly(tuphdr) && + ((tuphdr->t_infomask & HEAP_UPDATED) == 0)) + { + report_corruption(ctx, + psprintf("tuple is heap only, but not the result of an update")); + + /* Here again, we can still perform further checks. */ + } + + if (infomask & HEAP_HASNULL) + expected_hoff = MAXALIGN(SizeofHeapTupleHeader + BITMAPLEN(ctx->natts)); + else + expected_hoff = MAXALIGN(SizeofHeapTupleHeader); + if (ctx->tuphdr->t_hoff != expected_hoff) + { + if ((infomask & HEAP_HASNULL) && ctx->natts == 1) + report_corruption(ctx, + psprintf("tuple data should begin at byte %u, but actually begins at byte %u (1 attribute, has nulls)", + expected_hoff, ctx->tuphdr->t_hoff)); + else if ((infomask & HEAP_HASNULL)) + report_corruption(ctx, + psprintf("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, has nulls)", + expected_hoff, ctx->tuphdr->t_hoff, ctx->natts)); + else if (ctx->natts == 1) + report_corruption(ctx, + psprintf("tuple data should begin at byte %u, but actually begins at byte %u (1 attribute, no nulls)", + expected_hoff, ctx->tuphdr->t_hoff)); + else + report_corruption(ctx, + psprintf("tuple data should begin at byte %u, but actually begins at byte %u (%u attributes, no nulls)", + expected_hoff, ctx->tuphdr->t_hoff, ctx->natts)); + result = false; + } + + return result; +} + +/* + * Checks tuple visibility so we know which further checks are safe to + * perform. + * + * If a tuple could have been inserted by a transaction that also added a + * column to the table, but which ultimately did not commit, or which has not + * yet committed, then the table's current TupleDesc might differ from the one + * used to construct this tuple, so we must not check it. + * + * As a special case, if our own transaction inserted the tuple, even if we + * added a column to the table, our TupleDesc should match. We could check the + * tuple, but choose not to do so. + * + * If a tuple has been updated or deleted, we can still read the old tuple for + * corruption checking purposes, as long as we are careful about concurrent + * vacuums. The main table tuple itself cannot be vacuumed away because we + * hold a buffer lock on the page, but if the deleting transaction is older + * than our transaction snapshot's xmin, then vacuum could remove the toast at + * any time, so we must not try to follow TOAST pointers. + * + * If xmin or xmax values are older than can be checked against clog, or appear + * to be in the future (possibly due to wrap-around), then we cannot make a + * determination about the visibility of the tuple, so we skip further checks. + * + * Returns true if the tuple itself should be checked, false otherwise. Sets + * ctx->tuple_could_be_pruned if the tuple -- and thus also any associated + * TOAST tuples -- are eligible for pruning. + * + * Sets *xmin_commit_status_ok to true if the commit status of xmin is known + * and false otherwise. If it's set to true, then also set *xmin_commit_status + * to the actual commit status. + */ +static bool +check_tuple_visibility(HeapCheckContext *ctx, bool *xmin_commit_status_ok, + XidCommitStatus *xmin_commit_status) +{ + TransactionId xmin; + TransactionId xvac; + TransactionId xmax; + XidCommitStatus xmin_status; + XidCommitStatus xvac_status; + XidCommitStatus xmax_status; + HeapTupleHeader tuphdr = ctx->tuphdr; + + ctx->tuple_could_be_pruned = true; /* have not yet proven otherwise */ + *xmin_commit_status_ok = false; /* have not yet proven otherwise */ + + /* If xmin is normal, it should be within valid range */ + xmin = HeapTupleHeaderGetXmin(tuphdr); + switch (get_xid_status(xmin, ctx, &xmin_status)) + { + case XID_INVALID: + /* Could be the result of a speculative insertion that aborted. */ + return false; + case XID_BOUNDS_OK: + *xmin_commit_status_ok = true; + *xmin_commit_status = xmin_status; + break; + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("xmin %u equals or exceeds next valid transaction ID %u:%u", + xmin, + EpochFromFullTransactionId(ctx->next_fxid), + XidFromFullTransactionId(ctx->next_fxid))); + return false; + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("xmin %u precedes oldest valid transaction ID %u:%u", + xmin, + EpochFromFullTransactionId(ctx->oldest_fxid), + XidFromFullTransactionId(ctx->oldest_fxid))); + return false; + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("xmin %u precedes relation freeze threshold %u:%u", + xmin, + EpochFromFullTransactionId(ctx->relfrozenfxid), + XidFromFullTransactionId(ctx->relfrozenfxid))); + return false; + } + + /* + * Has inserting transaction committed? + */ + if (!HeapTupleHeaderXminCommitted(tuphdr)) + { + if (HeapTupleHeaderXminInvalid(tuphdr)) + return false; /* inserter aborted, don't check */ + /* Used by pre-9.0 binary upgrades */ + else if (tuphdr->t_infomask & HEAP_MOVED_OFF) + { + xvac = HeapTupleHeaderGetXvac(tuphdr); + + switch (get_xid_status(xvac, ctx, &xvac_status)) + { + case XID_INVALID: + report_corruption(ctx, + pstrdup("old-style VACUUM FULL transaction ID for moved off tuple is invalid")); + return false; + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple equals or exceeds next valid transaction ID %u:%u", + xvac, + EpochFromFullTransactionId(ctx->next_fxid), + XidFromFullTransactionId(ctx->next_fxid))); + return false; + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes relation freeze threshold %u:%u", + xvac, + EpochFromFullTransactionId(ctx->relfrozenfxid), + XidFromFullTransactionId(ctx->relfrozenfxid))); + return false; + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple precedes oldest valid transaction ID %u:%u", + xvac, + EpochFromFullTransactionId(ctx->oldest_fxid), + XidFromFullTransactionId(ctx->oldest_fxid))); + return false; + case XID_BOUNDS_OK: + break; + } + + switch (xvac_status) + { + case XID_IS_CURRENT_XID: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple matches our current transaction ID", + xvac)); + return false; + case XID_IN_PROGRESS: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u for moved off tuple appears to be in progress", + xvac)); + return false; + + case XID_COMMITTED: + + /* + * The tuple is dead, because the xvac transaction moved + * it off and committed. It's checkable, but also + * prunable. + */ + return true; + + case XID_ABORTED: + + /* + * The original xmin must have committed, because the xvac + * transaction tried to move it later. Since xvac is + * aborted, whether it's still alive now depends on the + * status of xmax. + */ + break; + } + } + /* Used by pre-9.0 binary upgrades */ + else if (tuphdr->t_infomask & HEAP_MOVED_IN) + { + xvac = HeapTupleHeaderGetXvac(tuphdr); + + switch (get_xid_status(xvac, ctx, &xvac_status)) + { + case XID_INVALID: + report_corruption(ctx, + pstrdup("old-style VACUUM FULL transaction ID for moved in tuple is invalid")); + return false; + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple equals or exceeds next valid transaction ID %u:%u", + xvac, + EpochFromFullTransactionId(ctx->next_fxid), + XidFromFullTransactionId(ctx->next_fxid))); + return false; + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes relation freeze threshold %u:%u", + xvac, + EpochFromFullTransactionId(ctx->relfrozenfxid), + XidFromFullTransactionId(ctx->relfrozenfxid))); + return false; + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple precedes oldest valid transaction ID %u:%u", + xvac, + EpochFromFullTransactionId(ctx->oldest_fxid), + XidFromFullTransactionId(ctx->oldest_fxid))); + return false; + case XID_BOUNDS_OK: + break; + } + + switch (xvac_status) + { + case XID_IS_CURRENT_XID: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple matches our current transaction ID", + xvac)); + return false; + case XID_IN_PROGRESS: + report_corruption(ctx, + psprintf("old-style VACUUM FULL transaction ID %u for moved in tuple appears to be in progress", + xvac)); + return false; + + case XID_COMMITTED: + + /* + * The original xmin must have committed, because the xvac + * transaction moved it later. Whether it's still alive + * now depends on the status of xmax. + */ + break; + + case XID_ABORTED: + + /* + * The tuple is dead, because the xvac transaction moved + * it off and committed. It's checkable, but also + * prunable. + */ + return true; + } + } + else if (xmin_status != XID_COMMITTED) + { + /* + * Inserting transaction is not in progress, and not committed, so + * it might have changed the TupleDesc in ways we don't know + * about. Thus, don't try to check the tuple structure. + * + * If xmin_status happens to be XID_IS_CURRENT_XID, then in theory + * any such DDL changes ought to be visible to us, so perhaps we + * could check anyway in that case. But, for now, let's be + * conservative and treat this like any other uncommitted insert. + */ + return false; + } + } + + /* + * Okay, the inserter committed, so it was good at some point. Now what + * about the deleting transaction? + */ + + if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI) + { + /* + * xmax is a multixact, so sanity-check the MXID. Note that we do this + * prior to checking for HEAP_XMAX_INVALID or + * HEAP_XMAX_IS_LOCKED_ONLY. This might therefore complain about + * things that wouldn't actually be a problem during a normal scan, + * but eventually we're going to have to freeze, and that process will + * ignore hint bits. + * + * Even if the MXID is out of range, we still know that the original + * insert committed, so we can check the tuple itself. However, we + * can't rule out the possibility that this tuple is dead, so don't + * clear ctx->tuple_could_be_pruned. Possibly we should go ahead and + * clear that flag anyway if HEAP_XMAX_INVALID is set or if + * HEAP_XMAX_IS_LOCKED_ONLY is true, but for now we err on the side of + * avoiding possibly-bogus complaints about missing TOAST entries. + */ + xmax = HeapTupleHeaderGetRawXmax(tuphdr); + switch (check_mxid_valid_in_rel(xmax, ctx)) + { + case XID_INVALID: + report_corruption(ctx, + pstrdup("multitransaction ID is invalid")); + return true; + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("multitransaction ID %u precedes relation minimum multitransaction ID threshold %u", + xmax, ctx->relminmxid)); + return true; + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("multitransaction ID %u precedes oldest valid multitransaction ID threshold %u", + xmax, ctx->oldest_mxact)); + return true; + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("multitransaction ID %u equals or exceeds next valid multitransaction ID %u", + xmax, + ctx->next_mxact)); + return true; + case XID_BOUNDS_OK: + break; + } + } + + if (tuphdr->t_infomask & HEAP_XMAX_INVALID) + { + /* + * This tuple is live. A concurrently running transaction could + * delete it before we get around to checking the toast, but any such + * running transaction is surely not less than our safe_xmin, so the + * toast cannot be vacuumed out from under us. + */ + ctx->tuple_could_be_pruned = false; + return true; + } + + if (HEAP_XMAX_IS_LOCKED_ONLY(tuphdr->t_infomask)) + { + /* + * "Deleting" xact really only locked it, so the tuple is live in any + * case. As above, a concurrently running transaction could delete + * it, but it cannot be vacuumed out from under us. + */ + ctx->tuple_could_be_pruned = false; + return true; + } + + if (tuphdr->t_infomask & HEAP_XMAX_IS_MULTI) + { + /* + * We already checked above that this multixact is within limits for + * this table. Now check the update xid from this multixact. + */ + xmax = HeapTupleGetUpdateXid(tuphdr); + switch (get_xid_status(xmax, ctx, &xmax_status)) + { + case XID_INVALID: + /* not LOCKED_ONLY, so it has to have an xmax */ + report_corruption(ctx, + pstrdup("update xid is invalid")); + return true; + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("update xid %u equals or exceeds next valid transaction ID %u:%u", + xmax, + EpochFromFullTransactionId(ctx->next_fxid), + XidFromFullTransactionId(ctx->next_fxid))); + return true; + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("update xid %u precedes relation freeze threshold %u:%u", + xmax, + EpochFromFullTransactionId(ctx->relfrozenfxid), + XidFromFullTransactionId(ctx->relfrozenfxid))); + return true; + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("update xid %u precedes oldest valid transaction ID %u:%u", + xmax, + EpochFromFullTransactionId(ctx->oldest_fxid), + XidFromFullTransactionId(ctx->oldest_fxid))); + return true; + case XID_BOUNDS_OK: + break; + } + + switch (xmax_status) + { + case XID_IS_CURRENT_XID: + case XID_IN_PROGRESS: + + /* + * The delete is in progress, so it cannot be visible to our + * snapshot. + */ + ctx->tuple_could_be_pruned = false; + break; + case XID_COMMITTED: + + /* + * The delete committed. Whether the toast can be vacuumed + * away depends on how old the deleting transaction is. + */ + ctx->tuple_could_be_pruned = TransactionIdPrecedes(xmax, + ctx->safe_xmin); + break; + case XID_ABORTED: + + /* + * The delete aborted or crashed. The tuple is still live. + */ + ctx->tuple_could_be_pruned = false; + break; + } + + /* Tuple itself is checkable even if it's dead. */ + return true; + } + + /* xmax is an XID, not a MXID. Sanity check it. */ + xmax = HeapTupleHeaderGetRawXmax(tuphdr); + switch (get_xid_status(xmax, ctx, &xmax_status)) + { + case XID_INVALID: + ctx->tuple_could_be_pruned = false; + return true; + case XID_IN_FUTURE: + report_corruption(ctx, + psprintf("xmax %u equals or exceeds next valid transaction ID %u:%u", + xmax, + EpochFromFullTransactionId(ctx->next_fxid), + XidFromFullTransactionId(ctx->next_fxid))); + return false; /* corrupt */ + case XID_PRECEDES_RELMIN: + report_corruption(ctx, + psprintf("xmax %u precedes relation freeze threshold %u:%u", + xmax, + EpochFromFullTransactionId(ctx->relfrozenfxid), + XidFromFullTransactionId(ctx->relfrozenfxid))); + return false; /* corrupt */ + case XID_PRECEDES_CLUSTERMIN: + report_corruption(ctx, + psprintf("xmax %u precedes oldest valid transaction ID %u:%u", + xmax, + EpochFromFullTransactionId(ctx->oldest_fxid), + XidFromFullTransactionId(ctx->oldest_fxid))); + return false; /* corrupt */ + case XID_BOUNDS_OK: + break; + } + + /* + * Whether the toast can be vacuumed away depends on how old the deleting + * transaction is. + */ + switch (xmax_status) + { + case XID_IS_CURRENT_XID: + case XID_IN_PROGRESS: + + /* + * The delete is in progress, so it cannot be visible to our + * snapshot. + */ + ctx->tuple_could_be_pruned = false; + break; + + case XID_COMMITTED: + + /* + * The delete committed. Whether the toast can be vacuumed away + * depends on how old the deleting transaction is. + */ + ctx->tuple_could_be_pruned = TransactionIdPrecedes(xmax, + ctx->safe_xmin); + break; + + case XID_ABORTED: + + /* + * The delete aborted or crashed. The tuple is still live. + */ + ctx->tuple_could_be_pruned = false; + break; + } + + /* Tuple itself is checkable even if it's dead. */ + return true; +} + + +/* + * Check the current toast tuple against the state tracked in ctx, recording + * any corruption found in ctx->tupstore. + * + * This is not equivalent to running verify_heapam on the toast table itself, + * and is not hardened against corruption of the toast table. Rather, when + * validating a toasted attribute in the main table, the sequence of toast + * tuples that store the toasted value are retrieved and checked in order, with + * each toast tuple being checked against where we are in the sequence, as well + * as each toast tuple having its varlena structure sanity checked. + * + * On entry, *expected_chunk_seq should be the chunk_seq value that we expect + * to find in toasttup. On exit, it will be updated to the value the next call + * to this function should expect to see. + */ +static void +check_toast_tuple(HeapTuple toasttup, HeapCheckContext *ctx, + ToastedAttribute *ta, int32 *expected_chunk_seq, + uint32 extsize) +{ + int32 chunk_seq; + int32 last_chunk_seq = (extsize - 1) / TOAST_MAX_CHUNK_SIZE; + Pointer chunk; + bool isnull; + int32 chunksize; + int32 expected_size; + + /* Sanity-check the sequence number. */ + chunk_seq = DatumGetInt32(fastgetattr(toasttup, 2, + ctx->toast_rel->rd_att, &isnull)); + if (isnull) + { + report_toast_corruption(ctx, ta, + psprintf("toast value %u has toast chunk with null sequence number", + ta->toast_pointer.va_valueid)); + return; + } + if (chunk_seq != *expected_chunk_seq) + { + /* Either the TOAST index is corrupt, or we don't have all chunks. */ + report_toast_corruption(ctx, ta, + psprintf("toast value %u index scan returned chunk %d when expecting chunk %d", + ta->toast_pointer.va_valueid, + chunk_seq, *expected_chunk_seq)); + } + *expected_chunk_seq = chunk_seq + 1; + + /* Sanity-check the chunk data. */ + chunk = DatumGetPointer(fastgetattr(toasttup, 3, + ctx->toast_rel->rd_att, &isnull)); + if (isnull) + { + report_toast_corruption(ctx, ta, + psprintf("toast value %u chunk %d has null data", + ta->toast_pointer.va_valueid, + chunk_seq)); + return; + } + if (!VARATT_IS_EXTENDED(chunk)) + chunksize = VARSIZE(chunk) - VARHDRSZ; + else if (VARATT_IS_SHORT(chunk)) + { + /* + * could happen due to heap_form_tuple doing its thing + */ + chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT; + } + else + { + /* should never happen */ + uint32 header = ((varattrib_4b *) chunk)->va_4byte.va_header; + + report_toast_corruption(ctx, ta, + psprintf("toast value %u chunk %d has invalid varlena header %0x", + ta->toast_pointer.va_valueid, + chunk_seq, header)); + return; + } + + /* + * Some checks on the data we've found + */ + if (chunk_seq > last_chunk_seq) + { + report_toast_corruption(ctx, ta, + psprintf("toast value %u chunk %d follows last expected chunk %d", + ta->toast_pointer.va_valueid, + chunk_seq, last_chunk_seq)); + return; + } + + expected_size = chunk_seq < last_chunk_seq ? TOAST_MAX_CHUNK_SIZE + : extsize - (last_chunk_seq * TOAST_MAX_CHUNK_SIZE); + + if (chunksize != expected_size) + report_toast_corruption(ctx, ta, + psprintf("toast value %u chunk %d has size %u, but expected size %u", + ta->toast_pointer.va_valueid, + chunk_seq, chunksize, expected_size)); +} + +/* + * Check the current attribute as tracked in ctx, recording any corruption + * found in ctx->tupstore. + * + * This function follows the logic performed by heap_deform_tuple(), and in the + * case of a toasted value, optionally stores the toast pointer so later it can + * be checked following the logic of detoast_external_attr(), checking for any + * conditions that would result in either of those functions Asserting or + * crashing the backend. The checks performed by Asserts present in those two + * functions are also performed here and in check_toasted_attribute. In cases + * where those two functions are a bit cavalier in their assumptions about data + * being correct, we perform additional checks not present in either of those + * two functions. Where some condition is checked in both of those functions, + * we perform it here twice, as we parallel the logical flow of those two + * functions. The presence of duplicate checks seems a reasonable price to pay + * for keeping this code tightly coupled with the code it protects. + * + * Returns true if the tuple attribute is sane enough for processing to + * continue on to the next attribute, false otherwise. + */ +static bool +check_tuple_attribute(HeapCheckContext *ctx) +{ + Datum attdatum; + struct varlena *attr; + char *tp; /* pointer to the tuple data */ + uint16 infomask; + Form_pg_attribute thisatt; + struct varatt_external toast_pointer; + + infomask = ctx->tuphdr->t_infomask; + thisatt = TupleDescAttr(RelationGetDescr(ctx->rel), ctx->attnum); + + tp = (char *) ctx->tuphdr + ctx->tuphdr->t_hoff; + + if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len) + { + report_corruption(ctx, + psprintf("attribute with length %u starts at offset %u beyond total tuple length %u", + thisatt->attlen, + ctx->tuphdr->t_hoff + ctx->offset, + ctx->lp_len)); + return false; + } + + /* Skip null values */ + if (infomask & HEAP_HASNULL && att_isnull(ctx->attnum, ctx->tuphdr->t_bits)) + return true; + + /* Skip non-varlena values, but update offset first */ + if (thisatt->attlen != -1) + { + ctx->offset = att_align_nominal(ctx->offset, thisatt->attalign); + ctx->offset = att_addlength_pointer(ctx->offset, thisatt->attlen, + tp + ctx->offset); + if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len) + { + report_corruption(ctx, + psprintf("attribute with length %u ends at offset %u beyond total tuple length %u", + thisatt->attlen, + ctx->tuphdr->t_hoff + ctx->offset, + ctx->lp_len)); + return false; + } + return true; + } + + /* Ok, we're looking at a varlena attribute. */ + ctx->offset = att_align_pointer(ctx->offset, thisatt->attalign, -1, + tp + ctx->offset); + + /* Get the (possibly corrupt) varlena datum */ + attdatum = fetchatt(thisatt, tp + ctx->offset); + + /* + * We have the datum, but we cannot decode it carelessly, as it may still + * be corrupt. + */ + + /* + * Check that VARTAG_SIZE won't hit an Assert on a corrupt va_tag before + * risking a call into att_addlength_pointer + */ + if (VARATT_IS_EXTERNAL(tp + ctx->offset)) + { + uint8 va_tag = VARTAG_EXTERNAL(tp + ctx->offset); + + if (va_tag != VARTAG_ONDISK) + { + report_corruption(ctx, + psprintf("toasted attribute has unexpected TOAST tag %u", + va_tag)); + /* We can't know where the next attribute begins */ + return false; + } + } + + /* Ok, should be safe now */ + ctx->offset = att_addlength_pointer(ctx->offset, thisatt->attlen, + tp + ctx->offset); + + if (ctx->tuphdr->t_hoff + ctx->offset > ctx->lp_len) + { + report_corruption(ctx, + psprintf("attribute with length %u ends at offset %u beyond total tuple length %u", + thisatt->attlen, + ctx->tuphdr->t_hoff + ctx->offset, + ctx->lp_len)); + + return false; + } + + /* + * heap_deform_tuple would be done with this attribute at this point, + * having stored it in values[], and would continue to the next attribute. + * We go further, because we need to check if the toast datum is corrupt. + */ + + attr = (struct varlena *) DatumGetPointer(attdatum); + + /* + * Now we follow the logic of detoast_external_attr(), with the same + * caveats about being paranoid about corruption. + */ + + /* Skip values that are not external */ + if (!VARATT_IS_EXTERNAL(attr)) + return true; + + /* It is external, and we're looking at a page on disk */ + + /* + * Must copy attr into toast_pointer for alignment considerations + */ + VARATT_EXTERNAL_GET_POINTER(toast_pointer, attr); + + /* Toasted attributes too large to be untoasted should never be stored */ + if (toast_pointer.va_rawsize > VARLENA_SIZE_LIMIT) + report_corruption(ctx, + psprintf("toast value %u rawsize %d exceeds limit %d", + toast_pointer.va_valueid, + toast_pointer.va_rawsize, + VARLENA_SIZE_LIMIT)); + + if (VARATT_EXTERNAL_IS_COMPRESSED(toast_pointer)) + { + ToastCompressionId cmid; + bool valid = false; + + /* Compressed attributes should have a valid compression method */ + cmid = TOAST_COMPRESS_METHOD(&toast_pointer); + switch (cmid) + { + /* List of all valid compression method IDs */ + case TOAST_PGLZ_COMPRESSION_ID: + case TOAST_LZ4_COMPRESSION_ID: + valid = true; + break; + + /* Recognized but invalid compression method ID */ + case TOAST_INVALID_COMPRESSION_ID: + break; + + /* Intentionally no default here */ + } + if (!valid) + report_corruption(ctx, + psprintf("toast value %u has invalid compression method id %d", + toast_pointer.va_valueid, cmid)); + } + + /* The tuple header better claim to contain toasted values */ + if (!(infomask & HEAP_HASEXTERNAL)) + { + report_corruption(ctx, + psprintf("toast value %u is external but tuple header flag HEAP_HASEXTERNAL not set", + toast_pointer.va_valueid)); + return true; + } + + /* The relation better have a toast table */ + if (!ctx->rel->rd_rel->reltoastrelid) + { + report_corruption(ctx, + psprintf("toast value %u is external but relation has no toast relation", + toast_pointer.va_valueid)); + return true; + } + + /* If we were told to skip toast checking, then we're done. */ + if (ctx->toast_rel == NULL) + return true; + + /* + * If this tuple is eligible to be pruned, we cannot check the toast. + * Otherwise, we push a copy of the toast tuple so we can check it after + * releasing the main table buffer lock. + */ + if (!ctx->tuple_could_be_pruned) + { + ToastedAttribute *ta; + + ta = (ToastedAttribute *) palloc0(sizeof(ToastedAttribute)); + + VARATT_EXTERNAL_GET_POINTER(ta->toast_pointer, attr); + ta->blkno = ctx->blkno; + ta->offnum = ctx->offnum; + ta->attnum = ctx->attnum; + ctx->toasted_attributes = lappend(ctx->toasted_attributes, ta); + } + + return true; +} + +/* + * For each attribute collected in ctx->toasted_attributes, look up the value + * in the toast table and perform checks on it. This function should only be + * called on toast pointers which cannot be vacuumed away during our + * processing. + */ +static void +check_toasted_attribute(HeapCheckContext *ctx, ToastedAttribute *ta) +{ + SnapshotData SnapshotToast; + ScanKeyData toastkey; + SysScanDesc toastscan; + bool found_toasttup; + HeapTuple toasttup; + uint32 extsize; + int32 expected_chunk_seq = 0; + int32 last_chunk_seq; + + extsize = VARATT_EXTERNAL_GET_EXTSIZE(ta->toast_pointer); + last_chunk_seq = (extsize - 1) / TOAST_MAX_CHUNK_SIZE; + + /* + * Setup a scan key to find chunks in toast table with matching va_valueid + */ + ScanKeyInit(&toastkey, + (AttrNumber) 1, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ta->toast_pointer.va_valueid)); + + /* + * Check if any chunks for this toasted object exist in the toast table, + * accessible via the index. + */ + init_toast_snapshot(&SnapshotToast); + toastscan = systable_beginscan_ordered(ctx->toast_rel, + ctx->valid_toast_index, + &SnapshotToast, 1, + &toastkey); + found_toasttup = false; + while ((toasttup = + systable_getnext_ordered(toastscan, + ForwardScanDirection)) != NULL) + { + found_toasttup = true; + check_toast_tuple(toasttup, ctx, ta, &expected_chunk_seq, extsize); + } + systable_endscan_ordered(toastscan); + + if (!found_toasttup) + report_toast_corruption(ctx, ta, + psprintf("toast value %u not found in toast table", + ta->toast_pointer.va_valueid)); + else if (expected_chunk_seq <= last_chunk_seq) + report_toast_corruption(ctx, ta, + psprintf("toast value %u was expected to end at chunk %d, but ended while expecting chunk %d", + ta->toast_pointer.va_valueid, + last_chunk_seq, expected_chunk_seq)); +} + +/* + * Check the current tuple as tracked in ctx, recording any corruption found in + * ctx->tupstore. + * + * We return some information about the status of xmin to aid in validating + * update chains. + */ +static void +check_tuple(HeapCheckContext *ctx, bool *xmin_commit_status_ok, + XidCommitStatus *xmin_commit_status) +{ + /* + * Check various forms of tuple header corruption, and if the header is + * too corrupt, do not continue with other checks. + */ + if (!check_tuple_header(ctx)) + return; + + /* + * Check tuple visibility. If the inserting transaction aborted, we + * cannot assume our relation description matches the tuple structure, and + * therefore cannot check it. + */ + if (!check_tuple_visibility(ctx, xmin_commit_status_ok, + xmin_commit_status)) + return; + + /* + * The tuple is visible, so it must be compatible with the current version + * of the relation descriptor. It might have fewer columns than are + * present in the relation descriptor, but it cannot have more. + */ + if (RelationGetDescr(ctx->rel)->natts < ctx->natts) + { + report_corruption(ctx, + psprintf("number of attributes %u exceeds maximum expected for table %u", + ctx->natts, + RelationGetDescr(ctx->rel)->natts)); + return; + } + + /* + * Check each attribute unless we hit corruption that confuses what to do + * next, at which point we abort further attribute checks for this tuple. + * Note that we don't abort for all types of corruption, only for those + * types where we don't know how to continue. We also don't abort the + * checking of toasted attributes collected from the tuple prior to + * aborting. Those will still be checked later along with other toasted + * attributes collected from the page. + */ + ctx->offset = 0; + for (ctx->attnum = 0; ctx->attnum < ctx->natts; ctx->attnum++) + if (!check_tuple_attribute(ctx)) + break; /* cannot continue */ + + /* revert attnum to -1 until we again examine individual attributes */ + ctx->attnum = -1; +} + +/* + * Convert a TransactionId into a FullTransactionId using our cached values of + * the valid transaction ID range. It is the caller's responsibility to have + * already updated the cached values, if necessary. + */ +static FullTransactionId +FullTransactionIdFromXidAndCtx(TransactionId xid, const HeapCheckContext *ctx) +{ + uint64 nextfxid_i; + int32 diff; + FullTransactionId fxid; + + Assert(TransactionIdIsNormal(ctx->next_xid)); + Assert(FullTransactionIdIsNormal(ctx->next_fxid)); + Assert(XidFromFullTransactionId(ctx->next_fxid) == ctx->next_xid); + + if (!TransactionIdIsNormal(xid)) + return FullTransactionIdFromEpochAndXid(0, xid); + + nextfxid_i = U64FromFullTransactionId(ctx->next_fxid); + + /* compute the 32bit modulo difference */ + diff = (int32) (ctx->next_xid - xid); + + /* + * In cases of corruption we might see a 32bit xid that is before epoch 0. + * We can't represent that as a 64bit xid, due to 64bit xids being + * unsigned integers, without the modulo arithmetic of 32bit xid. There's + * no really nice way to deal with that, but it works ok enough to use + * FirstNormalFullTransactionId in that case, as a freshly initdb'd + * cluster already has a newer horizon. + */ + if (diff > 0 && (nextfxid_i - FirstNormalTransactionId) < (int64) diff) + { + Assert(EpochFromFullTransactionId(ctx->next_fxid) == 0); + fxid = FirstNormalFullTransactionId; + } + else + fxid = FullTransactionIdFromU64(nextfxid_i - diff); + + Assert(FullTransactionIdIsNormal(fxid)); + return fxid; +} + +/* + * Update our cached range of valid transaction IDs. + */ +static void +update_cached_xid_range(HeapCheckContext *ctx) +{ + /* Make cached copies */ + LWLockAcquire(XidGenLock, LW_SHARED); + ctx->next_fxid = ShmemVariableCache->nextXid; + ctx->oldest_xid = ShmemVariableCache->oldestXid; + LWLockRelease(XidGenLock); + + /* And compute alternate versions of the same */ + ctx->next_xid = XidFromFullTransactionId(ctx->next_fxid); + ctx->oldest_fxid = FullTransactionIdFromXidAndCtx(ctx->oldest_xid, ctx); +} + +/* + * Update our cached range of valid multitransaction IDs. + */ +static void +update_cached_mxid_range(HeapCheckContext *ctx) +{ + ReadMultiXactIdRange(&ctx->oldest_mxact, &ctx->next_mxact); +} + +/* + * Return whether the given FullTransactionId is within our cached valid + * transaction ID range. + */ +static inline bool +fxid_in_cached_range(FullTransactionId fxid, const HeapCheckContext *ctx) +{ + return (FullTransactionIdPrecedesOrEquals(ctx->oldest_fxid, fxid) && + FullTransactionIdPrecedes(fxid, ctx->next_fxid)); +} + +/* + * Checks whether a multitransaction ID is in the cached valid range, returning + * the nature of the range violation, if any. + */ +static XidBoundsViolation +check_mxid_in_range(MultiXactId mxid, HeapCheckContext *ctx) +{ + if (!TransactionIdIsValid(mxid)) + return XID_INVALID; + if (MultiXactIdPrecedes(mxid, ctx->relminmxid)) + return XID_PRECEDES_RELMIN; + if (MultiXactIdPrecedes(mxid, ctx->oldest_mxact)) + return XID_PRECEDES_CLUSTERMIN; + if (MultiXactIdPrecedesOrEquals(ctx->next_mxact, mxid)) + return XID_IN_FUTURE; + return XID_BOUNDS_OK; +} + +/* + * Checks whether the given mxid is valid to appear in the heap being checked, + * returning the nature of the range violation, if any. + * + * This function attempts to return quickly by caching the known valid mxid + * range in ctx. Callers should already have performed the initial setup of + * the cache prior to the first call to this function. + */ +static XidBoundsViolation +check_mxid_valid_in_rel(MultiXactId mxid, HeapCheckContext *ctx) +{ + XidBoundsViolation result; + + result = check_mxid_in_range(mxid, ctx); + if (result == XID_BOUNDS_OK) + return XID_BOUNDS_OK; + + /* The range may have advanced. Recheck. */ + update_cached_mxid_range(ctx); + return check_mxid_in_range(mxid, ctx); +} + +/* + * Checks whether the given transaction ID is (or was recently) valid to appear + * in the heap being checked, or whether it is too old or too new to appear in + * the relation, returning information about the nature of the bounds violation. + * + * We cache the range of valid transaction IDs. If xid is in that range, we + * conclude that it is valid, even though concurrent changes to the table might + * invalidate it under certain corrupt conditions. (For example, if the table + * contains corrupt all-frozen bits, a concurrent vacuum might skip the page(s) + * containing the xid and then truncate clog and advance the relfrozenxid + * beyond xid.) Reporting the xid as valid under such conditions seems + * acceptable, since if we had checked it earlier in our scan it would have + * truly been valid at that time. + * + * If the status argument is not NULL, and if and only if the transaction ID + * appears to be valid in this relation, the status argument will be set with + * the commit status of the transaction ID. + */ +static XidBoundsViolation +get_xid_status(TransactionId xid, HeapCheckContext *ctx, + XidCommitStatus *status) +{ + FullTransactionId fxid; + FullTransactionId clog_horizon; + + /* Quick check for special xids */ + if (!TransactionIdIsValid(xid)) + return XID_INVALID; + else if (xid == BootstrapTransactionId || xid == FrozenTransactionId) + { + if (status != NULL) + *status = XID_COMMITTED; + return XID_BOUNDS_OK; + } + + /* Check if the xid is within bounds */ + fxid = FullTransactionIdFromXidAndCtx(xid, ctx); + if (!fxid_in_cached_range(fxid, ctx)) + { + /* + * We may have been checking against stale values. Update the cached + * range to be sure, and since we relied on the cached range when we + * performed the full xid conversion, reconvert. + */ + update_cached_xid_range(ctx); + fxid = FullTransactionIdFromXidAndCtx(xid, ctx); + } + + if (FullTransactionIdPrecedesOrEquals(ctx->next_fxid, fxid)) + return XID_IN_FUTURE; + if (FullTransactionIdPrecedes(fxid, ctx->oldest_fxid)) + return XID_PRECEDES_CLUSTERMIN; + if (FullTransactionIdPrecedes(fxid, ctx->relfrozenfxid)) + return XID_PRECEDES_RELMIN; + + /* Early return if the caller does not request clog checking */ + if (status == NULL) + return XID_BOUNDS_OK; + + /* Early return if we just checked this xid in a prior call */ + if (xid == ctx->cached_xid) + { + *status = ctx->cached_status; + return XID_BOUNDS_OK; + } + + *status = XID_COMMITTED; + LWLockAcquire(XactTruncationLock, LW_SHARED); + clog_horizon = + FullTransactionIdFromXidAndCtx(ShmemVariableCache->oldestClogXid, + ctx); + if (FullTransactionIdPrecedesOrEquals(clog_horizon, fxid)) + { + if (TransactionIdIsCurrentTransactionId(xid)) + *status = XID_IS_CURRENT_XID; + else if (TransactionIdIsInProgress(xid)) + *status = XID_IN_PROGRESS; + else if (TransactionIdDidCommit(xid)) + *status = XID_COMMITTED; + else + *status = XID_ABORTED; + } + LWLockRelease(XactTruncationLock); + ctx->cached_xid = xid; + ctx->cached_status = *status; + return XID_BOUNDS_OK; +} diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c new file mode 100644 index 0000000..dc7d4a5 --- /dev/null +++ b/contrib/amcheck/verify_nbtree.c @@ -0,0 +1,3347 @@ +/*------------------------------------------------------------------------- + * + * verify_nbtree.c + * Verifies the integrity of nbtree indexes based on invariants. + * + * For B-Tree indexes, verification includes checking that each page in the + * target index has items in logical order as reported by an insertion scankey + * (the insertion scankey sort-wise NULL semantics are needed for + * verification). + * + * When index-to-heap verification is requested, a Bloom filter is used to + * fingerprint all tuples in the target index, as the index is traversed to + * verify its structure. A heap scan later uses Bloom filter probes to verify + * that every visible heap tuple has a matching index tuple. + * + * + * Copyright (c) 2017-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/amcheck/verify_nbtree.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/nbtree.h" +#include "access/table.h" +#include "access/tableam.h" +#include "access/transam.h" +#include "access/xact.h" +#include "catalog/index.h" +#include "catalog/pg_am.h" +#include "catalog/pg_opfamily_d.h" +#include "commands/tablecmds.h" +#include "common/pg_prng.h" +#include "lib/bloomfilter.h" +#include "miscadmin.h" +#include "storage/lmgr.h" +#include "storage/smgr.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/snapmgr.h" + + +PG_MODULE_MAGIC; + +/* + * A B-Tree cannot possibly have this many levels, since there must be one + * block per level, which is bound by the range of BlockNumber: + */ +#define InvalidBtreeLevel ((uint32) InvalidBlockNumber) +#define BTreeTupleGetNKeyAtts(itup, rel) \ + Min(IndexRelationGetNumberOfKeyAttributes(rel), BTreeTupleGetNAtts(itup, rel)) + +/* + * State associated with verifying a B-Tree index + * + * target is the point of reference for a verification operation. + * + * Other B-Tree pages may be allocated, but those are always auxiliary (e.g., + * they are current target's child pages). Conceptually, problems are only + * ever found in the current target page (or for a particular heap tuple during + * heapallindexed verification). Each page found by verification's left/right, + * top/bottom scan becomes the target exactly once. + */ +typedef struct BtreeCheckState +{ + /* + * Unchanging state, established at start of verification: + */ + + /* B-Tree Index Relation and associated heap relation */ + Relation rel; + Relation heaprel; + /* rel is heapkeyspace index? */ + bool heapkeyspace; + /* ShareLock held on heap/index, rather than AccessShareLock? */ + bool readonly; + /* Also verifying heap has no unindexed tuples? */ + bool heapallindexed; + /* Also making sure non-pivot tuples can be found by new search? */ + bool rootdescend; + /* Per-page context */ + MemoryContext targetcontext; + /* Buffer access strategy */ + BufferAccessStrategy checkstrategy; + + /* + * Mutable state, for verification of particular page: + */ + + /* Current target page */ + Page target; + /* Target block number */ + BlockNumber targetblock; + /* Target page's LSN */ + XLogRecPtr targetlsn; + + /* + * Low key: high key of left sibling of target page. Used only for child + * verification. So, 'lowkey' is kept only when 'readonly' is set. + */ + IndexTuple lowkey; + + /* + * The rightlink and incomplete split flag of block one level down to the + * target page, which was visited last time via downlink from target page. + * We use it to check for missing downlinks. + */ + BlockNumber prevrightlink; + bool previncompletesplit; + + /* + * Mutable state, for optional heapallindexed verification: + */ + + /* Bloom filter fingerprints B-Tree index */ + bloom_filter *filter; + /* Debug counter */ + int64 heaptuplespresent; +} BtreeCheckState; + +/* + * Starting point for verifying an entire B-Tree index level + */ +typedef struct BtreeLevel +{ + /* Level number (0 is leaf page level). */ + uint32 level; + + /* Left most block on level. Scan of level begins here. */ + BlockNumber leftmost; + + /* Is this level reported as "true" root level by meta page? */ + bool istruerootlevel; +} BtreeLevel; + +PG_FUNCTION_INFO_V1(bt_index_check); +PG_FUNCTION_INFO_V1(bt_index_parent_check); + +static void bt_index_check_internal(Oid indrelid, bool parentcheck, + bool heapallindexed, bool rootdescend); +static inline void btree_index_checkable(Relation rel); +static inline bool btree_index_mainfork_expected(Relation rel); +static void bt_check_every_level(Relation rel, Relation heaprel, + bool heapkeyspace, bool readonly, bool heapallindexed, + bool rootdescend); +static BtreeLevel bt_check_level_from_leftmost(BtreeCheckState *state, + BtreeLevel level); +static bool bt_leftmost_ignoring_half_dead(BtreeCheckState *state, + BlockNumber start, + BTPageOpaque start_opaque); +static void bt_recheck_sibling_links(BtreeCheckState *state, + BlockNumber btpo_prev_from_target, + BlockNumber leftcurrent); +static void bt_target_page_check(BtreeCheckState *state); +static BTScanInsert bt_right_page_check_scankey(BtreeCheckState *state); +static void bt_child_check(BtreeCheckState *state, BTScanInsert targetkey, + OffsetNumber downlinkoffnum); +static void bt_child_highkey_check(BtreeCheckState *state, + OffsetNumber target_downlinkoffnum, + Page loaded_child, + uint32 target_level); +static void bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit, + BlockNumber blkno, Page page); +static void bt_tuple_present_callback(Relation index, ItemPointer tid, + Datum *values, bool *isnull, + bool tupleIsAlive, void *checkstate); +static IndexTuple bt_normalize_tuple(BtreeCheckState *state, + IndexTuple itup); +static inline IndexTuple bt_posting_plain_tuple(IndexTuple itup, int n); +static bool bt_rootdescend(BtreeCheckState *state, IndexTuple itup); +static inline bool offset_is_negative_infinity(BTPageOpaque opaque, + OffsetNumber offset); +static inline bool invariant_l_offset(BtreeCheckState *state, BTScanInsert key, + OffsetNumber upperbound); +static inline bool invariant_leq_offset(BtreeCheckState *state, + BTScanInsert key, + OffsetNumber upperbound); +static inline bool invariant_g_offset(BtreeCheckState *state, BTScanInsert key, + OffsetNumber lowerbound); +static inline bool invariant_l_nontarget_offset(BtreeCheckState *state, + BTScanInsert key, + BlockNumber nontargetblock, + Page nontarget, + OffsetNumber upperbound); +static Page palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum); +static inline BTScanInsert bt_mkscankey_pivotsearch(Relation rel, + IndexTuple itup); +static ItemId PageGetItemIdCareful(BtreeCheckState *state, BlockNumber block, + Page page, OffsetNumber offset); +static inline ItemPointer BTreeTupleGetHeapTIDCareful(BtreeCheckState *state, + IndexTuple itup, bool nonpivot); +static inline ItemPointer BTreeTupleGetPointsToTID(IndexTuple itup); + +/* + * bt_index_check(index regclass, heapallindexed boolean) + * + * Verify integrity of B-Tree index. + * + * Acquires AccessShareLock on heap & index relations. Does not consider + * invariants that exist between parent/child pages. Optionally verifies + * that heap does not contain any unindexed or incorrectly indexed tuples. + */ +Datum +bt_index_check(PG_FUNCTION_ARGS) +{ + Oid indrelid = PG_GETARG_OID(0); + bool heapallindexed = false; + + if (PG_NARGS() == 2) + heapallindexed = PG_GETARG_BOOL(1); + + bt_index_check_internal(indrelid, false, heapallindexed, false); + + PG_RETURN_VOID(); +} + +/* + * bt_index_parent_check(index regclass, heapallindexed boolean) + * + * Verify integrity of B-Tree index. + * + * Acquires ShareLock on heap & index relations. Verifies that downlinks in + * parent pages are valid lower bounds on child pages. Optionally verifies + * that heap does not contain any unindexed or incorrectly indexed tuples. + */ +Datum +bt_index_parent_check(PG_FUNCTION_ARGS) +{ + Oid indrelid = PG_GETARG_OID(0); + bool heapallindexed = false; + bool rootdescend = false; + + if (PG_NARGS() >= 2) + heapallindexed = PG_GETARG_BOOL(1); + if (PG_NARGS() == 3) + rootdescend = PG_GETARG_BOOL(2); + + bt_index_check_internal(indrelid, true, heapallindexed, rootdescend); + + PG_RETURN_VOID(); +} + +/* + * Helper for bt_index_[parent_]check, coordinating the bulk of the work. + */ +static void +bt_index_check_internal(Oid indrelid, bool parentcheck, bool heapallindexed, + bool rootdescend) +{ + Oid heapid; + Relation indrel; + Relation heaprel; + LOCKMODE lockmode; + Oid save_userid; + int save_sec_context; + int save_nestlevel; + + if (parentcheck) + lockmode = ShareLock; + else + lockmode = AccessShareLock; + + /* + * We must lock table before index to avoid deadlocks. However, if the + * passed indrelid isn't an index then IndexGetRelation() will fail. + * Rather than emitting a not-very-helpful error message, postpone + * complaining, expecting that the is-it-an-index test below will fail. + * + * In hot standby mode this will raise an error when parentcheck is true. + */ + heapid = IndexGetRelation(indrelid, true); + if (OidIsValid(heapid)) + { + heaprel = table_open(heapid, lockmode); + + /* + * Switch to the table owner's userid, so that any index functions are + * run as that user. Also lock down security-restricted operations + * and arrange to make GUC variable changes local to this command. + */ + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(heaprel->rd_rel->relowner, + save_sec_context | SECURITY_RESTRICTED_OPERATION); + save_nestlevel = NewGUCNestLevel(); + } + else + { + heaprel = NULL; + /* Set these just to suppress "uninitialized variable" warnings */ + save_userid = InvalidOid; + save_sec_context = -1; + save_nestlevel = -1; + } + + /* + * Open the target index relations separately (like relation_openrv(), but + * with heap relation locked first to prevent deadlocking). In hot + * standby mode this will raise an error when parentcheck is true. + * + * There is no need for the usual indcheckxmin usability horizon test + * here, even in the heapallindexed case, because index undergoing + * verification only needs to have entries for a new transaction snapshot. + * (If this is a parentcheck verification, there is no question about + * committed or recently dead heap tuples lacking index entries due to + * concurrent activity.) + */ + indrel = index_open(indrelid, lockmode); + + /* + * Since we did the IndexGetRelation call above without any lock, it's + * barely possible that a race against an index drop/recreation could have + * netted us the wrong table. + */ + if (heaprel == NULL || heapid != IndexGetRelation(indrelid, false)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("could not open parent table of index \"%s\"", + RelationGetRelationName(indrel)))); + + /* Relation suitable for checking as B-Tree? */ + btree_index_checkable(indrel); + + if (btree_index_mainfork_expected(indrel)) + { + bool heapkeyspace, + allequalimage; + + if (!smgrexists(RelationGetSmgr(indrel), MAIN_FORKNUM)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index \"%s\" lacks a main relation fork", + RelationGetRelationName(indrel)))); + + /* Extract metadata from metapage, and sanitize it in passing */ + _bt_metaversion(indrel, &heapkeyspace, &allequalimage); + if (allequalimage && !heapkeyspace) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index \"%s\" metapage has equalimage field set on unsupported nbtree version", + RelationGetRelationName(indrel)))); + if (allequalimage && !_bt_allequalimage(indrel, false)) + { + bool has_interval_ops = false; + + for (int i = 0; i < IndexRelationGetNumberOfKeyAttributes(indrel); i++) + if (indrel->rd_opfamily[i] == INTERVAL_BTREE_FAM_OID) + has_interval_ops = true; + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index \"%s\" metapage incorrectly indicates that deduplication is safe", + RelationGetRelationName(indrel)), + has_interval_ops + ? errhint("This is known of \"interval\" indexes last built on a version predating 2023-11.") + : 0)); + } + + /* Check index, possibly against table it is an index on */ + bt_check_every_level(indrel, heaprel, heapkeyspace, parentcheck, + heapallindexed, rootdescend); + } + + /* Roll back any GUC changes executed by index functions */ + AtEOXact_GUC(false, save_nestlevel); + + /* Restore userid and security context */ + SetUserIdAndSecContext(save_userid, save_sec_context); + + /* + * Release locks early. That's ok here because nothing in the called + * routines will trigger shared cache invalidations to be sent, so we can + * relax the usual pattern of only releasing locks after commit. + */ + index_close(indrel, lockmode); + if (heaprel) + table_close(heaprel, lockmode); +} + +/* + * Basic checks about the suitability of a relation for checking as a B-Tree + * index. + * + * NB: Intentionally not checking permissions, the function is normally not + * callable by non-superusers. If granted, it's useful to be able to check a + * whole cluster. + */ +static inline void +btree_index_checkable(Relation rel) +{ + if (rel->rd_rel->relkind != RELKIND_INDEX || + rel->rd_rel->relam != BTREE_AM_OID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only B-Tree indexes are supported as targets for verification"), + errdetail("Relation \"%s\" is not a B-Tree index.", + RelationGetRelationName(rel)))); + + if (RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary tables of other sessions"), + errdetail("Index \"%s\" is associated with temporary relation.", + RelationGetRelationName(rel)))); + + if (!rel->rd_index->indisvalid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot check index \"%s\"", + RelationGetRelationName(rel)), + errdetail("Index is not valid."))); +} + +/* + * Check if B-Tree index relation should have a file for its main relation + * fork. Verification uses this to skip unlogged indexes when in hot standby + * mode, where there is simply nothing to verify. We behave as if the + * relation is empty. + * + * NB: Caller should call btree_index_checkable() before calling here. + */ +static inline bool +btree_index_mainfork_expected(Relation rel) +{ + if (rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED || + !RecoveryInProgress()) + return true; + + ereport(DEBUG1, + (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION), + errmsg("cannot verify unlogged index \"%s\" during recovery, skipping", + RelationGetRelationName(rel)))); + + return false; +} + +/* + * Main entry point for B-Tree SQL-callable functions. Walks the B-Tree in + * logical order, verifying invariants as it goes. Optionally, verification + * checks if the heap relation contains any tuples that are not represented in + * the index but should be. + * + * It is the caller's responsibility to acquire appropriate heavyweight lock on + * the index relation, and advise us if extra checks are safe when a ShareLock + * is held. (A lock of the same type must also have been acquired on the heap + * relation.) + * + * A ShareLock is generally assumed to prevent any kind of physical + * modification to the index structure, including modifications that VACUUM may + * make. This does not include setting of the LP_DEAD bit by concurrent index + * scans, although that is just metadata that is not able to directly affect + * any check performed here. Any concurrent process that might act on the + * LP_DEAD bit being set (recycle space) requires a heavyweight lock that + * cannot be held while we hold a ShareLock. (Besides, even if that could + * happen, the ad-hoc recycling when a page might otherwise split is performed + * per-page, and requires an exclusive buffer lock, which wouldn't cause us + * trouble. _bt_delitems_vacuum() may only delete leaf items, and so the extra + * parent/child check cannot be affected.) + */ +static void +bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace, + bool readonly, bool heapallindexed, bool rootdescend) +{ + BtreeCheckState *state; + Page metapage; + BTMetaPageData *metad; + uint32 previouslevel; + BtreeLevel current; + Snapshot snapshot = SnapshotAny; + + if (!readonly) + elog(DEBUG1, "verifying consistency of tree structure for index \"%s\"", + RelationGetRelationName(rel)); + else + elog(DEBUG1, "verifying consistency of tree structure for index \"%s\" with cross-level checks", + RelationGetRelationName(rel)); + + /* + * This assertion matches the one in index_getnext_tid(). See page + * recycling/"visible to everyone" notes in nbtree README. + */ + Assert(TransactionIdIsValid(RecentXmin)); + + /* + * Initialize state for entire verification operation + */ + state = palloc0(sizeof(BtreeCheckState)); + state->rel = rel; + state->heaprel = heaprel; + state->heapkeyspace = heapkeyspace; + state->readonly = readonly; + state->heapallindexed = heapallindexed; + state->rootdescend = rootdescend; + + if (state->heapallindexed) + { + int64 total_pages; + int64 total_elems; + uint64 seed; + + /* + * Size Bloom filter based on estimated number of tuples in index, + * while conservatively assuming that each block must contain at least + * MaxTIDsPerBTreePage / 3 "plain" tuples -- see + * bt_posting_plain_tuple() for definition, and details of how posting + * list tuples are handled. + */ + total_pages = RelationGetNumberOfBlocks(rel); + total_elems = Max(total_pages * (MaxTIDsPerBTreePage / 3), + (int64) state->rel->rd_rel->reltuples); + /* Generate a random seed to avoid repetition */ + seed = pg_prng_uint64(&pg_global_prng_state); + /* Create Bloom filter to fingerprint index */ + state->filter = bloom_create(total_elems, maintenance_work_mem, seed); + state->heaptuplespresent = 0; + + /* + * Register our own snapshot in !readonly case, rather than asking + * table_index_build_scan() to do this for us later. This needs to + * happen before index fingerprinting begins, so we can later be + * certain that index fingerprinting should have reached all tuples + * returned by table_index_build_scan(). + */ + if (!state->readonly) + { + snapshot = RegisterSnapshot(GetTransactionSnapshot()); + + /* + * GetTransactionSnapshot() always acquires a new MVCC snapshot in + * READ COMMITTED mode. A new snapshot is guaranteed to have all + * the entries it requires in the index. + * + * We must defend against the possibility that an old xact + * snapshot was returned at higher isolation levels when that + * snapshot is not safe for index scans of the target index. This + * is possible when the snapshot sees tuples that are before the + * index's indcheckxmin horizon. Throwing an error here should be + * very rare. It doesn't seem worth using a secondary snapshot to + * avoid this. + */ + if (IsolationUsesXactSnapshot() && rel->rd_index->indcheckxmin && + !TransactionIdPrecedes(HeapTupleHeaderGetXmin(rel->rd_indextuple->t_data), + snapshot->xmin)) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("index \"%s\" cannot be verified using transaction snapshot", + RelationGetRelationName(rel)))); + } + } + + Assert(!state->rootdescend || state->readonly); + if (state->rootdescend && !state->heapkeyspace) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot verify that tuples from index \"%s\" can each be found by an independent index search", + RelationGetRelationName(rel)), + errhint("Only B-Tree version 4 indexes support rootdescend verification."))); + + /* Create context for page */ + state->targetcontext = AllocSetContextCreate(CurrentMemoryContext, + "amcheck context", + ALLOCSET_DEFAULT_SIZES); + state->checkstrategy = GetAccessStrategy(BAS_BULKREAD); + + /* Get true root block from meta-page */ + metapage = palloc_btree_page(state, BTREE_METAPAGE); + metad = BTPageGetMeta(metapage); + + /* + * Certain deletion patterns can result in "skinny" B-Tree indexes, where + * the fast root and true root differ. + * + * Start from the true root, not the fast root, unlike conventional index + * scans. This approach is more thorough, and removes the risk of + * following a stale fast root from the meta page. + */ + if (metad->btm_fastroot != metad->btm_root) + ereport(DEBUG1, + (errcode(ERRCODE_NO_DATA), + errmsg_internal("harmless fast root mismatch in index \"%s\"", + RelationGetRelationName(rel)), + errdetail_internal("Fast root block %u (level %u) differs from true root block %u (level %u).", + metad->btm_fastroot, metad->btm_fastlevel, + metad->btm_root, metad->btm_level))); + + /* + * Starting at the root, verify every level. Move left to right, top to + * bottom. Note that there may be no pages other than the meta page (meta + * page can indicate that root is P_NONE when the index is totally empty). + */ + previouslevel = InvalidBtreeLevel; + current.level = metad->btm_level; + current.leftmost = metad->btm_root; + current.istruerootlevel = true; + while (current.leftmost != P_NONE) + { + /* + * Verify this level, and get left most page for next level down, if + * not at leaf level + */ + current = bt_check_level_from_leftmost(state, current); + + if (current.leftmost == InvalidBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index \"%s\" has no valid pages on level below %u or first level", + RelationGetRelationName(rel), previouslevel))); + + previouslevel = current.level; + } + + /* + * * Check whether heap contains unindexed/malformed tuples * + */ + if (state->heapallindexed) + { + IndexInfo *indexinfo = BuildIndexInfo(state->rel); + TableScanDesc scan; + + /* + * Create our own scan for table_index_build_scan(), rather than + * getting it to do so for us. This is required so that we can + * actually use the MVCC snapshot registered earlier in !readonly + * case. + * + * Note that table_index_build_scan() calls heap_endscan() for us. + */ + scan = table_beginscan_strat(state->heaprel, /* relation */ + snapshot, /* snapshot */ + 0, /* number of keys */ + NULL, /* scan key */ + true, /* buffer access strategy OK */ + true); /* syncscan OK? */ + + /* + * Scan will behave as the first scan of a CREATE INDEX CONCURRENTLY + * behaves in !readonly case. + * + * It's okay that we don't actually use the same lock strength for the + * heap relation as any other ii_Concurrent caller would in !readonly + * case. We have no reason to care about a concurrent VACUUM + * operation, since there isn't going to be a second scan of the heap + * that needs to be sure that there was no concurrent recycling of + * TIDs. + */ + indexinfo->ii_Concurrent = !state->readonly; + + /* + * Don't wait for uncommitted tuple xact commit/abort when index is a + * unique index on a catalog (or an index used by an exclusion + * constraint). This could otherwise happen in the readonly case. + */ + indexinfo->ii_Unique = false; + indexinfo->ii_ExclusionOps = NULL; + indexinfo->ii_ExclusionProcs = NULL; + indexinfo->ii_ExclusionStrats = NULL; + + elog(DEBUG1, "verifying that tuples from index \"%s\" are present in \"%s\"", + RelationGetRelationName(state->rel), + RelationGetRelationName(state->heaprel)); + + table_index_build_scan(state->heaprel, state->rel, indexinfo, true, false, + bt_tuple_present_callback, (void *) state, scan); + + ereport(DEBUG1, + (errmsg_internal("finished verifying presence of " INT64_FORMAT " tuples from table \"%s\" with bitset %.2f%% set", + state->heaptuplespresent, RelationGetRelationName(heaprel), + 100.0 * bloom_prop_bits_set(state->filter)))); + + if (snapshot != SnapshotAny) + UnregisterSnapshot(snapshot); + + bloom_free(state->filter); + } + + /* Be tidy: */ + MemoryContextDelete(state->targetcontext); +} + +/* + * Given a left-most block at some level, move right, verifying each page + * individually (with more verification across pages for "readonly" + * callers). Caller should pass the true root page as the leftmost initially, + * working their way down by passing what is returned for the last call here + * until level 0 (leaf page level) was reached. + * + * Returns state for next call, if any. This includes left-most block number + * one level lower that should be passed on next level/call, which is set to + * P_NONE on last call here (when leaf level is verified). Level numbers + * follow the nbtree convention: higher levels have higher numbers, because new + * levels are added only due to a root page split. Note that prior to the + * first root page split, the root is also a leaf page, so there is always a + * level 0 (leaf level), and it's always the last level processed. + * + * Note on memory management: State's per-page context is reset here, between + * each call to bt_target_page_check(). + */ +static BtreeLevel +bt_check_level_from_leftmost(BtreeCheckState *state, BtreeLevel level) +{ + /* State to establish early, concerning entire level */ + BTPageOpaque opaque; + MemoryContext oldcontext; + BtreeLevel nextleveldown; + + /* Variables for iterating across level using right links */ + BlockNumber leftcurrent = P_NONE; + BlockNumber current = level.leftmost; + + /* Initialize return state */ + nextleveldown.leftmost = InvalidBlockNumber; + nextleveldown.level = InvalidBtreeLevel; + nextleveldown.istruerootlevel = false; + + /* Use page-level context for duration of this call */ + oldcontext = MemoryContextSwitchTo(state->targetcontext); + + elog(DEBUG1, "verifying level %u%s", level.level, + level.istruerootlevel ? + " (true root level)" : level.level == 0 ? " (leaf level)" : ""); + + state->prevrightlink = InvalidBlockNumber; + state->previncompletesplit = false; + + do + { + /* Don't rely on CHECK_FOR_INTERRUPTS() calls at lower level */ + CHECK_FOR_INTERRUPTS(); + + /* Initialize state for this iteration */ + state->targetblock = current; + state->target = palloc_btree_page(state, state->targetblock); + state->targetlsn = PageGetLSN(state->target); + + opaque = BTPageGetOpaque(state->target); + + if (P_IGNORE(opaque)) + { + /* + * Since there cannot be a concurrent VACUUM operation in readonly + * mode, and since a page has no links within other pages + * (siblings and parent) once it is marked fully deleted, it + * should be impossible to land on a fully deleted page in + * readonly mode. See bt_child_check() for further details. + * + * The bt_child_check() P_ISDELETED() check is repeated here so + * that pages that are only reachable through sibling links get + * checked. + */ + if (state->readonly && P_ISDELETED(opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("downlink or sibling link points to deleted block in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Block=%u left block=%u left link from block=%u.", + current, leftcurrent, opaque->btpo_prev))); + + if (P_RIGHTMOST(opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("block %u fell off the end of index \"%s\"", + current, RelationGetRelationName(state->rel)))); + else + ereport(DEBUG1, + (errcode(ERRCODE_NO_DATA), + errmsg_internal("block %u of index \"%s\" concurrently deleted", + current, RelationGetRelationName(state->rel)))); + goto nextpage; + } + else if (nextleveldown.leftmost == InvalidBlockNumber) + { + /* + * A concurrent page split could make the caller supplied leftmost + * block no longer contain the leftmost page, or no longer be the + * true root, but where that isn't possible due to heavyweight + * locking, check that the first valid page meets caller's + * expectations. + */ + if (state->readonly) + { + if (!bt_leftmost_ignoring_half_dead(state, current, opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("block %u is not leftmost in index \"%s\"", + current, RelationGetRelationName(state->rel)))); + + if (level.istruerootlevel && !P_ISROOT(opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("block %u is not true root in index \"%s\"", + current, RelationGetRelationName(state->rel)))); + } + + /* + * Before beginning any non-trivial examination of level, prepare + * state for next bt_check_level_from_leftmost() invocation for + * the next level for the next level down (if any). + * + * There should be at least one non-ignorable page per level, + * unless this is the leaf level, which is assumed by caller to be + * final level. + */ + if (!P_ISLEAF(opaque)) + { + IndexTuple itup; + ItemId itemid; + + /* Internal page -- downlink gets leftmost on next level */ + itemid = PageGetItemIdCareful(state, state->targetblock, + state->target, + P_FIRSTDATAKEY(opaque)); + itup = (IndexTuple) PageGetItem(state->target, itemid); + nextleveldown.leftmost = BTreeTupleGetDownLink(itup); + nextleveldown.level = opaque->btpo_level - 1; + } + else + { + /* + * Leaf page -- final level caller must process. + * + * Note that this could also be the root page, if there has + * been no root page split yet. + */ + nextleveldown.leftmost = P_NONE; + nextleveldown.level = InvalidBtreeLevel; + } + + /* + * Finished setting up state for this call/level. Control will + * never end up back here in any future loop iteration for this + * level. + */ + } + + /* + * Sibling links should be in mutual agreement. There arises + * leftcurrent == P_NONE && btpo_prev != P_NONE when the left sibling + * of the parent's low-key downlink is half-dead. (A half-dead page + * has no downlink from its parent.) Under heavyweight locking, the + * last bt_leftmost_ignoring_half_dead() validated this btpo_prev. + * Without heavyweight locking, validation of the P_NONE case remains + * unimplemented. + */ + if (opaque->btpo_prev != leftcurrent && leftcurrent != P_NONE) + bt_recheck_sibling_links(state, opaque->btpo_prev, leftcurrent); + + /* Check level */ + if (level.level != opaque->btpo_level) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("leftmost down link for level points to block in index \"%s\" whose level is not one level down", + RelationGetRelationName(state->rel)), + errdetail_internal("Block pointed to=%u expected level=%u level in pointed to block=%u.", + current, level.level, opaque->btpo_level))); + + /* Verify invariants for page */ + bt_target_page_check(state); + +nextpage: + + /* Try to detect circular links */ + if (current == leftcurrent || current == opaque->btpo_prev) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("circular link chain found in block %u of index \"%s\"", + current, RelationGetRelationName(state->rel)))); + + leftcurrent = current; + current = opaque->btpo_next; + + if (state->lowkey) + { + Assert(state->readonly); + pfree(state->lowkey); + state->lowkey = NULL; + } + + /* + * Copy current target high key as the low key of right sibling. + * Allocate memory in upper level context, so it would be cleared + * after reset of target context. + * + * We only need the low key in corner cases of checking child high + * keys. We use high key only when incomplete split on the child level + * falls to the boundary of pages on the target level. See + * bt_child_highkey_check() for details. So, typically we won't end + * up doing anything with low key, but it's simpler for general case + * high key verification to always have it available. + * + * The correctness of managing low key in the case of concurrent + * splits wasn't investigated yet. Thankfully we only need low key + * for readonly verification and concurrent splits won't happen. + */ + if (state->readonly && !P_RIGHTMOST(opaque)) + { + IndexTuple itup; + ItemId itemid; + + itemid = PageGetItemIdCareful(state, state->targetblock, + state->target, P_HIKEY); + itup = (IndexTuple) PageGetItem(state->target, itemid); + + state->lowkey = MemoryContextAlloc(oldcontext, IndexTupleSize(itup)); + memcpy(state->lowkey, itup, IndexTupleSize(itup)); + } + + /* Free page and associated memory for this iteration */ + MemoryContextReset(state->targetcontext); + } + while (current != P_NONE); + + if (state->lowkey) + { + Assert(state->readonly); + pfree(state->lowkey); + state->lowkey = NULL; + } + + /* Don't change context for caller */ + MemoryContextSwitchTo(oldcontext); + + return nextleveldown; +} + +/* + * Like P_LEFTMOST(start_opaque), but accept an arbitrarily-long chain of + * half-dead, sibling-linked pages to the left. If a half-dead page appears + * under state->readonly, the database exited recovery between the first-stage + * and second-stage WAL records of a deletion. + */ +static bool +bt_leftmost_ignoring_half_dead(BtreeCheckState *state, + BlockNumber start, + BTPageOpaque start_opaque) +{ + BlockNumber reached = start_opaque->btpo_prev, + reached_from = start; + bool all_half_dead = true; + + /* + * To handle the !readonly case, we'd need to accept BTP_DELETED pages and + * potentially observe nbtree/README "Page deletion and backwards scans". + */ + Assert(state->readonly); + + while (reached != P_NONE && all_half_dead) + { + Page page = palloc_btree_page(state, reached); + BTPageOpaque reached_opaque = BTPageGetOpaque(page); + + CHECK_FOR_INTERRUPTS(); + + /* + * Try to detect btpo_prev circular links. _bt_unlink_halfdead_page() + * writes that side-links will continue to point to the siblings. + * Check btpo_next for that property. + */ + all_half_dead = P_ISHALFDEAD(reached_opaque) && + reached != start && + reached != reached_from && + reached_opaque->btpo_next == reached_from; + if (all_half_dead) + { + XLogRecPtr pagelsn = PageGetLSN(page); + + /* pagelsn should point to an XLOG_BTREE_MARK_PAGE_HALFDEAD */ + ereport(DEBUG1, + (errcode(ERRCODE_NO_DATA), + errmsg_internal("harmless interrupted page deletion detected in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Block=%u right block=%u page lsn=%X/%X.", + reached, reached_from, + LSN_FORMAT_ARGS(pagelsn)))); + + reached_from = reached; + reached = reached_opaque->btpo_prev; + } + + pfree(page); + } + + return all_half_dead; +} + +/* + * Raise an error when target page's left link does not point back to the + * previous target page, called leftcurrent here. The leftcurrent page's + * right link was followed to get to the current target page, and we expect + * mutual agreement among leftcurrent and the current target page. Make sure + * that this condition has definitely been violated in the !readonly case, + * where concurrent page splits are something that we need to deal with. + * + * Cross-page inconsistencies involving pages that don't agree about being + * siblings are known to be a particularly good indicator of corruption + * involving partial writes/lost updates. The bt_right_page_check_scankey + * check also provides a way of detecting cross-page inconsistencies for + * !readonly callers, but it can only detect sibling pages that have an + * out-of-order keyspace, which can't catch many of the problems that we + * expect to catch here. + * + * The classic example of the kind of inconsistency that we can only catch + * with this check (when in !readonly mode) involves three sibling pages that + * were affected by a faulty page split at some point in the past. The + * effects of the split are reflected in the original page and its new right + * sibling page, with a lack of any accompanying changes for the _original_ + * right sibling page. The original right sibling page's left link fails to + * point to the new right sibling page (its left link still points to the + * original page), even though the first phase of a page split is supposed to + * work as a single atomic action. This subtle inconsistency will probably + * only break backwards scans in practice. + * + * Note that this is the only place where amcheck will "couple" buffer locks + * (and only for !readonly callers). In general we prefer to avoid more + * thorough cross-page checks in !readonly mode, but it seems worth the + * complexity here. Also, the performance overhead of performing lock + * coupling here is negligible in practice. Control only reaches here with a + * non-corrupt index when there is a concurrent page split at the instant + * caller crossed over to target page from leftcurrent page. + */ +static void +bt_recheck_sibling_links(BtreeCheckState *state, + BlockNumber btpo_prev_from_target, + BlockNumber leftcurrent) +{ + /* passing metapage to BTPageGetOpaque() would give irrelevant findings */ + Assert(leftcurrent != P_NONE); + + if (!state->readonly) + { + Buffer lbuf; + Buffer newtargetbuf; + Page page; + BTPageOpaque opaque; + BlockNumber newtargetblock; + + /* Couple locks in the usual order for nbtree: Left to right */ + lbuf = ReadBufferExtended(state->rel, MAIN_FORKNUM, leftcurrent, + RBM_NORMAL, state->checkstrategy); + LockBuffer(lbuf, BT_READ); + _bt_checkpage(state->rel, lbuf); + page = BufferGetPage(lbuf); + opaque = BTPageGetOpaque(page); + if (P_ISDELETED(opaque)) + { + /* + * Cannot reason about concurrently deleted page -- the left link + * in the page to the right is expected to point to some other + * page to the left (not leftcurrent page). + * + * Note that we deliberately don't give up with a half-dead page. + */ + UnlockReleaseBuffer(lbuf); + return; + } + + newtargetblock = opaque->btpo_next; + /* Avoid self-deadlock when newtargetblock == leftcurrent */ + if (newtargetblock != leftcurrent) + { + newtargetbuf = ReadBufferExtended(state->rel, MAIN_FORKNUM, + newtargetblock, RBM_NORMAL, + state->checkstrategy); + LockBuffer(newtargetbuf, BT_READ); + _bt_checkpage(state->rel, newtargetbuf); + page = BufferGetPage(newtargetbuf); + opaque = BTPageGetOpaque(page); + /* btpo_prev_from_target may have changed; update it */ + btpo_prev_from_target = opaque->btpo_prev; + } + else + { + /* + * leftcurrent right sibling points back to leftcurrent block. + * Index is corrupt. Easiest way to handle this is to pretend + * that we actually read from a distinct page that has an invalid + * block number in its btpo_prev. + */ + newtargetbuf = InvalidBuffer; + btpo_prev_from_target = InvalidBlockNumber; + } + + /* + * No need to check P_ISDELETED here, since new target block cannot be + * marked deleted as long as we hold a lock on lbuf + */ + if (BufferIsValid(newtargetbuf)) + UnlockReleaseBuffer(newtargetbuf); + UnlockReleaseBuffer(lbuf); + + if (btpo_prev_from_target == leftcurrent) + { + /* Report split in left sibling, not target (or new target) */ + ereport(DEBUG1, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg_internal("harmless concurrent page split detected in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Block=%u new right sibling=%u original right sibling=%u.", + leftcurrent, newtargetblock, + state->targetblock))); + return; + } + + /* + * Index is corrupt. Make sure that we report correct target page. + * + * This could have changed in cases where there was a concurrent page + * split, as well as index corruption (at least in theory). Note that + * btpo_prev_from_target was already updated above. + */ + state->targetblock = newtargetblock; + } + + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("left link/right link pair in index \"%s\" not in agreement", + RelationGetRelationName(state->rel)), + errdetail_internal("Block=%u left block=%u left link from block=%u.", + state->targetblock, leftcurrent, + btpo_prev_from_target))); +} + +/* + * Function performs the following checks on target page, or pages ancillary to + * target page: + * + * - That every "real" data item is less than or equal to the high key, which + * is an upper bound on the items on the page. Data items should be + * strictly less than the high key when the page is an internal page. + * + * - That within the page, every data item is strictly less than the item + * immediately to its right, if any (i.e., that the items are in order + * within the page, so that the binary searches performed by index scans are + * sane). + * + * - That the last data item stored on the page is strictly less than the + * first data item on the page to the right (when such a first item is + * available). + * + * - Various checks on the structure of tuples themselves. For example, check + * that non-pivot tuples have no truncated attributes. + * + * Furthermore, when state passed shows ShareLock held, function also checks: + * + * - That all child pages respect strict lower bound from parent's pivot + * tuple. + * + * - That downlink to block was encountered in parent where that's expected. + * + * - That high keys of child pages matches corresponding pivot keys in parent. + * + * This is also where heapallindexed callers use their Bloom filter to + * fingerprint IndexTuples for later table_index_build_scan() verification. + * + * Note: Memory allocated in this routine is expected to be released by caller + * resetting state->targetcontext. + */ +static void +bt_target_page_check(BtreeCheckState *state) +{ + OffsetNumber offset; + OffsetNumber max; + BTPageOpaque topaque; + + topaque = BTPageGetOpaque(state->target); + max = PageGetMaxOffsetNumber(state->target); + + elog(DEBUG2, "verifying %u items on %s block %u", max, + P_ISLEAF(topaque) ? "leaf" : "internal", state->targetblock); + + /* + * Check the number of attributes in high key. Note, rightmost page + * doesn't contain a high key, so nothing to check + */ + if (!P_RIGHTMOST(topaque)) + { + ItemId itemid; + IndexTuple itup; + + /* Verify line pointer before checking tuple */ + itemid = PageGetItemIdCareful(state, state->targetblock, + state->target, P_HIKEY); + if (!_bt_check_natts(state->rel, state->heapkeyspace, state->target, + P_HIKEY)) + { + itup = (IndexTuple) PageGetItem(state->target, itemid); + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("wrong number of high key index tuple attributes in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Index block=%u natts=%u block type=%s page lsn=%X/%X.", + state->targetblock, + BTreeTupleGetNAtts(itup, state->rel), + P_ISLEAF(topaque) ? "heap" : "index", + LSN_FORMAT_ARGS(state->targetlsn)))); + } + } + + /* + * Loop over page items, starting from first non-highkey item, not high + * key (if any). Most tests are not performed for the "negative infinity" + * real item (if any). + */ + for (offset = P_FIRSTDATAKEY(topaque); + offset <= max; + offset = OffsetNumberNext(offset)) + { + ItemId itemid; + IndexTuple itup; + size_t tupsize; + BTScanInsert skey; + bool lowersizelimit; + ItemPointer scantid; + + CHECK_FOR_INTERRUPTS(); + + itemid = PageGetItemIdCareful(state, state->targetblock, + state->target, offset); + itup = (IndexTuple) PageGetItem(state->target, itemid); + tupsize = IndexTupleSize(itup); + + /* + * lp_len should match the IndexTuple reported length exactly, since + * lp_len is completely redundant in indexes, and both sources of + * tuple length are MAXALIGN()'d. nbtree does not use lp_len all that + * frequently, and is surprisingly tolerant of corrupt lp_len fields. + */ + if (tupsize != ItemIdGetLength(itemid)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index tuple size does not equal lp_len in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Index tid=(%u,%u) tuple size=%zu lp_len=%u page lsn=%X/%X.", + state->targetblock, offset, + tupsize, ItemIdGetLength(itemid), + LSN_FORMAT_ARGS(state->targetlsn)), + errhint("This could be a torn page problem."))); + + /* Check the number of index tuple attributes */ + if (!_bt_check_natts(state->rel, state->heapkeyspace, state->target, + offset)) + { + ItemPointer tid; + char *itid, + *htid; + + itid = psprintf("(%u,%u)", state->targetblock, offset); + tid = BTreeTupleGetPointsToTID(itup); + htid = psprintf("(%u,%u)", + ItemPointerGetBlockNumberNoCheck(tid), + ItemPointerGetOffsetNumberNoCheck(tid)); + + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("wrong number of index tuple attributes in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Index tid=%s natts=%u points to %s tid=%s page lsn=%X/%X.", + itid, + BTreeTupleGetNAtts(itup, state->rel), + P_ISLEAF(topaque) ? "heap" : "index", + htid, + LSN_FORMAT_ARGS(state->targetlsn)))); + } + + /* + * Don't try to generate scankey using "negative infinity" item on + * internal pages. They are always truncated to zero attributes. + */ + if (offset_is_negative_infinity(topaque, offset)) + { + /* + * We don't call bt_child_check() for "negative infinity" items. + * But if we're performing downlink connectivity check, we do it + * for every item including "negative infinity" one. + */ + if (!P_ISLEAF(topaque) && state->readonly) + { + bt_child_highkey_check(state, + offset, + NULL, + topaque->btpo_level); + } + continue; + } + + /* + * Readonly callers may optionally verify that non-pivot tuples can + * each be found by an independent search that starts from the root. + * Note that we deliberately don't do individual searches for each + * TID, since the posting list itself is validated by other checks. + */ + if (state->rootdescend && P_ISLEAF(topaque) && + !bt_rootdescend(state, itup)) + { + ItemPointer tid = BTreeTupleGetPointsToTID(itup); + char *itid, + *htid; + + itid = psprintf("(%u,%u)", state->targetblock, offset); + htid = psprintf("(%u,%u)", ItemPointerGetBlockNumber(tid), + ItemPointerGetOffsetNumber(tid)); + + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("could not find tuple using search from root page in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Index tid=%s points to heap tid=%s page lsn=%X/%X.", + itid, htid, + LSN_FORMAT_ARGS(state->targetlsn)))); + } + + /* + * If tuple is a posting list tuple, make sure posting list TIDs are + * in order + */ + if (BTreeTupleIsPosting(itup)) + { + ItemPointerData last; + ItemPointer current; + + ItemPointerCopy(BTreeTupleGetHeapTID(itup), &last); + + for (int i = 1; i < BTreeTupleGetNPosting(itup); i++) + { + + current = BTreeTupleGetPostingN(itup, i); + + if (ItemPointerCompare(current, &last) <= 0) + { + char *itid = psprintf("(%u,%u)", state->targetblock, offset); + + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg_internal("posting list contains misplaced TID in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Index tid=%s posting list offset=%d page lsn=%X/%X.", + itid, i, + LSN_FORMAT_ARGS(state->targetlsn)))); + } + + ItemPointerCopy(current, &last); + } + } + + /* Build insertion scankey for current page offset */ + skey = bt_mkscankey_pivotsearch(state->rel, itup); + + /* + * Make sure tuple size does not exceed the relevant BTREE_VERSION + * specific limit. + * + * BTREE_VERSION 4 (which introduced heapkeyspace rules) requisitioned + * a small amount of space from BTMaxItemSize() in order to ensure + * that suffix truncation always has enough space to add an explicit + * heap TID back to a tuple -- we pessimistically assume that every + * newly inserted tuple will eventually need to have a heap TID + * appended during a future leaf page split, when the tuple becomes + * the basis of the new high key (pivot tuple) for the leaf page. + * + * Since the reclaimed space is reserved for that purpose, we must not + * enforce the slightly lower limit when the extra space has been used + * as intended. In other words, there is only a cross-version + * difference in the limit on tuple size within leaf pages. + * + * Still, we're particular about the details within BTREE_VERSION 4 + * internal pages. Pivot tuples may only use the extra space for its + * designated purpose. Enforce the lower limit for pivot tuples when + * an explicit heap TID isn't actually present. (In all other cases + * suffix truncation is guaranteed to generate a pivot tuple that's no + * larger than the firstright tuple provided to it by its caller.) + */ + lowersizelimit = skey->heapkeyspace && + (P_ISLEAF(topaque) || BTreeTupleGetHeapTID(itup) == NULL); + if (tupsize > (lowersizelimit ? BTMaxItemSize(state->target) : + BTMaxItemSizeNoHeapTid(state->target))) + { + ItemPointer tid = BTreeTupleGetPointsToTID(itup); + char *itid, + *htid; + + itid = psprintf("(%u,%u)", state->targetblock, offset); + htid = psprintf("(%u,%u)", + ItemPointerGetBlockNumberNoCheck(tid), + ItemPointerGetOffsetNumberNoCheck(tid)); + + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index row size %zu exceeds maximum for index \"%s\"", + tupsize, RelationGetRelationName(state->rel)), + errdetail_internal("Index tid=%s points to %s tid=%s page lsn=%X/%X.", + itid, + P_ISLEAF(topaque) ? "heap" : "index", + htid, + LSN_FORMAT_ARGS(state->targetlsn)))); + } + + /* Fingerprint leaf page tuples (those that point to the heap) */ + if (state->heapallindexed && P_ISLEAF(topaque) && !ItemIdIsDead(itemid)) + { + IndexTuple norm; + + if (BTreeTupleIsPosting(itup)) + { + /* Fingerprint all elements as distinct "plain" tuples */ + for (int i = 0; i < BTreeTupleGetNPosting(itup); i++) + { + IndexTuple logtuple; + + logtuple = bt_posting_plain_tuple(itup, i); + norm = bt_normalize_tuple(state, logtuple); + bloom_add_element(state->filter, (unsigned char *) norm, + IndexTupleSize(norm)); + /* Be tidy */ + if (norm != logtuple) + pfree(norm); + pfree(logtuple); + } + } + else + { + norm = bt_normalize_tuple(state, itup); + bloom_add_element(state->filter, (unsigned char *) norm, + IndexTupleSize(norm)); + /* Be tidy */ + if (norm != itup) + pfree(norm); + } + } + + /* + * * High key check * + * + * If there is a high key (if this is not the rightmost page on its + * entire level), check that high key actually is upper bound on all + * page items. If this is a posting list tuple, we'll need to set + * scantid to be highest TID in posting list. + * + * We prefer to check all items against high key rather than checking + * just the last and trusting that the operator class obeys the + * transitive law (which implies that all previous items also + * respected the high key invariant if they pass the item order + * check). + * + * Ideally, we'd compare every item in the index against every other + * item in the index, and not trust opclass obedience of the + * transitive law to bridge the gap between children and their + * grandparents (as well as great-grandparents, and so on). We don't + * go to those lengths because that would be prohibitively expensive, + * and probably not markedly more effective in practice. + * + * On the leaf level, we check that the key is <= the highkey. + * However, on non-leaf levels we check that the key is < the highkey, + * because the high key is "just another separator" rather than a copy + * of some existing key item; we expect it to be unique among all keys + * on the same level. (Suffix truncation will sometimes produce a + * leaf highkey that is an untruncated copy of the lastleft item, but + * never any other item, which necessitates weakening the leaf level + * check to <=.) + * + * Full explanation for why a highkey is never truly a copy of another + * item from the same level on internal levels: + * + * While the new left page's high key is copied from the first offset + * on the right page during an internal page split, that's not the + * full story. In effect, internal pages are split in the middle of + * the firstright tuple, not between the would-be lastleft and + * firstright tuples: the firstright key ends up on the left side as + * left's new highkey, and the firstright downlink ends up on the + * right side as right's new "negative infinity" item. The negative + * infinity tuple is truncated to zero attributes, so we're only left + * with the downlink. In other words, the copying is just an + * implementation detail of splitting in the middle of a (pivot) + * tuple. (See also: "Notes About Data Representation" in the nbtree + * README.) + */ + scantid = skey->scantid; + if (state->heapkeyspace && BTreeTupleIsPosting(itup)) + skey->scantid = BTreeTupleGetMaxHeapTID(itup); + + if (!P_RIGHTMOST(topaque) && + !(P_ISLEAF(topaque) ? invariant_leq_offset(state, skey, P_HIKEY) : + invariant_l_offset(state, skey, P_HIKEY))) + { + ItemPointer tid = BTreeTupleGetPointsToTID(itup); + char *itid, + *htid; + + itid = psprintf("(%u,%u)", state->targetblock, offset); + htid = psprintf("(%u,%u)", + ItemPointerGetBlockNumberNoCheck(tid), + ItemPointerGetOffsetNumberNoCheck(tid)); + + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("high key invariant violated for index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Index tid=%s points to %s tid=%s page lsn=%X/%X.", + itid, + P_ISLEAF(topaque) ? "heap" : "index", + htid, + LSN_FORMAT_ARGS(state->targetlsn)))); + } + /* Reset, in case scantid was set to (itup) posting tuple's max TID */ + skey->scantid = scantid; + + /* + * * Item order check * + * + * Check that items are stored on page in logical order, by checking + * current item is strictly less than next item (if any). + */ + if (OffsetNumberNext(offset) <= max && + !invariant_l_offset(state, skey, OffsetNumberNext(offset))) + { + ItemPointer tid; + char *itid, + *htid, + *nitid, + *nhtid; + + itid = psprintf("(%u,%u)", state->targetblock, offset); + tid = BTreeTupleGetPointsToTID(itup); + htid = psprintf("(%u,%u)", + ItemPointerGetBlockNumberNoCheck(tid), + ItemPointerGetOffsetNumberNoCheck(tid)); + nitid = psprintf("(%u,%u)", state->targetblock, + OffsetNumberNext(offset)); + + /* Reuse itup to get pointed-to heap location of second item */ + itemid = PageGetItemIdCareful(state, state->targetblock, + state->target, + OffsetNumberNext(offset)); + itup = (IndexTuple) PageGetItem(state->target, itemid); + tid = BTreeTupleGetPointsToTID(itup); + nhtid = psprintf("(%u,%u)", + ItemPointerGetBlockNumberNoCheck(tid), + ItemPointerGetOffsetNumberNoCheck(tid)); + + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("item order invariant violated for index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Lower index tid=%s (points to %s tid=%s) " + "higher index tid=%s (points to %s tid=%s) " + "page lsn=%X/%X.", + itid, + P_ISLEAF(topaque) ? "heap" : "index", + htid, + nitid, + P_ISLEAF(topaque) ? "heap" : "index", + nhtid, + LSN_FORMAT_ARGS(state->targetlsn)))); + } + + /* + * * Last item check * + * + * Check last item against next/right page's first data item's when + * last item on page is reached. This additional check will detect + * transposed pages iff the supposed right sibling page happens to + * belong before target in the key space. (Otherwise, a subsequent + * heap verification will probably detect the problem.) + * + * This check is similar to the item order check that will have + * already been performed for every other "real" item on target page + * when last item is checked. The difference is that the next item + * (the item that is compared to target's last item) needs to come + * from the next/sibling page. There may not be such an item + * available from sibling for various reasons, though (e.g., target is + * the rightmost page on level). + */ + else if (offset == max) + { + BTScanInsert rightkey; + + /* Get item in next/right page */ + rightkey = bt_right_page_check_scankey(state); + + if (rightkey && + !invariant_g_offset(state, rightkey, max)) + { + /* + * As explained at length in bt_right_page_check_scankey(), + * there is a known !readonly race that could account for + * apparent violation of invariant, which we must check for + * before actually proceeding with raising error. Our canary + * condition is that target page was deleted. + */ + if (!state->readonly) + { + /* Get fresh copy of target page */ + state->target = palloc_btree_page(state, state->targetblock); + /* Note that we deliberately do not update target LSN */ + topaque = BTPageGetOpaque(state->target); + + /* + * All !readonly checks now performed; just return + */ + if (P_IGNORE(topaque)) + return; + } + + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("cross page item order invariant violated for index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Last item on page tid=(%u,%u) page lsn=%X/%X.", + state->targetblock, offset, + LSN_FORMAT_ARGS(state->targetlsn)))); + } + } + + /* + * * Downlink check * + * + * Additional check of child items iff this is an internal page and + * caller holds a ShareLock. This happens for every downlink (item) + * in target excluding the negative-infinity downlink (again, this is + * because it has no useful value to compare). + */ + if (!P_ISLEAF(topaque) && state->readonly) + bt_child_check(state, skey, offset); + } + + /* + * Special case bt_child_highkey_check() call + * + * We don't pass a real downlink, but we've to finish the level + * processing. If condition is satisfied, we've already processed all the + * downlinks from the target level. But there still might be pages to the + * right of the child page pointer to by our rightmost downlink. And they + * might have missing downlinks. This final call checks for them. + */ + if (!P_ISLEAF(topaque) && P_RIGHTMOST(topaque) && state->readonly) + { + bt_child_highkey_check(state, InvalidOffsetNumber, + NULL, topaque->btpo_level); + } +} + +/* + * Return a scankey for an item on page to right of current target (or the + * first non-ignorable page), sufficient to check ordering invariant on last + * item in current target page. Returned scankey relies on local memory + * allocated for the child page, which caller cannot pfree(). Caller's memory + * context should be reset between calls here. + * + * This is the first data item, and so all adjacent items are checked against + * their immediate sibling item (which may be on a sibling page, or even a + * "cousin" page at parent boundaries where target's rightlink points to page + * with different parent page). If no such valid item is available, return + * NULL instead. + * + * Note that !readonly callers must reverify that target page has not + * been concurrently deleted. + */ +static BTScanInsert +bt_right_page_check_scankey(BtreeCheckState *state) +{ + BTPageOpaque opaque; + ItemId rightitem; + IndexTuple firstitup; + BlockNumber targetnext; + Page rightpage; + OffsetNumber nline; + + /* Determine target's next block number */ + opaque = BTPageGetOpaque(state->target); + + /* If target is already rightmost, no right sibling; nothing to do here */ + if (P_RIGHTMOST(opaque)) + return NULL; + + /* + * General notes on concurrent page splits and page deletion: + * + * Routines like _bt_search() don't require *any* page split interlock + * when descending the tree, including something very light like a buffer + * pin. That's why it's okay that we don't either. This avoidance of any + * need to "couple" buffer locks is the raison d' etre of the Lehman & Yao + * algorithm, in fact. + * + * That leaves deletion. A deleted page won't actually be recycled by + * VACUUM early enough for us to fail to at least follow its right link + * (or left link, or downlink) and find its sibling, because recycling + * does not occur until no possible index scan could land on the page. + * Index scans can follow links with nothing more than their snapshot as + * an interlock and be sure of at least that much. (See page + * recycling/"visible to everyone" notes in nbtree README.) + * + * Furthermore, it's okay if we follow a rightlink and find a half-dead or + * dead (ignorable) page one or more times. There will either be a + * further right link to follow that leads to a live page before too long + * (before passing by parent's rightmost child), or we will find the end + * of the entire level instead (possible when parent page is itself the + * rightmost on its level). + */ + targetnext = opaque->btpo_next; + for (;;) + { + CHECK_FOR_INTERRUPTS(); + + rightpage = palloc_btree_page(state, targetnext); + opaque = BTPageGetOpaque(rightpage); + + if (!P_IGNORE(opaque) || P_RIGHTMOST(opaque)) + break; + + /* + * We landed on a deleted or half-dead sibling page. Step right until + * we locate a live sibling page. + */ + ereport(DEBUG2, + (errcode(ERRCODE_NO_DATA), + errmsg_internal("level %u sibling page in block %u of index \"%s\" was found deleted or half dead", + opaque->btpo_level, targetnext, RelationGetRelationName(state->rel)), + errdetail_internal("Deleted page found when building scankey from right sibling."))); + + targetnext = opaque->btpo_next; + + /* Be slightly more pro-active in freeing this memory, just in case */ + pfree(rightpage); + } + + /* + * No ShareLock held case -- why it's safe to proceed. + * + * Problem: + * + * We must avoid false positive reports of corruption when caller treats + * item returned here as an upper bound on target's last item. In + * general, false positives are disallowed. Avoiding them here when + * caller is !readonly is subtle. + * + * A concurrent page deletion by VACUUM of the target page can result in + * the insertion of items on to this right sibling page that would + * previously have been inserted on our target page. There might have + * been insertions that followed the target's downlink after it was made + * to point to right sibling instead of target by page deletion's first + * phase. The inserters insert items that would belong on target page. + * This race is very tight, but it's possible. This is our only problem. + * + * Non-problems: + * + * We are not hindered by a concurrent page split of the target; we'll + * never land on the second half of the page anyway. A concurrent split + * of the right page will also not matter, because the first data item + * remains the same within the left half, which we'll reliably land on. If + * we had to skip over ignorable/deleted pages, it cannot matter because + * their key space has already been atomically merged with the first + * non-ignorable page we eventually find (doesn't matter whether the page + * we eventually find is a true sibling or a cousin of target, which we go + * into below). + * + * Solution: + * + * Caller knows that it should reverify that target is not ignorable + * (half-dead or deleted) when cross-page sibling item comparison appears + * to indicate corruption (invariant fails). This detects the single race + * condition that exists for caller. This is correct because the + * continued existence of target block as non-ignorable (not half-dead or + * deleted) implies that target page was not merged into from the right by + * deletion; the key space at or after target never moved left. Target's + * parent either has the same downlink to target as before, or a < + * downlink due to deletion at the left of target. Target either has the + * same highkey as before, or a highkey < before when there is a page + * split. (The rightmost concurrently-split-from-target-page page will + * still have the same highkey as target was originally found to have, + * which for our purposes is equivalent to target's highkey itself never + * changing, since we reliably skip over + * concurrently-split-from-target-page pages.) + * + * In simpler terms, we allow that the key space of the target may expand + * left (the key space can move left on the left side of target only), but + * the target key space cannot expand right and get ahead of us without + * our detecting it. The key space of the target cannot shrink, unless it + * shrinks to zero due to the deletion of the original page, our canary + * condition. (To be very precise, we're a bit stricter than that because + * it might just have been that the target page split and only the + * original target page was deleted. We can be more strict, just not more + * lax.) + * + * Top level tree walk caller moves on to next page (makes it the new + * target) following recovery from this race. (cf. The rationale for + * child/downlink verification needing a ShareLock within + * bt_child_check(), where page deletion is also the main source of + * trouble.) + * + * Note that it doesn't matter if right sibling page here is actually a + * cousin page, because in order for the key space to be readjusted in a + * way that causes us issues in next level up (guiding problematic + * concurrent insertions to the cousin from the grandparent rather than to + * the sibling from the parent), there'd have to be page deletion of + * target's parent page (affecting target's parent's downlink in target's + * grandparent page). Internal page deletion only occurs when there are + * no child pages (they were all fully deleted), and caller is checking + * that the target's parent has at least one non-deleted (so + * non-ignorable) child: the target page. (Note that the first phase of + * deletion atomically marks the page to be deleted half-dead/ignorable at + * the same time downlink in its parent is removed, so caller will + * definitely not fail to detect that this happened.) + * + * This trick is inspired by the method backward scans use for dealing + * with concurrent page splits; concurrent page deletion is a problem that + * similarly receives special consideration sometimes (it's possible that + * the backwards scan will re-read its "original" block after failing to + * find a right-link to it, having already moved in the opposite direction + * (right/"forwards") a few times to try to locate one). Just like us, + * that happens only to determine if there was a concurrent page deletion + * of a reference page, and just like us if there was a page deletion of + * that reference page it means we can move on from caring about the + * reference page. See the nbtree README for a full description of how + * that works. + */ + nline = PageGetMaxOffsetNumber(rightpage); + + /* + * Get first data item, if any + */ + if (P_ISLEAF(opaque) && nline >= P_FIRSTDATAKEY(opaque)) + { + /* Return first data item (if any) */ + rightitem = PageGetItemIdCareful(state, targetnext, rightpage, + P_FIRSTDATAKEY(opaque)); + } + else if (!P_ISLEAF(opaque) && + nline >= OffsetNumberNext(P_FIRSTDATAKEY(opaque))) + { + /* + * Return first item after the internal page's "negative infinity" + * item + */ + rightitem = PageGetItemIdCareful(state, targetnext, rightpage, + OffsetNumberNext(P_FIRSTDATAKEY(opaque))); + } + else + { + /* + * No first item. Page is probably empty leaf page, but it's also + * possible that it's an internal page with only a negative infinity + * item. + */ + ereport(DEBUG2, + (errcode(ERRCODE_NO_DATA), + errmsg_internal("%s block %u of index \"%s\" has no first data item", + P_ISLEAF(opaque) ? "leaf" : "internal", targetnext, + RelationGetRelationName(state->rel)))); + return NULL; + } + + /* + * Return first real item scankey. Note that this relies on right page + * memory remaining allocated. + */ + firstitup = (IndexTuple) PageGetItem(rightpage, rightitem); + return bt_mkscankey_pivotsearch(state->rel, firstitup); +} + +/* + * Check if two tuples are binary identical except the block number. So, + * this function is capable to compare pivot keys on different levels. + */ +static bool +bt_pivot_tuple_identical(bool heapkeyspace, IndexTuple itup1, IndexTuple itup2) +{ + if (IndexTupleSize(itup1) != IndexTupleSize(itup2)) + return false; + + if (heapkeyspace) + { + /* + * Offset number will contain important information in heapkeyspace + * indexes: the number of attributes left in the pivot tuple following + * suffix truncation. Don't skip over it (compare it too). + */ + if (memcmp(&itup1->t_tid.ip_posid, &itup2->t_tid.ip_posid, + IndexTupleSize(itup1) - + offsetof(ItemPointerData, ip_posid)) != 0) + return false; + } + else + { + /* + * Cannot rely on offset number field having consistent value across + * levels on pg_upgrade'd !heapkeyspace indexes. Compare contents of + * tuple starting from just after item pointer (i.e. after block + * number and offset number). + */ + if (memcmp(&itup1->t_info, &itup2->t_info, + IndexTupleSize(itup1) - + offsetof(IndexTupleData, t_info)) != 0) + return false; + } + + return true; +} + +/*--- + * Check high keys on the child level. Traverse rightlinks from previous + * downlink to the current one. Check that there are no intermediate pages + * with missing downlinks. + * + * If 'loaded_child' is given, it's assumed to be the page pointed to by the + * downlink referenced by 'downlinkoffnum' of the target page. + * + * Basically this function is called for each target downlink and checks two + * invariants: + * + * 1) You can reach the next child from previous one via rightlinks; + * 2) Each child high key have matching pivot key on target level. + * + * Consider the sample tree picture. + * + * 1 + * / \ + * 2 <-> 3 + * / \ / \ + * 4 <> 5 <> 6 <> 7 <> 8 + * + * This function will be called for blocks 4, 5, 6 and 8. Consider what is + * happening for each function call. + * + * - The function call for block 4 initializes data structure and matches high + * key of block 4 to downlink's pivot key of block 2. + * - The high key of block 5 is matched to the high key of block 2. + * - The block 6 has an incomplete split flag set, so its high key isn't + * matched to anything. + * - The function call for block 8 checks that block 8 can be found while + * following rightlinks from block 6. The high key of block 7 will be + * matched to downlink's pivot key in block 3. + * + * There is also final call of this function, which checks that there is no + * missing downlinks for children to the right of the child referenced by + * rightmost downlink in target level. + */ +static void +bt_child_highkey_check(BtreeCheckState *state, + OffsetNumber target_downlinkoffnum, + Page loaded_child, + uint32 target_level) +{ + BlockNumber blkno = state->prevrightlink; + Page page; + BTPageOpaque opaque; + bool rightsplit = state->previncompletesplit; + bool first = true; + ItemId itemid; + IndexTuple itup; + BlockNumber downlink; + + if (OffsetNumberIsValid(target_downlinkoffnum)) + { + itemid = PageGetItemIdCareful(state, state->targetblock, + state->target, target_downlinkoffnum); + itup = (IndexTuple) PageGetItem(state->target, itemid); + downlink = BTreeTupleGetDownLink(itup); + } + else + { + downlink = P_NONE; + } + + /* + * If no previous rightlink is memorized for current level just below + * target page's level, we are about to start from the leftmost page. We + * can't follow rightlinks from previous page, because there is no + * previous page. But we still can match high key. + * + * So we initialize variables for the loop above like there is previous + * page referencing current child. Also we imply previous page to not + * have incomplete split flag, that would make us require downlink for + * current child. That's correct, because leftmost page on the level + * should always have parent downlink. + */ + if (!BlockNumberIsValid(blkno)) + { + blkno = downlink; + rightsplit = false; + } + + /* Move to the right on the child level */ + while (true) + { + /* + * Did we traverse the whole tree level and this is check for pages to + * the right of rightmost downlink? + */ + if (blkno == P_NONE && downlink == P_NONE) + { + state->prevrightlink = InvalidBlockNumber; + state->previncompletesplit = false; + return; + } + + /* Did we traverse the whole tree level and don't find next downlink? */ + if (blkno == P_NONE) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("can't traverse from downlink %u to downlink %u of index \"%s\"", + state->prevrightlink, downlink, + RelationGetRelationName(state->rel)))); + + /* Load page contents */ + if (blkno == downlink && loaded_child) + page = loaded_child; + else + page = palloc_btree_page(state, blkno); + + opaque = BTPageGetOpaque(page); + + /* The first page we visit at the level should be leftmost */ + if (first && !BlockNumberIsValid(state->prevrightlink) && + !bt_leftmost_ignoring_half_dead(state, blkno, opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("the first child of leftmost target page is not leftmost of its level in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.", + state->targetblock, blkno, + LSN_FORMAT_ARGS(state->targetlsn)))); + + /* Do level sanity check */ + if ((!P_ISDELETED(opaque) || P_HAS_FULLXID(opaque)) && + opaque->btpo_level != target_level - 1) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("block found while following rightlinks from child of index \"%s\" has invalid level", + RelationGetRelationName(state->rel)), + errdetail_internal("Block pointed to=%u expected level=%u level in pointed to block=%u.", + blkno, target_level - 1, opaque->btpo_level))); + + /* Try to detect circular links */ + if ((!first && blkno == state->prevrightlink) || blkno == opaque->btpo_prev) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("circular link chain found in block %u of index \"%s\"", + blkno, RelationGetRelationName(state->rel)))); + + if (blkno != downlink && !P_IGNORE(opaque)) + { + /* blkno probably has missing parent downlink */ + bt_downlink_missing_check(state, rightsplit, blkno, page); + } + + rightsplit = P_INCOMPLETE_SPLIT(opaque); + + /* + * If we visit page with high key, check that it is equal to the + * target key next to corresponding downlink. + */ + if (!rightsplit && !P_RIGHTMOST(opaque)) + { + BTPageOpaque topaque; + IndexTuple highkey; + OffsetNumber pivotkey_offset; + + /* Get high key */ + itemid = PageGetItemIdCareful(state, blkno, page, P_HIKEY); + highkey = (IndexTuple) PageGetItem(page, itemid); + + /* + * There might be two situations when we examine high key. If + * current child page is referenced by given target downlink, we + * should look to the next offset number for matching key from + * target page. + * + * Alternatively, we're following rightlinks somewhere in the + * middle between page referenced by previous target's downlink + * and the page referenced by current target's downlink. If + * current child page hasn't incomplete split flag set, then its + * high key should match to the target's key of current offset + * number. This happens when a previous call here (to + * bt_child_highkey_check()) found an incomplete split, and we + * reach a right sibling page without a downlink -- the right + * sibling page's high key still needs to be matched to a + * separator key on the parent/target level. + * + * Don't apply OffsetNumberNext() to target_downlinkoffnum when we + * already had to step right on the child level. Our traversal of + * the child level must try to move in perfect lockstep behind (to + * the left of) the target/parent level traversal. + */ + if (blkno == downlink) + pivotkey_offset = OffsetNumberNext(target_downlinkoffnum); + else + pivotkey_offset = target_downlinkoffnum; + + topaque = BTPageGetOpaque(state->target); + + if (!offset_is_negative_infinity(topaque, pivotkey_offset)) + { + /* + * If we're looking for the next pivot tuple in target page, + * but there is no more pivot tuples, then we should match to + * high key instead. + */ + if (pivotkey_offset > PageGetMaxOffsetNumber(state->target)) + { + if (P_RIGHTMOST(topaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("child high key is greater than rightmost pivot key on target level in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.", + state->targetblock, blkno, + LSN_FORMAT_ARGS(state->targetlsn)))); + pivotkey_offset = P_HIKEY; + } + itemid = PageGetItemIdCareful(state, state->targetblock, + state->target, pivotkey_offset); + itup = (IndexTuple) PageGetItem(state->target, itemid); + } + else + { + /* + * We cannot try to match child's high key to a negative + * infinity key in target, since there is nothing to compare. + * However, it's still possible to match child's high key + * outside of target page. The reason why we're are is that + * bt_child_highkey_check() was previously called for the + * cousin page of 'loaded_child', which is incomplete split. + * So, now we traverse to the right of that cousin page and + * current child level page under consideration still belongs + * to the subtree of target's left sibling. Thus, we need to + * match child's high key to it's left uncle page high key. + * Thankfully we saved it, it's called a "low key" of target + * page. + */ + if (!state->lowkey) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("can't find left sibling high key in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.", + state->targetblock, blkno, + LSN_FORMAT_ARGS(state->targetlsn)))); + itup = state->lowkey; + } + + if (!bt_pivot_tuple_identical(state->heapkeyspace, highkey, itup)) + { + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("mismatch between parent key and child high key in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Target block=%u child block=%u target page lsn=%X/%X.", + state->targetblock, blkno, + LSN_FORMAT_ARGS(state->targetlsn)))); + } + } + + /* Exit if we already found next downlink */ + if (blkno == downlink) + { + state->prevrightlink = opaque->btpo_next; + state->previncompletesplit = rightsplit; + return; + } + + /* Traverse to the next page using rightlink */ + blkno = opaque->btpo_next; + + /* Free page contents if it's allocated by us */ + if (page != loaded_child) + pfree(page); + first = false; + } +} + +/* + * Checks one of target's downlink against its child page. + * + * Conceptually, the target page continues to be what is checked here. The + * target block is still blamed in the event of finding an invariant violation. + * The downlink insertion into the target is probably where any problem raised + * here arises, and there is no such thing as a parent link, so doing the + * verification this way around is much more practical. + * + * This function visits child page and it's sequentially called for each + * downlink of target page. Assuming this we also check downlink connectivity + * here in order to save child page visits. + */ +static void +bt_child_check(BtreeCheckState *state, BTScanInsert targetkey, + OffsetNumber downlinkoffnum) +{ + ItemId itemid; + IndexTuple itup; + BlockNumber childblock; + OffsetNumber offset; + OffsetNumber maxoffset; + Page child; + BTPageOpaque copaque; + BTPageOpaque topaque; + + itemid = PageGetItemIdCareful(state, state->targetblock, + state->target, downlinkoffnum); + itup = (IndexTuple) PageGetItem(state->target, itemid); + childblock = BTreeTupleGetDownLink(itup); + + /* + * Caller must have ShareLock on target relation, because of + * considerations around page deletion by VACUUM. + * + * NB: In general, page deletion deletes the right sibling's downlink, not + * the downlink of the page being deleted; the deleted page's downlink is + * reused for its sibling. The key space is thereby consolidated between + * the deleted page and its right sibling. (We cannot delete a parent + * page's rightmost child unless it is the last child page, and we intend + * to also delete the parent itself.) + * + * If this verification happened without a ShareLock, the following race + * condition could cause false positives: + * + * In general, concurrent page deletion might occur, including deletion of + * the left sibling of the child page that is examined here. If such a + * page deletion were to occur, closely followed by an insertion into the + * newly expanded key space of the child, a window for the false positive + * opens up: the stale parent/target downlink originally followed to get + * to the child legitimately ceases to be a lower bound on all items in + * the page, since the key space was concurrently expanded "left". + * (Insertion followed the "new" downlink for the child, not our now-stale + * downlink, which was concurrently physically removed in target/parent as + * part of deletion's first phase.) + * + * While we use various techniques elsewhere to perform cross-page + * verification for !readonly callers, a similar trick seems difficult + * here. The tricks used by bt_recheck_sibling_links and by + * bt_right_page_check_scankey both involve verification of a same-level, + * cross-sibling invariant. Cross-level invariants are far more squishy, + * though. The nbtree REDO routines do not actually couple buffer locks + * across levels during page splits, so making any cross-level check work + * reliably in !readonly mode may be impossible. + */ + Assert(state->readonly); + + /* + * Verify child page has the downlink key from target page (its parent) as + * a lower bound; downlink must be strictly less than all keys on the + * page. + * + * Check all items, rather than checking just the first and trusting that + * the operator class obeys the transitive law. + */ + topaque = BTPageGetOpaque(state->target); + child = palloc_btree_page(state, childblock); + copaque = BTPageGetOpaque(child); + maxoffset = PageGetMaxOffsetNumber(child); + + /* + * Since we've already loaded the child block, combine this check with + * check for downlink connectivity. + */ + bt_child_highkey_check(state, downlinkoffnum, + child, topaque->btpo_level); + + /* + * Since there cannot be a concurrent VACUUM operation in readonly mode, + * and since a page has no links within other pages (siblings and parent) + * once it is marked fully deleted, it should be impossible to land on a + * fully deleted page. + * + * It does not quite make sense to enforce that the page cannot even be + * half-dead, despite the fact the downlink is modified at the same stage + * that the child leaf page is marked half-dead. That's incorrect because + * there may occasionally be multiple downlinks from a chain of pages + * undergoing deletion, where multiple successive calls are made to + * _bt_unlink_halfdead_page() by VACUUM before it can finally safely mark + * the leaf page as fully dead. While _bt_mark_page_halfdead() usually + * removes the downlink to the leaf page that is marked half-dead, that's + * not guaranteed, so it's possible we'll land on a half-dead page with a + * downlink due to an interrupted multi-level page deletion. + * + * We go ahead with our checks if the child page is half-dead. It's safe + * to do so because we do not test the child's high key, so it does not + * matter that the original high key will have been replaced by a dummy + * truncated high key within _bt_mark_page_halfdead(). All other page + * items are left intact on a half-dead page, so there is still something + * to test. + */ + if (P_ISDELETED(copaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("downlink to deleted page found in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Parent block=%u child block=%u parent page lsn=%X/%X.", + state->targetblock, childblock, + LSN_FORMAT_ARGS(state->targetlsn)))); + + for (offset = P_FIRSTDATAKEY(copaque); + offset <= maxoffset; + offset = OffsetNumberNext(offset)) + { + /* + * Skip comparison of target page key against "negative infinity" + * item, if any. Checking it would indicate that it's not a strict + * lower bound, but that's only because of the hard-coding for + * negative infinity items within _bt_compare(). + * + * If nbtree didn't truncate negative infinity tuples during internal + * page splits then we'd expect child's negative infinity key to be + * equal to the scankey/downlink from target/parent (it would be a + * "low key" in this hypothetical scenario, and so it would still need + * to be treated as a special case here). + * + * Negative infinity items can be thought of as a strict lower bound + * that works transitively, with the last non-negative-infinity pivot + * followed during a descent from the root as its "true" strict lower + * bound. Only a small number of negative infinity items are truly + * negative infinity; those that are the first items of leftmost + * internal pages. In more general terms, a negative infinity item is + * only negative infinity with respect to the subtree that the page is + * at the root of. + * + * See also: bt_rootdescend(), which can even detect transitive + * inconsistencies on cousin leaf pages. + */ + if (offset_is_negative_infinity(copaque, offset)) + continue; + + if (!invariant_l_nontarget_offset(state, targetkey, childblock, child, + offset)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("down-link lower bound invariant violated for index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Parent block=%u child index tid=(%u,%u) parent page lsn=%X/%X.", + state->targetblock, childblock, offset, + LSN_FORMAT_ARGS(state->targetlsn)))); + } + + pfree(child); +} + +/* + * Checks if page is missing a downlink that it should have. + * + * A page that lacks a downlink/parent may indicate corruption. However, we + * must account for the fact that a missing downlink can occasionally be + * encountered in a non-corrupt index. This can be due to an interrupted page + * split, or an interrupted multi-level page deletion (i.e. there was a hard + * crash or an error during a page split, or while VACUUM was deleting a + * multi-level chain of pages). + * + * Note that this can only be called in readonly mode, so there is no need to + * be concerned about concurrent page splits or page deletions. + */ +static void +bt_downlink_missing_check(BtreeCheckState *state, bool rightsplit, + BlockNumber blkno, Page page) +{ + BTPageOpaque opaque = BTPageGetOpaque(page); + ItemId itemid; + IndexTuple itup; + Page child; + BTPageOpaque copaque; + uint32 level; + BlockNumber childblk; + XLogRecPtr pagelsn; + + Assert(state->readonly); + Assert(!P_IGNORE(opaque)); + + /* No next level up with downlinks to fingerprint from the true root */ + if (P_ISROOT(opaque)) + return; + + pagelsn = PageGetLSN(page); + + /* + * Incomplete (interrupted) page splits can account for the lack of a + * downlink. Some inserting transaction should eventually complete the + * page split in passing, when it notices that the left sibling page is + * P_INCOMPLETE_SPLIT(). + * + * In general, VACUUM is not prepared for there to be no downlink to a + * page that it deletes. This is the main reason why the lack of a + * downlink can be reported as corruption here. It's not obvious that an + * invalid missing downlink can result in wrong answers to queries, + * though, since index scans that land on the child may end up + * consistently moving right. The handling of concurrent page splits (and + * page deletions) within _bt_moveright() cannot distinguish + * inconsistencies that last for a moment from inconsistencies that are + * permanent and irrecoverable. + * + * VACUUM isn't even prepared to delete pages that have no downlink due to + * an incomplete page split, but it can detect and reason about that case + * by design, so it shouldn't be taken to indicate corruption. See + * _bt_pagedel() for full details. + */ + if (rightsplit) + { + ereport(DEBUG1, + (errcode(ERRCODE_NO_DATA), + errmsg_internal("harmless interrupted page split detected in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Block=%u level=%u left sibling=%u page lsn=%X/%X.", + blkno, opaque->btpo_level, + opaque->btpo_prev, + LSN_FORMAT_ARGS(pagelsn)))); + return; + } + + /* + * Page under check is probably the "top parent" of a multi-level page + * deletion. We'll need to descend the subtree to make sure that + * descendant pages are consistent with that, though. + * + * If the page (which must be non-ignorable) is a leaf page, then clearly + * it can't be the top parent. The lack of a downlink is probably a + * symptom of a broad problem that could just as easily cause + * inconsistencies anywhere else. + */ + if (P_ISLEAF(opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("leaf index block lacks downlink in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Block=%u page lsn=%X/%X.", + blkno, + LSN_FORMAT_ARGS(pagelsn)))); + + /* Descend from the given page, which is an internal page */ + elog(DEBUG1, "checking for interrupted multi-level deletion due to missing downlink in index \"%s\"", + RelationGetRelationName(state->rel)); + + level = opaque->btpo_level; + itemid = PageGetItemIdCareful(state, blkno, page, P_FIRSTDATAKEY(opaque)); + itup = (IndexTuple) PageGetItem(page, itemid); + childblk = BTreeTupleGetDownLink(itup); + for (;;) + { + CHECK_FOR_INTERRUPTS(); + + child = palloc_btree_page(state, childblk); + copaque = BTPageGetOpaque(child); + + if (P_ISLEAF(copaque)) + break; + + /* Do an extra sanity check in passing on internal pages */ + if (copaque->btpo_level != level - 1) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg_internal("downlink points to block in index \"%s\" whose level is not one level down", + RelationGetRelationName(state->rel)), + errdetail_internal("Top parent/under check block=%u block pointed to=%u expected level=%u level in pointed to block=%u.", + blkno, childblk, + level - 1, copaque->btpo_level))); + + level = copaque->btpo_level; + itemid = PageGetItemIdCareful(state, childblk, child, + P_FIRSTDATAKEY(copaque)); + itup = (IndexTuple) PageGetItem(child, itemid); + childblk = BTreeTupleGetDownLink(itup); + /* Be slightly more pro-active in freeing this memory, just in case */ + pfree(child); + } + + /* + * Since there cannot be a concurrent VACUUM operation in readonly mode, + * and since a page has no links within other pages (siblings and parent) + * once it is marked fully deleted, it should be impossible to land on a + * fully deleted page. See bt_child_check() for further details. + * + * The bt_child_check() P_ISDELETED() check is repeated here because + * bt_child_check() does not visit pages reachable through negative + * infinity items. Besides, bt_child_check() is unwilling to descend + * multiple levels. (The similar bt_child_check() P_ISDELETED() check + * within bt_check_level_from_leftmost() won't reach the page either, + * since the leaf's live siblings should have their sibling links updated + * to bypass the deletion target page when it is marked fully dead.) + * + * If this error is raised, it might be due to a previous multi-level page + * deletion that failed to realize that it wasn't yet safe to mark the + * leaf page as fully dead. A "dangling downlink" will still remain when + * this happens. The fact that the dangling downlink's page (the leaf's + * parent/ancestor page) lacked a downlink is incidental. + */ + if (P_ISDELETED(copaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg_internal("downlink to deleted leaf page found in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Top parent/target block=%u leaf block=%u top parent/under check lsn=%X/%X.", + blkno, childblk, + LSN_FORMAT_ARGS(pagelsn)))); + + /* + * Iff leaf page is half-dead, its high key top parent link should point + * to what VACUUM considered to be the top parent page at the instant it + * was interrupted. Provided the high key link actually points to the + * page under check, the missing downlink we detected is consistent with + * there having been an interrupted multi-level page deletion. This means + * that the subtree with the page under check at its root (a page deletion + * chain) is in a consistent state, enabling VACUUM to resume deleting the + * entire chain the next time it encounters the half-dead leaf page. + */ + if (P_ISHALFDEAD(copaque) && !P_RIGHTMOST(copaque)) + { + itemid = PageGetItemIdCareful(state, childblk, child, P_HIKEY); + itup = (IndexTuple) PageGetItem(child, itemid); + if (BTreeTupleGetTopParent(itup) == blkno) + return; + } + + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("internal index block lacks downlink in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Block=%u level=%u page lsn=%X/%X.", + blkno, opaque->btpo_level, + LSN_FORMAT_ARGS(pagelsn)))); +} + +/* + * Per-tuple callback from table_index_build_scan, used to determine if index has + * all the entries that definitely should have been observed in leaf pages of + * the target index (that is, all IndexTuples that were fingerprinted by our + * Bloom filter). All heapallindexed checks occur here. + * + * The redundancy between an index and the table it indexes provides a good + * opportunity to detect corruption, especially corruption within the table. + * The high level principle behind the verification performed here is that any + * IndexTuple that should be in an index following a fresh CREATE INDEX (based + * on the same index definition) should also have been in the original, + * existing index, which should have used exactly the same representation + * + * Since the overall structure of the index has already been verified, the most + * likely explanation for error here is a corrupt heap page (could be logical + * or physical corruption). Index corruption may still be detected here, + * though. Only readonly callers will have verified that left links and right + * links are in agreement, and so it's possible that a leaf page transposition + * within index is actually the source of corruption detected here (for + * !readonly callers). The checks performed only for readonly callers might + * more accurately frame the problem as a cross-page invariant issue (this + * could even be due to recovery not replaying all WAL records). The !readonly + * ERROR message raised here includes a HINT about retrying with readonly + * verification, just in case it's a cross-page invariant issue, though that + * isn't particularly likely. + * + * table_index_build_scan() expects to be able to find the root tuple when a + * heap-only tuple (the live tuple at the end of some HOT chain) needs to be + * indexed, in order to replace the actual tuple's TID with the root tuple's + * TID (which is what we're actually passed back here). The index build heap + * scan code will raise an error when a tuple that claims to be the root of the + * heap-only tuple's HOT chain cannot be located. This catches cases where the + * original root item offset/root tuple for a HOT chain indicates (for whatever + * reason) that the entire HOT chain is dead, despite the fact that the latest + * heap-only tuple should be indexed. When this happens, sequential scans may + * always give correct answers, and all indexes may be considered structurally + * consistent (i.e. the nbtree structural checks would not detect corruption). + * It may be the case that only index scans give wrong answers, and yet heap or + * SLRU corruption is the real culprit. (While it's true that LP_DEAD bit + * setting will probably also leave the index in a corrupt state before too + * long, the problem is nonetheless that there is heap corruption.) + * + * Heap-only tuple handling within table_index_build_scan() works in a way that + * helps us to detect index tuples that contain the wrong values (values that + * don't match the latest tuple in the HOT chain). This can happen when there + * is no superseding index tuple due to a faulty assessment of HOT safety, + * perhaps during the original CREATE INDEX. Because the latest tuple's + * contents are used with the root TID, an error will be raised when a tuple + * with the same TID but non-matching attribute values is passed back to us. + * Faulty assessment of HOT-safety was behind at least two distinct CREATE + * INDEX CONCURRENTLY bugs that made it into stable releases, one of which was + * undetected for many years. In short, the same principle that allows a + * REINDEX to repair corruption when there was an (undetected) broken HOT chain + * also allows us to detect the corruption in many cases. + */ +static void +bt_tuple_present_callback(Relation index, ItemPointer tid, Datum *values, + bool *isnull, bool tupleIsAlive, void *checkstate) +{ + BtreeCheckState *state = (BtreeCheckState *) checkstate; + IndexTuple itup, + norm; + + Assert(state->heapallindexed); + + /* Generate a normalized index tuple for fingerprinting */ + itup = index_form_tuple(RelationGetDescr(index), values, isnull); + itup->t_tid = *tid; + norm = bt_normalize_tuple(state, itup); + + /* Probe Bloom filter -- tuple should be present */ + if (bloom_lacks_element(state->filter, (unsigned char *) norm, + IndexTupleSize(norm))) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("heap tuple (%u,%u) from table \"%s\" lacks matching index tuple within index \"%s\"", + ItemPointerGetBlockNumber(&(itup->t_tid)), + ItemPointerGetOffsetNumber(&(itup->t_tid)), + RelationGetRelationName(state->heaprel), + RelationGetRelationName(state->rel)), + !state->readonly + ? errhint("Retrying verification using the function bt_index_parent_check() might provide a more specific error.") + : 0)); + + state->heaptuplespresent++; + pfree(itup); + /* Cannot leak memory here */ + if (norm != itup) + pfree(norm); +} + +/* + * Normalize an index tuple for fingerprinting. + * + * In general, index tuple formation is assumed to be deterministic by + * heapallindexed verification, and IndexTuples are assumed immutable. While + * the LP_DEAD bit is mutable in leaf pages, that's ItemId metadata, which is + * not fingerprinted. Normalization is required to compensate for corner + * cases where the determinism assumption doesn't quite work. + * + * There is currently one such case: index_form_tuple() does not try to hide + * the source TOAST state of input datums. The executor applies TOAST + * compression for heap tuples based on different criteria to the compression + * applied within btinsert()'s call to index_form_tuple(): it sometimes + * compresses more aggressively, resulting in compressed heap tuple datums but + * uncompressed corresponding index tuple datums. A subsequent heapallindexed + * verification will get a logically equivalent though bitwise unequal tuple + * from index_form_tuple(). False positive heapallindexed corruption reports + * could occur without normalizing away the inconsistency. + * + * Returned tuple is often caller's own original tuple. Otherwise, it is a + * new representation of caller's original index tuple, palloc()'d in caller's + * memory context. + * + * Note: This routine is not concerned with distinctions about the + * representation of tuples beyond those that might break heapallindexed + * verification. In particular, it won't try to normalize opclass-equal + * datums with potentially distinct representations (e.g., btree/numeric_ops + * index datums will not get their display scale normalized-away here). + * Caller does normalization for non-pivot tuples that have a posting list, + * since dummy CREATE INDEX callback code generates new tuples with the same + * normalized representation. + */ +static IndexTuple +bt_normalize_tuple(BtreeCheckState *state, IndexTuple itup) +{ + TupleDesc tupleDescriptor = RelationGetDescr(state->rel); + Datum normalized[INDEX_MAX_KEYS]; + bool isnull[INDEX_MAX_KEYS]; + bool toast_free[INDEX_MAX_KEYS]; + bool formnewtup = false; + IndexTuple reformed; + int i; + + /* Caller should only pass "logical" non-pivot tuples here */ + Assert(!BTreeTupleIsPosting(itup) && !BTreeTupleIsPivot(itup)); + + /* Easy case: It's immediately clear that tuple has no varlena datums */ + if (!IndexTupleHasVarwidths(itup)) + return itup; + + for (i = 0; i < tupleDescriptor->natts; i++) + { + Form_pg_attribute att; + + att = TupleDescAttr(tupleDescriptor, i); + + /* Assume untoasted/already normalized datum initially */ + toast_free[i] = false; + normalized[i] = index_getattr(itup, att->attnum, + tupleDescriptor, + &isnull[i]); + if (att->attbyval || att->attlen != -1 || isnull[i]) + continue; + + /* + * Callers always pass a tuple that could safely be inserted into the + * index without further processing, so an external varlena header + * should never be encountered here + */ + if (VARATT_IS_EXTERNAL(DatumGetPointer(normalized[i]))) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("external varlena datum in tuple that references heap row (%u,%u) in index \"%s\"", + ItemPointerGetBlockNumber(&(itup->t_tid)), + ItemPointerGetOffsetNumber(&(itup->t_tid)), + RelationGetRelationName(state->rel)))); + else if (VARATT_IS_COMPRESSED(DatumGetPointer(normalized[i]))) + { + formnewtup = true; + normalized[i] = PointerGetDatum(PG_DETOAST_DATUM(normalized[i])); + toast_free[i] = true; + } + } + + /* Easier case: Tuple has varlena datums, none of which are compressed */ + if (!formnewtup) + return itup; + + /* + * Hard case: Tuple had compressed varlena datums that necessitate + * creating normalized version of the tuple from uncompressed input datums + * (normalized input datums). This is rather naive, but shouldn't be + * necessary too often. + * + * Note that we rely on deterministic index_form_tuple() TOAST compression + * of normalized input. + */ + reformed = index_form_tuple(tupleDescriptor, normalized, isnull); + reformed->t_tid = itup->t_tid; + + /* Cannot leak memory here */ + for (i = 0; i < tupleDescriptor->natts; i++) + if (toast_free[i]) + pfree(DatumGetPointer(normalized[i])); + + return reformed; +} + +/* + * Produce palloc()'d "plain" tuple for nth posting list entry/TID. + * + * In general, deduplication is not supposed to change the logical contents of + * an index. Multiple index tuples are merged together into one equivalent + * posting list index tuple when convenient. + * + * heapallindexed verification must normalize-away this variation in + * representation by converting posting list tuples into two or more "plain" + * tuples. Each tuple must be fingerprinted separately -- there must be one + * tuple for each corresponding Bloom filter probe during the heap scan. + * + * Note: Caller still needs to call bt_normalize_tuple() with returned tuple. + */ +static inline IndexTuple +bt_posting_plain_tuple(IndexTuple itup, int n) +{ + Assert(BTreeTupleIsPosting(itup)); + + /* Returns non-posting-list tuple */ + return _bt_form_posting(itup, BTreeTupleGetPostingN(itup, n), 1); +} + +/* + * Search for itup in index, starting from fast root page. itup must be a + * non-pivot tuple. This is only supported with heapkeyspace indexes, since + * we rely on having fully unique keys to find a match with only a single + * visit to a leaf page, barring an interrupted page split, where we may have + * to move right. (A concurrent page split is impossible because caller must + * be readonly caller.) + * + * This routine can detect very subtle transitive consistency issues across + * more than one level of the tree. Leaf pages all have a high key (even the + * rightmost page has a conceptual positive infinity high key), but not a low + * key. Their downlink in parent is a lower bound, which along with the high + * key is almost enough to detect every possible inconsistency. A downlink + * separator key value won't always be available from parent, though, because + * the first items of internal pages are negative infinity items, truncated + * down to zero attributes during internal page splits. While it's true that + * bt_child_check() and the high key check can detect most imaginable key + * space problems, there are remaining problems it won't detect with non-pivot + * tuples in cousin leaf pages. Starting a search from the root for every + * existing leaf tuple detects small inconsistencies in upper levels of the + * tree that cannot be detected any other way. (Besides all this, this is + * probably also useful as a direct test of the code used by index scans + * themselves.) + */ +static bool +bt_rootdescend(BtreeCheckState *state, IndexTuple itup) +{ + BTScanInsert key; + BTStack stack; + Buffer lbuf; + bool exists; + + key = _bt_mkscankey(state->rel, itup); + Assert(key->heapkeyspace && key->scantid != NULL); + + /* + * Search from root. + * + * Ideally, we would arrange to only move right within _bt_search() when + * an interrupted page split is detected (i.e. when the incomplete split + * bit is found to be set), but for now we accept the possibility that + * that could conceal an inconsistency. + */ + Assert(state->readonly && state->rootdescend); + exists = false; + stack = _bt_search(state->rel, NULL, key, &lbuf, BT_READ, NULL); + + if (BufferIsValid(lbuf)) + { + BTInsertStateData insertstate; + OffsetNumber offnum; + Page page; + + insertstate.itup = itup; + insertstate.itemsz = MAXALIGN(IndexTupleSize(itup)); + insertstate.itup_key = key; + insertstate.postingoff = 0; + insertstate.bounds_valid = false; + insertstate.buf = lbuf; + + /* Get matching tuple on leaf page */ + offnum = _bt_binsrch_insert(state->rel, &insertstate); + /* Compare first >= matching item on leaf page, if any */ + page = BufferGetPage(lbuf); + /* Should match on first heap TID when tuple has a posting list */ + if (offnum <= PageGetMaxOffsetNumber(page) && + insertstate.postingoff <= 0 && + _bt_compare(state->rel, key, page, offnum) == 0) + exists = true; + _bt_relbuf(state->rel, lbuf); + } + + _bt_freestack(stack); + pfree(key); + + return exists; +} + +/* + * Is particular offset within page (whose special state is passed by caller) + * the page negative-infinity item? + * + * As noted in comments above _bt_compare(), there is special handling of the + * first data item as a "negative infinity" item. The hard-coding within + * _bt_compare() makes comparing this item for the purposes of verification + * pointless at best, since the IndexTuple only contains a valid TID (a + * reference TID to child page). + */ +static inline bool +offset_is_negative_infinity(BTPageOpaque opaque, OffsetNumber offset) +{ + /* + * For internal pages only, the first item after high key, if any, is + * negative infinity item. Internal pages always have a negative infinity + * item, whereas leaf pages never have one. This implies that negative + * infinity item is either first or second line item, or there is none + * within page. + * + * Negative infinity items are a special case among pivot tuples. They + * always have zero attributes, while all other pivot tuples always have + * nkeyatts attributes. + * + * Right-most pages don't have a high key, but could be said to + * conceptually have a "positive infinity" high key. Thus, there is a + * symmetry between down link items in parent pages, and high keys in + * children. Together, they represent the part of the key space that + * belongs to each page in the index. For example, all children of the + * root page will have negative infinity as a lower bound from root + * negative infinity downlink, and positive infinity as an upper bound + * (implicitly, from "imaginary" positive infinity high key in root). + */ + return !P_ISLEAF(opaque) && offset == P_FIRSTDATAKEY(opaque); +} + +/* + * Does the invariant hold that the key is strictly less than a given upper + * bound offset item? + * + * Verifies line pointer on behalf of caller. + * + * If this function returns false, convention is that caller throws error due + * to corruption. + */ +static inline bool +invariant_l_offset(BtreeCheckState *state, BTScanInsert key, + OffsetNumber upperbound) +{ + ItemId itemid; + int32 cmp; + + Assert(key->pivotsearch); + + /* Verify line pointer before checking tuple */ + itemid = PageGetItemIdCareful(state, state->targetblock, state->target, + upperbound); + /* pg_upgrade'd indexes may legally have equal sibling tuples */ + if (!key->heapkeyspace) + return invariant_leq_offset(state, key, upperbound); + + cmp = _bt_compare(state->rel, key, state->target, upperbound); + + /* + * _bt_compare() is capable of determining that a scankey with a + * filled-out attribute is greater than pivot tuples where the comparison + * is resolved at a truncated attribute (value of attribute in pivot is + * minus infinity). However, it is not capable of determining that a + * scankey is _less than_ a tuple on the basis of a comparison resolved at + * _scankey_ minus infinity attribute. Complete an extra step to simulate + * having minus infinity values for omitted scankey attribute(s). + */ + if (cmp == 0) + { + BTPageOpaque topaque; + IndexTuple ritup; + int uppnkeyatts; + ItemPointer rheaptid; + bool nonpivot; + + ritup = (IndexTuple) PageGetItem(state->target, itemid); + topaque = BTPageGetOpaque(state->target); + nonpivot = P_ISLEAF(topaque) && upperbound >= P_FIRSTDATAKEY(topaque); + + /* Get number of keys + heap TID for item to the right */ + uppnkeyatts = BTreeTupleGetNKeyAtts(ritup, state->rel); + rheaptid = BTreeTupleGetHeapTIDCareful(state, ritup, nonpivot); + + /* Heap TID is tiebreaker key attribute */ + if (key->keysz == uppnkeyatts) + return key->scantid == NULL && rheaptid != NULL; + + return key->keysz < uppnkeyatts; + } + + return cmp < 0; +} + +/* + * Does the invariant hold that the key is less than or equal to a given upper + * bound offset item? + * + * Caller should have verified that upperbound's line pointer is consistent + * using PageGetItemIdCareful() call. + * + * If this function returns false, convention is that caller throws error due + * to corruption. + */ +static inline bool +invariant_leq_offset(BtreeCheckState *state, BTScanInsert key, + OffsetNumber upperbound) +{ + int32 cmp; + + Assert(key->pivotsearch); + + cmp = _bt_compare(state->rel, key, state->target, upperbound); + + return cmp <= 0; +} + +/* + * Does the invariant hold that the key is strictly greater than a given lower + * bound offset item? + * + * Caller should have verified that lowerbound's line pointer is consistent + * using PageGetItemIdCareful() call. + * + * If this function returns false, convention is that caller throws error due + * to corruption. + */ +static inline bool +invariant_g_offset(BtreeCheckState *state, BTScanInsert key, + OffsetNumber lowerbound) +{ + int32 cmp; + + Assert(key->pivotsearch); + + cmp = _bt_compare(state->rel, key, state->target, lowerbound); + + /* pg_upgrade'd indexes may legally have equal sibling tuples */ + if (!key->heapkeyspace) + return cmp >= 0; + + /* + * No need to consider the possibility that scankey has attributes that we + * need to force to be interpreted as negative infinity. _bt_compare() is + * able to determine that scankey is greater than negative infinity. The + * distinction between "==" and "<" isn't interesting here, since + * corruption is indicated either way. + */ + return cmp > 0; +} + +/* + * Does the invariant hold that the key is strictly less than a given upper + * bound offset item, with the offset relating to a caller-supplied page that + * is not the current target page? + * + * Caller's non-target page is a child page of the target, checked as part of + * checking a property of the target page (i.e. the key comes from the + * target). Verifies line pointer on behalf of caller. + * + * If this function returns false, convention is that caller throws error due + * to corruption. + */ +static inline bool +invariant_l_nontarget_offset(BtreeCheckState *state, BTScanInsert key, + BlockNumber nontargetblock, Page nontarget, + OffsetNumber upperbound) +{ + ItemId itemid; + int32 cmp; + + Assert(key->pivotsearch); + + /* Verify line pointer before checking tuple */ + itemid = PageGetItemIdCareful(state, nontargetblock, nontarget, + upperbound); + cmp = _bt_compare(state->rel, key, nontarget, upperbound); + + /* pg_upgrade'd indexes may legally have equal sibling tuples */ + if (!key->heapkeyspace) + return cmp <= 0; + + /* See invariant_l_offset() for an explanation of this extra step */ + if (cmp == 0) + { + IndexTuple child; + int uppnkeyatts; + ItemPointer childheaptid; + BTPageOpaque copaque; + bool nonpivot; + + child = (IndexTuple) PageGetItem(nontarget, itemid); + copaque = BTPageGetOpaque(nontarget); + nonpivot = P_ISLEAF(copaque) && upperbound >= P_FIRSTDATAKEY(copaque); + + /* Get number of keys + heap TID for child/non-target item */ + uppnkeyatts = BTreeTupleGetNKeyAtts(child, state->rel); + childheaptid = BTreeTupleGetHeapTIDCareful(state, child, nonpivot); + + /* Heap TID is tiebreaker key attribute */ + if (key->keysz == uppnkeyatts) + return key->scantid == NULL && childheaptid != NULL; + + return key->keysz < uppnkeyatts; + } + + return cmp < 0; +} + +/* + * Given a block number of a B-Tree page, return page in palloc()'d memory. + * While at it, perform some basic checks of the page. + * + * There is never an attempt to get a consistent view of multiple pages using + * multiple concurrent buffer locks; in general, we only acquire a single pin + * and buffer lock at a time, which is often all that the nbtree code requires. + * (Actually, bt_recheck_sibling_links couples buffer locks, which is the only + * exception to this general rule.) + * + * Operating on a copy of the page is useful because it prevents control + * getting stuck in an uninterruptible state when an underlying operator class + * misbehaves. + */ +static Page +palloc_btree_page(BtreeCheckState *state, BlockNumber blocknum) +{ + Buffer buffer; + Page page; + BTPageOpaque opaque; + OffsetNumber maxoffset; + + page = palloc(BLCKSZ); + + /* + * We copy the page into local storage to avoid holding pin on the buffer + * longer than we must. + */ + buffer = ReadBufferExtended(state->rel, MAIN_FORKNUM, blocknum, RBM_NORMAL, + state->checkstrategy); + LockBuffer(buffer, BT_READ); + + /* + * Perform the same basic sanity checking that nbtree itself performs for + * every page: + */ + _bt_checkpage(state->rel, buffer); + + /* Only use copy of page in palloc()'d memory */ + memcpy(page, BufferGetPage(buffer), BLCKSZ); + UnlockReleaseBuffer(buffer); + + opaque = BTPageGetOpaque(page); + + if (P_ISMETA(opaque) && blocknum != BTREE_METAPAGE) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("invalid meta page found at block %u in index \"%s\"", + blocknum, RelationGetRelationName(state->rel)))); + + /* Check page from block that ought to be meta page */ + if (blocknum == BTREE_METAPAGE) + { + BTMetaPageData *metad = BTPageGetMeta(page); + + if (!P_ISMETA(opaque) || + metad->btm_magic != BTREE_MAGIC) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index \"%s\" meta page is corrupt", + RelationGetRelationName(state->rel)))); + + if (metad->btm_version < BTREE_MIN_VERSION || + metad->btm_version > BTREE_VERSION) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("version mismatch in index \"%s\": file version %d, " + "current version %d, minimum supported version %d", + RelationGetRelationName(state->rel), + metad->btm_version, BTREE_VERSION, + BTREE_MIN_VERSION))); + + /* Finished with metapage checks */ + return page; + } + + /* + * Deleted pages that still use the old 32-bit XID representation have no + * sane "level" field because they type pun the field, but all other pages + * (including pages deleted on Postgres 14+) have a valid value. + */ + if (!P_ISDELETED(opaque) || P_HAS_FULLXID(opaque)) + { + /* Okay, no reason not to trust btpo_level field from page */ + + if (P_ISLEAF(opaque) && opaque->btpo_level != 0) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg_internal("invalid leaf page level %u for block %u in index \"%s\"", + opaque->btpo_level, blocknum, + RelationGetRelationName(state->rel)))); + + if (!P_ISLEAF(opaque) && opaque->btpo_level == 0) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg_internal("invalid internal page level 0 for block %u in index \"%s\"", + blocknum, + RelationGetRelationName(state->rel)))); + } + + /* + * Sanity checks for number of items on page. + * + * As noted at the beginning of _bt_binsrch(), an internal page must have + * children, since there must always be a negative infinity downlink + * (there may also be a highkey). In the case of non-rightmost leaf + * pages, there must be at least a highkey. The exceptions are deleted + * pages, which contain no items. + * + * This is correct when pages are half-dead, since internal pages are + * never half-dead, and leaf pages must have a high key when half-dead + * (the rightmost page can never be deleted). It's also correct with + * fully deleted pages: _bt_unlink_halfdead_page() doesn't change anything + * about the target page other than setting the page as fully dead, and + * setting its xact field. In particular, it doesn't change the sibling + * links in the deletion target itself, since they're required when index + * scans land on the deletion target, and then need to move right (or need + * to move left, in the case of backward index scans). + */ + maxoffset = PageGetMaxOffsetNumber(page); + if (maxoffset > MaxIndexTuplesPerPage) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("Number of items on block %u of index \"%s\" exceeds MaxIndexTuplesPerPage (%u)", + blocknum, RelationGetRelationName(state->rel), + MaxIndexTuplesPerPage))); + + if (!P_ISLEAF(opaque) && !P_ISDELETED(opaque) && maxoffset < P_FIRSTDATAKEY(opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("internal block %u in index \"%s\" lacks high key and/or at least one downlink", + blocknum, RelationGetRelationName(state->rel)))); + + if (P_ISLEAF(opaque) && !P_ISDELETED(opaque) && !P_RIGHTMOST(opaque) && maxoffset < P_HIKEY) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("non-rightmost leaf block %u in index \"%s\" lacks high key item", + blocknum, RelationGetRelationName(state->rel)))); + + /* + * In general, internal pages are never marked half-dead, except on + * versions of Postgres prior to 9.4, where it can be valid transient + * state. This state is nonetheless treated as corruption by VACUUM on + * from version 9.4 on, so do the same here. See _bt_pagedel() for full + * details. + */ + if (!P_ISLEAF(opaque) && P_ISHALFDEAD(opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("internal page block %u in index \"%s\" is half-dead", + blocknum, RelationGetRelationName(state->rel)), + errhint("This can be caused by an interrupted VACUUM in version 9.3 or older, before upgrade. Please REINDEX it."))); + + /* + * Check that internal pages have no garbage items, and that no page has + * an invalid combination of deletion-related page level flags + */ + if (!P_ISLEAF(opaque) && P_HAS_GARBAGE(opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg_internal("internal page block %u in index \"%s\" has garbage items", + blocknum, RelationGetRelationName(state->rel)))); + + if (P_HAS_FULLXID(opaque) && !P_ISDELETED(opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg_internal("full transaction id page flag appears in non-deleted block %u in index \"%s\"", + blocknum, RelationGetRelationName(state->rel)))); + + if (P_ISDELETED(opaque) && P_ISHALFDEAD(opaque)) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg_internal("deleted page block %u in index \"%s\" is half-dead", + blocknum, RelationGetRelationName(state->rel)))); + + return page; +} + +/* + * _bt_mkscankey() wrapper that automatically prevents insertion scankey from + * being considered greater than the pivot tuple that its values originated + * from (or some other identical pivot tuple) in the common case where there + * are truncated/minus infinity attributes. Without this extra step, there + * are forms of corruption that amcheck could theoretically fail to report. + * + * For example, invariant_g_offset() might miss a cross-page invariant failure + * on an internal level if the scankey built from the first item on the + * target's right sibling page happened to be equal to (not greater than) the + * last item on target page. The !pivotsearch tiebreaker in _bt_compare() + * might otherwise cause amcheck to assume (rather than actually verify) that + * the scankey is greater. + */ +static inline BTScanInsert +bt_mkscankey_pivotsearch(Relation rel, IndexTuple itup) +{ + BTScanInsert skey; + + skey = _bt_mkscankey(rel, itup); + skey->pivotsearch = true; + + return skey; +} + +/* + * PageGetItemId() wrapper that validates returned line pointer. + * + * Buffer page/page item access macros generally trust that line pointers are + * not corrupt, which might cause problems for verification itself. For + * example, there is no bounds checking in PageGetItem(). Passing it a + * corrupt line pointer can cause it to return a tuple/pointer that is unsafe + * to dereference. + * + * Validating line pointers before tuples avoids undefined behavior and + * assertion failures with corrupt indexes, making the verification process + * more robust and predictable. + */ +static ItemId +PageGetItemIdCareful(BtreeCheckState *state, BlockNumber block, Page page, + OffsetNumber offset) +{ + ItemId itemid = PageGetItemId(page, offset); + + if (ItemIdGetOffset(itemid) + ItemIdGetLength(itemid) > + BLCKSZ - MAXALIGN(sizeof(BTPageOpaqueData))) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("line pointer points past end of tuple space in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Index tid=(%u,%u) lp_off=%u, lp_len=%u lp_flags=%u.", + block, offset, ItemIdGetOffset(itemid), + ItemIdGetLength(itemid), + ItemIdGetFlags(itemid)))); + + /* + * Verify that line pointer isn't LP_REDIRECT or LP_UNUSED, since nbtree + * never uses either. Verify that line pointer has storage, too, since + * even LP_DEAD items should within nbtree. + */ + if (ItemIdIsRedirected(itemid) || !ItemIdIsUsed(itemid) || + ItemIdGetLength(itemid) == 0) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("invalid line pointer storage in index \"%s\"", + RelationGetRelationName(state->rel)), + errdetail_internal("Index tid=(%u,%u) lp_off=%u, lp_len=%u lp_flags=%u.", + block, offset, ItemIdGetOffset(itemid), + ItemIdGetLength(itemid), + ItemIdGetFlags(itemid)))); + + return itemid; +} + +/* + * BTreeTupleGetHeapTID() wrapper that enforces that a heap TID is present in + * cases where that is mandatory (i.e. for non-pivot tuples) + */ +static inline ItemPointer +BTreeTupleGetHeapTIDCareful(BtreeCheckState *state, IndexTuple itup, + bool nonpivot) +{ + ItemPointer htid; + + /* + * Caller determines whether this is supposed to be a pivot or non-pivot + * tuple using page type and item offset number. Verify that tuple + * metadata agrees with this. + */ + Assert(state->heapkeyspace); + if (BTreeTupleIsPivot(itup) && nonpivot) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg_internal("block %u or its right sibling block or child block in index \"%s\" has unexpected pivot tuple", + state->targetblock, + RelationGetRelationName(state->rel)))); + + if (!BTreeTupleIsPivot(itup) && !nonpivot) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg_internal("block %u or its right sibling block or child block in index \"%s\" has unexpected non-pivot tuple", + state->targetblock, + RelationGetRelationName(state->rel)))); + + htid = BTreeTupleGetHeapTID(itup); + if (!ItemPointerIsValid(htid) && nonpivot) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("block %u or its right sibling block or child block in index \"%s\" contains non-pivot tuple that lacks a heap TID", + state->targetblock, + RelationGetRelationName(state->rel)))); + + return htid; +} + +/* + * Return the "pointed to" TID for itup, which is used to generate a + * descriptive error message. itup must be a "data item" tuple (it wouldn't + * make much sense to call here with a high key tuple, since there won't be a + * valid downlink/block number to display). + * + * Returns either a heap TID (which will be the first heap TID in posting list + * if itup is posting list tuple), or a TID that contains downlink block + * number, plus some encoded metadata (e.g., the number of attributes present + * in itup). + */ +static inline ItemPointer +BTreeTupleGetPointsToTID(IndexTuple itup) +{ + /* + * Rely on the assumption that !heapkeyspace internal page data items will + * correctly return TID with downlink here -- BTreeTupleGetHeapTID() won't + * recognize it as a pivot tuple, but everything still works out because + * the t_tid field is still returned + */ + if (!BTreeTupleIsPivot(itup)) + return BTreeTupleGetHeapTID(itup); + + /* Pivot tuple returns TID with downlink block (heapkeyspace variant) */ + return &itup->t_tid; +} diff --git a/contrib/auth_delay/Makefile b/contrib/auth_delay/Makefile new file mode 100644 index 0000000..4b86ec3 --- /dev/null +++ b/contrib/auth_delay/Makefile @@ -0,0 +1,15 @@ +# contrib/auth_delay/Makefile + +MODULES = auth_delay +PGFILEDESC = "auth_delay - delay authentication failure reports" + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/auth_delay +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/auth_delay/auth_delay.c b/contrib/auth_delay/auth_delay.c new file mode 100644 index 0000000..8d6e4d2 --- /dev/null +++ b/contrib/auth_delay/auth_delay.c @@ -0,0 +1,74 @@ +/* ------------------------------------------------------------------------- + * + * auth_delay.c + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/auth_delay/auth_delay.c + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#include "libpq/auth.h" +#include "port.h" +#include "utils/guc.h" +#include "utils/timestamp.h" + +PG_MODULE_MAGIC; + +/* GUC Variables */ +static int auth_delay_milliseconds = 0; + +/* Original Hook */ +static ClientAuthentication_hook_type original_client_auth_hook = NULL; + +/* + * Check authentication + */ +static void +auth_delay_checks(Port *port, int status) +{ + /* + * Any other plugins which use ClientAuthentication_hook. + */ + if (original_client_auth_hook) + original_client_auth_hook(port, status); + + /* + * Inject a short delay if authentication failed. + */ + if (status != STATUS_OK) + { + pg_usleep(1000L * auth_delay_milliseconds); + } +} + +/* + * Module Load Callback + */ +void +_PG_init(void) +{ + /* Define custom GUC variables */ + DefineCustomIntVariable("auth_delay.milliseconds", + "Milliseconds to delay before reporting authentication failure", + NULL, + &auth_delay_milliseconds, + 0, + 0, INT_MAX / 1000, + PGC_SIGHUP, + GUC_UNIT_MS, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("auth_delay"); + + /* Install Hooks */ + original_client_auth_hook = ClientAuthentication_hook; + ClientAuthentication_hook = auth_delay_checks; +} diff --git a/contrib/auth_delay/meson.build b/contrib/auth_delay/meson.build new file mode 100644 index 0000000..f2b2da0 --- /dev/null +++ b/contrib/auth_delay/meson.build @@ -0,0 +1,17 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +auth_delay_sources = files( + 'auth_delay.c', +) + +if host_system == 'windows' + auth_delay_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'auth_delay', + '--FILEDESC', 'auth_delay - delay authentication failure reports',]) +endif + +auth_delay = shared_module('auth_delay', + auth_delay_sources, + kwargs: contrib_mod_args, +) +contrib_targets += auth_delay diff --git a/contrib/auto_explain/.gitignore b/contrib/auto_explain/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/auto_explain/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/auto_explain/Makefile b/contrib/auto_explain/Makefile new file mode 100644 index 0000000..efd127d --- /dev/null +++ b/contrib/auto_explain/Makefile @@ -0,0 +1,20 @@ +# contrib/auto_explain/Makefile + +MODULE_big = auto_explain +OBJS = \ + $(WIN32RES) \ + auto_explain.o +PGFILEDESC = "auto_explain - logging facility for execution plans" + +TAP_TESTS = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/auto_explain +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c new file mode 100644 index 0000000..c3ac27a --- /dev/null +++ b/contrib/auto_explain/auto_explain.c @@ -0,0 +1,442 @@ +/*------------------------------------------------------------------------- + * + * auto_explain.c + * + * + * Copyright (c) 2008-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/auto_explain/auto_explain.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#include "access/parallel.h" +#include "commands/explain.h" +#include "common/pg_prng.h" +#include "executor/instrument.h" +#include "jit/jit.h" +#include "nodes/params.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC; + +/* GUC variables */ +static int auto_explain_log_min_duration = -1; /* msec or -1 */ +static int auto_explain_log_parameter_max_length = -1; /* bytes or -1 */ +static bool auto_explain_log_analyze = false; +static bool auto_explain_log_verbose = false; +static bool auto_explain_log_buffers = false; +static bool auto_explain_log_wal = false; +static bool auto_explain_log_triggers = false; +static bool auto_explain_log_timing = true; +static bool auto_explain_log_settings = false; +static int auto_explain_log_format = EXPLAIN_FORMAT_TEXT; +static int auto_explain_log_level = LOG; +static bool auto_explain_log_nested_statements = false; +static double auto_explain_sample_rate = 1; + +static const struct config_enum_entry format_options[] = { + {"text", EXPLAIN_FORMAT_TEXT, false}, + {"xml", EXPLAIN_FORMAT_XML, false}, + {"json", EXPLAIN_FORMAT_JSON, false}, + {"yaml", EXPLAIN_FORMAT_YAML, false}, + {NULL, 0, false} +}; + +static const struct config_enum_entry loglevel_options[] = { + {"debug5", DEBUG5, false}, + {"debug4", DEBUG4, false}, + {"debug3", DEBUG3, false}, + {"debug2", DEBUG2, false}, + {"debug1", DEBUG1, false}, + {"debug", DEBUG2, true}, + {"info", INFO, false}, + {"notice", NOTICE, false}, + {"warning", WARNING, false}, + {"log", LOG, false}, + {NULL, 0, false} +}; + +/* Current nesting depth of ExecutorRun calls */ +static int nesting_level = 0; + +/* Is the current top-level query to be sampled? */ +static bool current_query_sampled = false; + +#define auto_explain_enabled() \ + (auto_explain_log_min_duration >= 0 && \ + (nesting_level == 0 || auto_explain_log_nested_statements) && \ + current_query_sampled) + +/* Saved hook values in case of unload */ +static ExecutorStart_hook_type prev_ExecutorStart = NULL; +static ExecutorRun_hook_type prev_ExecutorRun = NULL; +static ExecutorFinish_hook_type prev_ExecutorFinish = NULL; +static ExecutorEnd_hook_type prev_ExecutorEnd = NULL; + +static void explain_ExecutorStart(QueryDesc *queryDesc, int eflags); +static void explain_ExecutorRun(QueryDesc *queryDesc, + ScanDirection direction, + uint64 count, bool execute_once); +static void explain_ExecutorFinish(QueryDesc *queryDesc); +static void explain_ExecutorEnd(QueryDesc *queryDesc); + + +/* + * Module load callback + */ +void +_PG_init(void) +{ + /* Define custom GUC variables. */ + DefineCustomIntVariable("auto_explain.log_min_duration", + "Sets the minimum execution time above which plans will be logged.", + "Zero prints all plans. -1 turns this feature off.", + &auto_explain_log_min_duration, + -1, + -1, INT_MAX, + PGC_SUSET, + GUC_UNIT_MS, + NULL, + NULL, + NULL); + + DefineCustomIntVariable("auto_explain.log_parameter_max_length", + "Sets the maximum length of query parameters to log.", + "Zero logs no query parameters, -1 logs them in full.", + &auto_explain_log_parameter_max_length, + -1, + -1, INT_MAX, + PGC_SUSET, + GUC_UNIT_BYTE, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("auto_explain.log_analyze", + "Use EXPLAIN ANALYZE for plan logging.", + NULL, + &auto_explain_log_analyze, + false, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("auto_explain.log_settings", + "Log modified configuration parameters affecting query planning.", + NULL, + &auto_explain_log_settings, + false, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("auto_explain.log_verbose", + "Use EXPLAIN VERBOSE for plan logging.", + NULL, + &auto_explain_log_verbose, + false, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("auto_explain.log_buffers", + "Log buffers usage.", + NULL, + &auto_explain_log_buffers, + false, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("auto_explain.log_wal", + "Log WAL usage.", + NULL, + &auto_explain_log_wal, + false, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("auto_explain.log_triggers", + "Include trigger statistics in plans.", + "This has no effect unless log_analyze is also set.", + &auto_explain_log_triggers, + false, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomEnumVariable("auto_explain.log_format", + "EXPLAIN format to be used for plan logging.", + NULL, + &auto_explain_log_format, + EXPLAIN_FORMAT_TEXT, + format_options, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomEnumVariable("auto_explain.log_level", + "Log level for the plan.", + NULL, + &auto_explain_log_level, + LOG, + loglevel_options, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("auto_explain.log_nested_statements", + "Log nested statements.", + NULL, + &auto_explain_log_nested_statements, + false, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("auto_explain.log_timing", + "Collect timing data, not just row counts.", + NULL, + &auto_explain_log_timing, + true, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomRealVariable("auto_explain.sample_rate", + "Fraction of queries to process.", + NULL, + &auto_explain_sample_rate, + 1.0, + 0.0, + 1.0, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("auto_explain"); + + /* Install hooks. */ + prev_ExecutorStart = ExecutorStart_hook; + ExecutorStart_hook = explain_ExecutorStart; + prev_ExecutorRun = ExecutorRun_hook; + ExecutorRun_hook = explain_ExecutorRun; + prev_ExecutorFinish = ExecutorFinish_hook; + ExecutorFinish_hook = explain_ExecutorFinish; + prev_ExecutorEnd = ExecutorEnd_hook; + ExecutorEnd_hook = explain_ExecutorEnd; +} + +/* + * ExecutorStart hook: start up logging if needed + */ +static void +explain_ExecutorStart(QueryDesc *queryDesc, int eflags) +{ + /* + * At the beginning of each top-level statement, decide whether we'll + * sample this statement. If nested-statement explaining is enabled, + * either all nested statements will be explained or none will. + * + * When in a parallel worker, we should do nothing, which we can implement + * cheaply by pretending we decided not to sample the current statement. + * If EXPLAIN is active in the parent session, data will be collected and + * reported back to the parent, and it's no business of ours to interfere. + */ + if (nesting_level == 0) + { + if (auto_explain_log_min_duration >= 0 && !IsParallelWorker()) + current_query_sampled = (pg_prng_double(&pg_global_prng_state) < auto_explain_sample_rate); + else + current_query_sampled = false; + } + + if (auto_explain_enabled()) + { + /* Enable per-node instrumentation iff log_analyze is required. */ + if (auto_explain_log_analyze && (eflags & EXEC_FLAG_EXPLAIN_ONLY) == 0) + { + if (auto_explain_log_timing) + queryDesc->instrument_options |= INSTRUMENT_TIMER; + else + queryDesc->instrument_options |= INSTRUMENT_ROWS; + if (auto_explain_log_buffers) + queryDesc->instrument_options |= INSTRUMENT_BUFFERS; + if (auto_explain_log_wal) + queryDesc->instrument_options |= INSTRUMENT_WAL; + } + } + + if (prev_ExecutorStart) + prev_ExecutorStart(queryDesc, eflags); + else + standard_ExecutorStart(queryDesc, eflags); + + if (auto_explain_enabled()) + { + /* + * Set up to track total elapsed time in ExecutorRun. Make sure the + * space is allocated in the per-query context so it will go away at + * ExecutorEnd. + */ + if (queryDesc->totaltime == NULL) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt); + queryDesc->totaltime = InstrAlloc(1, INSTRUMENT_ALL, false); + MemoryContextSwitchTo(oldcxt); + } + } +} + +/* + * ExecutorRun hook: all we need do is track nesting depth + */ +static void +explain_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, + uint64 count, bool execute_once) +{ + nesting_level++; + PG_TRY(); + { + if (prev_ExecutorRun) + prev_ExecutorRun(queryDesc, direction, count, execute_once); + else + standard_ExecutorRun(queryDesc, direction, count, execute_once); + } + PG_FINALLY(); + { + nesting_level--; + } + PG_END_TRY(); +} + +/* + * ExecutorFinish hook: all we need do is track nesting depth + */ +static void +explain_ExecutorFinish(QueryDesc *queryDesc) +{ + nesting_level++; + PG_TRY(); + { + if (prev_ExecutorFinish) + prev_ExecutorFinish(queryDesc); + else + standard_ExecutorFinish(queryDesc); + } + PG_FINALLY(); + { + nesting_level--; + } + PG_END_TRY(); +} + +/* + * ExecutorEnd hook: log results if needed + */ +static void +explain_ExecutorEnd(QueryDesc *queryDesc) +{ + if (queryDesc->totaltime && auto_explain_enabled()) + { + MemoryContext oldcxt; + double msec; + + /* + * Make sure we operate in the per-query context, so any cruft will be + * discarded later during ExecutorEnd. + */ + oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt); + + /* + * Make sure stats accumulation is done. (Note: it's okay if several + * levels of hook all do this.) + */ + InstrEndLoop(queryDesc->totaltime); + + /* Log plan if duration is exceeded. */ + msec = queryDesc->totaltime->total * 1000.0; + if (msec >= auto_explain_log_min_duration) + { + ExplainState *es = NewExplainState(); + + es->analyze = (queryDesc->instrument_options && auto_explain_log_analyze); + es->verbose = auto_explain_log_verbose; + es->buffers = (es->analyze && auto_explain_log_buffers); + es->wal = (es->analyze && auto_explain_log_wal); + es->timing = (es->analyze && auto_explain_log_timing); + es->summary = es->analyze; + es->format = auto_explain_log_format; + es->settings = auto_explain_log_settings; + + ExplainBeginOutput(es); + ExplainQueryText(es, queryDesc); + ExplainQueryParameters(es, queryDesc->params, auto_explain_log_parameter_max_length); + ExplainPrintPlan(es, queryDesc); + if (es->analyze && auto_explain_log_triggers) + ExplainPrintTriggers(es, queryDesc); + if (es->costs) + ExplainPrintJITSummary(es, queryDesc); + ExplainEndOutput(es); + + /* Remove last line break */ + if (es->str->len > 0 && es->str->data[es->str->len - 1] == '\n') + es->str->data[--es->str->len] = '\0'; + + /* Fix JSON to output an object */ + if (auto_explain_log_format == EXPLAIN_FORMAT_JSON) + { + es->str->data[0] = '{'; + es->str->data[es->str->len - 1] = '}'; + } + + /* + * Note: we rely on the existing logging of context or + * debug_query_string to identify just which statement is being + * reported. This isn't ideal but trying to do it here would + * often result in duplication. + */ + ereport(auto_explain_log_level, + (errmsg("duration: %.3f ms plan:\n%s", + msec, es->str->data), + errhidestmt(true))); + } + + MemoryContextSwitchTo(oldcxt); + } + + if (prev_ExecutorEnd) + prev_ExecutorEnd(queryDesc); + else + standard_ExecutorEnd(queryDesc); +} diff --git a/contrib/auto_explain/meson.build b/contrib/auto_explain/meson.build new file mode 100644 index 0000000..2d5e6aa --- /dev/null +++ b/contrib/auto_explain/meson.build @@ -0,0 +1,28 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +auto_explain_sources = files( + 'auto_explain.c', +) + +if host_system == 'windows' + auto_explain_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'auto_explain', + '--FILEDESC', 'auto_explain - logging facility for execution plans',]) +endif + +auto_explain = shared_module('auto_explain', + auto_explain_sources, + kwargs: contrib_mod_args, +) +contrib_targets += auto_explain + +tests += { + 'name': 'auto_explain', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_auto_explain.pl', + ], + }, +} diff --git a/contrib/auto_explain/t/001_auto_explain.pl b/contrib/auto_explain/t/001_auto_explain.pl new file mode 100644 index 0000000..abb422f --- /dev/null +++ b/contrib/auto_explain/t/001_auto_explain.pl @@ -0,0 +1,215 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# Runs the specified query and returns the emitted server log. +# params is an optional hash mapping GUC names to values; +# any such settings are transmitted to the backend via PGOPTIONS. +sub query_log +{ + my ($node, $sql, $params) = @_; + $params ||= {}; + + local $ENV{PGOPTIONS} = join " ", + map { "-c $_=$params->{$_}" } keys %$params; + + my $log = $node->logfile(); + my $offset = -s $log; + + $node->safe_psql("postgres", $sql); + + return slurp_file($log, $offset); +} + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init('auth_extra' => [ '--create-role', 'regress_user1' ]); +$node->append_conf('postgresql.conf', + "session_preload_libraries = 'auto_explain'"); +$node->append_conf('postgresql.conf', "auto_explain.log_min_duration = 0"); +$node->append_conf('postgresql.conf', "auto_explain.log_analyze = on"); +$node->start; + +# Simple query. +my $log_contents = query_log($node, "SELECT * FROM pg_class;"); + +like( + $log_contents, + qr/Query Text: SELECT \* FROM pg_class;/, + "query text logged, text mode"); + +unlike( + $log_contents, + qr/Query Parameters:/, + "no query parameters logged when none, text mode"); + +like( + $log_contents, + qr/Seq Scan on pg_class/, + "sequential scan logged, text mode"); + +# Prepared query. +$log_contents = query_log($node, + q{PREPARE get_proc(name) AS SELECT * FROM pg_proc WHERE proname = $1; EXECUTE get_proc('int4pl');} +); + +like( + $log_contents, + qr/Query Text: PREPARE get_proc\(name\) AS SELECT \* FROM pg_proc WHERE proname = \$1;/, + "prepared query text logged, text mode"); + +like( + $log_contents, + qr/Query Parameters: \$1 = 'int4pl'/, + "query parameters logged, text mode"); + +like( + $log_contents, + qr/Index Scan using pg_proc_proname_args_nsp_index on pg_proc/, + "index scan logged, text mode"); + + +# Prepared query with truncated parameters. +$log_contents = query_log( + $node, + q{PREPARE get_type(name) AS SELECT * FROM pg_type WHERE typname = $1; EXECUTE get_type('float8');}, + { "auto_explain.log_parameter_max_length" => 3 }); + +like( + $log_contents, + qr/Query Text: PREPARE get_type\(name\) AS SELECT \* FROM pg_type WHERE typname = \$1;/, + "prepared query text logged, text mode"); + +like( + $log_contents, + qr/Query Parameters: \$1 = 'flo\.\.\.'/, + "query parameters truncated, text mode"); + +# Prepared query with parameter logging disabled. +$log_contents = query_log( + $node, + q{PREPARE get_type(name) AS SELECT * FROM pg_type WHERE typname = $1; EXECUTE get_type('float8');}, + { "auto_explain.log_parameter_max_length" => 0 }); + +like( + $log_contents, + qr/Query Text: PREPARE get_type\(name\) AS SELECT \* FROM pg_type WHERE typname = \$1;/, + "prepared query text logged, text mode"); + +unlike( + $log_contents, + qr/Query Parameters:/, + "query parameters not logged when disabled, text mode"); + +# Query Identifier. +# Logging enabled. +$log_contents = query_log( + $node, + "SELECT * FROM pg_class;", + { + "auto_explain.log_verbose" => "on", + "compute_query_id" => "on" + }); + +like( + $log_contents, + qr/Query Identifier:/, + "query identifier logged with compute_query_id=on, text mode"); + +# Logging disabled. +$log_contents = query_log( + $node, + "SELECT * FROM pg_class;", + { + "auto_explain.log_verbose" => "on", + "compute_query_id" => "regress" + }); + +unlike( + $log_contents, + qr/Query Identifier:/, + "query identifier not logged with compute_query_id=regress, text mode"); + +# JSON format. +$log_contents = query_log( + $node, + "SELECT * FROM pg_class;", + { "auto_explain.log_format" => "json" }); + +like( + $log_contents, + qr/"Query Text": "SELECT \* FROM pg_class;"/, + "query text logged, json mode"); + +unlike( + $log_contents, + qr/"Query Parameters":/, + "query parameters not logged when none, json mode"); + +like( + $log_contents, + qr/"Node Type": "Seq Scan"[^}]*"Relation Name": "pg_class"/s, + "sequential scan logged, json mode"); + +# Prepared query in JSON format. +$log_contents = query_log( + $node, + q{PREPARE get_class(name) AS SELECT * FROM pg_class WHERE relname = $1; EXECUTE get_class('pg_class');}, + { "auto_explain.log_format" => "json" }); + +like( + $log_contents, + qr/"Query Text": "PREPARE get_class\(name\) AS SELECT \* FROM pg_class WHERE relname = \$1;"/, + "prepared query text logged, json mode"); + +like( + $log_contents, + qr/"Node Type": "Index Scan"[^}]*"Index Name": "pg_class_relname_nsp_index"/s, + "index scan logged, json mode"); + +# Check that PGC_SUSET parameters can be set by non-superuser if granted, +# otherwise not + +$node->safe_psql( + "postgres", q{ +CREATE USER regress_user1; +GRANT SET ON PARAMETER auto_explain.log_format TO regress_user1; +}); + +{ + local $ENV{PGUSER} = "regress_user1"; + + $log_contents = query_log( + $node, + "SELECT * FROM pg_database;", + { "auto_explain.log_format" => "json" }); + + like( + $log_contents, + qr/"Query Text": "SELECT \* FROM pg_database;"/, + "query text logged, json mode selected by non-superuser"); + + $log_contents = query_log( + $node, + "SELECT * FROM pg_database;", + { "auto_explain.log_level" => "log" }); + + like( + $log_contents, + qr/WARNING: ( 42501:)? permission denied to set parameter "auto_explain\.log_level"/, + "permission failure logged"); + +} # end queries run as regress_user1 + +$node->safe_psql( + "postgres", q{ +REVOKE SET ON PARAMETER auto_explain.log_format FROM regress_user1; +DROP USER regress_user1; +}); + +done_testing(); diff --git a/contrib/basebackup_to_shell/.gitignore b/contrib/basebackup_to_shell/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/basebackup_to_shell/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/basebackup_to_shell/Makefile b/contrib/basebackup_to_shell/Makefile new file mode 100644 index 0000000..58bd269 --- /dev/null +++ b/contrib/basebackup_to_shell/Makefile @@ -0,0 +1,24 @@ +# contrib/basebackup_to_shell/Makefile + +MODULE_big = basebackup_to_shell +OBJS = \ + $(WIN32RES) \ + basebackup_to_shell.o + +PGFILEDESC = "basebackup_to_shell - target basebackup to shell command" + +TAP_TESTS = 1 + +export GZIP_PROGRAM=$(GZIP) +export TAR + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/basebackup_to_shell +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/basebackup_to_shell/basebackup_to_shell.c b/contrib/basebackup_to_shell/basebackup_to_shell.c new file mode 100644 index 0000000..57ed587 --- /dev/null +++ b/contrib/basebackup_to_shell/basebackup_to_shell.c @@ -0,0 +1,373 @@ +/*------------------------------------------------------------------------- + * + * basebackup_to_shell.c + * target base backup files to a shell command + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * contrib/basebackup_to_shell/basebackup_to_shell.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/xact.h" +#include "backup/basebackup_target.h" +#include "common/percentrepl.h" +#include "miscadmin.h" +#include "storage/fd.h" +#include "utils/acl.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC; + +typedef struct bbsink_shell +{ + /* Common information for all types of sink. */ + bbsink base; + + /* User-supplied target detail string. */ + char *target_detail; + + /* Shell command pattern being used for this backup. */ + char *shell_command; + + /* The command that is currently running. */ + char *current_command; + + /* Pipe to the running command. */ + FILE *pipe; +} bbsink_shell; + +static void *shell_check_detail(char *target, char *target_detail); +static bbsink *shell_get_sink(bbsink *next_sink, void *detail_arg); + +static void bbsink_shell_begin_archive(bbsink *sink, + const char *archive_name); +static void bbsink_shell_archive_contents(bbsink *sink, size_t len); +static void bbsink_shell_end_archive(bbsink *sink); +static void bbsink_shell_begin_manifest(bbsink *sink); +static void bbsink_shell_manifest_contents(bbsink *sink, size_t len); +static void bbsink_shell_end_manifest(bbsink *sink); + +static const bbsink_ops bbsink_shell_ops = { + .begin_backup = bbsink_forward_begin_backup, + .begin_archive = bbsink_shell_begin_archive, + .archive_contents = bbsink_shell_archive_contents, + .end_archive = bbsink_shell_end_archive, + .begin_manifest = bbsink_shell_begin_manifest, + .manifest_contents = bbsink_shell_manifest_contents, + .end_manifest = bbsink_shell_end_manifest, + .end_backup = bbsink_forward_end_backup, + .cleanup = bbsink_forward_cleanup +}; + +static char *shell_command = ""; +static char *shell_required_role = ""; + +void +_PG_init(void) +{ + DefineCustomStringVariable("basebackup_to_shell.command", + "Shell command to be executed for each backup file.", + NULL, + &shell_command, + "", + PGC_SIGHUP, + 0, + NULL, NULL, NULL); + + DefineCustomStringVariable("basebackup_to_shell.required_role", + "Backup user must be a member of this role to use shell backup target.", + NULL, + &shell_required_role, + "", + PGC_SIGHUP, + 0, + NULL, NULL, NULL); + + MarkGUCPrefixReserved("basebackup_to_shell"); + + BaseBackupAddTarget("shell", shell_check_detail, shell_get_sink); +} + +/* + * We choose to defer sanity checking until shell_get_sink(), and so + * just pass the target detail through without doing anything. However, we do + * permissions checks here, before any real work has been done. + */ +static void * +shell_check_detail(char *target, char *target_detail) +{ + if (shell_required_role[0] != '\0') + { + Oid roleid; + + StartTransactionCommand(); + roleid = get_role_oid(shell_required_role, true); + if (!has_privs_of_role(GetUserId(), roleid)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to use basebackup_to_shell"))); + CommitTransactionCommand(); + } + + return target_detail; +} + +/* + * Set up a bbsink to implement this base backup target. + * + * This is also a convenient place to sanity check that a target detail was + * given if and only if %d is present. + */ +static bbsink * +shell_get_sink(bbsink *next_sink, void *detail_arg) +{ + bbsink_shell *sink; + bool has_detail_escape = false; + char *c; + + /* + * Set up the bbsink. + * + * We remember the current value of basebackup_to_shell.shell_command to + * be certain that it can't change under us during the backup. + */ + sink = palloc0(sizeof(bbsink_shell)); + *((const bbsink_ops **) &sink->base.bbs_ops) = &bbsink_shell_ops; + sink->base.bbs_next = next_sink; + sink->target_detail = detail_arg; + sink->shell_command = pstrdup(shell_command); + + /* Reject an empty shell command. */ + if (sink->shell_command[0] == '\0') + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("shell command for backup is not configured")); + + /* Determine whether the shell command we're using contains %d. */ + for (c = sink->shell_command; *c != '\0'; ++c) + { + if (c[0] == '%' && c[1] != '\0') + { + if (c[1] == 'd') + has_detail_escape = true; + ++c; + } + } + + /* There should be a target detail if %d was used, and not otherwise. */ + if (has_detail_escape && sink->target_detail == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("a target detail is required because the configured command includes %%d"), + errhint("Try \"pg_basebackup --target shell:DETAIL ...\""))); + else if (!has_detail_escape && sink->target_detail != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("a target detail is not permitted because the configured command does not include %%d"))); + + /* + * Since we're passing the string provided by the user to popen(), it will + * be interpreted by the shell, which is a potential security + * vulnerability, since the user invoking this module is not necessarily a + * superuser. To stay out of trouble, we must disallow any shell + * metacharacters here; to be conservative and keep things simple, we + * allow only alphanumerics. + */ + if (sink->target_detail != NULL) + { + char *d; + bool scary = false; + + for (d = sink->target_detail; *d != '\0'; ++d) + { + if (*d >= 'a' && *d <= 'z') + continue; + if (*d >= 'A' && *d <= 'Z') + continue; + if (*d >= '0' && *d <= '9') + continue; + scary = true; + break; + } + + if (scary) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("target detail must contain only alphanumeric characters")); + } + + return &sink->base; +} + +/* + * Construct the exact shell command that we're actually going to run, + * making substitutions as appropriate for escape sequences. + */ +static char * +shell_construct_command(const char *base_command, const char *filename, + const char *target_detail) +{ + return replace_percent_placeholders(base_command, "basebackup_to_shell.command", + "df", target_detail, filename); +} + +/* + * Finish executing the shell command once all data has been written. + */ +static void +shell_finish_command(bbsink_shell *sink) +{ + int pclose_rc; + + /* There should be a command running. */ + Assert(sink->current_command != NULL); + Assert(sink->pipe != NULL); + + /* Close down the pipe we opened. */ + pclose_rc = ClosePipeStream(sink->pipe); + if (pclose_rc == -1) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not close pipe to external command: %m"))); + else if (pclose_rc != 0) + { + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg("shell command \"%s\" failed", + sink->current_command), + errdetail_internal("%s", wait_result_to_str(pclose_rc)))); + } + + /* Clean up. */ + sink->pipe = NULL; + pfree(sink->current_command); + sink->current_command = NULL; +} + +/* + * Start up the shell command, substituting %f in for the current filename. + */ +static void +shell_run_command(bbsink_shell *sink, const char *filename) +{ + /* There should not be anything already running. */ + Assert(sink->current_command == NULL); + Assert(sink->pipe == NULL); + + /* Construct a suitable command. */ + sink->current_command = shell_construct_command(sink->shell_command, + filename, + sink->target_detail); + + /* Run it. */ + sink->pipe = OpenPipeStream(sink->current_command, PG_BINARY_W); + if (sink->pipe == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not execute command \"%s\": %m", + sink->current_command))); +} + +/* + * Send accumulated data to the running shell command. + */ +static void +shell_send_data(bbsink_shell *sink, size_t len) +{ + /* There should be a command running. */ + Assert(sink->current_command != NULL); + Assert(sink->pipe != NULL); + + /* Try to write the data. */ + if (fwrite(sink->base.bbs_buffer, len, 1, sink->pipe) != 1 || + ferror(sink->pipe)) + { + if (errno == EPIPE) + { + /* + * The error we're about to throw would shut down the command + * anyway, but we may get a more meaningful error message by doing + * this. If not, we'll fall through to the generic error below. + */ + shell_finish_command(sink); + errno = EPIPE; + } + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to shell backup program: %m"))); + } +} + +/* + * At start of archive, start up the shell command and forward to next sink. + */ +static void +bbsink_shell_begin_archive(bbsink *sink, const char *archive_name) +{ + bbsink_shell *mysink = (bbsink_shell *) sink; + + shell_run_command(mysink, archive_name); + bbsink_forward_begin_archive(sink, archive_name); +} + +/* + * Send archive contents to command's stdin and forward to next sink. + */ +static void +bbsink_shell_archive_contents(bbsink *sink, size_t len) +{ + bbsink_shell *mysink = (bbsink_shell *) sink; + + shell_send_data(mysink, len); + bbsink_forward_archive_contents(sink, len); +} + +/* + * At end of archive, shut down the shell command and forward to next sink. + */ +static void +bbsink_shell_end_archive(bbsink *sink) +{ + bbsink_shell *mysink = (bbsink_shell *) sink; + + shell_finish_command(mysink); + bbsink_forward_end_archive(sink); +} + +/* + * At start of manifest, start up the shell command and forward to next sink. + */ +static void +bbsink_shell_begin_manifest(bbsink *sink) +{ + bbsink_shell *mysink = (bbsink_shell *) sink; + + shell_run_command(mysink, "backup_manifest"); + bbsink_forward_begin_manifest(sink); +} + +/* + * Send manifest contents to command's stdin and forward to next sink. + */ +static void +bbsink_shell_manifest_contents(bbsink *sink, size_t len) +{ + bbsink_shell *mysink = (bbsink_shell *) sink; + + shell_send_data(mysink, len); + bbsink_forward_manifest_contents(sink, len); +} + +/* + * At end of manifest, shut down the shell command and forward to next sink. + */ +static void +bbsink_shell_end_manifest(bbsink *sink) +{ + bbsink_shell *mysink = (bbsink_shell *) sink; + + shell_finish_command(mysink); + bbsink_forward_end_manifest(sink); +} diff --git a/contrib/basebackup_to_shell/meson.build b/contrib/basebackup_to_shell/meson.build new file mode 100644 index 0000000..a5488c3 --- /dev/null +++ b/contrib/basebackup_to_shell/meson.build @@ -0,0 +1,30 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +basebackup_to_shell_sources = files( + 'basebackup_to_shell.c', +) + +if host_system == 'windows' + basebackup_to_shell_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'basebackup_to_shell', + '--FILEDESC', 'basebackup_to_shell - target basebackup to shell command',]) +endif + +basebackup_to_shell = shared_module('basebackup_to_shell', + basebackup_to_shell_sources, + kwargs: contrib_mod_args, +) +contrib_targets += basebackup_to_shell + +tests += { + 'name': 'basebackup_to_shell', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_basic.pl', + ], + 'env': {'GZIP_PROGRAM': gzip.path(), + 'TAR': tar.path()}, + }, +} diff --git a/contrib/basebackup_to_shell/t/001_basic.pl b/contrib/basebackup_to_shell/t/001_basic.pl new file mode 100644 index 0000000..e2cdd2e --- /dev/null +++ b/contrib/basebackup_to_shell/t/001_basic.pl @@ -0,0 +1,142 @@ +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# For testing purposes, we just want basebackup_to_shell to write standard +# input to a file. However, Windows doesn't have "cat" or any equivalent, so +# we use "gzip" for this purpose. +my $gzip = $ENV{'GZIP_PROGRAM'}; +if (!defined $gzip || $gzip eq '') +{ + plan skip_all => 'gzip not available'; +} + +# to ensure path can be embedded in postgresql.conf +$gzip =~ s{\\}{/}g if ($PostgreSQL::Test::Utils::windows_os); + +my $node = PostgreSQL::Test::Cluster->new('primary'); + +# Make sure pg_hba.conf is set up to allow connections from backupuser. +# This is only needed on Windows machines that don't use UNIX sockets. +$node->init( + 'allows_streaming' => 1, + 'auth_extra' => [ '--create-role', 'backupuser' ]); + +$node->append_conf('postgresql.conf', + "shared_preload_libraries = 'basebackup_to_shell'"); +$node->start; +$node->safe_psql('postgres', 'CREATE USER backupuser REPLICATION'); +$node->safe_psql('postgres', 'CREATE ROLE trustworthy'); + +# For nearly all pg_basebackup invocations some options should be specified, +# to keep test times reasonable. Using @pg_basebackup_defs as the first +# element of the array passed to IPC::Run interpolate the array (as it is +# not a reference to an array)... +my @pg_basebackup_defs = ('pg_basebackup', '--no-sync', '-cfast'); + +# This particular test module generally wants to run with -Xfetch, because +# -Xstream is not supported with a backup target, and with -U backupuser. +my @pg_basebackup_cmd = (@pg_basebackup_defs, '-U', 'backupuser', '-Xfetch'); + +# Can't use this module without setting basebackup_to_shell.command. +$node->command_fails_like( + [ @pg_basebackup_cmd, '--target', 'shell' ], + qr/shell command for backup is not configured/, + 'fails if basebackup_to_shell.command is not set'); + +# Configure basebackup_to_shell.command and reload the configuration file. +my $backup_path = PostgreSQL::Test::Utils::tempdir; +my $escaped_backup_path = $backup_path; +$escaped_backup_path =~ s{\\}{\\\\}g + if ($PostgreSQL::Test::Utils::windows_os); +my $shell_command = + $PostgreSQL::Test::Utils::windows_os + ? qq{"$gzip" --fast > "$escaped_backup_path\\\\%f.gz"} + : qq{"$gzip" --fast > "$escaped_backup_path/%f.gz"}; +$node->append_conf('postgresql.conf', + "basebackup_to_shell.command='$shell_command'"); +$node->reload(); + +# Should work now. +$node->command_ok( + [ @pg_basebackup_cmd, '--target', 'shell' ], + 'backup with no detail: pg_basebackup'); +verify_backup('', $backup_path, "backup with no detail"); + +# Should fail with a detail. +$node->command_fails_like( + [ @pg_basebackup_cmd, '--target', 'shell:foo' ], + qr/a target detail is not permitted because the configured command does not include %d/, + 'fails if detail provided without %d'); + +# Reconfigure to restrict access and require a detail. +$shell_command = + $PostgreSQL::Test::Utils::windows_os + ? qq{"$gzip" --fast > "$escaped_backup_path\\\\%d.%f.gz"} + : qq{"$gzip" --fast > "$escaped_backup_path/%d.%f.gz"}; +$node->append_conf('postgresql.conf', + "basebackup_to_shell.command='$shell_command'"); +$node->append_conf('postgresql.conf', + "basebackup_to_shell.required_role='trustworthy'"); +$node->reload(); + +# Should fail due to lack of permission. +$node->command_fails_like( + [ @pg_basebackup_cmd, '--target', 'shell' ], + qr/permission denied to use basebackup_to_shell/, + 'fails if required_role not granted'); + +# Should fail due to lack of a detail. +$node->safe_psql('postgres', 'GRANT trustworthy TO backupuser'); +$node->command_fails_like( + [ @pg_basebackup_cmd, '--target', 'shell' ], + qr/a target detail is required because the configured command includes %d/, + 'fails if %d is present and detail not given'); + +# Should work. +$node->command_ok([ @pg_basebackup_cmd, '--target', 'shell:bar' ], + 'backup with detail: pg_basebackup'); +verify_backup('bar.', $backup_path, "backup with detail"); + +done_testing(); + +sub verify_backup +{ + my ($prefix, $backup_dir, $test_name) = @_; + + ok( -f "$backup_dir/${prefix}backup_manifest.gz", + "$test_name: backup_manifest.gz was created"); + ok( -f "$backup_dir/${prefix}base.tar.gz", + "$test_name: base.tar.gz was created"); + + SKIP: + { + my $tar = $ENV{TAR}; + skip "no tar program available", 1 if (!defined $tar || $tar eq ''); + + # Decompress. + system_or_bail($gzip, '-d', + $backup_dir . '/' . $prefix . 'backup_manifest.gz'); + system_or_bail($gzip, '-d', + $backup_dir . '/' . $prefix . 'base.tar.gz'); + + # Untar. + my $extract_path = PostgreSQL::Test::Utils::tempdir; + system_or_bail($tar, 'xf', $backup_dir . '/' . $prefix . 'base.tar', + '-C', $extract_path); + + # Verify. + $node->command_ok( + [ + 'pg_verifybackup', '-n', + '-m', "${backup_dir}/${prefix}backup_manifest", + '-e', $extract_path + ], + "$test_name: backup verifies ok"); + } +} diff --git a/contrib/basic_archive/.gitignore b/contrib/basic_archive/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/basic_archive/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile new file mode 100644 index 0000000..55d299d --- /dev/null +++ b/contrib/basic_archive/Makefile @@ -0,0 +1,21 @@ +# contrib/basic_archive/Makefile + +MODULES = basic_archive +PGFILEDESC = "basic_archive - basic archive module" + +REGRESS = basic_archive +REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf +# Disabled because these tests require "shared_preload_libraries=basic_archive", +# 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 = contrib/basic_archive +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c new file mode 100644 index 0000000..4d78c31 --- /dev/null +++ b/contrib/basic_archive/basic_archive.c @@ -0,0 +1,428 @@ +/*------------------------------------------------------------------------- + * + * basic_archive.c + * + * This file demonstrates a basic archive library implementation that is + * roughly equivalent to the following shell command: + * + * test ! -f /path/to/dest && cp /path/to/src /path/to/dest + * + * One notable difference between this module and the shell command above + * is that this module first copies the file to a temporary destination, + * syncs it to disk, and then durably moves it to the final destination. + * + * Another notable difference is that if /path/to/dest already exists + * but has contents identical to /path/to/src, archiving will succeed, + * whereas the command shown above would fail. This prevents problems if + * a file is successfully archived and then the system crashes before + * a durable record of the success has been made. + * + * Copyright (c) 2022-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/basic_archive/basic_archive.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include +#include +#include + +#include "archive/archive_module.h" +#include "common/int.h" +#include "miscadmin.h" +#include "storage/copydir.h" +#include "storage/fd.h" +#include "utils/guc.h" +#include "utils/memutils.h" + +PG_MODULE_MAGIC; + +typedef struct BasicArchiveData +{ + MemoryContext context; +} BasicArchiveData; + +static char *archive_directory = NULL; + +static void basic_archive_startup(ArchiveModuleState *state); +static bool basic_archive_configured(ArchiveModuleState *state); +static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path); +static void basic_archive_file_internal(const char *file, const char *path); +static bool check_archive_directory(char **newval, void **extra, GucSource source); +static bool compare_files(const char *file1, const char *file2); +static void basic_archive_shutdown(ArchiveModuleState *state); + +static const ArchiveModuleCallbacks basic_archive_callbacks = { + .startup_cb = basic_archive_startup, + .check_configured_cb = basic_archive_configured, + .archive_file_cb = basic_archive_file, + .shutdown_cb = basic_archive_shutdown +}; + +/* + * _PG_init + * + * Defines the module's GUC. + */ +void +_PG_init(void) +{ + DefineCustomStringVariable("basic_archive.archive_directory", + gettext_noop("Archive file destination directory."), + NULL, + &archive_directory, + "", + PGC_SIGHUP, + 0, + check_archive_directory, NULL, NULL); + + MarkGUCPrefixReserved("basic_archive"); +} + +/* + * _PG_archive_module_init + * + * Returns the module's archiving callbacks. + */ +const ArchiveModuleCallbacks * +_PG_archive_module_init(void) +{ + return &basic_archive_callbacks; +} + +/* + * basic_archive_startup + * + * Creates the module's memory context. + */ +void +basic_archive_startup(ArchiveModuleState *state) +{ + BasicArchiveData *data; + + data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext, + sizeof(BasicArchiveData)); + data->context = AllocSetContextCreate(TopMemoryContext, + "basic_archive", + ALLOCSET_DEFAULT_SIZES); + state->private_data = (void *) data; +} + +/* + * check_archive_directory + * + * Checks that the provided archive directory exists. + */ +static bool +check_archive_directory(char **newval, void **extra, GucSource source) +{ + struct stat st; + + /* + * The default value is an empty string, so we have to accept that value. + * Our check_configured callback also checks for this and prevents + * archiving from proceeding if it is still empty. + */ + if (*newval == NULL || *newval[0] == '\0') + return true; + + /* + * Make sure the file paths won't be too long. The docs indicate that the + * file names to be archived can be up to 64 characters long. + */ + if (strlen(*newval) + 64 + 2 >= MAXPGPATH) + { + GUC_check_errdetail("Archive directory too long."); + return false; + } + + /* + * Do a basic sanity check that the specified archive directory exists. It + * could be removed at some point in the future, so we still need to be + * prepared for it not to exist in the actual archiving logic. + */ + if (stat(*newval, &st) != 0 || !S_ISDIR(st.st_mode)) + { + GUC_check_errdetail("Specified archive directory does not exist."); + return false; + } + + return true; +} + +/* + * basic_archive_configured + * + * Checks that archive_directory is not blank. + */ +static bool +basic_archive_configured(ArchiveModuleState *state) +{ + return archive_directory != NULL && archive_directory[0] != '\0'; +} + +/* + * basic_archive_file + * + * Archives one file. + */ +static bool +basic_archive_file(ArchiveModuleState *state, const char *file, const char *path) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext oldcontext; + BasicArchiveData *data = (BasicArchiveData *) state->private_data; + MemoryContext basic_archive_context = data->context; + + /* + * We run basic_archive_file_internal() in our own memory context so that + * we can easily reset it during error recovery (thus avoiding memory + * leaks). + */ + oldcontext = MemoryContextSwitchTo(basic_archive_context); + + /* + * Since the archiver operates at the bottom of the exception stack, + * ERRORs turn into FATALs and cause the archiver process to restart. + * However, using ereport(ERROR, ...) when there are problems is easy to + * code and maintain. Therefore, we create our own exception handler to + * catch ERRORs and return false instead of restarting the archiver + * whenever there is a failure. + */ + if (sigsetjmp(local_sigjmp_buf, 1) != 0) + { + /* Since not using PG_TRY, must reset error stack by hand */ + error_context_stack = NULL; + + /* Prevent interrupts while cleaning up */ + HOLD_INTERRUPTS(); + + /* Report the error and clear ErrorContext for next time */ + EmitErrorReport(); + FlushErrorState(); + + /* Close any files left open by copy_file() or compare_files() */ + AtEOSubXact_Files(false, InvalidSubTransactionId, InvalidSubTransactionId); + + /* Reset our memory context and switch back to the original one */ + MemoryContextSwitchTo(oldcontext); + MemoryContextReset(basic_archive_context); + + /* Remove our exception handler */ + PG_exception_stack = NULL; + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* Report failure so that the archiver retries this file */ + return false; + } + + /* Enable our exception handler */ + PG_exception_stack = &local_sigjmp_buf; + + /* Archive the file! */ + basic_archive_file_internal(file, path); + + /* Remove our exception handler */ + PG_exception_stack = NULL; + + /* Reset our memory context and switch back to the original one */ + MemoryContextSwitchTo(oldcontext); + MemoryContextReset(basic_archive_context); + + return true; +} + +static void +basic_archive_file_internal(const char *file, const char *path) +{ + char destination[MAXPGPATH]; + char temp[MAXPGPATH + 256]; + struct stat st; + struct timeval tv; + uint64 epoch; /* milliseconds */ + + ereport(DEBUG3, + (errmsg("archiving \"%s\" via basic_archive", file))); + + snprintf(destination, MAXPGPATH, "%s/%s", archive_directory, file); + + /* + * First, check if the file has already been archived. If it already + * exists and has the same contents as the file we're trying to archive, + * we can return success (after ensuring the file is persisted to disk). + * This scenario is possible if the server crashed after archiving the + * file but before renaming its .ready file to .done. + * + * If the archive file already exists but has different contents, + * something might be wrong, so we just fail. + */ + if (stat(destination, &st) == 0) + { + if (compare_files(path, destination)) + { + ereport(DEBUG3, + (errmsg("archive file \"%s\" already exists with identical contents", + destination))); + + fsync_fname(destination, false); + fsync_fname(archive_directory, true); + + return; + } + + ereport(ERROR, + (errmsg("archive file \"%s\" already exists", destination))); + } + else if (errno != ENOENT) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", destination))); + + /* + * Pick a sufficiently unique name for the temporary file so that a + * collision is unlikely. This helps avoid problems in case a temporary + * file was left around after a crash or another server happens to be + * archiving to the same directory. + */ + gettimeofday(&tv, NULL); + if (pg_mul_u64_overflow((uint64) 1000, (uint64) tv.tv_sec, &epoch) || + pg_add_u64_overflow(epoch, (uint64) (tv.tv_usec / 1000), &epoch)) + elog(ERROR, "could not generate temporary file name for archiving"); + + snprintf(temp, sizeof(temp), "%s/%s.%s.%d." UINT64_FORMAT, + archive_directory, "archtemp", file, MyProcPid, epoch); + + /* + * Copy the file to its temporary destination. Note that this will fail + * if temp already exists. + */ + copy_file(path, temp); + + /* + * Sync the temporary file to disk and move it to its final destination. + * Note that this will overwrite any existing file, but this is only + * possible if someone else created the file since the stat() above. + */ + (void) durable_rename(temp, destination, ERROR); + + ereport(DEBUG1, + (errmsg("archived \"%s\" via basic_archive", file))); +} + +/* + * compare_files + * + * Returns whether the contents of the files are the same. + */ +static bool +compare_files(const char *file1, const char *file2) +{ +#define CMP_BUF_SIZE (4096) + char buf1[CMP_BUF_SIZE]; + char buf2[CMP_BUF_SIZE]; + int fd1; + int fd2; + bool ret = true; + + fd1 = OpenTransientFile(file1, O_RDONLY | PG_BINARY); + if (fd1 < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", file1))); + + fd2 = OpenTransientFile(file2, O_RDONLY | PG_BINARY); + if (fd2 < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", file2))); + + for (;;) + { + int nbytes = 0; + int buf1_len = 0; + int buf2_len = 0; + + while (buf1_len < CMP_BUF_SIZE) + { + nbytes = read(fd1, buf1 + buf1_len, CMP_BUF_SIZE - buf1_len); + if (nbytes < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", file1))); + else if (nbytes == 0) + break; + + buf1_len += nbytes; + } + + while (buf2_len < CMP_BUF_SIZE) + { + nbytes = read(fd2, buf2 + buf2_len, CMP_BUF_SIZE - buf2_len); + if (nbytes < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", file2))); + else if (nbytes == 0) + break; + + buf2_len += nbytes; + } + + if (buf1_len != buf2_len || memcmp(buf1, buf2, buf1_len) != 0) + { + ret = false; + break; + } + else if (buf1_len == 0) + break; + } + + if (CloseTransientFile(fd1) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not close file \"%s\": %m", file1))); + + if (CloseTransientFile(fd2) != 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not close file \"%s\": %m", file2))); + + return ret; +} + +/* + * basic_archive_shutdown + * + * Frees our allocated state. + */ +static void +basic_archive_shutdown(ArchiveModuleState *state) +{ + BasicArchiveData *data = (BasicArchiveData *) state->private_data; + MemoryContext basic_archive_context; + + /* + * If we didn't get to storing the pointer to our allocated state, we + * don't have anything to clean up. + */ + if (data == NULL) + return; + + basic_archive_context = data->context; + Assert(CurrentMemoryContext != basic_archive_context); + + if (MemoryContextIsValid(basic_archive_context)) + MemoryContextDelete(basic_archive_context); + data->context = NULL; + + /* + * Finally, free the state. + */ + pfree(data); + state->private_data = NULL; +} diff --git a/contrib/basic_archive/basic_archive.conf b/contrib/basic_archive/basic_archive.conf new file mode 100644 index 0000000..7c82a4b --- /dev/null +++ b/contrib/basic_archive/basic_archive.conf @@ -0,0 +1,4 @@ +archive_mode = on +archive_library = 'basic_archive' +basic_archive.archive_directory = '.' +wal_level = replica diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out new file mode 100644 index 0000000..0015053 --- /dev/null +++ b/contrib/basic_archive/expected/basic_archive.out @@ -0,0 +1,29 @@ +CREATE TABLE test (a INT); +SELECT 1 FROM pg_switch_wal(); + ?column? +---------- + 1 +(1 row) + +DO $$ +DECLARE + archived bool; + loops int := 0; +BEGIN + LOOP + archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a + WHERE a ~ '^[0-9A-F]{24}$'; + IF archived OR loops > 120 * 10 THEN EXIT; END IF; + PERFORM pg_sleep(0.1); + loops := loops + 1; + END LOOP; +END +$$; +SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a + WHERE a ~ '^[0-9A-F]{24}$'; + ?column? +---------- + t +(1 row) + +DROP TABLE test; diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build new file mode 100644 index 0000000..bc1380e --- /dev/null +++ b/contrib/basic_archive/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +basic_archive_sources = files( + 'basic_archive.c', +) + +if host_system == 'windows' + basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'basic_archive', + '--FILEDESC', 'basic_archive - basic archive module',]) +endif + +basic_archive = shared_module('basic_archive', + basic_archive_sources, + kwargs: contrib_mod_args, +) +contrib_targets += basic_archive + +tests += { + 'name': 'basic_archive', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'basic_archive', + ], + 'regress_args': [ + '--temp-config', files('basic_archive.conf'), + ], + # Disabled because these tests require "shared_preload_libraries=basic_archive", + # which typical runningcheck users do not have (e.g. buildfarm clients). + 'runningcheck': false, + }, +} diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql new file mode 100644 index 0000000..14e236d --- /dev/null +++ b/contrib/basic_archive/sql/basic_archive.sql @@ -0,0 +1,22 @@ +CREATE TABLE test (a INT); +SELECT 1 FROM pg_switch_wal(); + +DO $$ +DECLARE + archived bool; + loops int := 0; +BEGIN + LOOP + archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a + WHERE a ~ '^[0-9A-F]{24}$'; + IF archived OR loops > 120 * 10 THEN EXIT; END IF; + PERFORM pg_sleep(0.1); + loops := loops + 1; + END LOOP; +END +$$; + +SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a + WHERE a ~ '^[0-9A-F]{24}$'; + +DROP TABLE test; diff --git a/contrib/bloom/.gitignore b/contrib/bloom/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/bloom/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/bloom/Makefile b/contrib/bloom/Makefile new file mode 100644 index 0000000..8a781e4 --- /dev/null +++ b/contrib/bloom/Makefile @@ -0,0 +1,30 @@ +# contrib/bloom/Makefile + +MODULE_big = bloom +OBJS = \ + $(WIN32RES) \ + blcost.o \ + blinsert.o \ + blscan.o \ + blutils.o \ + blvacuum.o \ + blvalidate.o + +EXTENSION = bloom +DATA = bloom--1.0.sql +PGFILEDESC = "bloom access method - signature file based index" + +REGRESS = bloom + +TAP_TESTS = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/bloom +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/bloom/blcost.c b/contrib/bloom/blcost.c new file mode 100644 index 0000000..288e8ed --- /dev/null +++ b/contrib/bloom/blcost.c @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------- + * + * blcost.c + * Cost estimate function for bloom indexes. + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/bloom/blcost.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "bloom.h" +#include "fmgr.h" +#include "utils/selfuncs.h" + +/* + * Estimate cost of bloom index scan. + */ +void +blcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, + Cost *indexStartupCost, Cost *indexTotalCost, + Selectivity *indexSelectivity, double *indexCorrelation, + double *indexPages) +{ + IndexOptInfo *index = path->indexinfo; + GenericCosts costs = {0}; + + /* We have to visit all index tuples anyway */ + costs.numIndexTuples = index->tuples; + + /* Use generic estimate */ + genericcostestimate(root, path, loop_count, &costs); + + *indexStartupCost = costs.indexStartupCost; + *indexTotalCost = costs.indexTotalCost; + *indexSelectivity = costs.indexSelectivity; + *indexCorrelation = costs.indexCorrelation; + *indexPages = costs.numIndexPages; +} diff --git a/contrib/bloom/blinsert.c b/contrib/bloom/blinsert.c new file mode 100644 index 0000000..b901451 --- /dev/null +++ b/contrib/bloom/blinsert.c @@ -0,0 +1,341 @@ +/*------------------------------------------------------------------------- + * + * blinsert.c + * Bloom index build and insert functions. + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/bloom/blinsert.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/generic_xlog.h" +#include "access/tableam.h" +#include "bloom.h" +#include "catalog/index.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/indexfsm.h" +#include "storage/smgr.h" +#include "utils/memutils.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +/* + * State of bloom index build. We accumulate one page data here before + * flushing it to buffer manager. + */ +typedef struct +{ + BloomState blstate; /* bloom index state */ + int64 indtuples; /* total number of tuples indexed */ + MemoryContext tmpCtx; /* temporary memory context reset after each + * tuple */ + PGAlignedBlock data; /* cached page */ + int count; /* number of tuples in cached page */ +} BloomBuildState; + +/* + * Flush page cached in BloomBuildState. + */ +static void +flushCachedPage(Relation index, BloomBuildState *buildstate) +{ + Page page; + Buffer buffer = BloomNewBuffer(index); + GenericXLogState *state; + + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buffer, GENERIC_XLOG_FULL_IMAGE); + memcpy(page, buildstate->data.data, BLCKSZ); + GenericXLogFinish(state); + UnlockReleaseBuffer(buffer); +} + +/* + * (Re)initialize cached page in BloomBuildState. + */ +static void +initCachedPage(BloomBuildState *buildstate) +{ + BloomInitPage(buildstate->data.data, 0); + buildstate->count = 0; +} + +/* + * Per-tuple callback for table_index_build_scan. + */ +static void +bloomBuildCallback(Relation index, ItemPointer tid, Datum *values, + bool *isnull, bool tupleIsAlive, void *state) +{ + BloomBuildState *buildstate = (BloomBuildState *) state; + MemoryContext oldCtx; + BloomTuple *itup; + + oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx); + + itup = BloomFormTuple(&buildstate->blstate, tid, values, isnull); + + /* Try to add next item to cached page */ + if (BloomPageAddItem(&buildstate->blstate, buildstate->data.data, itup)) + { + /* Next item was added successfully */ + buildstate->count++; + } + else + { + /* Cached page is full, flush it out and make a new one */ + flushCachedPage(index, buildstate); + + CHECK_FOR_INTERRUPTS(); + + initCachedPage(buildstate); + + if (!BloomPageAddItem(&buildstate->blstate, buildstate->data.data, itup)) + { + /* We shouldn't be here since we're inserting to the empty page */ + elog(ERROR, "could not add new bloom tuple to empty page"); + } + + /* Next item was added successfully */ + buildstate->count++; + } + + /* Update total tuple count */ + buildstate->indtuples += 1; + + MemoryContextSwitchTo(oldCtx); + MemoryContextReset(buildstate->tmpCtx); +} + +/* + * Build a new bloom index. + */ +IndexBuildResult * +blbuild(Relation heap, Relation index, IndexInfo *indexInfo) +{ + IndexBuildResult *result; + double reltuples; + BloomBuildState buildstate; + + if (RelationGetNumberOfBlocks(index) != 0) + elog(ERROR, "index \"%s\" already contains data", + RelationGetRelationName(index)); + + /* Initialize the meta page */ + BloomInitMetapage(index, MAIN_FORKNUM); + + /* Initialize the bloom build state */ + memset(&buildstate, 0, sizeof(buildstate)); + initBloomState(&buildstate.blstate, index); + buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext, + "Bloom build temporary context", + ALLOCSET_DEFAULT_SIZES); + initCachedPage(&buildstate); + + /* Do the heap scan */ + reltuples = table_index_build_scan(heap, index, indexInfo, true, true, + bloomBuildCallback, (void *) &buildstate, + NULL); + + /* Flush last page if needed (it will be, unless heap was empty) */ + if (buildstate.count > 0) + flushCachedPage(index, &buildstate); + + MemoryContextDelete(buildstate.tmpCtx); + + result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult)); + result->heap_tuples = reltuples; + result->index_tuples = buildstate.indtuples; + + return result; +} + +/* + * Build an empty bloom index in the initialization fork. + */ +void +blbuildempty(Relation index) +{ + /* Initialize the meta page */ + BloomInitMetapage(index, INIT_FORKNUM); +} + +/* + * Insert new tuple to the bloom index. + */ +bool +blinsert(Relation index, Datum *values, bool *isnull, + ItemPointer ht_ctid, Relation heapRel, + IndexUniqueCheck checkUnique, + bool indexUnchanged, + IndexInfo *indexInfo) +{ + BloomState blstate; + BloomTuple *itup; + MemoryContext oldCtx; + MemoryContext insertCtx; + BloomMetaPageData *metaData; + Buffer buffer, + metaBuffer; + Page page, + metaPage; + BlockNumber blkno = InvalidBlockNumber; + OffsetNumber nStart; + GenericXLogState *state; + + insertCtx = AllocSetContextCreate(CurrentMemoryContext, + "Bloom insert temporary context", + ALLOCSET_DEFAULT_SIZES); + + oldCtx = MemoryContextSwitchTo(insertCtx); + + initBloomState(&blstate, index); + itup = BloomFormTuple(&blstate, ht_ctid, values, isnull); + + /* + * At first, try to insert new tuple to the first page in notFullPage + * array. If successful, we don't need to modify the meta page. + */ + metaBuffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO); + LockBuffer(metaBuffer, BUFFER_LOCK_SHARE); + metaData = BloomPageGetMeta(BufferGetPage(metaBuffer)); + + if (metaData->nEnd > metaData->nStart) + { + blkno = metaData->notFullPage[metaData->nStart]; + Assert(blkno != InvalidBlockNumber); + + /* Don't hold metabuffer lock while doing insert */ + LockBuffer(metaBuffer, BUFFER_LOCK_UNLOCK); + + buffer = ReadBuffer(index, blkno); + LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + + state = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(state, buffer, 0); + + /* + * We might have found a page that was recently deleted by VACUUM. If + * so, we can reuse it, but we must reinitialize it. + */ + if (PageIsNew(page) || BloomPageIsDeleted(page)) + BloomInitPage(page, 0); + + if (BloomPageAddItem(&blstate, page, itup)) + { + /* Success! Apply the change, clean up, and exit */ + GenericXLogFinish(state); + UnlockReleaseBuffer(buffer); + ReleaseBuffer(metaBuffer); + MemoryContextSwitchTo(oldCtx); + MemoryContextDelete(insertCtx); + return false; + } + + /* Didn't fit, must try other pages */ + GenericXLogAbort(state); + UnlockReleaseBuffer(buffer); + } + else + { + /* No entries in notFullPage */ + LockBuffer(metaBuffer, BUFFER_LOCK_UNLOCK); + } + + /* + * Try other pages in notFullPage array. We will have to change nStart in + * metapage. Thus, grab exclusive lock on metapage. + */ + LockBuffer(metaBuffer, BUFFER_LOCK_EXCLUSIVE); + + /* nStart might have changed while we didn't have lock */ + nStart = metaData->nStart; + + /* Skip first page if we already tried it above */ + if (nStart < metaData->nEnd && + blkno == metaData->notFullPage[nStart]) + nStart++; + + /* + * This loop iterates for each page we try from the notFullPage array, and + * will also initialize a GenericXLogState for the fallback case of having + * to allocate a new page. + */ + for (;;) + { + state = GenericXLogStart(index); + + /* get modifiable copy of metapage */ + metaPage = GenericXLogRegisterBuffer(state, metaBuffer, 0); + metaData = BloomPageGetMeta(metaPage); + + if (nStart >= metaData->nEnd) + break; /* no more entries in notFullPage array */ + + blkno = metaData->notFullPage[nStart]; + Assert(blkno != InvalidBlockNumber); + + buffer = ReadBuffer(index, blkno); + LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + page = GenericXLogRegisterBuffer(state, buffer, 0); + + /* Basically same logic as above */ + if (PageIsNew(page) || BloomPageIsDeleted(page)) + BloomInitPage(page, 0); + + if (BloomPageAddItem(&blstate, page, itup)) + { + /* Success! Apply the changes, clean up, and exit */ + metaData->nStart = nStart; + GenericXLogFinish(state); + UnlockReleaseBuffer(buffer); + UnlockReleaseBuffer(metaBuffer); + MemoryContextSwitchTo(oldCtx); + MemoryContextDelete(insertCtx); + return false; + } + + /* Didn't fit, must try other pages */ + GenericXLogAbort(state); + UnlockReleaseBuffer(buffer); + nStart++; + } + + /* + * Didn't find place to insert in notFullPage array. Allocate new page. + * (XXX is it good to do this while holding ex-lock on the metapage??) + */ + buffer = BloomNewBuffer(index); + + page = GenericXLogRegisterBuffer(state, buffer, GENERIC_XLOG_FULL_IMAGE); + BloomInitPage(page, 0); + + if (!BloomPageAddItem(&blstate, page, itup)) + { + /* We shouldn't be here since we're inserting to an empty page */ + elog(ERROR, "could not add new bloom tuple to empty page"); + } + + /* Reset notFullPage array to contain just this new page */ + metaData->nStart = 0; + metaData->nEnd = 1; + metaData->notFullPage[0] = BufferGetBlockNumber(buffer); + + /* Apply the changes, clean up, and exit */ + GenericXLogFinish(state); + + UnlockReleaseBuffer(buffer); + UnlockReleaseBuffer(metaBuffer); + + MemoryContextSwitchTo(oldCtx); + MemoryContextDelete(insertCtx); + + return false; +} diff --git a/contrib/bloom/bloom--1.0.sql b/contrib/bloom/bloom--1.0.sql new file mode 100644 index 0000000..4e7c922 --- /dev/null +++ b/contrib/bloom/bloom--1.0.sql @@ -0,0 +1,25 @@ +/* contrib/bloom/bloom--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION bloom" to load this file. \quit + +CREATE FUNCTION blhandler(internal) +RETURNS index_am_handler +AS 'MODULE_PATHNAME' +LANGUAGE C; + +-- Access method +CREATE ACCESS METHOD bloom TYPE INDEX HANDLER blhandler; +COMMENT ON ACCESS METHOD bloom IS 'bloom index access method'; + +-- Opclasses + +CREATE OPERATOR CLASS int4_ops +DEFAULT FOR TYPE int4 USING bloom AS + OPERATOR 1 =(int4, int4), + FUNCTION 1 hashint4(int4); + +CREATE OPERATOR CLASS text_ops +DEFAULT FOR TYPE text USING bloom AS + OPERATOR 1 =(text, text), + FUNCTION 1 hashtext(text); diff --git a/contrib/bloom/bloom.control b/contrib/bloom/bloom.control new file mode 100644 index 0000000..4d4124b --- /dev/null +++ b/contrib/bloom/bloom.control @@ -0,0 +1,5 @@ +# bloom extension +comment = 'bloom access method - signature file based index' +default_version = '1.0' +module_pathname = '$libdir/bloom' +relocatable = true diff --git a/contrib/bloom/bloom.h b/contrib/bloom/bloom.h new file mode 100644 index 0000000..330811e --- /dev/null +++ b/contrib/bloom/bloom.h @@ -0,0 +1,215 @@ +/*------------------------------------------------------------------------- + * + * bloom.h + * Header for bloom index. + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/bloom/bloom.h + * + *------------------------------------------------------------------------- + */ +#ifndef _BLOOM_H_ +#define _BLOOM_H_ + +#include "access/amapi.h" +#include "access/generic_xlog.h" +#include "access/itup.h" +#include "access/xlog.h" +#include "fmgr.h" +#include "nodes/pathnodes.h" + +/* Support procedures numbers */ +#define BLOOM_HASH_PROC 1 +#define BLOOM_OPTIONS_PROC 2 +#define BLOOM_NPROC 2 + +/* Scan strategies */ +#define BLOOM_EQUAL_STRATEGY 1 +#define BLOOM_NSTRATEGIES 1 + +/* Opaque for bloom pages */ +typedef struct BloomPageOpaqueData +{ + OffsetNumber maxoff; /* number of index tuples on page */ + uint16 flags; /* see bit definitions below */ + uint16 unused; /* placeholder to force maxaligning of size of + * BloomPageOpaqueData and to place + * bloom_page_id exactly at the end of page */ + uint16 bloom_page_id; /* for identification of BLOOM indexes */ +} BloomPageOpaqueData; + +typedef BloomPageOpaqueData *BloomPageOpaque; + +/* Bloom page flags */ +#define BLOOM_META (1<<0) +#define BLOOM_DELETED (2<<0) + +/* + * The page ID is for the convenience of pg_filedump and similar utilities, + * which otherwise would have a hard time telling pages of different index + * types apart. It should be the last 2 bytes on the page. This is more or + * less "free" due to alignment considerations. + * + * See comments above GinPageOpaqueData. + */ +#define BLOOM_PAGE_ID 0xFF83 + +/* Macros for accessing bloom page structures */ +#define BloomPageGetOpaque(page) ((BloomPageOpaque) PageGetSpecialPointer(page)) +#define BloomPageGetMaxOffset(page) (BloomPageGetOpaque(page)->maxoff) +#define BloomPageIsMeta(page) \ + ((BloomPageGetOpaque(page)->flags & BLOOM_META) != 0) +#define BloomPageIsDeleted(page) \ + ((BloomPageGetOpaque(page)->flags & BLOOM_DELETED) != 0) +#define BloomPageSetDeleted(page) \ + (BloomPageGetOpaque(page)->flags |= BLOOM_DELETED) +#define BloomPageSetNonDeleted(page) \ + (BloomPageGetOpaque(page)->flags &= ~BLOOM_DELETED) +#define BloomPageGetData(page) ((BloomTuple *)PageGetContents(page)) +#define BloomPageGetTuple(state, page, offset) \ + ((BloomTuple *)(PageGetContents(page) \ + + (state)->sizeOfBloomTuple * ((offset) - 1))) +#define BloomPageGetNextTuple(state, tuple) \ + ((BloomTuple *)((Pointer)(tuple) + (state)->sizeOfBloomTuple)) + +/* Preserved page numbers */ +#define BLOOM_METAPAGE_BLKNO (0) +#define BLOOM_HEAD_BLKNO (1) /* first data page */ + +/* + * We store Bloom signatures as arrays of uint16 words. + */ +typedef uint16 BloomSignatureWord; + +#define SIGNWORDBITS ((int) (BITS_PER_BYTE * sizeof(BloomSignatureWord))) + +/* + * Default and maximum Bloom signature length in bits. + */ +#define DEFAULT_BLOOM_LENGTH (5 * SIGNWORDBITS) +#define MAX_BLOOM_LENGTH (256 * SIGNWORDBITS) + +/* + * Default and maximum signature bits generated per index key. + */ +#define DEFAULT_BLOOM_BITS 2 +#define MAX_BLOOM_BITS (MAX_BLOOM_LENGTH - 1) + +/* Bloom index options */ +typedef struct BloomOptions +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int bloomLength; /* length of signature in words (not bits!) */ + int bitSize[INDEX_MAX_KEYS]; /* # of bits generated for each + * index key */ +} BloomOptions; + +/* + * FreeBlockNumberArray - array of block numbers sized so that metadata fill + * all space in metapage. + */ +typedef BlockNumber FreeBlockNumberArray[ + MAXALIGN_DOWN( + BLCKSZ - SizeOfPageHeaderData - MAXALIGN(sizeof(BloomPageOpaqueData)) + - MAXALIGN(sizeof(uint16) * 2 + sizeof(uint32) + sizeof(BloomOptions)) + ) / sizeof(BlockNumber) +]; + +/* Metadata of bloom index */ +typedef struct BloomMetaPageData +{ + uint32 magickNumber; + uint16 nStart; + uint16 nEnd; + BloomOptions opts; + FreeBlockNumberArray notFullPage; +} BloomMetaPageData; + +/* Magic number to distinguish bloom pages among anothers */ +#define BLOOM_MAGICK_NUMBER (0xDBAC0DED) + +/* Number of blocks numbers fit in BloomMetaPageData */ +#define BloomMetaBlockN (sizeof(FreeBlockNumberArray) / sizeof(BlockNumber)) + +#define BloomPageGetMeta(page) ((BloomMetaPageData *) PageGetContents(page)) + +typedef struct BloomState +{ + FmgrInfo hashFn[INDEX_MAX_KEYS]; + Oid collations[INDEX_MAX_KEYS]; + BloomOptions opts; /* copy of options on index's metapage */ + int32 nColumns; + + /* + * sizeOfBloomTuple is index-specific, and it depends on reloptions, so + * precompute it + */ + Size sizeOfBloomTuple; +} BloomState; + +#define BloomPageGetFreeSpace(state, page) \ + (BLCKSZ - MAXALIGN(SizeOfPageHeaderData) \ + - BloomPageGetMaxOffset(page) * (state)->sizeOfBloomTuple \ + - MAXALIGN(sizeof(BloomPageOpaqueData))) + +/* + * Tuples are very different from all other relations + */ +typedef struct BloomTuple +{ + ItemPointerData heapPtr; + BloomSignatureWord sign[FLEXIBLE_ARRAY_MEMBER]; +} BloomTuple; + +#define BLOOMTUPLEHDRSZ offsetof(BloomTuple, sign) + +/* Opaque data structure for bloom index scan */ +typedef struct BloomScanOpaqueData +{ + BloomSignatureWord *sign; /* Scan signature */ + BloomState state; +} BloomScanOpaqueData; + +typedef BloomScanOpaqueData *BloomScanOpaque; + +/* blutils.c */ +extern void initBloomState(BloomState *state, Relation index); +extern void BloomFillMetapage(Relation index, Page metaPage); +extern void BloomInitMetapage(Relation index, ForkNumber forknum); +extern void BloomInitPage(Page page, uint16 flags); +extern Buffer BloomNewBuffer(Relation index); +extern void signValue(BloomState *state, BloomSignatureWord *sign, Datum value, int attno); +extern BloomTuple *BloomFormTuple(BloomState *state, ItemPointer iptr, Datum *values, bool *isnull); +extern bool BloomPageAddItem(BloomState *state, Page page, BloomTuple *tuple); + +/* blvalidate.c */ +extern bool blvalidate(Oid opclassoid); + +/* index access method interface functions */ +extern bool blinsert(Relation index, Datum *values, bool *isnull, + ItemPointer ht_ctid, Relation heapRel, + IndexUniqueCheck checkUnique, + bool indexUnchanged, + struct IndexInfo *indexInfo); +extern IndexScanDesc blbeginscan(Relation r, int nkeys, int norderbys); +extern int64 blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm); +extern void blrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, + ScanKey orderbys, int norderbys); +extern void blendscan(IndexScanDesc scan); +extern IndexBuildResult *blbuild(Relation heap, Relation index, + struct IndexInfo *indexInfo); +extern void blbuildempty(Relation index); +extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, + IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, + void *callback_state); +extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, + IndexBulkDeleteResult *stats); +extern bytea *bloptions(Datum reloptions, bool validate); +extern void blcostestimate(PlannerInfo *root, IndexPath *path, + double loop_count, Cost *indexStartupCost, + Cost *indexTotalCost, Selectivity *indexSelectivity, + double *indexCorrelation, double *indexPages); + +#endif diff --git a/contrib/bloom/blscan.c b/contrib/bloom/blscan.c new file mode 100644 index 0000000..6cc7d07 --- /dev/null +++ b/contrib/bloom/blscan.c @@ -0,0 +1,172 @@ +/*------------------------------------------------------------------------- + * + * blscan.c + * Bloom index scan functions. + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/bloom/blscan.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/relscan.h" +#include "bloom.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "storage/bufmgr.h" +#include "storage/lmgr.h" +#include "utils/memutils.h" +#include "utils/rel.h" + +/* + * Begin scan of bloom index. + */ +IndexScanDesc +blbeginscan(Relation r, int nkeys, int norderbys) +{ + IndexScanDesc scan; + BloomScanOpaque so; + + scan = RelationGetIndexScan(r, nkeys, norderbys); + + so = (BloomScanOpaque) palloc(sizeof(BloomScanOpaqueData)); + initBloomState(&so->state, scan->indexRelation); + so->sign = NULL; + + scan->opaque = so; + + return scan; +} + +/* + * Rescan a bloom index. + */ +void +blrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, + ScanKey orderbys, int norderbys) +{ + BloomScanOpaque so = (BloomScanOpaque) scan->opaque; + + if (so->sign) + pfree(so->sign); + so->sign = NULL; + + if (scankey && scan->numberOfKeys > 0) + { + memmove(scan->keyData, scankey, + scan->numberOfKeys * sizeof(ScanKeyData)); + } +} + +/* + * End scan of bloom index. + */ +void +blendscan(IndexScanDesc scan) +{ + BloomScanOpaque so = (BloomScanOpaque) scan->opaque; + + if (so->sign) + pfree(so->sign); + so->sign = NULL; +} + +/* + * Insert all matching tuples into a bitmap. + */ +int64 +blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm) +{ + int64 ntids = 0; + BlockNumber blkno = BLOOM_HEAD_BLKNO, + npages; + int i; + BufferAccessStrategy bas; + BloomScanOpaque so = (BloomScanOpaque) scan->opaque; + + if (so->sign == NULL) + { + /* New search: have to calculate search signature */ + ScanKey skey = scan->keyData; + + so->sign = palloc0(sizeof(BloomSignatureWord) * so->state.opts.bloomLength); + + for (i = 0; i < scan->numberOfKeys; i++) + { + /* + * Assume bloom-indexable operators to be strict, so nothing could + * be found for NULL key. + */ + if (skey->sk_flags & SK_ISNULL) + { + pfree(so->sign); + so->sign = NULL; + return 0; + } + + /* Add next value to the signature */ + signValue(&so->state, so->sign, skey->sk_argument, + skey->sk_attno - 1); + + skey++; + } + } + + /* + * We're going to read the whole index. This is why we use appropriate + * buffer access strategy. + */ + bas = GetAccessStrategy(BAS_BULKREAD); + npages = RelationGetNumberOfBlocks(scan->indexRelation); + + for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++) + { + Buffer buffer; + Page page; + + buffer = ReadBufferExtended(scan->indexRelation, MAIN_FORKNUM, + blkno, RBM_NORMAL, bas); + + LockBuffer(buffer, BUFFER_LOCK_SHARE); + page = BufferGetPage(buffer); + TestForOldSnapshot(scan->xs_snapshot, scan->indexRelation, page); + + if (!PageIsNew(page) && !BloomPageIsDeleted(page)) + { + OffsetNumber offset, + maxOffset = BloomPageGetMaxOffset(page); + + for (offset = 1; offset <= maxOffset; offset++) + { + BloomTuple *itup = BloomPageGetTuple(&so->state, page, offset); + bool res = true; + + /* Check index signature with scan signature */ + for (i = 0; i < so->state.opts.bloomLength; i++) + { + if ((itup->sign[i] & so->sign[i]) != so->sign[i]) + { + res = false; + break; + } + } + + /* Add matching tuples to bitmap */ + if (res) + { + tbm_add_tuples(tbm, &itup->heapPtr, 1, true); + ntids++; + } + } + } + + UnlockReleaseBuffer(buffer); + CHECK_FOR_INTERRUPTS(); + } + FreeAccessStrategy(bas); + + return ntids; +} diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c new file mode 100644 index 0000000..f23fbb1 --- /dev/null +++ b/contrib/bloom/blutils.c @@ -0,0 +1,491 @@ +/*------------------------------------------------------------------------- + * + * blutils.c + * Bloom index utilities. + * + * Portions Copyright (c) 2016-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1990-1993, Regents of the University of California + * + * IDENTIFICATION + * contrib/bloom/blutils.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/amapi.h" +#include "access/generic_xlog.h" +#include "access/reloptions.h" +#include "bloom.h" +#include "catalog/index.h" +#include "commands/vacuum.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/freespace.h" +#include "storage/indexfsm.h" +#include "storage/lmgr.h" +#include "utils/memutils.h" + +/* Signature dealing macros - note i is assumed to be of type int */ +#define GETWORD(x,i) ( *( (BloomSignatureWord *)(x) + ( (i) / SIGNWORDBITS ) ) ) +#define CLRBIT(x,i) GETWORD(x,i) &= ~( 0x01 << ( (i) % SIGNWORDBITS ) ) +#define SETBIT(x,i) GETWORD(x,i) |= ( 0x01 << ( (i) % SIGNWORDBITS ) ) +#define GETBIT(x,i) ( (GETWORD(x,i) >> ( (i) % SIGNWORDBITS )) & 0x01 ) + +PG_FUNCTION_INFO_V1(blhandler); + +/* Kind of relation options for bloom index */ +static relopt_kind bl_relopt_kind; + +/* parse table for fillRelOptions */ +static relopt_parse_elt bl_relopt_tab[INDEX_MAX_KEYS + 1]; + +static int32 myRand(void); +static void mySrand(uint32 seed); + +/* + * Module initialize function: initialize info about Bloom relation options. + * + * Note: keep this in sync with makeDefaultBloomOptions(). + */ +void +_PG_init(void) +{ + int i; + char buf[16]; + + bl_relopt_kind = add_reloption_kind(); + + /* Option for length of signature */ + add_int_reloption(bl_relopt_kind, "length", + "Length of signature in bits", + DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH, + AccessExclusiveLock); + bl_relopt_tab[0].optname = "length"; + bl_relopt_tab[0].opttype = RELOPT_TYPE_INT; + bl_relopt_tab[0].offset = offsetof(BloomOptions, bloomLength); + + /* Number of bits for each possible index column: col1, col2, ... */ + for (i = 0; i < INDEX_MAX_KEYS; i++) + { + snprintf(buf, sizeof(buf), "col%d", i + 1); + add_int_reloption(bl_relopt_kind, buf, + "Number of bits generated for each index column", + DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS, + AccessExclusiveLock); + bl_relopt_tab[i + 1].optname = MemoryContextStrdup(TopMemoryContext, + buf); + bl_relopt_tab[i + 1].opttype = RELOPT_TYPE_INT; + bl_relopt_tab[i + 1].offset = offsetof(BloomOptions, bitSize[0]) + sizeof(int) * i; + } +} + +/* + * Construct a default set of Bloom options. + */ +static BloomOptions * +makeDefaultBloomOptions(void) +{ + BloomOptions *opts; + int i; + + opts = (BloomOptions *) palloc0(sizeof(BloomOptions)); + /* Convert DEFAULT_BLOOM_LENGTH from # of bits to # of words */ + opts->bloomLength = (DEFAULT_BLOOM_LENGTH + SIGNWORDBITS - 1) / SIGNWORDBITS; + for (i = 0; i < INDEX_MAX_KEYS; i++) + opts->bitSize[i] = DEFAULT_BLOOM_BITS; + SET_VARSIZE(opts, sizeof(BloomOptions)); + return opts; +} + +/* + * Bloom handler function: return IndexAmRoutine with access method parameters + * and callbacks. + */ +Datum +blhandler(PG_FUNCTION_ARGS) +{ + IndexAmRoutine *amroutine = makeNode(IndexAmRoutine); + + amroutine->amstrategies = BLOOM_NSTRATEGIES; + amroutine->amsupport = BLOOM_NPROC; + amroutine->amoptsprocnum = BLOOM_OPTIONS_PROC; + amroutine->amcanorder = false; + amroutine->amcanorderbyop = false; + amroutine->amcanbackward = false; + amroutine->amcanunique = false; + amroutine->amcanmulticol = true; + amroutine->amoptionalkey = true; + 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_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_CLEANUP; + amroutine->amkeytype = InvalidOid; + + amroutine->ambuild = blbuild; + amroutine->ambuildempty = blbuildempty; + amroutine->aminsert = blinsert; + amroutine->ambulkdelete = blbulkdelete; + amroutine->amvacuumcleanup = blvacuumcleanup; + amroutine->amcanreturn = NULL; + amroutine->amcostestimate = blcostestimate; + amroutine->amoptions = bloptions; + amroutine->amproperty = NULL; + amroutine->ambuildphasename = NULL; + amroutine->amvalidate = blvalidate; + amroutine->amadjustmembers = NULL; + amroutine->ambeginscan = blbeginscan; + amroutine->amrescan = blrescan; + amroutine->amgettuple = NULL; + amroutine->amgetbitmap = blgetbitmap; + amroutine->amendscan = blendscan; + amroutine->ammarkpos = NULL; + amroutine->amrestrpos = NULL; + amroutine->amestimateparallelscan = NULL; + amroutine->aminitparallelscan = NULL; + amroutine->amparallelrescan = NULL; + + PG_RETURN_POINTER(amroutine); +} + +/* + * Fill BloomState structure for particular index. + */ +void +initBloomState(BloomState *state, Relation index) +{ + int i; + + state->nColumns = index->rd_att->natts; + + /* Initialize hash function for each attribute */ + for (i = 0; i < index->rd_att->natts; i++) + { + fmgr_info_copy(&(state->hashFn[i]), + index_getprocinfo(index, i + 1, BLOOM_HASH_PROC), + CurrentMemoryContext); + state->collations[i] = index->rd_indcollation[i]; + } + + /* Initialize amcache if needed with options from metapage */ + if (!index->rd_amcache) + { + Buffer buffer; + Page page; + BloomMetaPageData *meta; + BloomOptions *opts; + + opts = MemoryContextAlloc(index->rd_indexcxt, sizeof(BloomOptions)); + + buffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + page = BufferGetPage(buffer); + + if (!BloomPageIsMeta(page)) + elog(ERROR, "Relation is not a bloom index"); + meta = BloomPageGetMeta(BufferGetPage(buffer)); + + if (meta->magickNumber != BLOOM_MAGICK_NUMBER) + elog(ERROR, "Relation is not a bloom index"); + + *opts = meta->opts; + + UnlockReleaseBuffer(buffer); + + index->rd_amcache = (void *) opts; + } + + memcpy(&state->opts, index->rd_amcache, sizeof(state->opts)); + state->sizeOfBloomTuple = BLOOMTUPLEHDRSZ + + sizeof(BloomSignatureWord) * state->opts.bloomLength; +} + +/* + * Random generator copied from FreeBSD. Using own random generator here for + * two reasons: + * + * 1) In this case random numbers are used for on-disk storage. Usage of + * PostgreSQL number generator would obstruct it from all possible changes. + * 2) Changing seed of PostgreSQL random generator would be undesirable side + * effect. + */ +static int32 next; + +static int32 +myRand(void) +{ + /*---------- + * Compute x = (7^5 * x) mod (2^31 - 1) + * without overflowing 31 bits: + * (2^31 - 1) = 127773 * (7^5) + 2836 + * From "Random number generators: good ones are hard to find", + * Park and Miller, Communications of the ACM, vol. 31, no. 10, + * October 1988, p. 1195. + *---------- + */ + int32 hi, + lo, + x; + + /* Must be in [1, 0x7ffffffe] range at this point. */ + hi = next / 127773; + lo = next % 127773; + x = 16807 * lo - 2836 * hi; + if (x < 0) + x += 0x7fffffff; + next = x; + /* Transform to [0, 0x7ffffffd] range. */ + return (x - 1); +} + +static void +mySrand(uint32 seed) +{ + next = seed; + /* Transform to [1, 0x7ffffffe] range. */ + next = (next % 0x7ffffffe) + 1; +} + +/* + * Add bits of given value to the signature. + */ +void +signValue(BloomState *state, BloomSignatureWord *sign, Datum value, int attno) +{ + uint32 hashVal; + int nBit, + j; + + /* + * init generator with "column's" number to get "hashed" seed for new + * value. We don't want to map the same numbers from different columns + * into the same bits! + */ + mySrand(attno); + + /* + * Init hash sequence to map our value into bits. the same values in + * different columns will be mapped into different bits because of step + * above + */ + hashVal = DatumGetInt32(FunctionCall1Coll(&state->hashFn[attno], state->collations[attno], value)); + mySrand(hashVal ^ myRand()); + + for (j = 0; j < state->opts.bitSize[attno]; j++) + { + /* prevent multiple evaluation in SETBIT macro */ + nBit = myRand() % (state->opts.bloomLength * SIGNWORDBITS); + SETBIT(sign, nBit); + } +} + +/* + * Make bloom tuple from values. + */ +BloomTuple * +BloomFormTuple(BloomState *state, ItemPointer iptr, Datum *values, bool *isnull) +{ + int i; + BloomTuple *res = (BloomTuple *) palloc0(state->sizeOfBloomTuple); + + res->heapPtr = *iptr; + + /* Blooming each column */ + for (i = 0; i < state->nColumns; i++) + { + /* skip nulls */ + if (isnull[i]) + continue; + + signValue(state, res->sign, values[i], i); + } + + return res; +} + +/* + * Add new bloom tuple to the page. Returns true if new tuple was successfully + * added to the page. Returns false if it doesn't fit on the page. + */ +bool +BloomPageAddItem(BloomState *state, Page page, BloomTuple *tuple) +{ + BloomTuple *itup; + BloomPageOpaque opaque; + Pointer ptr; + + /* We shouldn't be pointed to an invalid page */ + Assert(!PageIsNew(page) && !BloomPageIsDeleted(page)); + + /* Does new tuple fit on the page? */ + if (BloomPageGetFreeSpace(state, page) < state->sizeOfBloomTuple) + return false; + + /* Copy new tuple to the end of page */ + opaque = BloomPageGetOpaque(page); + itup = BloomPageGetTuple(state, page, opaque->maxoff + 1); + memcpy((Pointer) itup, (Pointer) tuple, state->sizeOfBloomTuple); + + /* Adjust maxoff and pd_lower */ + opaque->maxoff++; + ptr = (Pointer) BloomPageGetTuple(state, page, opaque->maxoff + 1); + ((PageHeader) page)->pd_lower = ptr - page; + + /* Assert we didn't overrun available space */ + Assert(((PageHeader) page)->pd_lower <= ((PageHeader) page)->pd_upper); + + return true; +} + +/* + * Allocate a new page (either by recycling, or by extending the index file) + * The returned buffer is already pinned and exclusive-locked + * Caller is responsible for initializing the page by calling BloomInitPage + */ +Buffer +BloomNewBuffer(Relation index) +{ + Buffer buffer; + + /* First, try to get a page from FSM */ + for (;;) + { + BlockNumber blkno = GetFreeIndexPage(index); + + if (blkno == InvalidBlockNumber) + break; + + buffer = ReadBuffer(index, blkno); + + /* + * We have to guard against the possibility that someone else already + * recycled this page; the buffer may be locked if so. + */ + if (ConditionalLockBuffer(buffer)) + { + Page page = BufferGetPage(buffer); + + if (PageIsNew(page)) + return buffer; /* OK to use, if never initialized */ + + if (BloomPageIsDeleted(page)) + return buffer; /* OK to use */ + + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + } + + /* Can't use it, so release buffer and try again */ + ReleaseBuffer(buffer); + } + + /* Must extend the file */ + buffer = ExtendBufferedRel(BMR_REL(index), MAIN_FORKNUM, NULL, + EB_LOCK_FIRST); + + return buffer; +} + +/* + * Initialize any page of a bloom index. + */ +void +BloomInitPage(Page page, uint16 flags) +{ + BloomPageOpaque opaque; + + PageInit(page, BLCKSZ, sizeof(BloomPageOpaqueData)); + + opaque = BloomPageGetOpaque(page); + opaque->flags = flags; + opaque->bloom_page_id = BLOOM_PAGE_ID; +} + +/* + * Fill in metapage for bloom index. + */ +void +BloomFillMetapage(Relation index, Page metaPage) +{ + BloomOptions *opts; + BloomMetaPageData *metadata; + + /* + * Choose the index's options. If reloptions have been assigned, use + * those, otherwise create default options. + */ + opts = (BloomOptions *) index->rd_options; + if (!opts) + opts = makeDefaultBloomOptions(); + + /* + * Initialize contents of meta page, including a copy of the options, + * which are now frozen for the life of the index. + */ + BloomInitPage(metaPage, BLOOM_META); + metadata = BloomPageGetMeta(metaPage); + memset(metadata, 0, sizeof(BloomMetaPageData)); + metadata->magickNumber = BLOOM_MAGICK_NUMBER; + metadata->opts = *opts; + ((PageHeader) metaPage)->pd_lower += sizeof(BloomMetaPageData); + + /* If this fails, probably FreeBlockNumberArray size calc is wrong: */ + Assert(((PageHeader) metaPage)->pd_lower <= ((PageHeader) metaPage)->pd_upper); +} + +/* + * Initialize metapage for bloom index. + */ +void +BloomInitMetapage(Relation index, ForkNumber forknum) +{ + Buffer metaBuffer; + Page metaPage; + GenericXLogState *state; + + /* + * Make a new page; since it is first page it should be associated with + * block number 0 (BLOOM_METAPAGE_BLKNO). No need to hold the extension + * lock because there cannot be concurrent inserters yet. + */ + metaBuffer = ReadBufferExtended(index, forknum, P_NEW, RBM_NORMAL, NULL); + LockBuffer(metaBuffer, BUFFER_LOCK_EXCLUSIVE); + Assert(BufferGetBlockNumber(metaBuffer) == BLOOM_METAPAGE_BLKNO); + + /* Initialize contents of meta page */ + state = GenericXLogStart(index); + metaPage = GenericXLogRegisterBuffer(state, metaBuffer, + GENERIC_XLOG_FULL_IMAGE); + BloomFillMetapage(index, metaPage); + GenericXLogFinish(state); + + UnlockReleaseBuffer(metaBuffer); +} + +/* + * Parse reloptions for bloom index, producing a BloomOptions struct. + */ +bytea * +bloptions(Datum reloptions, bool validate) +{ + BloomOptions *rdopts; + + /* Parse the user-given reloptions */ + rdopts = (BloomOptions *) build_reloptions(reloptions, validate, + bl_relopt_kind, + sizeof(BloomOptions), + bl_relopt_tab, + lengthof(bl_relopt_tab)); + + /* Convert signature length from # of bits to # to words, rounding up */ + if (rdopts) + rdopts->bloomLength = (rdopts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS; + + return (bytea *) rdopts; +} diff --git a/contrib/bloom/blvacuum.c b/contrib/bloom/blvacuum.c new file mode 100644 index 0000000..2340d49 --- /dev/null +++ b/contrib/bloom/blvacuum.c @@ -0,0 +1,217 @@ +/*------------------------------------------------------------------------- + * + * blvacuum.c + * Bloom VACUUM functions. + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/bloom/blvacuum.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "bloom.h" +#include "catalog/storage.h" +#include "commands/vacuum.h" +#include "miscadmin.h" +#include "postmaster/autovacuum.h" +#include "storage/bufmgr.h" +#include "storage/indexfsm.h" +#include "storage/lmgr.h" + + +/* + * Bulk deletion of all index entries pointing to a set of heap tuples. + * The set of target tuples is specified via a callback routine that tells + * whether any given heap tuple (identified by ItemPointer) is being deleted. + * + * Result: a palloc'd struct containing statistical info for VACUUM displays. + */ +IndexBulkDeleteResult * +blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, + IndexBulkDeleteCallback callback, void *callback_state) +{ + Relation index = info->index; + BlockNumber blkno, + npages; + FreeBlockNumberArray notFullPage; + int countPage = 0; + BloomState state; + Buffer buffer; + Page page; + BloomMetaPageData *metaData; + GenericXLogState *gxlogState; + + if (stats == NULL) + stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); + + initBloomState(&state, index); + + /* + * Iterate over the pages. We don't care about concurrently added pages, + * they can't contain tuples to delete. + */ + npages = RelationGetNumberOfBlocks(index); + for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++) + { + BloomTuple *itup, + *itupPtr, + *itupEnd; + + vacuum_delay_point(); + + buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno, + RBM_NORMAL, info->strategy); + + LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + gxlogState = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(gxlogState, buffer, 0); + + /* Ignore empty/deleted pages until blvacuumcleanup() */ + if (PageIsNew(page) || BloomPageIsDeleted(page)) + { + UnlockReleaseBuffer(buffer); + GenericXLogAbort(gxlogState); + continue; + } + + /* + * Iterate over the tuples. itup points to current tuple being + * scanned, itupPtr points to where to save next non-deleted tuple. + */ + itup = itupPtr = BloomPageGetTuple(&state, page, FirstOffsetNumber); + itupEnd = BloomPageGetTuple(&state, page, + OffsetNumberNext(BloomPageGetMaxOffset(page))); + while (itup < itupEnd) + { + /* Do we have to delete this tuple? */ + if (callback(&itup->heapPtr, callback_state)) + { + /* Yes; adjust count of tuples that will be left on page */ + BloomPageGetOpaque(page)->maxoff--; + stats->tuples_removed += 1; + } + else + { + /* No; copy it to itupPtr++, but skip copy if not needed */ + if (itupPtr != itup) + memmove((Pointer) itupPtr, (Pointer) itup, + state.sizeOfBloomTuple); + itupPtr = BloomPageGetNextTuple(&state, itupPtr); + } + + itup = BloomPageGetNextTuple(&state, itup); + } + + /* Assert that we counted correctly */ + Assert(itupPtr == BloomPageGetTuple(&state, page, + OffsetNumberNext(BloomPageGetMaxOffset(page)))); + + /* + * Add page to new notFullPage list if we will not mark page as + * deleted and there is free space on it + */ + if (BloomPageGetMaxOffset(page) != 0 && + BloomPageGetFreeSpace(&state, page) >= state.sizeOfBloomTuple && + countPage < BloomMetaBlockN) + notFullPage[countPage++] = blkno; + + /* Did we delete something? */ + if (itupPtr != itup) + { + /* Is it empty page now? */ + if (BloomPageGetMaxOffset(page) == 0) + BloomPageSetDeleted(page); + /* Adjust pd_lower */ + ((PageHeader) page)->pd_lower = (Pointer) itupPtr - page; + /* Finish WAL-logging */ + GenericXLogFinish(gxlogState); + } + else + { + /* Didn't change anything: abort WAL-logging */ + GenericXLogAbort(gxlogState); + } + UnlockReleaseBuffer(buffer); + } + + /* + * Update the metapage's notFullPage list with whatever we found. Our + * info could already be out of date at this point, but blinsert() will + * cope if so. + */ + buffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO); + LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + + gxlogState = GenericXLogStart(index); + page = GenericXLogRegisterBuffer(gxlogState, buffer, 0); + + metaData = BloomPageGetMeta(page); + memcpy(metaData->notFullPage, notFullPage, sizeof(BlockNumber) * countPage); + metaData->nStart = 0; + metaData->nEnd = countPage; + + GenericXLogFinish(gxlogState); + UnlockReleaseBuffer(buffer); + + return stats; +} + +/* + * Post-VACUUM cleanup. + * + * Result: a palloc'd struct containing statistical info for VACUUM displays. + */ +IndexBulkDeleteResult * +blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) +{ + Relation index = info->index; + BlockNumber npages, + blkno; + + if (info->analyze_only) + return stats; + + if (stats == NULL) + stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); + + /* + * Iterate over the pages: insert deleted pages into FSM and collect + * statistics. + */ + npages = RelationGetNumberOfBlocks(index); + stats->num_pages = npages; + stats->pages_free = 0; + stats->num_index_tuples = 0; + for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++) + { + Buffer buffer; + Page page; + + vacuum_delay_point(); + + buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno, + RBM_NORMAL, info->strategy); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + page = (Page) BufferGetPage(buffer); + + if (PageIsNew(page) || BloomPageIsDeleted(page)) + { + RecordFreeIndexPage(index, blkno); + stats->pages_free++; + } + else + { + stats->num_index_tuples += BloomPageGetMaxOffset(page); + } + + UnlockReleaseBuffer(buffer); + } + + IndexFreeSpaceMapVacuum(info->index); + + return stats; +} diff --git a/contrib/bloom/blvalidate.c b/contrib/bloom/blvalidate.c new file mode 100644 index 0000000..74bb3f8 --- /dev/null +++ b/contrib/bloom/blvalidate.c @@ -0,0 +1,225 @@ +/*------------------------------------------------------------------------- + * + * blvalidate.c + * Opclass validator for bloom. + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/bloom/blvalidate.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/amvalidate.h" +#include "access/htup_details.h" +#include "bloom.h" +#include "catalog/pg_amop.h" +#include "catalog/pg_amproc.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/regproc.h" +#include "utils/syscache.h" + +/* + * Validator for a bloom opclass. + */ +bool +blvalidate(Oid opclassoid) +{ + bool result = true; + HeapTuple classtup; + Form_pg_opclass classform; + Oid opfamilyoid; + Oid opcintype; + Oid opckeytype; + char *opclassname; + HeapTuple familytup; + Form_pg_opfamily familyform; + char *opfamilyname; + CatCList *proclist, + *oprlist; + List *grouplist; + OpFamilyOpFuncGroup *opclassgroup; + int i; + ListCell *lc; + + /* Fetch opclass information */ + classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid)); + if (!HeapTupleIsValid(classtup)) + elog(ERROR, "cache lookup failed for operator class %u", opclassoid); + classform = (Form_pg_opclass) GETSTRUCT(classtup); + + opfamilyoid = classform->opcfamily; + opcintype = classform->opcintype; + opckeytype = classform->opckeytype; + if (!OidIsValid(opckeytype)) + opckeytype = opcintype; + opclassname = NameStr(classform->opcname); + + /* Fetch opfamily information */ + familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid)); + if (!HeapTupleIsValid(familytup)) + elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid); + familyform = (Form_pg_opfamily) GETSTRUCT(familytup); + + opfamilyname = NameStr(familyform->opfname); + + /* Fetch all operators and support functions of the opfamily */ + oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid)); + proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid)); + + /* Check individual support functions */ + for (i = 0; i < proclist->n_members; i++) + { + HeapTuple proctup = &proclist->members[i]->tuple; + Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); + bool ok; + + /* + * All bloom support functions should be registered with matching + * left/right types + */ + if (procform->amproclefttype != procform->amprocrighttype) + { + ereport(INFO, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("bloom opfamily %s contains support procedure %s with cross-type registration", + opfamilyname, + format_procedure(procform->amproc)))); + result = false; + } + + /* + * We can't check signatures except within the specific opclass, since + * we need to know the associated opckeytype in many cases. + */ + if (procform->amproclefttype != opcintype) + continue; + + /* Check procedure numbers and function signatures */ + switch (procform->amprocnum) + { + case BLOOM_HASH_PROC: + ok = check_amproc_signature(procform->amproc, INT4OID, false, + 1, 1, opckeytype); + break; + case BLOOM_OPTIONS_PROC: + ok = check_amoptsproc_signature(procform->amproc); + break; + default: + ereport(INFO, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("bloom opfamily %s contains function %s with invalid support number %d", + opfamilyname, + format_procedure(procform->amproc), + procform->amprocnum))); + result = false; + continue; /* don't want additional message */ + } + + if (!ok) + { + ereport(INFO, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("gist opfamily %s contains function %s with wrong signature for support number %d", + opfamilyname, + format_procedure(procform->amproc), + procform->amprocnum))); + result = false; + } + } + + /* Check individual operators */ + for (i = 0; i < oprlist->n_members; i++) + { + HeapTuple oprtup = &oprlist->members[i]->tuple; + Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); + + /* Check it's allowed strategy for bloom */ + if (oprform->amopstrategy < 1 || + oprform->amopstrategy > BLOOM_NSTRATEGIES) + { + ereport(INFO, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("bloom opfamily %s contains operator %s with invalid strategy number %d", + opfamilyname, + format_operator(oprform->amopopr), + oprform->amopstrategy))); + result = false; + } + + /* bloom doesn't support ORDER BY operators */ + if (oprform->amoppurpose != AMOP_SEARCH || + OidIsValid(oprform->amopsortfamily)) + { + ereport(INFO, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("bloom opfamily %s contains invalid ORDER BY specification for operator %s", + opfamilyname, + format_operator(oprform->amopopr)))); + result = false; + } + + /* Check operator signature --- same for all bloom strategies */ + if (!check_amop_signature(oprform->amopopr, BOOLOID, + oprform->amoplefttype, + oprform->amoprighttype)) + { + ereport(INFO, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("bloom opfamily %s contains operator %s with wrong signature", + opfamilyname, + format_operator(oprform->amopopr)))); + result = false; + } + } + + /* Now check for inconsistent groups of operators/functions */ + grouplist = identify_opfamily_groups(oprlist, proclist); + opclassgroup = NULL; + foreach(lc, grouplist) + { + OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc); + + /* Remember the group exactly matching the test opclass */ + if (thisgroup->lefttype == opcintype && + thisgroup->righttype == opcintype) + opclassgroup = thisgroup; + + /* + * There is not a lot we can do to check the operator sets, since each + * bloom opclass is more or less a law unto itself, and some contain + * only operators that are binary-compatible with the opclass datatype + * (meaning that empty operator sets can be OK). That case also means + * that we shouldn't insist on nonempty function sets except for the + * opclass's own group. + */ + } + + /* Check that the originally-named opclass is complete */ + for (i = 1; i <= BLOOM_NPROC; i++) + { + if (opclassgroup && + (opclassgroup->functionset & (((uint64) 1) << i)) != 0) + continue; /* got it */ + if (i == BLOOM_OPTIONS_PROC) + continue; /* optional method */ + ereport(INFO, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("bloom opclass %s is missing support function %d", + opclassname, i))); + result = false; + } + + ReleaseCatCacheList(proclist); + ReleaseCatCacheList(oprlist); + ReleaseSysCache(familytup); + ReleaseSysCache(classtup); + + return result; +} diff --git a/contrib/bloom/expected/bloom.out b/contrib/bloom/expected/bloom.out new file mode 100644 index 0000000..dae12a7 --- /dev/null +++ b/contrib/bloom/expected/bloom.out @@ -0,0 +1,230 @@ +CREATE EXTENSION bloom; +CREATE TABLE tst ( + i int4, + t text +); +INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i; +CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (col1 = 3); +ALTER INDEX bloomidx SET (length=80); +SET enable_seqscan=on; +SET enable_bitmapscan=off; +SET enable_indexscan=off; +SELECT count(*) FROM tst WHERE i = 7; + count +------- + 200 +(1 row) + +SELECT count(*) FROM tst WHERE t = '5'; + count +------- + 112 +(1 row) + +SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + count +------- + 13 +(1 row) + +SET enable_seqscan=off; +SET enable_bitmapscan=on; +SET enable_indexscan=on; +EXPLAIN (COSTS OFF) SELECT count(*) FROM tst WHERE i = 7; + QUERY PLAN +------------------------------------------- + Aggregate + -> Bitmap Heap Scan on tst + Recheck Cond: (i = 7) + -> Bitmap Index Scan on bloomidx + Index Cond: (i = 7) +(5 rows) + +EXPLAIN (COSTS OFF) SELECT count(*) FROM tst WHERE t = '5'; + QUERY PLAN +------------------------------------------- + Aggregate + -> Bitmap Heap Scan on tst + Recheck Cond: (t = '5'::text) + -> Bitmap Index Scan on bloomidx + Index Cond: (t = '5'::text) +(5 rows) + +EXPLAIN (COSTS OFF) SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + QUERY PLAN +--------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on tst + Recheck Cond: ((i = 7) AND (t = '5'::text)) + -> Bitmap Index Scan on bloomidx + Index Cond: ((i = 7) AND (t = '5'::text)) +(5 rows) + +SELECT count(*) FROM tst WHERE i = 7; + count +------- + 200 +(1 row) + +SELECT count(*) FROM tst WHERE t = '5'; + count +------- + 112 +(1 row) + +SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + count +------- + 13 +(1 row) + +DELETE FROM tst; +INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i; +VACUUM ANALYZE tst; +SELECT count(*) FROM tst WHERE i = 7; + count +------- + 200 +(1 row) + +SELECT count(*) FROM tst WHERE t = '5'; + count +------- + 112 +(1 row) + +SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + count +------- + 13 +(1 row) + +DELETE FROM tst WHERE i > 1 OR t = '5'; +VACUUM tst; +INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i; +SELECT count(*) FROM tst WHERE i = 7; + count +------- + 200 +(1 row) + +SELECT count(*) FROM tst WHERE t = '5'; + count +------- + 112 +(1 row) + +SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + count +------- + 13 +(1 row) + +VACUUM FULL tst; +SELECT count(*) FROM tst WHERE i = 7; + count +------- + 200 +(1 row) + +SELECT count(*) FROM tst WHERE t = '5'; + count +------- + 112 +(1 row) + +SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + count +------- + 13 +(1 row) + +-- Try an unlogged table too +CREATE UNLOGGED TABLE tstu ( + i int4, + t text +); +INSERT INTO tstu SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i; +CREATE INDEX bloomidxu ON tstu USING bloom (i, t) WITH (col2 = 4); +SET enable_seqscan=off; +SET enable_bitmapscan=on; +SET enable_indexscan=on; +EXPLAIN (COSTS OFF) SELECT count(*) FROM tstu WHERE i = 7; + QUERY PLAN +-------------------------------------------- + Aggregate + -> Bitmap Heap Scan on tstu + Recheck Cond: (i = 7) + -> Bitmap Index Scan on bloomidxu + Index Cond: (i = 7) +(5 rows) + +EXPLAIN (COSTS OFF) SELECT count(*) FROM tstu WHERE t = '5'; + QUERY PLAN +-------------------------------------------- + Aggregate + -> Bitmap Heap Scan on tstu + Recheck Cond: (t = '5'::text) + -> Bitmap Index Scan on bloomidxu + Index Cond: (t = '5'::text) +(5 rows) + +EXPLAIN (COSTS OFF) SELECT count(*) FROM tstu WHERE i = 7 AND t = '5'; + QUERY PLAN +--------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on tstu + Recheck Cond: ((i = 7) AND (t = '5'::text)) + -> Bitmap Index Scan on bloomidxu + Index Cond: ((i = 7) AND (t = '5'::text)) +(5 rows) + +SELECT count(*) FROM tstu WHERE i = 7; + count +------- + 200 +(1 row) + +SELECT count(*) FROM tstu WHERE t = '5'; + count +------- + 112 +(1 row) + +SELECT count(*) FROM tstu WHERE i = 7 AND t = '5'; + count +------- + 13 +(1 row) + +RESET enable_seqscan; +RESET enable_bitmapscan; +RESET enable_indexscan; +-- Run amvalidator function on our opclasses +SELECT opcname, amvalidate(opc.oid) +FROM pg_opclass opc JOIN pg_am am ON am.oid = opcmethod +WHERE amname = 'bloom' +ORDER BY 1; + opcname | amvalidate +----------+------------ + int4_ops | t + text_ops | t +(2 rows) + +-- +-- relation options +-- +DROP INDEX bloomidx; +CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (length=7, col1=4); +SELECT reloptions FROM pg_class WHERE oid = 'bloomidx'::regclass; + reloptions +------------------- + {length=7,col1=4} +(1 row) + +-- check for min and max values +\set VERBOSITY terse +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0); +ERROR: value 0 out of bounds for option "length" +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0); +ERROR: value 0 out of bounds for option "col1" diff --git a/contrib/bloom/meson.build b/contrib/bloom/meson.build new file mode 100644 index 0000000..5d481a5 --- /dev/null +++ b/contrib/bloom/meson.build @@ -0,0 +1,45 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +bloom_sources = files( + 'blcost.c', + 'blinsert.c', + 'blscan.c', + 'blutils.c', + 'blvacuum.c', + 'blvalidate.c', +) + +if host_system == 'windows' + bloom_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'bloom', + '--FILEDESC', 'bloom access method - signature file based index',]) +endif + +bloom = shared_module('bloom', + bloom_sources, + c_pch: pch_postgres_h, + kwargs: contrib_mod_args, +) +contrib_targets += bloom + +install_data( + 'bloom.control', + 'bloom--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'bloom', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'bloom', + ], + }, + 'tap': { + 'tests': [ + 't/001_wal.pl', + ], + }, +} diff --git a/contrib/bloom/sql/bloom.sql b/contrib/bloom/sql/bloom.sql new file mode 100644 index 0000000..4733e1e --- /dev/null +++ b/contrib/bloom/sql/bloom.sql @@ -0,0 +1,95 @@ +CREATE EXTENSION bloom; + +CREATE TABLE tst ( + i int4, + t text +); + +INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i; +CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (col1 = 3); +ALTER INDEX bloomidx SET (length=80); + +SET enable_seqscan=on; +SET enable_bitmapscan=off; +SET enable_indexscan=off; + +SELECT count(*) FROM tst WHERE i = 7; +SELECT count(*) FROM tst WHERE t = '5'; +SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + +SET enable_seqscan=off; +SET enable_bitmapscan=on; +SET enable_indexscan=on; + +EXPLAIN (COSTS OFF) SELECT count(*) FROM tst WHERE i = 7; +EXPLAIN (COSTS OFF) SELECT count(*) FROM tst WHERE t = '5'; +EXPLAIN (COSTS OFF) SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + +SELECT count(*) FROM tst WHERE i = 7; +SELECT count(*) FROM tst WHERE t = '5'; +SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + +DELETE FROM tst; +INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i; +VACUUM ANALYZE tst; + +SELECT count(*) FROM tst WHERE i = 7; +SELECT count(*) FROM tst WHERE t = '5'; +SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + +DELETE FROM tst WHERE i > 1 OR t = '5'; +VACUUM tst; +INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i; + +SELECT count(*) FROM tst WHERE i = 7; +SELECT count(*) FROM tst WHERE t = '5'; +SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + +VACUUM FULL tst; + +SELECT count(*) FROM tst WHERE i = 7; +SELECT count(*) FROM tst WHERE t = '5'; +SELECT count(*) FROM tst WHERE i = 7 AND t = '5'; + +-- Try an unlogged table too + +CREATE UNLOGGED TABLE tstu ( + i int4, + t text +); + +INSERT INTO tstu SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,2000) i; +CREATE INDEX bloomidxu ON tstu USING bloom (i, t) WITH (col2 = 4); + +SET enable_seqscan=off; +SET enable_bitmapscan=on; +SET enable_indexscan=on; + +EXPLAIN (COSTS OFF) SELECT count(*) FROM tstu WHERE i = 7; +EXPLAIN (COSTS OFF) SELECT count(*) FROM tstu WHERE t = '5'; +EXPLAIN (COSTS OFF) SELECT count(*) FROM tstu WHERE i = 7 AND t = '5'; + +SELECT count(*) FROM tstu WHERE i = 7; +SELECT count(*) FROM tstu WHERE t = '5'; +SELECT count(*) FROM tstu WHERE i = 7 AND t = '5'; + +RESET enable_seqscan; +RESET enable_bitmapscan; +RESET enable_indexscan; + +-- Run amvalidator function on our opclasses +SELECT opcname, amvalidate(opc.oid) +FROM pg_opclass opc JOIN pg_am am ON am.oid = opcmethod +WHERE amname = 'bloom' +ORDER BY 1; + +-- +-- relation options +-- +DROP INDEX bloomidx; +CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (length=7, col1=4); +SELECT reloptions FROM pg_class WHERE oid = 'bloomidx'::regclass; +-- check for min and max values +\set VERBOSITY terse +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0); +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0); diff --git a/contrib/bloom/t/001_wal.pl b/contrib/bloom/t/001_wal.pl new file mode 100644 index 0000000..c1614e9 --- /dev/null +++ b/contrib/bloom/t/001_wal.pl @@ -0,0 +1,84 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# Test generic xlog record work for bloom index replication. +use strict; +use warnings; +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +my $node_primary; +my $node_standby; + +# Run few queries on both primary and standby and check their results match. +sub test_index_replay +{ + my ($test_name) = @_; + + local $Test::Builder::Level = $Test::Builder::Level + 1; + + # Wait for standby to catch up + $node_primary->wait_for_catchup($node_standby); + + my $queries = qq(SET enable_seqscan=off; +SET enable_bitmapscan=on; +SET enable_indexscan=on; +SELECT * FROM tst WHERE i = 0; +SELECT * FROM tst WHERE i = 3; +SELECT * FROM tst WHERE t = 'b'; +SELECT * FROM tst WHERE t = 'f'; +SELECT * FROM tst WHERE i = 3 AND t = 'c'; +SELECT * FROM tst WHERE i = 7 AND t = 'e'; +); + + # Run test queries and compare their result + my $primary_result = $node_primary->safe_psql("postgres", $queries); + my $standby_result = $node_standby->safe_psql("postgres", $queries); + + is($primary_result, $standby_result, "$test_name: query result matches"); + return; +} + +# Initialize primary node +$node_primary = PostgreSQL::Test::Cluster->new('primary'); +$node_primary->init(allows_streaming => 1); +$node_primary->start; +my $backup_name = 'my_backup'; + +# Take backup +$node_primary->backup($backup_name); + +# Create streaming standby linking to primary +$node_standby = PostgreSQL::Test::Cluster->new('standby'); +$node_standby->init_from_backup($node_primary, $backup_name, + has_streaming => 1); +$node_standby->start; + +# Create some bloom index on primary +$node_primary->safe_psql("postgres", "CREATE EXTENSION bloom;"); +$node_primary->safe_psql("postgres", "CREATE TABLE tst (i int4, t text);"); +$node_primary->safe_psql("postgres", + "INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series(1,100000) i;" +); +$node_primary->safe_psql("postgres", + "CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (col1 = 3);"); + +# Test that queries give same result +test_index_replay('initial'); + +# Run 10 cycles of table modification. Run test queries after each modification. +for my $i (1 .. 10) +{ + $node_primary->safe_psql("postgres", "DELETE FROM tst WHERE i = $i;"); + test_index_replay("delete $i"); + $node_primary->safe_psql("postgres", "VACUUM tst;"); + test_index_replay("vacuum $i"); + my ($start, $end) = (100001 + ($i - 1) * 10000, 100000 + $i * 10000); + $node_primary->safe_psql("postgres", + "INSERT INTO tst SELECT i%10, substr(md5(i::text), 1, 1) FROM generate_series($start,$end) i;" + ); + test_index_replay("insert $i"); +} + +done_testing(); diff --git a/contrib/bool_plperl/.gitignore b/contrib/bool_plperl/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/bool_plperl/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/bool_plperl/Makefile b/contrib/bool_plperl/Makefile new file mode 100644 index 0000000..efe1de9 --- /dev/null +++ b/contrib/bool_plperl/Makefile @@ -0,0 +1,39 @@ +# contrib/bool_plperl/Makefile + +MODULE_big = bool_plperl +OBJS = \ + $(WIN32RES) \ + bool_plperl.o +PGFILEDESC = "bool_plperl - bool transform for plperl" + +PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl + +EXTENSION = bool_plperlu bool_plperl +DATA = bool_plperlu--1.0.sql bool_plperl--1.0.sql + +REGRESS = bool_plperl bool_plperlu + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/bool_plperl +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +# We must link libperl explicitly +ifeq ($(PORTNAME), win32) +# these settings are the same as for plperl +override CPPFLAGS += -DPLPERL_HAVE_UID_GID -Wno-comment +# ... see silliness in plperl Makefile ... +SHLIB_LINK_INTERNAL += $(sort $(wildcard ../../src/pl/plperl/libperl*.a)) +else +rpathdir = $(perl_archlibexp)/CORE +SHLIB_LINK += $(perl_embed_ldflags) +endif + +# As with plperl we need to include the perl_includespec directory last. +override CPPFLAGS := $(CPPFLAGS) $(perl_embed_ccflags) $(perl_includespec) diff --git a/contrib/bool_plperl/bool_plperl--1.0.sql b/contrib/bool_plperl/bool_plperl--1.0.sql new file mode 100644 index 0000000..00dc3b8 --- /dev/null +++ b/contrib/bool_plperl/bool_plperl--1.0.sql @@ -0,0 +1,19 @@ +/* contrib/bool_plperl/bool_plperl--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION bool_plperl" to load this file. \quit + +CREATE FUNCTION bool_to_plperl(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION plperl_to_bool(val internal) RETURNS bool +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE TRANSFORM FOR bool LANGUAGE plperl ( + FROM SQL WITH FUNCTION bool_to_plperl(internal), + TO SQL WITH FUNCTION plperl_to_bool(internal) +); + +COMMENT ON TRANSFORM FOR bool LANGUAGE plperl IS 'transform between bool and Perl'; diff --git a/contrib/bool_plperl/bool_plperl.c b/contrib/bool_plperl/bool_plperl.c new file mode 100644 index 0000000..0fa1eee --- /dev/null +++ b/contrib/bool_plperl/bool_plperl.c @@ -0,0 +1,30 @@ +#include "postgres.h" + +#include "fmgr.h" +#include "plperl.h" + + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(bool_to_plperl); + +Datum +bool_to_plperl(PG_FUNCTION_ARGS) +{ + dTHX; + bool in = PG_GETARG_BOOL(0); + + return PointerGetDatum(in ? &PL_sv_yes : &PL_sv_no); +} + + +PG_FUNCTION_INFO_V1(plperl_to_bool); + +Datum +plperl_to_bool(PG_FUNCTION_ARGS) +{ + dTHX; + SV *in = (SV *) PG_GETARG_POINTER(0); + + PG_RETURN_BOOL(SvTRUE(in)); +} diff --git a/contrib/bool_plperl/bool_plperl.control b/contrib/bool_plperl/bool_plperl.control new file mode 100644 index 0000000..af3e6b1 --- /dev/null +++ b/contrib/bool_plperl/bool_plperl.control @@ -0,0 +1,7 @@ +# bool_plperl extension +comment = 'transform between bool and plperl' +default_version = '1.0' +module_pathname = '$libdir/bool_plperl' +relocatable = true +trusted = true +requires = 'plperl' diff --git a/contrib/bool_plperl/bool_plperlu--1.0.sql b/contrib/bool_plperl/bool_plperlu--1.0.sql new file mode 100644 index 0000000..52c55b6 --- /dev/null +++ b/contrib/bool_plperl/bool_plperlu--1.0.sql @@ -0,0 +1,19 @@ +/* contrib/bool_plperl/bool_plperlu--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION bool_plperlu" to load this file. \quit + +CREATE FUNCTION bool_to_plperlu(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'bool_to_plperl'; + +CREATE FUNCTION plperlu_to_bool(val internal) RETURNS bool +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'plperl_to_bool'; + +CREATE TRANSFORM FOR bool LANGUAGE plperlu ( + FROM SQL WITH FUNCTION bool_to_plperlu(internal), + TO SQL WITH FUNCTION plperlu_to_bool(internal) +); + +COMMENT ON TRANSFORM FOR bool LANGUAGE plperlu IS 'transform between bool and Perl'; diff --git a/contrib/bool_plperl/bool_plperlu.control b/contrib/bool_plperl/bool_plperlu.control new file mode 100644 index 0000000..d03a584 --- /dev/null +++ b/contrib/bool_plperl/bool_plperlu.control @@ -0,0 +1,6 @@ +# bool_plperlu extension +comment = 'transform between bool and plperlu' +default_version = '1.0' +module_pathname = '$libdir/bool_plperl' +relocatable = true +requires = 'plperlu' diff --git a/contrib/bool_plperl/expected/bool_plperl.out b/contrib/bool_plperl/expected/bool_plperl.out new file mode 100644 index 0000000..187df8d --- /dev/null +++ b/contrib/bool_plperl/expected/bool_plperl.out @@ -0,0 +1,112 @@ +CREATE EXTENSION bool_plperl CASCADE; +NOTICE: installing required extension "plperl" +--- test transforming from perl +CREATE FUNCTION perl2int(int) RETURNS bool +LANGUAGE plperl +TRANSFORM FOR TYPE bool +AS $$ +return shift; +$$; +CREATE FUNCTION perl2text(text) RETURNS bool +LANGUAGE plperl +TRANSFORM FOR TYPE bool +AS $$ +return shift; +$$; +CREATE FUNCTION perl2undef() RETURNS bool +LANGUAGE plperl +TRANSFORM FOR TYPE bool +AS $$ +return undef; +$$; +SELECT perl2int(1); + perl2int +---------- + t +(1 row) + +SELECT perl2int(0); + perl2int +---------- + f +(1 row) + +SELECT perl2text('foo'); + perl2text +----------- + t +(1 row) + +SELECT perl2text(''); + perl2text +----------- + f +(1 row) + +SELECT perl2undef() IS NULL AS p; + p +--- + t +(1 row) + +--- test transforming to perl +CREATE FUNCTION bool2perl(bool, bool, bool) RETURNS void +LANGUAGE plperl +TRANSFORM FOR TYPE bool, for type boolean -- duplicate to test ruleutils +AS $$ +my ($x, $y, $z) = @_; + +die("NULL mistransformed") if (defined($z)); +die("TRUE mistransformed to UNDEF") if (!defined($x)); +die("FALSE mistransformed to UNDEF") if (!defined($y)); +die("TRUE mistransformed") if (!$x); +die("FALSE mistransformed") if ($y); +$$; +SELECT bool2perl (true, false, NULL); + bool2perl +----------- + +(1 row) + +--- test ruleutils +\sf bool2perl +CREATE OR REPLACE FUNCTION public.bool2perl(boolean, boolean, boolean) + RETURNS void + TRANSFORM FOR TYPE boolean, FOR TYPE boolean + LANGUAGE plperl +AS $function$ +my ($x, $y, $z) = @_; + +die("NULL mistransformed") if (defined($z)); +die("TRUE mistransformed to UNDEF") if (!defined($x)); +die("FALSE mistransformed to UNDEF") if (!defined($y)); +die("TRUE mistransformed") if (!$x); +die("FALSE mistransformed") if ($y); +$function$ +--- test selecting bool through SPI +CREATE FUNCTION spi_test() RETURNS void +LANGUAGE plperl +TRANSFORM FOR TYPE bool +AS $$ +my $rv = spi_exec_query('SELECT true t, false f, NULL n')->{rows}->[0]; + +die("TRUE mistransformed to UNDEF in SPI") if (!defined ($rv->{t})); +die("FALSE mistransformed to UNDEF in SPI") if (!defined ($rv->{f})); +die("NULL mistransformed in SPI") if (defined ($rv->{n})); +die("TRUE mistransformed in SPI") if (!$rv->{t}); +die("FALSE mistransformed in SPI") if ($rv->{f}); +$$; +SELECT spi_test(); + spi_test +---------- + +(1 row) + +DROP EXTENSION plperl CASCADE; +NOTICE: drop cascades to 6 other objects +DETAIL: drop cascades to function spi_test() +drop cascades to extension bool_plperl +drop cascades to function perl2int(integer) +drop cascades to function perl2text(text) +drop cascades to function perl2undef() +drop cascades to function bool2perl(boolean,boolean,boolean) diff --git a/contrib/bool_plperl/expected/bool_plperlu.out b/contrib/bool_plperl/expected/bool_plperlu.out new file mode 100644 index 0000000..8337d33 --- /dev/null +++ b/contrib/bool_plperl/expected/bool_plperlu.out @@ -0,0 +1,112 @@ +CREATE EXTENSION bool_plperlu CASCADE; +NOTICE: installing required extension "plperlu" +--- test transforming from perl +CREATE FUNCTION perl2int(int) RETURNS bool +LANGUAGE plperlu +TRANSFORM FOR TYPE bool +AS $$ +return shift; +$$; +CREATE FUNCTION perl2text(text) RETURNS bool +LANGUAGE plperlu +TRANSFORM FOR TYPE bool +AS $$ +return shift; +$$; +CREATE FUNCTION perl2undef() RETURNS bool +LANGUAGE plperlu +TRANSFORM FOR TYPE bool +AS $$ +return undef; +$$; +SELECT perl2int(1); + perl2int +---------- + t +(1 row) + +SELECT perl2int(0); + perl2int +---------- + f +(1 row) + +SELECT perl2text('foo'); + perl2text +----------- + t +(1 row) + +SELECT perl2text(''); + perl2text +----------- + f +(1 row) + +SELECT perl2undef() IS NULL AS p; + p +--- + t +(1 row) + +--- test transforming to perl +CREATE FUNCTION bool2perl(bool, bool, bool) RETURNS void +LANGUAGE plperlu +TRANSFORM FOR TYPE bool, for type boolean -- duplicate to test ruleutils +AS $$ +my ($x, $y, $z) = @_; + +die("NULL mistransformed") if (defined($z)); +die("TRUE mistransformed to UNDEF") if (!defined($x)); +die("FALSE mistransformed to UNDEF") if (!defined($y)); +die("TRUE mistransformed") if (!$x); +die("FALSE mistransformed") if ($y); +$$; +SELECT bool2perl (true, false, NULL); + bool2perl +----------- + +(1 row) + +--- test ruleutils +\sf bool2perl +CREATE OR REPLACE FUNCTION public.bool2perl(boolean, boolean, boolean) + RETURNS void + TRANSFORM FOR TYPE boolean, FOR TYPE boolean + LANGUAGE plperlu +AS $function$ +my ($x, $y, $z) = @_; + +die("NULL mistransformed") if (defined($z)); +die("TRUE mistransformed to UNDEF") if (!defined($x)); +die("FALSE mistransformed to UNDEF") if (!defined($y)); +die("TRUE mistransformed") if (!$x); +die("FALSE mistransformed") if ($y); +$function$ +--- test selecting bool through SPI +CREATE FUNCTION spi_test() RETURNS void +LANGUAGE plperlu +TRANSFORM FOR TYPE bool +AS $$ +my $rv = spi_exec_query('SELECT true t, false f, NULL n')->{rows}->[0]; + +die("TRUE mistransformed to UNDEF in SPI") if (!defined ($rv->{t})); +die("FALSE mistransformed to UNDEF in SPI") if (!defined ($rv->{f})); +die("NULL mistransformed in SPI") if (defined ($rv->{n})); +die("TRUE mistransformed in SPI") if (!$rv->{t}); +die("FALSE mistransformed in SPI") if ($rv->{f}); +$$; +SELECT spi_test(); + spi_test +---------- + +(1 row) + +DROP EXTENSION plperlu CASCADE; +NOTICE: drop cascades to 6 other objects +DETAIL: drop cascades to function spi_test() +drop cascades to extension bool_plperlu +drop cascades to function perl2int(integer) +drop cascades to function perl2text(text) +drop cascades to function perl2undef() +drop cascades to function bool2perl(boolean,boolean,boolean) diff --git a/contrib/bool_plperl/meson.build b/contrib/bool_plperl/meson.build new file mode 100644 index 0000000..8d29d33 --- /dev/null +++ b/contrib/bool_plperl/meson.build @@ -0,0 +1,50 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not perl_dep.found() + subdir_done() +endif + +bool_plperl_sources = files( + 'bool_plperl.c', +) + +if host_system == 'windows' + bool_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'bool_plperl', + '--FILEDESC', 'bool_plperl - bool transform for plperl',]) +endif + +bool_plperl = shared_module('bool_plperl', + bool_plperl_sources, + include_directories: [plperl_inc, include_directories('.')], + kwargs: contrib_mod_args + { + 'dependencies': [perl_dep, contrib_mod_args['dependencies']], + 'install_rpath': ':'.join(mod_install_rpaths + ['@0@/CORE'.format(archlibexp)]), + 'build_rpath': '@0@/CORE'.format(archlibexp), + }, +) +contrib_targets += bool_plperl + +install_data( + 'bool_plperl.control', + 'bool_plperl--1.0.sql', + kwargs: contrib_data_args, +) + +install_data( + 'bool_plperlu.control', + 'bool_plperlu--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'bool_plperl', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'bool_plperl', + 'bool_plperlu', + ], + }, +} diff --git a/contrib/bool_plperl/sql/bool_plperl.sql b/contrib/bool_plperl/sql/bool_plperl.sql new file mode 100644 index 0000000..b7f5708 --- /dev/null +++ b/contrib/bool_plperl/sql/bool_plperl.sql @@ -0,0 +1,70 @@ +CREATE EXTENSION bool_plperl CASCADE; + +--- test transforming from perl + +CREATE FUNCTION perl2int(int) RETURNS bool +LANGUAGE plperl +TRANSFORM FOR TYPE bool +AS $$ +return shift; +$$; + +CREATE FUNCTION perl2text(text) RETURNS bool +LANGUAGE plperl +TRANSFORM FOR TYPE bool +AS $$ +return shift; +$$; + +CREATE FUNCTION perl2undef() RETURNS bool +LANGUAGE plperl +TRANSFORM FOR TYPE bool +AS $$ +return undef; +$$; + +SELECT perl2int(1); +SELECT perl2int(0); +SELECT perl2text('foo'); +SELECT perl2text(''); +SELECT perl2undef() IS NULL AS p; + +--- test transforming to perl + +CREATE FUNCTION bool2perl(bool, bool, bool) RETURNS void +LANGUAGE plperl +TRANSFORM FOR TYPE bool, for type boolean -- duplicate to test ruleutils +AS $$ +my ($x, $y, $z) = @_; + +die("NULL mistransformed") if (defined($z)); +die("TRUE mistransformed to UNDEF") if (!defined($x)); +die("FALSE mistransformed to UNDEF") if (!defined($y)); +die("TRUE mistransformed") if (!$x); +die("FALSE mistransformed") if ($y); +$$; + +SELECT bool2perl (true, false, NULL); + +--- test ruleutils + +\sf bool2perl + +--- test selecting bool through SPI + +CREATE FUNCTION spi_test() RETURNS void +LANGUAGE plperl +TRANSFORM FOR TYPE bool +AS $$ +my $rv = spi_exec_query('SELECT true t, false f, NULL n')->{rows}->[0]; + +die("TRUE mistransformed to UNDEF in SPI") if (!defined ($rv->{t})); +die("FALSE mistransformed to UNDEF in SPI") if (!defined ($rv->{f})); +die("NULL mistransformed in SPI") if (defined ($rv->{n})); +die("TRUE mistransformed in SPI") if (!$rv->{t}); +die("FALSE mistransformed in SPI") if ($rv->{f}); +$$; + +SELECT spi_test(); + +DROP EXTENSION plperl CASCADE; diff --git a/contrib/bool_plperl/sql/bool_plperlu.sql b/contrib/bool_plperl/sql/bool_plperlu.sql new file mode 100644 index 0000000..1480a04 --- /dev/null +++ b/contrib/bool_plperl/sql/bool_plperlu.sql @@ -0,0 +1,70 @@ +CREATE EXTENSION bool_plperlu CASCADE; + +--- test transforming from perl + +CREATE FUNCTION perl2int(int) RETURNS bool +LANGUAGE plperlu +TRANSFORM FOR TYPE bool +AS $$ +return shift; +$$; + +CREATE FUNCTION perl2text(text) RETURNS bool +LANGUAGE plperlu +TRANSFORM FOR TYPE bool +AS $$ +return shift; +$$; + +CREATE FUNCTION perl2undef() RETURNS bool +LANGUAGE plperlu +TRANSFORM FOR TYPE bool +AS $$ +return undef; +$$; + +SELECT perl2int(1); +SELECT perl2int(0); +SELECT perl2text('foo'); +SELECT perl2text(''); +SELECT perl2undef() IS NULL AS p; + +--- test transforming to perl + +CREATE FUNCTION bool2perl(bool, bool, bool) RETURNS void +LANGUAGE plperlu +TRANSFORM FOR TYPE bool, for type boolean -- duplicate to test ruleutils +AS $$ +my ($x, $y, $z) = @_; + +die("NULL mistransformed") if (defined($z)); +die("TRUE mistransformed to UNDEF") if (!defined($x)); +die("FALSE mistransformed to UNDEF") if (!defined($y)); +die("TRUE mistransformed") if (!$x); +die("FALSE mistransformed") if ($y); +$$; + +SELECT bool2perl (true, false, NULL); + +--- test ruleutils + +\sf bool2perl + +--- test selecting bool through SPI + +CREATE FUNCTION spi_test() RETURNS void +LANGUAGE plperlu +TRANSFORM FOR TYPE bool +AS $$ +my $rv = spi_exec_query('SELECT true t, false f, NULL n')->{rows}->[0]; + +die("TRUE mistransformed to UNDEF in SPI") if (!defined ($rv->{t})); +die("FALSE mistransformed to UNDEF in SPI") if (!defined ($rv->{f})); +die("NULL mistransformed in SPI") if (defined ($rv->{n})); +die("TRUE mistransformed in SPI") if (!$rv->{t}); +die("FALSE mistransformed in SPI") if ($rv->{f}); +$$; + +SELECT spi_test(); + +DROP EXTENSION plperlu CASCADE; diff --git a/contrib/btree_gin/.gitignore b/contrib/btree_gin/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/btree_gin/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/btree_gin/Makefile b/contrib/btree_gin/Makefile new file mode 100644 index 0000000..0a15811 --- /dev/null +++ b/contrib/btree_gin/Makefile @@ -0,0 +1,27 @@ +# contrib/btree_gin/Makefile + +MODULE_big = btree_gin +OBJS = \ + $(WIN32RES) \ + btree_gin.o + +EXTENSION = btree_gin +DATA = btree_gin--1.0.sql btree_gin--1.0--1.1.sql btree_gin--1.1--1.2.sql \ + btree_gin--1.2--1.3.sql +PGFILEDESC = "btree_gin - B-tree equivalent GIN operator classes" + +REGRESS = install_btree_gin int2 int4 int8 float4 float8 money oid \ + timestamp timestamptz time timetz date interval \ + macaddr macaddr8 inet cidr text varchar char bytea bit varbit \ + numeric enum uuid name bool bpchar + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/btree_gin +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/btree_gin/btree_gin--1.0--1.1.sql b/contrib/btree_gin/btree_gin--1.0--1.1.sql new file mode 100644 index 0000000..dd81d27 --- /dev/null +++ b/contrib/btree_gin/btree_gin--1.0--1.1.sql @@ -0,0 +1,35 @@ +/* contrib/btree_gin/btree_gin--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION btree_gin UPDATE TO '1.1'" to load this file. \quit + +-- macaddr8 datatype support new in 10.0. +CREATE FUNCTION gin_extract_value_macaddr8(macaddr8, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_macaddr8(macaddr8, macaddr8, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_macaddr8(macaddr8, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS macaddr8_ops +DEFAULT FOR TYPE macaddr8 USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 macaddr8_cmp(macaddr8, macaddr8), + FUNCTION 2 gin_extract_value_macaddr8(macaddr8, internal), + FUNCTION 3 gin_extract_query_macaddr8(macaddr8, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_macaddr8(macaddr8, macaddr8, int2, internal), +STORAGE macaddr8; diff --git a/contrib/btree_gin/btree_gin--1.0.sql b/contrib/btree_gin/btree_gin--1.0.sql new file mode 100644 index 0000000..cf867ef --- /dev/null +++ b/contrib/btree_gin/btree_gin--1.0.sql @@ -0,0 +1,689 @@ +/* contrib/btree_gin/btree_gin--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION btree_gin" to load this file. \quit + +CREATE FUNCTION gin_btree_consistent(internal, int2, anyelement, int4, internal, internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_value_int2(int2, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_int2(int2, int2, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_int2(int2, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS int2_ops +DEFAULT FOR TYPE int2 USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btint2cmp(int2,int2), + FUNCTION 2 gin_extract_value_int2(int2, internal), + FUNCTION 3 gin_extract_query_int2(int2, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_int2(int2,int2,int2, internal), +STORAGE int2; + +CREATE FUNCTION gin_extract_value_int4(int4, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_int4(int4, int4, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_int4(int4, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS int4_ops +DEFAULT FOR TYPE int4 USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btint4cmp(int4,int4), + FUNCTION 2 gin_extract_value_int4(int4, internal), + FUNCTION 3 gin_extract_query_int4(int4, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_int4(int4,int4,int2, internal), +STORAGE int4; + +CREATE FUNCTION gin_extract_value_int8(int8, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_int8(int8, int8, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_int8(int8, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS int8_ops +DEFAULT FOR TYPE int8 USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btint8cmp(int8,int8), + FUNCTION 2 gin_extract_value_int8(int8, internal), + FUNCTION 3 gin_extract_query_int8(int8, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_int8(int8,int8,int2, internal), +STORAGE int8; + +CREATE FUNCTION gin_extract_value_float4(float4, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_float4(float4, float4, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_float4(float4, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS float4_ops +DEFAULT FOR TYPE float4 USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btfloat4cmp(float4,float4), + FUNCTION 2 gin_extract_value_float4(float4, internal), + FUNCTION 3 gin_extract_query_float4(float4, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_float4(float4,float4,int2, internal), +STORAGE float4; + +CREATE FUNCTION gin_extract_value_float8(float8, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_float8(float8, float8, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_float8(float8, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS float8_ops +DEFAULT FOR TYPE float8 USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btfloat8cmp(float8,float8), + FUNCTION 2 gin_extract_value_float8(float8, internal), + FUNCTION 3 gin_extract_query_float8(float8, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_float8(float8,float8,int2, internal), +STORAGE float8; + +CREATE FUNCTION gin_extract_value_money(money, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_money(money, money, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_money(money, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS money_ops +DEFAULT FOR TYPE money USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 cash_cmp(money,money), + FUNCTION 2 gin_extract_value_money(money, internal), + FUNCTION 3 gin_extract_query_money(money, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_money(money,money,int2, internal), +STORAGE money; + +CREATE FUNCTION gin_extract_value_oid(oid, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_oid(oid, oid, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_oid(oid, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS oid_ops +DEFAULT FOR TYPE oid USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btoidcmp(oid,oid), + FUNCTION 2 gin_extract_value_oid(oid, internal), + FUNCTION 3 gin_extract_query_oid(oid, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_oid(oid,oid,int2, internal), +STORAGE oid; + +CREATE FUNCTION gin_extract_value_timestamp(timestamp, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_timestamp(timestamp, timestamp, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_timestamp(timestamp, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS timestamp_ops +DEFAULT FOR TYPE timestamp USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 timestamp_cmp(timestamp,timestamp), + FUNCTION 2 gin_extract_value_timestamp(timestamp, internal), + FUNCTION 3 gin_extract_query_timestamp(timestamp, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_timestamp(timestamp,timestamp,int2, internal), +STORAGE timestamp; + +CREATE FUNCTION gin_extract_value_timestamptz(timestamptz, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_timestamptz(timestamptz, timestamptz, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_timestamptz(timestamptz, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS timestamptz_ops +DEFAULT FOR TYPE timestamptz USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 timestamptz_cmp(timestamptz,timestamptz), + FUNCTION 2 gin_extract_value_timestamptz(timestamptz, internal), + FUNCTION 3 gin_extract_query_timestamptz(timestamptz, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_timestamptz(timestamptz,timestamptz,int2, internal), +STORAGE timestamptz; + +CREATE FUNCTION gin_extract_value_time(time, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_time(time, time, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_time(time, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS time_ops +DEFAULT FOR TYPE time USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 time_cmp(time,time), + FUNCTION 2 gin_extract_value_time(time, internal), + FUNCTION 3 gin_extract_query_time(time, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_time(time,time,int2, internal), +STORAGE time; + +CREATE FUNCTION gin_extract_value_timetz(timetz, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_timetz(timetz, timetz, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_timetz(timetz, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS timetz_ops +DEFAULT FOR TYPE timetz USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 timetz_cmp(timetz,timetz), + FUNCTION 2 gin_extract_value_timetz(timetz, internal), + FUNCTION 3 gin_extract_query_timetz(timetz, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_timetz(timetz,timetz,int2, internal), +STORAGE timetz; + +CREATE FUNCTION gin_extract_value_date(date, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_date(date, date, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_date(date, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS date_ops +DEFAULT FOR TYPE date USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 date_cmp(date,date), + FUNCTION 2 gin_extract_value_date(date, internal), + FUNCTION 3 gin_extract_query_date(date, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_date(date,date,int2, internal), +STORAGE date; + +CREATE FUNCTION gin_extract_value_interval(interval, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_interval(interval, interval, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_interval(interval, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS interval_ops +DEFAULT FOR TYPE interval USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 interval_cmp(interval,interval), + FUNCTION 2 gin_extract_value_interval(interval, internal), + FUNCTION 3 gin_extract_query_interval(interval, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_interval(interval,interval,int2, internal), +STORAGE interval; + +CREATE FUNCTION gin_extract_value_macaddr(macaddr, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_macaddr(macaddr, macaddr, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_macaddr(macaddr, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS macaddr_ops +DEFAULT FOR TYPE macaddr USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 macaddr_cmp(macaddr,macaddr), + FUNCTION 2 gin_extract_value_macaddr(macaddr, internal), + FUNCTION 3 gin_extract_query_macaddr(macaddr, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_macaddr(macaddr,macaddr,int2, internal), +STORAGE macaddr; + +CREATE FUNCTION gin_extract_value_inet(inet, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_inet(inet, inet, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_inet(inet, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS inet_ops +DEFAULT FOR TYPE inet USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 network_cmp(inet,inet), + FUNCTION 2 gin_extract_value_inet(inet, internal), + FUNCTION 3 gin_extract_query_inet(inet, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_inet(inet,inet,int2, internal), +STORAGE inet; + +CREATE FUNCTION gin_extract_value_cidr(cidr, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_cidr(cidr, cidr, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_cidr(cidr, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS cidr_ops +DEFAULT FOR TYPE cidr USING gin +AS + OPERATOR 1 <(inet,inet), + OPERATOR 2 <=(inet,inet), + OPERATOR 3 =(inet,inet), + OPERATOR 4 >=(inet,inet), + OPERATOR 5 >(inet,inet), + FUNCTION 1 network_cmp(inet,inet), + FUNCTION 2 gin_extract_value_cidr(cidr, internal), + FUNCTION 3 gin_extract_query_cidr(cidr, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_cidr(cidr,cidr,int2, internal), +STORAGE cidr; + +CREATE FUNCTION gin_extract_value_text(text, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_text(text, text, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_text(text, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS text_ops +DEFAULT FOR TYPE text USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 bttextcmp(text,text), + FUNCTION 2 gin_extract_value_text(text, internal), + FUNCTION 3 gin_extract_query_text(text, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_text(text,text,int2, internal), +STORAGE text; + +CREATE OPERATOR CLASS varchar_ops +DEFAULT FOR TYPE varchar USING gin +AS + OPERATOR 1 <(text,text), + OPERATOR 2 <=(text,text), + OPERATOR 3 =(text,text), + OPERATOR 4 >=(text,text), + OPERATOR 5 >(text,text), + FUNCTION 1 bttextcmp(text,text), + FUNCTION 2 gin_extract_value_text(text, internal), + FUNCTION 3 gin_extract_query_text(text, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_text(text,text,int2, internal), +STORAGE varchar; + +CREATE FUNCTION gin_extract_value_char("char", internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_char("char", "char", int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_char("char", internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS char_ops +DEFAULT FOR TYPE "char" USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btcharcmp("char","char"), + FUNCTION 2 gin_extract_value_char("char", internal), + FUNCTION 3 gin_extract_query_char("char", internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_char("char","char",int2, internal), +STORAGE "char"; + +CREATE FUNCTION gin_extract_value_bytea(bytea, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_bytea(bytea, bytea, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_bytea(bytea, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS bytea_ops +DEFAULT FOR TYPE bytea USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 byteacmp(bytea,bytea), + FUNCTION 2 gin_extract_value_bytea(bytea, internal), + FUNCTION 3 gin_extract_query_bytea(bytea, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_bytea(bytea,bytea,int2, internal), +STORAGE bytea; + +CREATE FUNCTION gin_extract_value_bit(bit, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_bit(bit, bit, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_bit(bit, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS bit_ops +DEFAULT FOR TYPE bit USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 bitcmp(bit,bit), + FUNCTION 2 gin_extract_value_bit(bit, internal), + FUNCTION 3 gin_extract_query_bit(bit, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_bit(bit,bit,int2, internal), +STORAGE bit; + +CREATE FUNCTION gin_extract_value_varbit(varbit, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_varbit(varbit, varbit, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_varbit(varbit, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS varbit_ops +DEFAULT FOR TYPE varbit USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 varbitcmp(varbit,varbit), + FUNCTION 2 gin_extract_value_varbit(varbit, internal), + FUNCTION 3 gin_extract_query_varbit(varbit, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_varbit(varbit,varbit,int2, internal), +STORAGE varbit; + +CREATE FUNCTION gin_extract_value_numeric(numeric, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_numeric(numeric, numeric, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_numeric(numeric, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_numeric_cmp(numeric, numeric) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS numeric_ops +DEFAULT FOR TYPE numeric USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 gin_numeric_cmp(numeric,numeric), + FUNCTION 2 gin_extract_value_numeric(numeric, internal), + FUNCTION 3 gin_extract_query_numeric(numeric, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_numeric(numeric,numeric,int2, internal), +STORAGE numeric; diff --git a/contrib/btree_gin/btree_gin--1.1--1.2.sql b/contrib/btree_gin/btree_gin--1.1--1.2.sql new file mode 100644 index 0000000..2a16837 --- /dev/null +++ b/contrib/btree_gin/btree_gin--1.1--1.2.sql @@ -0,0 +1,47 @@ +/* contrib/btree_gin/btree_gin--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION btree_gin UPDATE TO '1.1'" to load this file. \quit + +-- +-- +-- +-- enum ops +-- +-- + + +CREATE FUNCTION gin_extract_value_anyenum(anyenum, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_anyenum(anyenum, anyenum, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_anyenum(anyenum, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_enum_cmp(anyenum, anyenum) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS enum_ops +DEFAULT FOR TYPE anyenum USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 gin_enum_cmp(anyenum,anyenum), + FUNCTION 2 gin_extract_value_anyenum(anyenum, internal), + FUNCTION 3 gin_extract_query_anyenum(anyenum, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_anyenum(anyenum,anyenum,int2, internal), +STORAGE anyenum; diff --git a/contrib/btree_gin/btree_gin--1.2--1.3.sql b/contrib/btree_gin/btree_gin--1.2--1.3.sql new file mode 100644 index 0000000..db675b7 --- /dev/null +++ b/contrib/btree_gin/btree_gin--1.2--1.3.sql @@ -0,0 +1,128 @@ +/* contrib/btree_gin/btree_gin--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION btree_gin UPDATE TO '1.3'" to load this file. \quit + +-- uuid datatype support new in 1.3. +CREATE FUNCTION gin_extract_value_uuid(uuid, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_uuid(uuid, uuid, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_uuid(uuid, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS uuid_ops +DEFAULT FOR TYPE uuid USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 uuid_cmp(uuid,uuid), + FUNCTION 2 gin_extract_value_uuid(uuid, internal), + FUNCTION 3 gin_extract_query_uuid(uuid, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_uuid(uuid,uuid,int2, internal), +STORAGE uuid; + +-- name datatype support new in 1.3. +CREATE FUNCTION gin_extract_value_name(name, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_name(name, name, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_name(name, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS name_ops +DEFAULT FOR TYPE name USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btnamecmp(name,name), + FUNCTION 2 gin_extract_value_name(name, internal), + FUNCTION 3 gin_extract_query_name(name, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_name(name,name,int2, internal), +STORAGE name; + +-- bool datatype support new in 1.3. +CREATE FUNCTION gin_extract_value_bool(bool, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_bool(bool, bool, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_bool(bool, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS bool_ops +DEFAULT FOR TYPE bool USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btboolcmp(bool,bool), + FUNCTION 2 gin_extract_value_bool(bool, internal), + FUNCTION 3 gin_extract_query_bool(bool, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_bool(bool,bool,int2, internal), +STORAGE bool; + +-- bpchar datatype support new in 1.3. +CREATE FUNCTION gin_extract_value_bpchar(bpchar, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_compare_prefix_bpchar(bpchar, bpchar, int2, internal) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION gin_extract_query_bpchar(bpchar, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR CLASS bpchar_ops +DEFAULT FOR TYPE bpchar USING gin +AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 bpcharcmp(bpchar, bpchar), + FUNCTION 2 gin_extract_value_bpchar(bpchar, internal), + FUNCTION 3 gin_extract_query_bpchar(bpchar, internal, int2, internal, internal), + FUNCTION 4 gin_btree_consistent(internal, int2, anyelement, int4, internal, internal), + FUNCTION 5 gin_compare_prefix_bpchar(bpchar,bpchar,int2, internal), +STORAGE bpchar; diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c new file mode 100644 index 0000000..b09bb8d --- /dev/null +++ b/contrib/btree_gin/btree_gin.c @@ -0,0 +1,513 @@ +/* + * contrib/btree_gin/btree_gin.c + */ +#include "postgres.h" + +#include + +#include "access/stratnum.h" +#include "utils/builtins.h" +#include "utils/bytea.h" +#include "utils/cash.h" +#include "utils/date.h" +#include "utils/float.h" +#include "utils/inet.h" +#include "utils/numeric.h" +#include "utils/timestamp.h" +#include "utils/uuid.h" +#include "utils/varbit.h" + +PG_MODULE_MAGIC; + +typedef struct QueryInfo +{ + StrategyNumber strategy; + Datum datum; + bool is_varlena; + Datum (*typecmp) (FunctionCallInfo); +} QueryInfo; + +/*** GIN support functions shared by all datatypes ***/ + +static Datum +gin_btree_extract_value(FunctionCallInfo fcinfo, bool is_varlena) +{ + Datum datum = PG_GETARG_DATUM(0); + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + Datum *entries = (Datum *) palloc(sizeof(Datum)); + + if (is_varlena) + datum = PointerGetDatum(PG_DETOAST_DATUM(datum)); + entries[0] = datum; + *nentries = 1; + + PG_RETURN_POINTER(entries); +} + +/* + * For BTGreaterEqualStrategyNumber, BTGreaterStrategyNumber, and + * BTEqualStrategyNumber we want to start the index scan at the + * supplied query datum, and work forward. For BTLessStrategyNumber + * and BTLessEqualStrategyNumber, we need to start at the leftmost + * key, and work forward until the supplied query datum (which must be + * sent along inside the QueryInfo structure). + */ +static Datum +gin_btree_extract_query(FunctionCallInfo fcinfo, + bool is_varlena, + Datum (*leftmostvalue) (void), + Datum (*typecmp) (FunctionCallInfo)) +{ + Datum datum = PG_GETARG_DATUM(0); + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + StrategyNumber strategy = PG_GETARG_UINT16(2); + bool **partialmatch = (bool **) PG_GETARG_POINTER(3); + Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4); + Datum *entries = (Datum *) palloc(sizeof(Datum)); + QueryInfo *data = (QueryInfo *) palloc(sizeof(QueryInfo)); + bool *ptr_partialmatch; + + *nentries = 1; + ptr_partialmatch = *partialmatch = (bool *) palloc(sizeof(bool)); + *ptr_partialmatch = false; + if (is_varlena) + datum = PointerGetDatum(PG_DETOAST_DATUM(datum)); + data->strategy = strategy; + data->datum = datum; + data->is_varlena = is_varlena; + data->typecmp = typecmp; + *extra_data = (Pointer *) palloc(sizeof(Pointer)); + **extra_data = (Pointer) data; + + switch (strategy) + { + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + entries[0] = leftmostvalue(); + *ptr_partialmatch = true; + break; + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + *ptr_partialmatch = true; + /* FALLTHROUGH */ + case BTEqualStrategyNumber: + entries[0] = datum; + break; + default: + elog(ERROR, "unrecognized strategy number: %d", strategy); + } + + PG_RETURN_POINTER(entries); +} + +/* + * Datum a is a value from extract_query method and for BTLess* + * strategy it is a left-most value. So, use original datum from QueryInfo + * to decide to stop scanning or not. Datum b is always from index. + */ +static Datum +gin_btree_compare_prefix(FunctionCallInfo fcinfo) +{ + Datum a = PG_GETARG_DATUM(0); + Datum b = PG_GETARG_DATUM(1); + QueryInfo *data = (QueryInfo *) PG_GETARG_POINTER(3); + int32 res, + cmp; + + cmp = DatumGetInt32(CallerFInfoFunctionCall2(data->typecmp, + fcinfo->flinfo, + PG_GET_COLLATION(), + (data->strategy == BTLessStrategyNumber || + data->strategy == BTLessEqualStrategyNumber) + ? data->datum : a, + b)); + + switch (data->strategy) + { + case BTLessStrategyNumber: + /* If original datum > indexed one then return match */ + if (cmp > 0) + res = 0; + else + res = 1; + break; + case BTLessEqualStrategyNumber: + /* The same except equality */ + if (cmp >= 0) + res = 0; + else + res = 1; + break; + case BTEqualStrategyNumber: + if (cmp != 0) + res = 1; + else + res = 0; + break; + case BTGreaterEqualStrategyNumber: + /* If original datum <= indexed one then return match */ + if (cmp <= 0) + res = 0; + else + res = 1; + break; + case BTGreaterStrategyNumber: + /* If original datum <= indexed one then return match */ + /* If original datum == indexed one then continue scan */ + if (cmp < 0) + res = 0; + else if (cmp == 0) + res = -1; + else + res = 1; + break; + default: + elog(ERROR, "unrecognized strategy number: %d", + data->strategy); + res = 0; + } + + PG_RETURN_INT32(res); +} + +PG_FUNCTION_INFO_V1(gin_btree_consistent); +Datum +gin_btree_consistent(PG_FUNCTION_ARGS) +{ + bool *recheck = (bool *) PG_GETARG_POINTER(5); + + *recheck = false; + PG_RETURN_BOOL(true); +} + +/*** GIN_SUPPORT macro defines the datatype specific functions ***/ + +#define GIN_SUPPORT(type, is_varlena, leftmostvalue, typecmp) \ +PG_FUNCTION_INFO_V1(gin_extract_value_##type); \ +Datum \ +gin_extract_value_##type(PG_FUNCTION_ARGS) \ +{ \ + return gin_btree_extract_value(fcinfo, is_varlena); \ +} \ +PG_FUNCTION_INFO_V1(gin_extract_query_##type); \ +Datum \ +gin_extract_query_##type(PG_FUNCTION_ARGS) \ +{ \ + return gin_btree_extract_query(fcinfo, \ + is_varlena, leftmostvalue, typecmp); \ +} \ +PG_FUNCTION_INFO_V1(gin_compare_prefix_##type); \ +Datum \ +gin_compare_prefix_##type(PG_FUNCTION_ARGS) \ +{ \ + return gin_btree_compare_prefix(fcinfo); \ +} + + +/*** Datatype specifications ***/ + +static Datum +leftmostvalue_int2(void) +{ + return Int16GetDatum(SHRT_MIN); +} + +GIN_SUPPORT(int2, false, leftmostvalue_int2, btint2cmp) + +static Datum +leftmostvalue_int4(void) +{ + return Int32GetDatum(INT_MIN); +} + +GIN_SUPPORT(int4, false, leftmostvalue_int4, btint4cmp) + +static Datum +leftmostvalue_int8(void) +{ + return Int64GetDatum(PG_INT64_MIN); +} + +GIN_SUPPORT(int8, false, leftmostvalue_int8, btint8cmp) + +static Datum +leftmostvalue_float4(void) +{ + return Float4GetDatum(-get_float4_infinity()); +} + +GIN_SUPPORT(float4, false, leftmostvalue_float4, btfloat4cmp) + +static Datum +leftmostvalue_float8(void) +{ + return Float8GetDatum(-get_float8_infinity()); +} + +GIN_SUPPORT(float8, false, leftmostvalue_float8, btfloat8cmp) + +static Datum +leftmostvalue_money(void) +{ + return Int64GetDatum(PG_INT64_MIN); +} + +GIN_SUPPORT(money, false, leftmostvalue_money, cash_cmp) + +static Datum +leftmostvalue_oid(void) +{ + return ObjectIdGetDatum(0); +} + +GIN_SUPPORT(oid, false, leftmostvalue_oid, btoidcmp) + +static Datum +leftmostvalue_timestamp(void) +{ + return TimestampGetDatum(DT_NOBEGIN); +} + +GIN_SUPPORT(timestamp, false, leftmostvalue_timestamp, timestamp_cmp) + +GIN_SUPPORT(timestamptz, false, leftmostvalue_timestamp, timestamp_cmp) + +static Datum +leftmostvalue_time(void) +{ + return TimeADTGetDatum(0); +} + +GIN_SUPPORT(time, false, leftmostvalue_time, time_cmp) + +static Datum +leftmostvalue_timetz(void) +{ + TimeTzADT *v = palloc(sizeof(TimeTzADT)); + + v->time = 0; + v->zone = -24 * 3600; /* XXX is that true? */ + + return TimeTzADTPGetDatum(v); +} + +GIN_SUPPORT(timetz, false, leftmostvalue_timetz, timetz_cmp) + +static Datum +leftmostvalue_date(void) +{ + return DateADTGetDatum(DATEVAL_NOBEGIN); +} + +GIN_SUPPORT(date, false, leftmostvalue_date, date_cmp) + +static Datum +leftmostvalue_interval(void) +{ + Interval *v = palloc(sizeof(Interval)); + + v->time = PG_INT64_MIN; + v->day = PG_INT32_MIN; + v->month = PG_INT32_MIN; + return IntervalPGetDatum(v); +} + +GIN_SUPPORT(interval, false, leftmostvalue_interval, interval_cmp) + +static Datum +leftmostvalue_macaddr(void) +{ + macaddr *v = palloc0(sizeof(macaddr)); + + return MacaddrPGetDatum(v); +} + +GIN_SUPPORT(macaddr, false, leftmostvalue_macaddr, macaddr_cmp) + +static Datum +leftmostvalue_macaddr8(void) +{ + macaddr8 *v = palloc0(sizeof(macaddr8)); + + return Macaddr8PGetDatum(v); +} + +GIN_SUPPORT(macaddr8, false, leftmostvalue_macaddr8, macaddr8_cmp) + +static Datum +leftmostvalue_inet(void) +{ + return DirectFunctionCall1(inet_in, CStringGetDatum("0.0.0.0/0")); +} + +GIN_SUPPORT(inet, true, leftmostvalue_inet, network_cmp) + +GIN_SUPPORT(cidr, true, leftmostvalue_inet, network_cmp) + +static Datum +leftmostvalue_text(void) +{ + return PointerGetDatum(cstring_to_text_with_len("", 0)); +} + +GIN_SUPPORT(text, true, leftmostvalue_text, bttextcmp) + +GIN_SUPPORT(bpchar, true, leftmostvalue_text, bpcharcmp) + +static Datum +leftmostvalue_char(void) +{ + return CharGetDatum(0); +} + +GIN_SUPPORT(char, false, leftmostvalue_char, btcharcmp) + +GIN_SUPPORT(bytea, true, leftmostvalue_text, byteacmp) + +static Datum +leftmostvalue_bit(void) +{ + return DirectFunctionCall3(bit_in, + CStringGetDatum(""), + ObjectIdGetDatum(0), + Int32GetDatum(-1)); +} + +GIN_SUPPORT(bit, true, leftmostvalue_bit, bitcmp) + +static Datum +leftmostvalue_varbit(void) +{ + return DirectFunctionCall3(varbit_in, + CStringGetDatum(""), + ObjectIdGetDatum(0), + Int32GetDatum(-1)); +} + +GIN_SUPPORT(varbit, true, leftmostvalue_varbit, bitcmp) + +/* + * Numeric type hasn't a real left-most value, so we use PointerGetDatum(NULL) + * (*not* a SQL NULL) to represent that. We can get away with that because + * the value returned by our leftmostvalue function will never be stored in + * the index nor passed to anything except our compare and prefix-comparison + * functions. The same trick could be used for other pass-by-reference types. + */ + +#define NUMERIC_IS_LEFTMOST(x) ((x) == NULL) + +PG_FUNCTION_INFO_V1(gin_numeric_cmp); + +Datum +gin_numeric_cmp(PG_FUNCTION_ARGS) +{ + Numeric a = (Numeric) PG_GETARG_POINTER(0); + Numeric b = (Numeric) PG_GETARG_POINTER(1); + int res = 0; + + if (NUMERIC_IS_LEFTMOST(a)) + { + res = (NUMERIC_IS_LEFTMOST(b)) ? 0 : -1; + } + else if (NUMERIC_IS_LEFTMOST(b)) + { + res = 1; + } + else + { + res = DatumGetInt32(DirectFunctionCall2(numeric_cmp, + NumericGetDatum(a), + NumericGetDatum(b))); + } + + PG_RETURN_INT32(res); +} + +static Datum +leftmostvalue_numeric(void) +{ + return PointerGetDatum(NULL); +} + +GIN_SUPPORT(numeric, true, leftmostvalue_numeric, gin_numeric_cmp) + +/* + * Use a similar trick to that used for numeric for enums, since we don't + * actually know the leftmost value of any enum without knowing the concrete + * type, so we use a dummy leftmost value of InvalidOid. + * + * Note that we use CallerFInfoFunctionCall2 here so that enum_cmp + * gets a valid fn_extra to work with. Unlike most other type comparison + * routines it needs it, so we can't use DirectFunctionCall2. + */ + +#define ENUM_IS_LEFTMOST(x) ((x) == InvalidOid) + +PG_FUNCTION_INFO_V1(gin_enum_cmp); + +Datum +gin_enum_cmp(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + int res = 0; + + if (ENUM_IS_LEFTMOST(a)) + { + res = (ENUM_IS_LEFTMOST(b)) ? 0 : -1; + } + else if (ENUM_IS_LEFTMOST(b)) + { + res = 1; + } + else + { + res = DatumGetInt32(CallerFInfoFunctionCall2(enum_cmp, + fcinfo->flinfo, + PG_GET_COLLATION(), + ObjectIdGetDatum(a), + ObjectIdGetDatum(b))); + } + + PG_RETURN_INT32(res); +} + +static Datum +leftmostvalue_enum(void) +{ + return ObjectIdGetDatum(InvalidOid); +} + +GIN_SUPPORT(anyenum, false, leftmostvalue_enum, gin_enum_cmp) + +static Datum +leftmostvalue_uuid(void) +{ + /* + * palloc0 will create the UUID with all zeroes: + * "00000000-0000-0000-0000-000000000000" + */ + pg_uuid_t *retval = (pg_uuid_t *) palloc0(sizeof(pg_uuid_t)); + + return UUIDPGetDatum(retval); +} + +GIN_SUPPORT(uuid, false, leftmostvalue_uuid, uuid_cmp) + +static Datum +leftmostvalue_name(void) +{ + NameData *result = (NameData *) palloc0(NAMEDATALEN); + + return NameGetDatum(result); +} + +GIN_SUPPORT(name, false, leftmostvalue_name, btnamecmp) + +static Datum +leftmostvalue_bool(void) +{ + return BoolGetDatum(false); +} + +GIN_SUPPORT(bool, false, leftmostvalue_bool, btboolcmp) diff --git a/contrib/btree_gin/btree_gin.control b/contrib/btree_gin/btree_gin.control new file mode 100644 index 0000000..67d0c99 --- /dev/null +++ b/contrib/btree_gin/btree_gin.control @@ -0,0 +1,6 @@ +# btree_gin extension +comment = 'support for indexing common datatypes in GIN' +default_version = '1.3' +module_pathname = '$libdir/btree_gin' +relocatable = true +trusted = true diff --git a/contrib/btree_gin/expected/bit.out b/contrib/btree_gin/expected/bit.out new file mode 100644 index 0000000..3c00a20 --- /dev/null +++ b/contrib/btree_gin/expected/bit.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_bit ( + i bit(3) +); +INSERT INTO test_bit VALUES ('001'),('010'),('011'),('100'),('101'),('110'); +CREATE INDEX idx_bit ON test_bit USING gin (i); +SELECT * FROM test_bit WHERE i<'100'::bit(3) ORDER BY i; + i +----- + 001 + 010 + 011 +(3 rows) + +SELECT * FROM test_bit WHERE i<='100'::bit(3) ORDER BY i; + i +----- + 001 + 010 + 011 + 100 +(4 rows) + +SELECT * FROM test_bit WHERE i='100'::bit(3) ORDER BY i; + i +----- + 100 +(1 row) + +SELECT * FROM test_bit WHERE i>='100'::bit(3) ORDER BY i; + i +----- + 100 + 101 + 110 +(3 rows) + +SELECT * FROM test_bit WHERE i>'100'::bit(3) ORDER BY i; + i +----- + 101 + 110 +(2 rows) + diff --git a/contrib/btree_gin/expected/bool.out b/contrib/btree_gin/expected/bool.out new file mode 100644 index 0000000..b379622 --- /dev/null +++ b/contrib/btree_gin/expected/bool.out @@ -0,0 +1,133 @@ +set enable_seqscan=off; +CREATE TABLE test_bool ( + i boolean +); +INSERT INTO test_bool VALUES (false),(true),(null); +CREATE INDEX idx_bool ON test_bool USING gin (i); +SELECT * FROM test_bool WHERE i=true ORDER BY i; + i +--- + t +(1 row) + +SELECT * FROM test_bool WHERE i>true ORDER BY i; + i +--- +(0 rows) + +SELECT * FROM test_bool WHERE i=false ORDER BY i; + i +--- + f + t +(2 rows) + +SELECT * FROM test_bool WHERE i>false ORDER BY i; + i +--- + t +(1 row) + +EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i Bitmap Heap Scan on test_bool + Recheck Cond: (i < true) + -> Bitmap Index Scan on idx_bool + Index Cond: (i < true) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i<=true ORDER BY i; + QUERY PLAN +------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_bool + Recheck Cond: (i <= true) + -> Bitmap Index Scan on idx_bool + Index Cond: (i <= true) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i=true ORDER BY i; + QUERY PLAN +------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_bool + Recheck Cond: i + -> Bitmap Index Scan on idx_bool + Index Cond: (i = true) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i>=true ORDER BY i; + QUERY PLAN +------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_bool + Recheck Cond: (i >= true) + -> Bitmap Index Scan on idx_bool + Index Cond: (i >= true) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i>true ORDER BY i; + QUERY PLAN +------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_bool + Recheck Cond: (i > true) + -> Bitmap Index Scan on idx_bool + Index Cond: (i > true) +(6 rows) + +-- probably sufficient to check just this one: +EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i=false ORDER BY i; + QUERY PLAN +------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_bool + Recheck Cond: (NOT i) + -> Bitmap Index Scan on idx_bool + Index Cond: (i = false) +(6 rows) + diff --git a/contrib/btree_gin/expected/bpchar.out b/contrib/btree_gin/expected/bpchar.out new file mode 100644 index 0000000..2eb8855 --- /dev/null +++ b/contrib/btree_gin/expected/bpchar.out @@ -0,0 +1,109 @@ +set enable_seqscan=off; +CREATE TABLE test_bpchar ( + i char(10) +); +INSERT INTO test_bpchar VALUES ('a'),('ab'),('abc'),('abc '),('abb'),('axy'),('xyz'),('xyz '); +CREATE INDEX idx_bpchar ON test_bpchar USING gin (i); +SELECT * FROM test_bpchar WHERE i<'abc' ORDER BY i; + i +------------ + a + ab + abb +(3 rows) + +SELECT * FROM test_bpchar WHERE i<='abc' ORDER BY i; + i +------------ + a + ab + abb + abc + abc +(5 rows) + +SELECT * FROM test_bpchar WHERE i='abc' ORDER BY i; + i +------------ + abc + abc +(2 rows) + +SELECT * FROM test_bpchar WHERE i='abc ' ORDER BY i; + i +------------ + abc + abc +(2 rows) + +SELECT * FROM test_bpchar WHERE i>='abc' ORDER BY i; + i +------------ + abc + abc + axy + xyz + xyz +(5 rows) + +SELECT * FROM test_bpchar WHERE i>'abc' ORDER BY i; + i +------------ + axy + xyz + xyz +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_bpchar WHERE i<'abc' ORDER BY i; + QUERY PLAN +----------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_bpchar + Recheck Cond: (i < 'abc'::bpchar) + -> Bitmap Index Scan on idx_bpchar + Index Cond: (i < 'abc'::bpchar) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_bpchar WHERE i<='abc' ORDER BY i; + QUERY PLAN +------------------------------------------------ + Sort + Sort Key: i + -> Bitmap Heap Scan on test_bpchar + Recheck Cond: (i <= 'abc'::bpchar) + -> Bitmap Index Scan on idx_bpchar + Index Cond: (i <= 'abc'::bpchar) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_bpchar WHERE i='abc' ORDER BY i; + QUERY PLAN +----------------------------------------- + Bitmap Heap Scan on test_bpchar + Recheck Cond: (i = 'abc'::bpchar) + -> Bitmap Index Scan on idx_bpchar + Index Cond: (i = 'abc'::bpchar) +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_bpchar WHERE i>='abc' ORDER BY i; + QUERY PLAN +------------------------------------------------ + Sort + Sort Key: i + -> Bitmap Heap Scan on test_bpchar + Recheck Cond: (i >= 'abc'::bpchar) + -> Bitmap Index Scan on idx_bpchar + Index Cond: (i >= 'abc'::bpchar) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_bpchar WHERE i>'abc' ORDER BY i; + QUERY PLAN +----------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_bpchar + Recheck Cond: (i > 'abc'::bpchar) + -> Bitmap Index Scan on idx_bpchar + Index Cond: (i > 'abc'::bpchar) +(6 rows) + diff --git a/contrib/btree_gin/expected/bytea.out b/contrib/btree_gin/expected/bytea.out new file mode 100644 index 0000000..b0ed7a5 --- /dev/null +++ b/contrib/btree_gin/expected/bytea.out @@ -0,0 +1,46 @@ +set enable_seqscan=off; +-- ensure consistent test output regardless of the default bytea format +SET bytea_output TO escape; +CREATE TABLE test_bytea ( + i bytea +); +INSERT INTO test_bytea VALUES ('a'),('ab'),('abc'),('abb'),('axy'),('xyz'); +CREATE INDEX idx_bytea ON test_bytea USING gin (i); +SELECT * FROM test_bytea WHERE i<'abc'::bytea ORDER BY i; + i +----- + a + ab + abb +(3 rows) + +SELECT * FROM test_bytea WHERE i<='abc'::bytea ORDER BY i; + i +----- + a + ab + abb + abc +(4 rows) + +SELECT * FROM test_bytea WHERE i='abc'::bytea ORDER BY i; + i +----- + abc +(1 row) + +SELECT * FROM test_bytea WHERE i>='abc'::bytea ORDER BY i; + i +----- + abc + axy + xyz +(3 rows) + +SELECT * FROM test_bytea WHERE i>'abc'::bytea ORDER BY i; + i +----- + axy + xyz +(2 rows) + diff --git a/contrib/btree_gin/expected/char.out b/contrib/btree_gin/expected/char.out new file mode 100644 index 0000000..6563546 --- /dev/null +++ b/contrib/btree_gin/expected/char.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_char ( + i "char" +); +INSERT INTO test_char VALUES ('a'),('b'),('c'),('d'),('e'),('f'); +CREATE INDEX idx_char ON test_char USING gin (i); +SELECT * FROM test_char WHERE i<'d'::"char" ORDER BY i; + i +--- + a + b + c +(3 rows) + +SELECT * FROM test_char WHERE i<='d'::"char" ORDER BY i; + i +--- + a + b + c + d +(4 rows) + +SELECT * FROM test_char WHERE i='d'::"char" ORDER BY i; + i +--- + d +(1 row) + +SELECT * FROM test_char WHERE i>='d'::"char" ORDER BY i; + i +--- + d + e + f +(3 rows) + +SELECT * FROM test_char WHERE i>'d'::"char" ORDER BY i; + i +--- + e + f +(2 rows) + diff --git a/contrib/btree_gin/expected/cidr.out b/contrib/btree_gin/expected/cidr.out new file mode 100644 index 0000000..3d1198a --- /dev/null +++ b/contrib/btree_gin/expected/cidr.out @@ -0,0 +1,51 @@ +set enable_seqscan=off; +CREATE TABLE test_cidr ( + i cidr +); +INSERT INTO test_cidr VALUES + ( '1.2.3.4' ), + ( '1.2.4.4' ), + ( '1.2.5.4' ), + ( '1.2.6.4' ), + ( '1.2.7.4' ), + ( '1.2.8.4' ) +; +CREATE INDEX idx_cidr ON test_cidr USING gin (i); +SELECT * FROM test_cidr WHERE i<'1.2.6.4'::cidr ORDER BY i; + i +------------ + 1.2.3.4/32 + 1.2.4.4/32 + 1.2.5.4/32 +(3 rows) + +SELECT * FROM test_cidr WHERE i<='1.2.6.4'::cidr ORDER BY i; + i +------------ + 1.2.3.4/32 + 1.2.4.4/32 + 1.2.5.4/32 + 1.2.6.4/32 +(4 rows) + +SELECT * FROM test_cidr WHERE i='1.2.6.4'::cidr ORDER BY i; + i +------------ + 1.2.6.4/32 +(1 row) + +SELECT * FROM test_cidr WHERE i>='1.2.6.4'::cidr ORDER BY i; + i +------------ + 1.2.6.4/32 + 1.2.7.4/32 + 1.2.8.4/32 +(3 rows) + +SELECT * FROM test_cidr WHERE i>'1.2.6.4'::cidr ORDER BY i; + i +------------ + 1.2.7.4/32 + 1.2.8.4/32 +(2 rows) + diff --git a/contrib/btree_gin/expected/date.out b/contrib/btree_gin/expected/date.out new file mode 100644 index 0000000..40dfa30 --- /dev/null +++ b/contrib/btree_gin/expected/date.out @@ -0,0 +1,51 @@ +set enable_seqscan=off; +CREATE TABLE test_date ( + i date +); +INSERT INTO test_date VALUES + ( '2004-10-23' ), + ( '2004-10-24' ), + ( '2004-10-25' ), + ( '2004-10-26' ), + ( '2004-10-27' ), + ( '2004-10-28' ) +; +CREATE INDEX idx_date ON test_date USING gin (i); +SELECT * FROM test_date WHERE i<'2004-10-26'::date ORDER BY i; + i +------------ + 10-23-2004 + 10-24-2004 + 10-25-2004 +(3 rows) + +SELECT * FROM test_date WHERE i<='2004-10-26'::date ORDER BY i; + i +------------ + 10-23-2004 + 10-24-2004 + 10-25-2004 + 10-26-2004 +(4 rows) + +SELECT * FROM test_date WHERE i='2004-10-26'::date ORDER BY i; + i +------------ + 10-26-2004 +(1 row) + +SELECT * FROM test_date WHERE i>='2004-10-26'::date ORDER BY i; + i +------------ + 10-26-2004 + 10-27-2004 + 10-28-2004 +(3 rows) + +SELECT * FROM test_date WHERE i>'2004-10-26'::date ORDER BY i; + i +------------ + 10-27-2004 + 10-28-2004 +(2 rows) + diff --git a/contrib/btree_gin/expected/enum.out b/contrib/btree_gin/expected/enum.out new file mode 100644 index 0000000..71e0c4b --- /dev/null +++ b/contrib/btree_gin/expected/enum.out @@ -0,0 +1,63 @@ +set enable_seqscan=off; +CREATE TYPE rainbow AS ENUM ('r','o','y','g','b','i','v'); +CREATE TABLE test_enum ( + i rainbow +); +INSERT INTO test_enum VALUES ('v'),('y'),('r'),('g'),('o'),('i'),('b'); +CREATE INDEX idx_enum ON test_enum USING gin (i); +SELECT * FROM test_enum WHERE i<'g'::rainbow ORDER BY i; + i +--- + r + o + y +(3 rows) + +SELECT * FROM test_enum WHERE i<='g'::rainbow ORDER BY i; + i +--- + r + o + y + g +(4 rows) + +SELECT * FROM test_enum WHERE i='g'::rainbow ORDER BY i; + i +--- + g +(1 row) + +SELECT * FROM test_enum WHERE i>='g'::rainbow ORDER BY i; + i +--- + g + b + i + v +(4 rows) + +SELECT * FROM test_enum WHERE i>'g'::rainbow ORDER BY i; + i +--- + b + i + v +(3 rows) + +explain (costs off) SELECT * FROM test_enum WHERE i>='g'::rainbow ORDER BY i; + QUERY PLAN +----------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_enum + Recheck Cond: (i >= 'g'::rainbow) + -> Bitmap Index Scan on idx_enum + Index Cond: (i >= 'g'::rainbow) +(6 rows) + +-- make sure we handle the non-evenly-numbered oid case for enums +create type e as enum ('0', '2', '3'); +alter type e add value '1' after '0'; +create table t as select (i % 4)::text::e from generate_series(0, 100000) as i; +create index on t using gin (e); diff --git a/contrib/btree_gin/expected/float4.out b/contrib/btree_gin/expected/float4.out new file mode 100644 index 0000000..7b9134f --- /dev/null +++ b/contrib/btree_gin/expected/float4.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_float4 ( + i float4 +); +INSERT INTO test_float4 VALUES (-2),(-1),(0),(1),(2),(3); +CREATE INDEX idx_float4 ON test_float4 USING gin (i); +SELECT * FROM test_float4 WHERE i<1::float4 ORDER BY i; + i +---- + -2 + -1 + 0 +(3 rows) + +SELECT * FROM test_float4 WHERE i<=1::float4 ORDER BY i; + i +---- + -2 + -1 + 0 + 1 +(4 rows) + +SELECT * FROM test_float4 WHERE i=1::float4 ORDER BY i; + i +--- + 1 +(1 row) + +SELECT * FROM test_float4 WHERE i>=1::float4 ORDER BY i; + i +--- + 1 + 2 + 3 +(3 rows) + +SELECT * FROM test_float4 WHERE i>1::float4 ORDER BY i; + i +--- + 2 + 3 +(2 rows) + diff --git a/contrib/btree_gin/expected/float8.out b/contrib/btree_gin/expected/float8.out new file mode 100644 index 0000000..a41d4f9 --- /dev/null +++ b/contrib/btree_gin/expected/float8.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_float8 ( + i float8 +); +INSERT INTO test_float8 VALUES (-2),(-1),(0),(1),(2),(3); +CREATE INDEX idx_float8 ON test_float8 USING gin (i); +SELECT * FROM test_float8 WHERE i<1::float8 ORDER BY i; + i +---- + -2 + -1 + 0 +(3 rows) + +SELECT * FROM test_float8 WHERE i<=1::float8 ORDER BY i; + i +---- + -2 + -1 + 0 + 1 +(4 rows) + +SELECT * FROM test_float8 WHERE i=1::float8 ORDER BY i; + i +--- + 1 +(1 row) + +SELECT * FROM test_float8 WHERE i>=1::float8 ORDER BY i; + i +--- + 1 + 2 + 3 +(3 rows) + +SELECT * FROM test_float8 WHERE i>1::float8 ORDER BY i; + i +--- + 2 + 3 +(2 rows) + diff --git a/contrib/btree_gin/expected/inet.out b/contrib/btree_gin/expected/inet.out new file mode 100644 index 0000000..aa6147f --- /dev/null +++ b/contrib/btree_gin/expected/inet.out @@ -0,0 +1,51 @@ +set enable_seqscan=off; +CREATE TABLE test_inet ( + i inet +); +INSERT INTO test_inet VALUES + ( '1.2.3.4/16' ), + ( '1.2.4.4/16' ), + ( '1.2.5.4/16' ), + ( '1.2.6.4/16' ), + ( '1.2.7.4/16' ), + ( '1.2.8.4/16' ) +; +CREATE INDEX idx_inet ON test_inet USING gin (i); +SELECT * FROM test_inet WHERE i<'1.2.6.4/16'::inet ORDER BY i; + i +------------ + 1.2.3.4/16 + 1.2.4.4/16 + 1.2.5.4/16 +(3 rows) + +SELECT * FROM test_inet WHERE i<='1.2.6.4/16'::inet ORDER BY i; + i +------------ + 1.2.3.4/16 + 1.2.4.4/16 + 1.2.5.4/16 + 1.2.6.4/16 +(4 rows) + +SELECT * FROM test_inet WHERE i='1.2.6.4/16'::inet ORDER BY i; + i +------------ + 1.2.6.4/16 +(1 row) + +SELECT * FROM test_inet WHERE i>='1.2.6.4/16'::inet ORDER BY i; + i +------------ + 1.2.6.4/16 + 1.2.7.4/16 + 1.2.8.4/16 +(3 rows) + +SELECT * FROM test_inet WHERE i>'1.2.6.4/16'::inet ORDER BY i; + i +------------ + 1.2.7.4/16 + 1.2.8.4/16 +(2 rows) + diff --git a/contrib/btree_gin/expected/install_btree_gin.out b/contrib/btree_gin/expected/install_btree_gin.out new file mode 100644 index 0000000..631a0df --- /dev/null +++ b/contrib/btree_gin/expected/install_btree_gin.out @@ -0,0 +1,9 @@ +CREATE EXTENSION btree_gin; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + amname | opcname +--------+--------- +(0 rows) + diff --git a/contrib/btree_gin/expected/int2.out b/contrib/btree_gin/expected/int2.out new file mode 100644 index 0000000..20d66a1 --- /dev/null +++ b/contrib/btree_gin/expected/int2.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_int2 ( + i int2 +); +INSERT INTO test_int2 VALUES (-2),(-1),(0),(1),(2),(3); +CREATE INDEX idx_int2 ON test_int2 USING gin (i); +SELECT * FROM test_int2 WHERE i<1::int2 ORDER BY i; + i +---- + -2 + -1 + 0 +(3 rows) + +SELECT * FROM test_int2 WHERE i<=1::int2 ORDER BY i; + i +---- + -2 + -1 + 0 + 1 +(4 rows) + +SELECT * FROM test_int2 WHERE i=1::int2 ORDER BY i; + i +--- + 1 +(1 row) + +SELECT * FROM test_int2 WHERE i>=1::int2 ORDER BY i; + i +--- + 1 + 2 + 3 +(3 rows) + +SELECT * FROM test_int2 WHERE i>1::int2 ORDER BY i; + i +--- + 2 + 3 +(2 rows) + diff --git a/contrib/btree_gin/expected/int4.out b/contrib/btree_gin/expected/int4.out new file mode 100644 index 0000000..0f0122c --- /dev/null +++ b/contrib/btree_gin/expected/int4.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_int4 ( + i int4 +); +INSERT INTO test_int4 VALUES (-2),(-1),(0),(1),(2),(3); +CREATE INDEX idx_int4 ON test_int4 USING gin (i); +SELECT * FROM test_int4 WHERE i<1::int4 ORDER BY i; + i +---- + -2 + -1 + 0 +(3 rows) + +SELECT * FROM test_int4 WHERE i<=1::int4 ORDER BY i; + i +---- + -2 + -1 + 0 + 1 +(4 rows) + +SELECT * FROM test_int4 WHERE i=1::int4 ORDER BY i; + i +--- + 1 +(1 row) + +SELECT * FROM test_int4 WHERE i>=1::int4 ORDER BY i; + i +--- + 1 + 2 + 3 +(3 rows) + +SELECT * FROM test_int4 WHERE i>1::int4 ORDER BY i; + i +--- + 2 + 3 +(2 rows) + diff --git a/contrib/btree_gin/expected/int8.out b/contrib/btree_gin/expected/int8.out new file mode 100644 index 0000000..307e19e --- /dev/null +++ b/contrib/btree_gin/expected/int8.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_int8 ( + i int8 +); +INSERT INTO test_int8 VALUES (-2),(-1),(0),(1),(2),(3); +CREATE INDEX idx_int8 ON test_int8 USING gin (i); +SELECT * FROM test_int8 WHERE i<1::int8 ORDER BY i; + i +---- + -2 + -1 + 0 +(3 rows) + +SELECT * FROM test_int8 WHERE i<=1::int8 ORDER BY i; + i +---- + -2 + -1 + 0 + 1 +(4 rows) + +SELECT * FROM test_int8 WHERE i=1::int8 ORDER BY i; + i +--- + 1 +(1 row) + +SELECT * FROM test_int8 WHERE i>=1::int8 ORDER BY i; + i +--- + 1 + 2 + 3 +(3 rows) + +SELECT * FROM test_int8 WHERE i>1::int8 ORDER BY i; + i +--- + 2 + 3 +(2 rows) + diff --git a/contrib/btree_gin/expected/interval.out b/contrib/btree_gin/expected/interval.out new file mode 100644 index 0000000..8bb9806 --- /dev/null +++ b/contrib/btree_gin/expected/interval.out @@ -0,0 +1,57 @@ +set enable_seqscan=off; +CREATE TABLE test_interval ( + i interval +); +INSERT INTO test_interval VALUES + ( '-178000000 years' ), + ( '03:55:08' ), + ( '04:55:08' ), + ( '05:55:08' ), + ( '08:55:08' ), + ( '09:55:08' ), + ( '10:55:08' ), + ( '178000000 years' ) +; +CREATE INDEX idx_interval ON test_interval USING gin (i); +SELECT * FROM test_interval WHERE i<'08:55:08'::interval ORDER BY i; + i +-------------------------- + @ 178000000 years ago + @ 3 hours 55 mins 8 secs + @ 4 hours 55 mins 8 secs + @ 5 hours 55 mins 8 secs +(4 rows) + +SELECT * FROM test_interval WHERE i<='08:55:08'::interval ORDER BY i; + i +-------------------------- + @ 178000000 years ago + @ 3 hours 55 mins 8 secs + @ 4 hours 55 mins 8 secs + @ 5 hours 55 mins 8 secs + @ 8 hours 55 mins 8 secs +(5 rows) + +SELECT * FROM test_interval WHERE i='08:55:08'::interval ORDER BY i; + i +-------------------------- + @ 8 hours 55 mins 8 secs +(1 row) + +SELECT * FROM test_interval WHERE i>='08:55:08'::interval ORDER BY i; + i +--------------------------- + @ 8 hours 55 mins 8 secs + @ 9 hours 55 mins 8 secs + @ 10 hours 55 mins 8 secs + @ 178000000 years +(4 rows) + +SELECT * FROM test_interval WHERE i>'08:55:08'::interval ORDER BY i; + i +--------------------------- + @ 9 hours 55 mins 8 secs + @ 10 hours 55 mins 8 secs + @ 178000000 years +(3 rows) + diff --git a/contrib/btree_gin/expected/macaddr.out b/contrib/btree_gin/expected/macaddr.out new file mode 100644 index 0000000..ebceb01 --- /dev/null +++ b/contrib/btree_gin/expected/macaddr.out @@ -0,0 +1,51 @@ +set enable_seqscan=off; +CREATE TABLE test_macaddr ( + i macaddr +); +INSERT INTO test_macaddr VALUES + ( '22:00:5c:03:55:08' ), + ( '22:00:5c:04:55:08' ), + ( '22:00:5c:05:55:08' ), + ( '22:00:5c:08:55:08' ), + ( '22:00:5c:09:55:08' ), + ( '22:00:5c:10:55:08' ) +; +CREATE INDEX idx_macaddr ON test_macaddr USING gin (i); +SELECT * FROM test_macaddr WHERE i<'22:00:5c:08:55:08'::macaddr ORDER BY i; + i +------------------- + 22:00:5c:03:55:08 + 22:00:5c:04:55:08 + 22:00:5c:05:55:08 +(3 rows) + +SELECT * FROM test_macaddr WHERE i<='22:00:5c:08:55:08'::macaddr ORDER BY i; + i +------------------- + 22:00:5c:03:55:08 + 22:00:5c:04:55:08 + 22:00:5c:05:55:08 + 22:00:5c:08:55:08 +(4 rows) + +SELECT * FROM test_macaddr WHERE i='22:00:5c:08:55:08'::macaddr ORDER BY i; + i +------------------- + 22:00:5c:08:55:08 +(1 row) + +SELECT * FROM test_macaddr WHERE i>='22:00:5c:08:55:08'::macaddr ORDER BY i; + i +------------------- + 22:00:5c:08:55:08 + 22:00:5c:09:55:08 + 22:00:5c:10:55:08 +(3 rows) + +SELECT * FROM test_macaddr WHERE i>'22:00:5c:08:55:08'::macaddr ORDER BY i; + i +------------------- + 22:00:5c:09:55:08 + 22:00:5c:10:55:08 +(2 rows) + diff --git a/contrib/btree_gin/expected/macaddr8.out b/contrib/btree_gin/expected/macaddr8.out new file mode 100644 index 0000000..025b0c1 --- /dev/null +++ b/contrib/btree_gin/expected/macaddr8.out @@ -0,0 +1,51 @@ +set enable_seqscan=off; +CREATE TABLE test_macaddr8 ( + i macaddr8 +); +INSERT INTO test_macaddr8 VALUES + ( '22:00:5c:03:55:08:01:02' ), + ( '22:00:5c:04:55:08:01:02' ), + ( '22:00:5c:05:55:08:01:02' ), + ( '22:00:5c:08:55:08:01:02' ), + ( '22:00:5c:09:55:08:01:02' ), + ( '22:00:5c:10:55:08:01:02' ) +; +CREATE INDEX idx_macaddr8 ON test_macaddr8 USING gin (i); +SELECT * FROM test_macaddr8 WHERE i<'22:00:5c:08:55:08:01:02'::macaddr8 ORDER BY i; + i +------------------------- + 22:00:5c:03:55:08:01:02 + 22:00:5c:04:55:08:01:02 + 22:00:5c:05:55:08:01:02 +(3 rows) + +SELECT * FROM test_macaddr8 WHERE i<='22:00:5c:08:55:08:01:02'::macaddr8 ORDER BY i; + i +------------------------- + 22:00:5c:03:55:08:01:02 + 22:00:5c:04:55:08:01:02 + 22:00:5c:05:55:08:01:02 + 22:00:5c:08:55:08:01:02 +(4 rows) + +SELECT * FROM test_macaddr8 WHERE i='22:00:5c:08:55:08:01:02'::macaddr8 ORDER BY i; + i +------------------------- + 22:00:5c:08:55:08:01:02 +(1 row) + +SELECT * FROM test_macaddr8 WHERE i>='22:00:5c:08:55:08:01:02'::macaddr8 ORDER BY i; + i +------------------------- + 22:00:5c:08:55:08:01:02 + 22:00:5c:09:55:08:01:02 + 22:00:5c:10:55:08:01:02 +(3 rows) + +SELECT * FROM test_macaddr8 WHERE i>'22:00:5c:08:55:08:01:02'::macaddr8 ORDER BY i; + i +------------------------- + 22:00:5c:09:55:08:01:02 + 22:00:5c:10:55:08:01:02 +(2 rows) + diff --git a/contrib/btree_gin/expected/money.out b/contrib/btree_gin/expected/money.out new file mode 100644 index 0000000..a0ba571 --- /dev/null +++ b/contrib/btree_gin/expected/money.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_money ( + i money +); +INSERT INTO test_money VALUES ('-2'),('-1'),('0'),('1'),('2'),('3'); +CREATE INDEX idx_money ON test_money USING gin (i); +SELECT * FROM test_money WHERE i<'1'::money ORDER BY i; + i +-------- + -$2.00 + -$1.00 + $0.00 +(3 rows) + +SELECT * FROM test_money WHERE i<='1'::money ORDER BY i; + i +-------- + -$2.00 + -$1.00 + $0.00 + $1.00 +(4 rows) + +SELECT * FROM test_money WHERE i='1'::money ORDER BY i; + i +------- + $1.00 +(1 row) + +SELECT * FROM test_money WHERE i>='1'::money ORDER BY i; + i +------- + $1.00 + $2.00 + $3.00 +(3 rows) + +SELECT * FROM test_money WHERE i>'1'::money ORDER BY i; + i +------- + $2.00 + $3.00 +(2 rows) + diff --git a/contrib/btree_gin/expected/name.out b/contrib/btree_gin/expected/name.out new file mode 100644 index 0000000..174de65 --- /dev/null +++ b/contrib/btree_gin/expected/name.out @@ -0,0 +1,97 @@ +set enable_seqscan=off; +CREATE TABLE test_name ( + i name +); +INSERT INTO test_name VALUES ('a'),('ab'),('abc'),('abb'),('axy'),('xyz'); +CREATE INDEX idx_name ON test_name USING gin (i); +SELECT * FROM test_name WHERE i<'abc' ORDER BY i; + i +----- + a + ab + abb +(3 rows) + +SELECT * FROM test_name WHERE i<='abc' ORDER BY i; + i +----- + a + ab + abb + abc +(4 rows) + +SELECT * FROM test_name WHERE i='abc' ORDER BY i; + i +----- + abc +(1 row) + +SELECT * FROM test_name WHERE i>='abc' ORDER BY i; + i +----- + abc + axy + xyz +(3 rows) + +SELECT * FROM test_name WHERE i>'abc' ORDER BY i; + i +----- + axy + xyz +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_name WHERE i<'abc' ORDER BY i; + QUERY PLAN +--------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_name + Recheck Cond: (i < 'abc'::name) + -> Bitmap Index Scan on idx_name + Index Cond: (i < 'abc'::name) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_name WHERE i<='abc' ORDER BY i; + QUERY PLAN +---------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_name + Recheck Cond: (i <= 'abc'::name) + -> Bitmap Index Scan on idx_name + Index Cond: (i <= 'abc'::name) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_name WHERE i='abc' ORDER BY i; + QUERY PLAN +--------------------------------------- + Bitmap Heap Scan on test_name + Recheck Cond: (i = 'abc'::name) + -> Bitmap Index Scan on idx_name + Index Cond: (i = 'abc'::name) +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_name WHERE i>='abc' ORDER BY i; + QUERY PLAN +---------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_name + Recheck Cond: (i >= 'abc'::name) + -> Bitmap Index Scan on idx_name + Index Cond: (i >= 'abc'::name) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_name WHERE i>'abc' ORDER BY i; + QUERY PLAN +--------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_name + Recheck Cond: (i > 'abc'::name) + -> Bitmap Index Scan on idx_name + Index Cond: (i > 'abc'::name) +(6 rows) + diff --git a/contrib/btree_gin/expected/numeric.out b/contrib/btree_gin/expected/numeric.out new file mode 100644 index 0000000..f10a672 --- /dev/null +++ b/contrib/btree_gin/expected/numeric.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_numeric ( + i numeric +); +INSERT INTO test_numeric VALUES (-2),(-1),(0),(1),(2),(3); +CREATE INDEX idx_numeric ON test_numeric USING gin (i); +SELECT * FROM test_numeric WHERE i<'1'::numeric ORDER BY i; + i +---- + -2 + -1 + 0 +(3 rows) + +SELECT * FROM test_numeric WHERE i<='1'::numeric ORDER BY i; + i +---- + -2 + -1 + 0 + 1 +(4 rows) + +SELECT * FROM test_numeric WHERE i='1'::numeric ORDER BY i; + i +--- + 1 +(1 row) + +SELECT * FROM test_numeric WHERE i>='1'::numeric ORDER BY i; + i +--- + 1 + 2 + 3 +(3 rows) + +SELECT * FROM test_numeric WHERE i>'1'::numeric ORDER BY i; + i +--- + 2 + 3 +(2 rows) + diff --git a/contrib/btree_gin/expected/oid.out b/contrib/btree_gin/expected/oid.out new file mode 100644 index 0000000..19e15c7 --- /dev/null +++ b/contrib/btree_gin/expected/oid.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_oid ( + i oid +); +INSERT INTO test_oid VALUES (0),(1),(2),(3),(4),(5); +CREATE INDEX idx_oid ON test_oid USING gin (i); +SELECT * FROM test_oid WHERE i<3::oid ORDER BY i; + i +--- + 0 + 1 + 2 +(3 rows) + +SELECT * FROM test_oid WHERE i<=3::oid ORDER BY i; + i +--- + 0 + 1 + 2 + 3 +(4 rows) + +SELECT * FROM test_oid WHERE i=3::oid ORDER BY i; + i +--- + 3 +(1 row) + +SELECT * FROM test_oid WHERE i>=3::oid ORDER BY i; + i +--- + 3 + 4 + 5 +(3 rows) + +SELECT * FROM test_oid WHERE i>3::oid ORDER BY i; + i +--- + 4 + 5 +(2 rows) + diff --git a/contrib/btree_gin/expected/text.out b/contrib/btree_gin/expected/text.out new file mode 100644 index 0000000..3e31ad7 --- /dev/null +++ b/contrib/btree_gin/expected/text.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_text ( + i text +); +INSERT INTO test_text VALUES ('a'),('ab'),('abc'),('abb'),('axy'),('xyz'); +CREATE INDEX idx_text ON test_text USING gin (i); +SELECT * FROM test_text WHERE i<'abc' ORDER BY i; + i +----- + a + ab + abb +(3 rows) + +SELECT * FROM test_text WHERE i<='abc' ORDER BY i; + i +----- + a + ab + abb + abc +(4 rows) + +SELECT * FROM test_text WHERE i='abc' ORDER BY i; + i +----- + abc +(1 row) + +SELECT * FROM test_text WHERE i>='abc' ORDER BY i; + i +----- + abc + axy + xyz +(3 rows) + +SELECT * FROM test_text WHERE i>'abc' ORDER BY i; + i +----- + axy + xyz +(2 rows) + diff --git a/contrib/btree_gin/expected/time.out b/contrib/btree_gin/expected/time.out new file mode 100644 index 0000000..be6b084 --- /dev/null +++ b/contrib/btree_gin/expected/time.out @@ -0,0 +1,51 @@ +set enable_seqscan=off; +CREATE TABLE test_time ( + i time +); +INSERT INTO test_time VALUES + ( '03:55:08' ), + ( '04:55:08' ), + ( '05:55:08' ), + ( '08:55:08' ), + ( '09:55:08' ), + ( '10:55:08' ) +; +CREATE INDEX idx_time ON test_time USING gin (i); +SELECT * FROM test_time WHERE i<'08:55:08'::time ORDER BY i; + i +---------- + 03:55:08 + 04:55:08 + 05:55:08 +(3 rows) + +SELECT * FROM test_time WHERE i<='08:55:08'::time ORDER BY i; + i +---------- + 03:55:08 + 04:55:08 + 05:55:08 + 08:55:08 +(4 rows) + +SELECT * FROM test_time WHERE i='08:55:08'::time ORDER BY i; + i +---------- + 08:55:08 +(1 row) + +SELECT * FROM test_time WHERE i>='08:55:08'::time ORDER BY i; + i +---------- + 08:55:08 + 09:55:08 + 10:55:08 +(3 rows) + +SELECT * FROM test_time WHERE i>'08:55:08'::time ORDER BY i; + i +---------- + 09:55:08 + 10:55:08 +(2 rows) + diff --git a/contrib/btree_gin/expected/timestamp.out b/contrib/btree_gin/expected/timestamp.out new file mode 100644 index 0000000..a236cdc --- /dev/null +++ b/contrib/btree_gin/expected/timestamp.out @@ -0,0 +1,51 @@ +set enable_seqscan=off; +CREATE TABLE test_timestamp ( + i timestamp +); +INSERT INTO test_timestamp VALUES + ( '2004-10-26 03:55:08' ), + ( '2004-10-26 04:55:08' ), + ( '2004-10-26 05:55:08' ), + ( '2004-10-26 08:55:08' ), + ( '2004-10-26 09:55:08' ), + ( '2004-10-26 10:55:08' ) +; +CREATE INDEX idx_timestamp ON test_timestamp USING gin (i); +SELECT * FROM test_timestamp WHERE i<'2004-10-26 08:55:08'::timestamp ORDER BY i; + i +-------------------------- + Tue Oct 26 03:55:08 2004 + Tue Oct 26 04:55:08 2004 + Tue Oct 26 05:55:08 2004 +(3 rows) + +SELECT * FROM test_timestamp WHERE i<='2004-10-26 08:55:08'::timestamp ORDER BY i; + i +-------------------------- + Tue Oct 26 03:55:08 2004 + Tue Oct 26 04:55:08 2004 + Tue Oct 26 05:55:08 2004 + Tue Oct 26 08:55:08 2004 +(4 rows) + +SELECT * FROM test_timestamp WHERE i='2004-10-26 08:55:08'::timestamp ORDER BY i; + i +-------------------------- + Tue Oct 26 08:55:08 2004 +(1 row) + +SELECT * FROM test_timestamp WHERE i>='2004-10-26 08:55:08'::timestamp ORDER BY i; + i +-------------------------- + Tue Oct 26 08:55:08 2004 + Tue Oct 26 09:55:08 2004 + Tue Oct 26 10:55:08 2004 +(3 rows) + +SELECT * FROM test_timestamp WHERE i>'2004-10-26 08:55:08'::timestamp ORDER BY i; + i +-------------------------- + Tue Oct 26 09:55:08 2004 + Tue Oct 26 10:55:08 2004 +(2 rows) + diff --git a/contrib/btree_gin/expected/timestamptz.out b/contrib/btree_gin/expected/timestamptz.out new file mode 100644 index 0000000..d53963d --- /dev/null +++ b/contrib/btree_gin/expected/timestamptz.out @@ -0,0 +1,51 @@ +set enable_seqscan=off; +CREATE TABLE test_timestamptz ( + i timestamptz +); +INSERT INTO test_timestamptz VALUES + ( '2004-10-26 03:55:08' ), + ( '2004-10-26 04:55:08' ), + ( '2004-10-26 05:55:08' ), + ( '2004-10-26 08:55:08' ), + ( '2004-10-26 09:55:08' ), + ( '2004-10-26 10:55:08' ) +; +CREATE INDEX idx_timestamptz ON test_timestamptz USING gin (i); +SELECT * FROM test_timestamptz WHERE i<'2004-10-26 08:55:08'::timestamptz ORDER BY i; + i +------------------------------ + Tue Oct 26 03:55:08 2004 PDT + Tue Oct 26 04:55:08 2004 PDT + Tue Oct 26 05:55:08 2004 PDT +(3 rows) + +SELECT * FROM test_timestamptz WHERE i<='2004-10-26 08:55:08'::timestamptz ORDER BY i; + i +------------------------------ + Tue Oct 26 03:55:08 2004 PDT + Tue Oct 26 04:55:08 2004 PDT + Tue Oct 26 05:55:08 2004 PDT + Tue Oct 26 08:55:08 2004 PDT +(4 rows) + +SELECT * FROM test_timestamptz WHERE i='2004-10-26 08:55:08'::timestamptz ORDER BY i; + i +------------------------------ + Tue Oct 26 08:55:08 2004 PDT +(1 row) + +SELECT * FROM test_timestamptz WHERE i>='2004-10-26 08:55:08'::timestamptz ORDER BY i; + i +------------------------------ + Tue Oct 26 08:55:08 2004 PDT + Tue Oct 26 09:55:08 2004 PDT + Tue Oct 26 10:55:08 2004 PDT +(3 rows) + +SELECT * FROM test_timestamptz WHERE i>'2004-10-26 08:55:08'::timestamptz ORDER BY i; + i +------------------------------ + Tue Oct 26 09:55:08 2004 PDT + Tue Oct 26 10:55:08 2004 PDT +(2 rows) + diff --git a/contrib/btree_gin/expected/timetz.out b/contrib/btree_gin/expected/timetz.out new file mode 100644 index 0000000..45aee71 --- /dev/null +++ b/contrib/btree_gin/expected/timetz.out @@ -0,0 +1,51 @@ +set enable_seqscan=off; +CREATE TABLE test_timetz ( + i timetz +); +INSERT INTO test_timetz VALUES + ( '03:55:08 GMT+2' ), + ( '04:55:08 GMT+2' ), + ( '05:55:08 GMT+2' ), + ( '08:55:08 GMT+2' ), + ( '09:55:08 GMT+2' ), + ( '10:55:08 GMT+2' ) +; +CREATE INDEX idx_timetz ON test_timetz USING gin (i); +SELECT * FROM test_timetz WHERE i<'08:55:08 GMT+2'::timetz ORDER BY i; + i +------------- + 03:55:08-02 + 04:55:08-02 + 05:55:08-02 +(3 rows) + +SELECT * FROM test_timetz WHERE i<='08:55:08 GMT+2'::timetz ORDER BY i; + i +------------- + 03:55:08-02 + 04:55:08-02 + 05:55:08-02 + 08:55:08-02 +(4 rows) + +SELECT * FROM test_timetz WHERE i='08:55:08 GMT+2'::timetz ORDER BY i; + i +------------- + 08:55:08-02 +(1 row) + +SELECT * FROM test_timetz WHERE i>='08:55:08 GMT+2'::timetz ORDER BY i; + i +------------- + 08:55:08-02 + 09:55:08-02 + 10:55:08-02 +(3 rows) + +SELECT * FROM test_timetz WHERE i>'08:55:08 GMT+2'::timetz ORDER BY i; + i +------------- + 09:55:08-02 + 10:55:08-02 +(2 rows) + diff --git a/contrib/btree_gin/expected/uuid.out b/contrib/btree_gin/expected/uuid.out new file mode 100644 index 0000000..60fd8d6 --- /dev/null +++ b/contrib/btree_gin/expected/uuid.out @@ -0,0 +1,104 @@ +set enable_seqscan=off; +CREATE TABLE test_uuid ( + i uuid +); +INSERT INTO test_uuid VALUES + ( '00000000-0000-0000-0000-000000000000' ), + ( '299bc99f-2f79-4e3e-bfea-2cbfd62a7c27' ), + ( '6264af33-0d43-4337-bf4e-43509b8a4be8' ), + ( 'ce41c936-6acb-4feb-8c91-852a673e5a5c' ), + ( 'd2ce731f-f2a8-4a2b-be37-8f0ba637427f' ), + ( 'ffffffff-ffff-ffff-ffff-ffffffffffff' ) +; +CREATE INDEX idx_uuid ON test_uuid USING gin (i); +SELECT * FROM test_uuid WHERE i<'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + i +-------------------------------------- + 00000000-0000-0000-0000-000000000000 + 299bc99f-2f79-4e3e-bfea-2cbfd62a7c27 + 6264af33-0d43-4337-bf4e-43509b8a4be8 +(3 rows) + +SELECT * FROM test_uuid WHERE i<='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + i +-------------------------------------- + 00000000-0000-0000-0000-000000000000 + 299bc99f-2f79-4e3e-bfea-2cbfd62a7c27 + 6264af33-0d43-4337-bf4e-43509b8a4be8 + ce41c936-6acb-4feb-8c91-852a673e5a5c +(4 rows) + +SELECT * FROM test_uuid WHERE i='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + i +-------------------------------------- + ce41c936-6acb-4feb-8c91-852a673e5a5c +(1 row) + +SELECT * FROM test_uuid WHERE i>='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + i +-------------------------------------- + ce41c936-6acb-4feb-8c91-852a673e5a5c + d2ce731f-f2a8-4a2b-be37-8f0ba637427f + ffffffff-ffff-ffff-ffff-ffffffffffff +(3 rows) + +SELECT * FROM test_uuid WHERE i>'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + i +-------------------------------------- + d2ce731f-f2a8-4a2b-be37-8f0ba637427f + ffffffff-ffff-ffff-ffff-ffffffffffff +(2 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_uuid WHERE i<'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Sort Key: i + -> Bitmap Heap Scan on test_uuid + Recheck Cond: (i < 'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid) + -> Bitmap Index Scan on idx_uuid + Index Cond: (i < 'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_uuid WHERE i<='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + QUERY PLAN +------------------------------------------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_uuid + Recheck Cond: (i <= 'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid) + -> Bitmap Index Scan on idx_uuid + Index Cond: (i <= 'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_uuid WHERE i='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + QUERY PLAN +------------------------------------------------------------------------ + Bitmap Heap Scan on test_uuid + Recheck Cond: (i = 'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid) + -> Bitmap Index Scan on idx_uuid + Index Cond: (i = 'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid) +(4 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_uuid WHERE i>='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + QUERY PLAN +------------------------------------------------------------------------------- + Sort + Sort Key: i + -> Bitmap Heap Scan on test_uuid + Recheck Cond: (i >= 'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid) + -> Bitmap Index Scan on idx_uuid + Index Cond: (i >= 'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid) +(6 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM test_uuid WHERE i>'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Sort Key: i + -> Bitmap Heap Scan on test_uuid + Recheck Cond: (i > 'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid) + -> Bitmap Index Scan on idx_uuid + Index Cond: (i > 'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid) +(6 rows) + diff --git a/contrib/btree_gin/expected/varbit.out b/contrib/btree_gin/expected/varbit.out new file mode 100644 index 0000000..e8a7718 --- /dev/null +++ b/contrib/btree_gin/expected/varbit.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_varbit ( + i varbit +); +INSERT INTO test_varbit VALUES ('001'),('010'),('011'),('100'),('101'),('110'); +CREATE INDEX idx_varbit ON test_varbit USING gin (i); +SELECT * FROM test_varbit WHERE i<'100'::varbit ORDER BY i; + i +----- + 001 + 010 + 011 +(3 rows) + +SELECT * FROM test_varbit WHERE i<='100'::varbit ORDER BY i; + i +----- + 001 + 010 + 011 + 100 +(4 rows) + +SELECT * FROM test_varbit WHERE i='100'::varbit ORDER BY i; + i +----- + 100 +(1 row) + +SELECT * FROM test_varbit WHERE i>='100'::varbit ORDER BY i; + i +----- + 100 + 101 + 110 +(3 rows) + +SELECT * FROM test_varbit WHERE i>'100'::varbit ORDER BY i; + i +----- + 101 + 110 +(2 rows) + diff --git a/contrib/btree_gin/expected/varchar.out b/contrib/btree_gin/expected/varchar.out new file mode 100644 index 0000000..086afbc --- /dev/null +++ b/contrib/btree_gin/expected/varchar.out @@ -0,0 +1,44 @@ +set enable_seqscan=off; +CREATE TABLE test_varchar ( + i varchar +); +INSERT INTO test_varchar VALUES ('a'),('ab'),('abc'),('abb'),('axy'),('xyz'); +CREATE INDEX idx_varchar ON test_varchar USING gin (i); +SELECT * FROM test_varchar WHERE i<'abc'::varchar ORDER BY i; + i +----- + a + ab + abb +(3 rows) + +SELECT * FROM test_varchar WHERE i<='abc'::varchar ORDER BY i; + i +----- + a + ab + abb + abc +(4 rows) + +SELECT * FROM test_varchar WHERE i='abc'::varchar ORDER BY i; + i +----- + abc +(1 row) + +SELECT * FROM test_varchar WHERE i>='abc'::varchar ORDER BY i; + i +----- + abc + axy + xyz +(3 rows) + +SELECT * FROM test_varchar WHERE i>'abc'::varchar ORDER BY i; + i +----- + axy + xyz +(2 rows) + diff --git a/contrib/btree_gin/meson.build b/contrib/btree_gin/meson.build new file mode 100644 index 0000000..b529401 --- /dev/null +++ b/contrib/btree_gin/meson.build @@ -0,0 +1,66 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +btree_gin_sources = files( + 'btree_gin.c', +) + +if host_system == 'windows' + btree_gin_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'btree_gin', + '--FILEDESC', 'btree_gin - B-tree equivalent GIN operator classes',]) +endif + +btree_gin = shared_module('btree_gin', + btree_gin_sources, + kwargs: contrib_mod_args, +) +contrib_targets += btree_gin + +install_data( + 'btree_gin.control', + 'btree_gin--1.0.sql', + 'btree_gin--1.0--1.1.sql', + 'btree_gin--1.1--1.2.sql', + 'btree_gin--1.2--1.3.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'btree_gin', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'install_btree_gin', + 'int2', + 'int4', + 'int8', + 'float4', + 'float8', + 'money', + 'oid', + 'timestamp', + 'timestamptz', + 'time', + 'timetz', + 'date', + 'interval', + 'macaddr', + 'macaddr8', + 'inet', + 'cidr', + 'text', + 'varchar', + 'char', + 'bytea', + 'bit', + 'varbit', + 'numeric', + 'enum', + 'uuid', + 'name', + 'bool', + 'bpchar', + ], + }, +} diff --git a/contrib/btree_gin/sql/bit.sql b/contrib/btree_gin/sql/bit.sql new file mode 100644 index 0000000..6762be0 --- /dev/null +++ b/contrib/btree_gin/sql/bit.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_bit ( + i bit(3) +); + +INSERT INTO test_bit VALUES ('001'),('010'),('011'),('100'),('101'),('110'); + +CREATE INDEX idx_bit ON test_bit USING gin (i); + +SELECT * FROM test_bit WHERE i<'100'::bit(3) ORDER BY i; +SELECT * FROM test_bit WHERE i<='100'::bit(3) ORDER BY i; +SELECT * FROM test_bit WHERE i='100'::bit(3) ORDER BY i; +SELECT * FROM test_bit WHERE i>='100'::bit(3) ORDER BY i; +SELECT * FROM test_bit WHERE i>'100'::bit(3) ORDER BY i; diff --git a/contrib/btree_gin/sql/bool.sql b/contrib/btree_gin/sql/bool.sql new file mode 100644 index 0000000..08f2986 --- /dev/null +++ b/contrib/btree_gin/sql/bool.sql @@ -0,0 +1,29 @@ +set enable_seqscan=off; + +CREATE TABLE test_bool ( + i boolean +); + +INSERT INTO test_bool VALUES (false),(true),(null); + +CREATE INDEX idx_bool ON test_bool USING gin (i); + +SELECT * FROM test_bool WHERE i=true ORDER BY i; +SELECT * FROM test_bool WHERE i>true ORDER BY i; + +SELECT * FROM test_bool WHERE i=false ORDER BY i; +SELECT * FROM test_bool WHERE i>false ORDER BY i; + +EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i=true ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i>true ORDER BY i; +-- probably sufficient to check just this one: +EXPLAIN (COSTS OFF) SELECT * FROM test_bool WHERE i=false ORDER BY i; diff --git a/contrib/btree_gin/sql/bpchar.sql b/contrib/btree_gin/sql/bpchar.sql new file mode 100644 index 0000000..4c951e3 --- /dev/null +++ b/contrib/btree_gin/sql/bpchar.sql @@ -0,0 +1,22 @@ +set enable_seqscan=off; + +CREATE TABLE test_bpchar ( + i char(10) +); + +INSERT INTO test_bpchar VALUES ('a'),('ab'),('abc'),('abc '),('abb'),('axy'),('xyz'),('xyz '); + +CREATE INDEX idx_bpchar ON test_bpchar USING gin (i); + +SELECT * FROM test_bpchar WHERE i<'abc' ORDER BY i; +SELECT * FROM test_bpchar WHERE i<='abc' ORDER BY i; +SELECT * FROM test_bpchar WHERE i='abc' ORDER BY i; +SELECT * FROM test_bpchar WHERE i='abc ' ORDER BY i; +SELECT * FROM test_bpchar WHERE i>='abc' ORDER BY i; +SELECT * FROM test_bpchar WHERE i>'abc' ORDER BY i; + +EXPLAIN (COSTS OFF) SELECT * FROM test_bpchar WHERE i<'abc' ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_bpchar WHERE i<='abc' ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_bpchar WHERE i='abc' ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_bpchar WHERE i>='abc' ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_bpchar WHERE i>'abc' ORDER BY i; diff --git a/contrib/btree_gin/sql/bytea.sql b/contrib/btree_gin/sql/bytea.sql new file mode 100644 index 0000000..5f3eb11 --- /dev/null +++ b/contrib/btree_gin/sql/bytea.sql @@ -0,0 +1,17 @@ +set enable_seqscan=off; +-- ensure consistent test output regardless of the default bytea format +SET bytea_output TO escape; + +CREATE TABLE test_bytea ( + i bytea +); + +INSERT INTO test_bytea VALUES ('a'),('ab'),('abc'),('abb'),('axy'),('xyz'); + +CREATE INDEX idx_bytea ON test_bytea USING gin (i); + +SELECT * FROM test_bytea WHERE i<'abc'::bytea ORDER BY i; +SELECT * FROM test_bytea WHERE i<='abc'::bytea ORDER BY i; +SELECT * FROM test_bytea WHERE i='abc'::bytea ORDER BY i; +SELECT * FROM test_bytea WHERE i>='abc'::bytea ORDER BY i; +SELECT * FROM test_bytea WHERE i>'abc'::bytea ORDER BY i; diff --git a/contrib/btree_gin/sql/char.sql b/contrib/btree_gin/sql/char.sql new file mode 100644 index 0000000..1f114a5 --- /dev/null +++ b/contrib/btree_gin/sql/char.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_char ( + i "char" +); + +INSERT INTO test_char VALUES ('a'),('b'),('c'),('d'),('e'),('f'); + +CREATE INDEX idx_char ON test_char USING gin (i); + +SELECT * FROM test_char WHERE i<'d'::"char" ORDER BY i; +SELECT * FROM test_char WHERE i<='d'::"char" ORDER BY i; +SELECT * FROM test_char WHERE i='d'::"char" ORDER BY i; +SELECT * FROM test_char WHERE i>='d'::"char" ORDER BY i; +SELECT * FROM test_char WHERE i>'d'::"char" ORDER BY i; diff --git a/contrib/btree_gin/sql/cidr.sql b/contrib/btree_gin/sql/cidr.sql new file mode 100644 index 0000000..4a76e5f --- /dev/null +++ b/contrib/btree_gin/sql/cidr.sql @@ -0,0 +1,22 @@ +set enable_seqscan=off; + +CREATE TABLE test_cidr ( + i cidr +); + +INSERT INTO test_cidr VALUES + ( '1.2.3.4' ), + ( '1.2.4.4' ), + ( '1.2.5.4' ), + ( '1.2.6.4' ), + ( '1.2.7.4' ), + ( '1.2.8.4' ) +; + +CREATE INDEX idx_cidr ON test_cidr USING gin (i); + +SELECT * FROM test_cidr WHERE i<'1.2.6.4'::cidr ORDER BY i; +SELECT * FROM test_cidr WHERE i<='1.2.6.4'::cidr ORDER BY i; +SELECT * FROM test_cidr WHERE i='1.2.6.4'::cidr ORDER BY i; +SELECT * FROM test_cidr WHERE i>='1.2.6.4'::cidr ORDER BY i; +SELECT * FROM test_cidr WHERE i>'1.2.6.4'::cidr ORDER BY i; diff --git a/contrib/btree_gin/sql/date.sql b/contrib/btree_gin/sql/date.sql new file mode 100644 index 0000000..35086f6 --- /dev/null +++ b/contrib/btree_gin/sql/date.sql @@ -0,0 +1,22 @@ +set enable_seqscan=off; + +CREATE TABLE test_date ( + i date +); + +INSERT INTO test_date VALUES + ( '2004-10-23' ), + ( '2004-10-24' ), + ( '2004-10-25' ), + ( '2004-10-26' ), + ( '2004-10-27' ), + ( '2004-10-28' ) +; + +CREATE INDEX idx_date ON test_date USING gin (i); + +SELECT * FROM test_date WHERE i<'2004-10-26'::date ORDER BY i; +SELECT * FROM test_date WHERE i<='2004-10-26'::date ORDER BY i; +SELECT * FROM test_date WHERE i='2004-10-26'::date ORDER BY i; +SELECT * FROM test_date WHERE i>='2004-10-26'::date ORDER BY i; +SELECT * FROM test_date WHERE i>'2004-10-26'::date ORDER BY i; diff --git a/contrib/btree_gin/sql/enum.sql b/contrib/btree_gin/sql/enum.sql new file mode 100644 index 0000000..ec2a622 --- /dev/null +++ b/contrib/btree_gin/sql/enum.sql @@ -0,0 +1,26 @@ +set enable_seqscan=off; + +CREATE TYPE rainbow AS ENUM ('r','o','y','g','b','i','v'); + +CREATE TABLE test_enum ( + i rainbow +); + +INSERT INTO test_enum VALUES ('v'),('y'),('r'),('g'),('o'),('i'),('b'); + +CREATE INDEX idx_enum ON test_enum USING gin (i); + +SELECT * FROM test_enum WHERE i<'g'::rainbow ORDER BY i; +SELECT * FROM test_enum WHERE i<='g'::rainbow ORDER BY i; +SELECT * FROM test_enum WHERE i='g'::rainbow ORDER BY i; +SELECT * FROM test_enum WHERE i>='g'::rainbow ORDER BY i; +SELECT * FROM test_enum WHERE i>'g'::rainbow ORDER BY i; + +explain (costs off) SELECT * FROM test_enum WHERE i>='g'::rainbow ORDER BY i; + + +-- make sure we handle the non-evenly-numbered oid case for enums +create type e as enum ('0', '2', '3'); +alter type e add value '1' after '0'; +create table t as select (i % 4)::text::e from generate_series(0, 100000) as i; +create index on t using gin (e); diff --git a/contrib/btree_gin/sql/float4.sql b/contrib/btree_gin/sql/float4.sql new file mode 100644 index 0000000..759778a --- /dev/null +++ b/contrib/btree_gin/sql/float4.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_float4 ( + i float4 +); + +INSERT INTO test_float4 VALUES (-2),(-1),(0),(1),(2),(3); + +CREATE INDEX idx_float4 ON test_float4 USING gin (i); + +SELECT * FROM test_float4 WHERE i<1::float4 ORDER BY i; +SELECT * FROM test_float4 WHERE i<=1::float4 ORDER BY i; +SELECT * FROM test_float4 WHERE i=1::float4 ORDER BY i; +SELECT * FROM test_float4 WHERE i>=1::float4 ORDER BY i; +SELECT * FROM test_float4 WHERE i>1::float4 ORDER BY i; diff --git a/contrib/btree_gin/sql/float8.sql b/contrib/btree_gin/sql/float8.sql new file mode 100644 index 0000000..b046ac4 --- /dev/null +++ b/contrib/btree_gin/sql/float8.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_float8 ( + i float8 +); + +INSERT INTO test_float8 VALUES (-2),(-1),(0),(1),(2),(3); + +CREATE INDEX idx_float8 ON test_float8 USING gin (i); + +SELECT * FROM test_float8 WHERE i<1::float8 ORDER BY i; +SELECT * FROM test_float8 WHERE i<=1::float8 ORDER BY i; +SELECT * FROM test_float8 WHERE i=1::float8 ORDER BY i; +SELECT * FROM test_float8 WHERE i>=1::float8 ORDER BY i; +SELECT * FROM test_float8 WHERE i>1::float8 ORDER BY i; diff --git a/contrib/btree_gin/sql/inet.sql b/contrib/btree_gin/sql/inet.sql new file mode 100644 index 0000000..e5ec087 --- /dev/null +++ b/contrib/btree_gin/sql/inet.sql @@ -0,0 +1,22 @@ +set enable_seqscan=off; + +CREATE TABLE test_inet ( + i inet +); + +INSERT INTO test_inet VALUES + ( '1.2.3.4/16' ), + ( '1.2.4.4/16' ), + ( '1.2.5.4/16' ), + ( '1.2.6.4/16' ), + ( '1.2.7.4/16' ), + ( '1.2.8.4/16' ) +; + +CREATE INDEX idx_inet ON test_inet USING gin (i); + +SELECT * FROM test_inet WHERE i<'1.2.6.4/16'::inet ORDER BY i; +SELECT * FROM test_inet WHERE i<='1.2.6.4/16'::inet ORDER BY i; +SELECT * FROM test_inet WHERE i='1.2.6.4/16'::inet ORDER BY i; +SELECT * FROM test_inet WHERE i>='1.2.6.4/16'::inet ORDER BY i; +SELECT * FROM test_inet WHERE i>'1.2.6.4/16'::inet ORDER BY i; diff --git a/contrib/btree_gin/sql/install_btree_gin.sql b/contrib/btree_gin/sql/install_btree_gin.sql new file mode 100644 index 0000000..746e776 --- /dev/null +++ b/contrib/btree_gin/sql/install_btree_gin.sql @@ -0,0 +1,6 @@ +CREATE EXTENSION btree_gin; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); diff --git a/contrib/btree_gin/sql/int2.sql b/contrib/btree_gin/sql/int2.sql new file mode 100644 index 0000000..f06f117 --- /dev/null +++ b/contrib/btree_gin/sql/int2.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_int2 ( + i int2 +); + +INSERT INTO test_int2 VALUES (-2),(-1),(0),(1),(2),(3); + +CREATE INDEX idx_int2 ON test_int2 USING gin (i); + +SELECT * FROM test_int2 WHERE i<1::int2 ORDER BY i; +SELECT * FROM test_int2 WHERE i<=1::int2 ORDER BY i; +SELECT * FROM test_int2 WHERE i=1::int2 ORDER BY i; +SELECT * FROM test_int2 WHERE i>=1::int2 ORDER BY i; +SELECT * FROM test_int2 WHERE i>1::int2 ORDER BY i; diff --git a/contrib/btree_gin/sql/int4.sql b/contrib/btree_gin/sql/int4.sql new file mode 100644 index 0000000..6499c29 --- /dev/null +++ b/contrib/btree_gin/sql/int4.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_int4 ( + i int4 +); + +INSERT INTO test_int4 VALUES (-2),(-1),(0),(1),(2),(3); + +CREATE INDEX idx_int4 ON test_int4 USING gin (i); + +SELECT * FROM test_int4 WHERE i<1::int4 ORDER BY i; +SELECT * FROM test_int4 WHERE i<=1::int4 ORDER BY i; +SELECT * FROM test_int4 WHERE i=1::int4 ORDER BY i; +SELECT * FROM test_int4 WHERE i>=1::int4 ORDER BY i; +SELECT * FROM test_int4 WHERE i>1::int4 ORDER BY i; diff --git a/contrib/btree_gin/sql/int8.sql b/contrib/btree_gin/sql/int8.sql new file mode 100644 index 0000000..4d9c287 --- /dev/null +++ b/contrib/btree_gin/sql/int8.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_int8 ( + i int8 +); + +INSERT INTO test_int8 VALUES (-2),(-1),(0),(1),(2),(3); + +CREATE INDEX idx_int8 ON test_int8 USING gin (i); + +SELECT * FROM test_int8 WHERE i<1::int8 ORDER BY i; +SELECT * FROM test_int8 WHERE i<=1::int8 ORDER BY i; +SELECT * FROM test_int8 WHERE i=1::int8 ORDER BY i; +SELECT * FROM test_int8 WHERE i>=1::int8 ORDER BY i; +SELECT * FROM test_int8 WHERE i>1::int8 ORDER BY i; diff --git a/contrib/btree_gin/sql/interval.sql b/contrib/btree_gin/sql/interval.sql new file mode 100644 index 0000000..7a2f3ac --- /dev/null +++ b/contrib/btree_gin/sql/interval.sql @@ -0,0 +1,24 @@ +set enable_seqscan=off; + +CREATE TABLE test_interval ( + i interval +); + +INSERT INTO test_interval VALUES + ( '-178000000 years' ), + ( '03:55:08' ), + ( '04:55:08' ), + ( '05:55:08' ), + ( '08:55:08' ), + ( '09:55:08' ), + ( '10:55:08' ), + ( '178000000 years' ) +; + +CREATE INDEX idx_interval ON test_interval USING gin (i); + +SELECT * FROM test_interval WHERE i<'08:55:08'::interval ORDER BY i; +SELECT * FROM test_interval WHERE i<='08:55:08'::interval ORDER BY i; +SELECT * FROM test_interval WHERE i='08:55:08'::interval ORDER BY i; +SELECT * FROM test_interval WHERE i>='08:55:08'::interval ORDER BY i; +SELECT * FROM test_interval WHERE i>'08:55:08'::interval ORDER BY i; diff --git a/contrib/btree_gin/sql/macaddr.sql b/contrib/btree_gin/sql/macaddr.sql new file mode 100644 index 0000000..66566aa --- /dev/null +++ b/contrib/btree_gin/sql/macaddr.sql @@ -0,0 +1,22 @@ +set enable_seqscan=off; + +CREATE TABLE test_macaddr ( + i macaddr +); + +INSERT INTO test_macaddr VALUES + ( '22:00:5c:03:55:08' ), + ( '22:00:5c:04:55:08' ), + ( '22:00:5c:05:55:08' ), + ( '22:00:5c:08:55:08' ), + ( '22:00:5c:09:55:08' ), + ( '22:00:5c:10:55:08' ) +; + +CREATE INDEX idx_macaddr ON test_macaddr USING gin (i); + +SELECT * FROM test_macaddr WHERE i<'22:00:5c:08:55:08'::macaddr ORDER BY i; +SELECT * FROM test_macaddr WHERE i<='22:00:5c:08:55:08'::macaddr ORDER BY i; +SELECT * FROM test_macaddr WHERE i='22:00:5c:08:55:08'::macaddr ORDER BY i; +SELECT * FROM test_macaddr WHERE i>='22:00:5c:08:55:08'::macaddr ORDER BY i; +SELECT * FROM test_macaddr WHERE i>'22:00:5c:08:55:08'::macaddr ORDER BY i; diff --git a/contrib/btree_gin/sql/macaddr8.sql b/contrib/btree_gin/sql/macaddr8.sql new file mode 100644 index 0000000..86785c3 --- /dev/null +++ b/contrib/btree_gin/sql/macaddr8.sql @@ -0,0 +1,22 @@ +set enable_seqscan=off; + +CREATE TABLE test_macaddr8 ( + i macaddr8 +); + +INSERT INTO test_macaddr8 VALUES + ( '22:00:5c:03:55:08:01:02' ), + ( '22:00:5c:04:55:08:01:02' ), + ( '22:00:5c:05:55:08:01:02' ), + ( '22:00:5c:08:55:08:01:02' ), + ( '22:00:5c:09:55:08:01:02' ), + ( '22:00:5c:10:55:08:01:02' ) +; + +CREATE INDEX idx_macaddr8 ON test_macaddr8 USING gin (i); + +SELECT * FROM test_macaddr8 WHERE i<'22:00:5c:08:55:08:01:02'::macaddr8 ORDER BY i; +SELECT * FROM test_macaddr8 WHERE i<='22:00:5c:08:55:08:01:02'::macaddr8 ORDER BY i; +SELECT * FROM test_macaddr8 WHERE i='22:00:5c:08:55:08:01:02'::macaddr8 ORDER BY i; +SELECT * FROM test_macaddr8 WHERE i>='22:00:5c:08:55:08:01:02'::macaddr8 ORDER BY i; +SELECT * FROM test_macaddr8 WHERE i>'22:00:5c:08:55:08:01:02'::macaddr8 ORDER BY i; diff --git a/contrib/btree_gin/sql/money.sql b/contrib/btree_gin/sql/money.sql new file mode 100644 index 0000000..4a9c8c5 --- /dev/null +++ b/contrib/btree_gin/sql/money.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_money ( + i money +); + +INSERT INTO test_money VALUES ('-2'),('-1'),('0'),('1'),('2'),('3'); + +CREATE INDEX idx_money ON test_money USING gin (i); + +SELECT * FROM test_money WHERE i<'1'::money ORDER BY i; +SELECT * FROM test_money WHERE i<='1'::money ORDER BY i; +SELECT * FROM test_money WHERE i='1'::money ORDER BY i; +SELECT * FROM test_money WHERE i>='1'::money ORDER BY i; +SELECT * FROM test_money WHERE i>'1'::money ORDER BY i; diff --git a/contrib/btree_gin/sql/name.sql b/contrib/btree_gin/sql/name.sql new file mode 100644 index 0000000..c11580c --- /dev/null +++ b/contrib/btree_gin/sql/name.sql @@ -0,0 +1,21 @@ +set enable_seqscan=off; + +CREATE TABLE test_name ( + i name +); + +INSERT INTO test_name VALUES ('a'),('ab'),('abc'),('abb'),('axy'),('xyz'); + +CREATE INDEX idx_name ON test_name USING gin (i); + +SELECT * FROM test_name WHERE i<'abc' ORDER BY i; +SELECT * FROM test_name WHERE i<='abc' ORDER BY i; +SELECT * FROM test_name WHERE i='abc' ORDER BY i; +SELECT * FROM test_name WHERE i>='abc' ORDER BY i; +SELECT * FROM test_name WHERE i>'abc' ORDER BY i; + +EXPLAIN (COSTS OFF) SELECT * FROM test_name WHERE i<'abc' ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_name WHERE i<='abc' ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_name WHERE i='abc' ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_name WHERE i>='abc' ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_name WHERE i>'abc' ORDER BY i; diff --git a/contrib/btree_gin/sql/numeric.sql b/contrib/btree_gin/sql/numeric.sql new file mode 100644 index 0000000..dbaaa2c --- /dev/null +++ b/contrib/btree_gin/sql/numeric.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_numeric ( + i numeric +); + +INSERT INTO test_numeric VALUES (-2),(-1),(0),(1),(2),(3); + +CREATE INDEX idx_numeric ON test_numeric USING gin (i); + +SELECT * FROM test_numeric WHERE i<'1'::numeric ORDER BY i; +SELECT * FROM test_numeric WHERE i<='1'::numeric ORDER BY i; +SELECT * FROM test_numeric WHERE i='1'::numeric ORDER BY i; +SELECT * FROM test_numeric WHERE i>='1'::numeric ORDER BY i; +SELECT * FROM test_numeric WHERE i>'1'::numeric ORDER BY i; diff --git a/contrib/btree_gin/sql/oid.sql b/contrib/btree_gin/sql/oid.sql new file mode 100644 index 0000000..1739f80 --- /dev/null +++ b/contrib/btree_gin/sql/oid.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_oid ( + i oid +); + +INSERT INTO test_oid VALUES (0),(1),(2),(3),(4),(5); + +CREATE INDEX idx_oid ON test_oid USING gin (i); + +SELECT * FROM test_oid WHERE i<3::oid ORDER BY i; +SELECT * FROM test_oid WHERE i<=3::oid ORDER BY i; +SELECT * FROM test_oid WHERE i=3::oid ORDER BY i; +SELECT * FROM test_oid WHERE i>=3::oid ORDER BY i; +SELECT * FROM test_oid WHERE i>3::oid ORDER BY i; diff --git a/contrib/btree_gin/sql/text.sql b/contrib/btree_gin/sql/text.sql new file mode 100644 index 0000000..d5b3b39 --- /dev/null +++ b/contrib/btree_gin/sql/text.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_text ( + i text +); + +INSERT INTO test_text VALUES ('a'),('ab'),('abc'),('abb'),('axy'),('xyz'); + +CREATE INDEX idx_text ON test_text USING gin (i); + +SELECT * FROM test_text WHERE i<'abc' ORDER BY i; +SELECT * FROM test_text WHERE i<='abc' ORDER BY i; +SELECT * FROM test_text WHERE i='abc' ORDER BY i; +SELECT * FROM test_text WHERE i>='abc' ORDER BY i; +SELECT * FROM test_text WHERE i>'abc' ORDER BY i; diff --git a/contrib/btree_gin/sql/time.sql b/contrib/btree_gin/sql/time.sql new file mode 100644 index 0000000..62d709a --- /dev/null +++ b/contrib/btree_gin/sql/time.sql @@ -0,0 +1,22 @@ +set enable_seqscan=off; + +CREATE TABLE test_time ( + i time +); + +INSERT INTO test_time VALUES + ( '03:55:08' ), + ( '04:55:08' ), + ( '05:55:08' ), + ( '08:55:08' ), + ( '09:55:08' ), + ( '10:55:08' ) +; + +CREATE INDEX idx_time ON test_time USING gin (i); + +SELECT * FROM test_time WHERE i<'08:55:08'::time ORDER BY i; +SELECT * FROM test_time WHERE i<='08:55:08'::time ORDER BY i; +SELECT * FROM test_time WHERE i='08:55:08'::time ORDER BY i; +SELECT * FROM test_time WHERE i>='08:55:08'::time ORDER BY i; +SELECT * FROM test_time WHERE i>'08:55:08'::time ORDER BY i; diff --git a/contrib/btree_gin/sql/timestamp.sql b/contrib/btree_gin/sql/timestamp.sql new file mode 100644 index 0000000..56727e8 --- /dev/null +++ b/contrib/btree_gin/sql/timestamp.sql @@ -0,0 +1,22 @@ +set enable_seqscan=off; + +CREATE TABLE test_timestamp ( + i timestamp +); + +INSERT INTO test_timestamp VALUES + ( '2004-10-26 03:55:08' ), + ( '2004-10-26 04:55:08' ), + ( '2004-10-26 05:55:08' ), + ( '2004-10-26 08:55:08' ), + ( '2004-10-26 09:55:08' ), + ( '2004-10-26 10:55:08' ) +; + +CREATE INDEX idx_timestamp ON test_timestamp USING gin (i); + +SELECT * FROM test_timestamp WHERE i<'2004-10-26 08:55:08'::timestamp ORDER BY i; +SELECT * FROM test_timestamp WHERE i<='2004-10-26 08:55:08'::timestamp ORDER BY i; +SELECT * FROM test_timestamp WHERE i='2004-10-26 08:55:08'::timestamp ORDER BY i; +SELECT * FROM test_timestamp WHERE i>='2004-10-26 08:55:08'::timestamp ORDER BY i; +SELECT * FROM test_timestamp WHERE i>'2004-10-26 08:55:08'::timestamp ORDER BY i; diff --git a/contrib/btree_gin/sql/timestamptz.sql b/contrib/btree_gin/sql/timestamptz.sql new file mode 100644 index 0000000..e6cfdb1 --- /dev/null +++ b/contrib/btree_gin/sql/timestamptz.sql @@ -0,0 +1,22 @@ +set enable_seqscan=off; + +CREATE TABLE test_timestamptz ( + i timestamptz +); + +INSERT INTO test_timestamptz VALUES + ( '2004-10-26 03:55:08' ), + ( '2004-10-26 04:55:08' ), + ( '2004-10-26 05:55:08' ), + ( '2004-10-26 08:55:08' ), + ( '2004-10-26 09:55:08' ), + ( '2004-10-26 10:55:08' ) +; + +CREATE INDEX idx_timestamptz ON test_timestamptz USING gin (i); + +SELECT * FROM test_timestamptz WHERE i<'2004-10-26 08:55:08'::timestamptz ORDER BY i; +SELECT * FROM test_timestamptz WHERE i<='2004-10-26 08:55:08'::timestamptz ORDER BY i; +SELECT * FROM test_timestamptz WHERE i='2004-10-26 08:55:08'::timestamptz ORDER BY i; +SELECT * FROM test_timestamptz WHERE i>='2004-10-26 08:55:08'::timestamptz ORDER BY i; +SELECT * FROM test_timestamptz WHERE i>'2004-10-26 08:55:08'::timestamptz ORDER BY i; diff --git a/contrib/btree_gin/sql/timetz.sql b/contrib/btree_gin/sql/timetz.sql new file mode 100644 index 0000000..ca947b7 --- /dev/null +++ b/contrib/btree_gin/sql/timetz.sql @@ -0,0 +1,22 @@ +set enable_seqscan=off; + +CREATE TABLE test_timetz ( + i timetz +); + +INSERT INTO test_timetz VALUES + ( '03:55:08 GMT+2' ), + ( '04:55:08 GMT+2' ), + ( '05:55:08 GMT+2' ), + ( '08:55:08 GMT+2' ), + ( '09:55:08 GMT+2' ), + ( '10:55:08 GMT+2' ) +; + +CREATE INDEX idx_timetz ON test_timetz USING gin (i); + +SELECT * FROM test_timetz WHERE i<'08:55:08 GMT+2'::timetz ORDER BY i; +SELECT * FROM test_timetz WHERE i<='08:55:08 GMT+2'::timetz ORDER BY i; +SELECT * FROM test_timetz WHERE i='08:55:08 GMT+2'::timetz ORDER BY i; +SELECT * FROM test_timetz WHERE i>='08:55:08 GMT+2'::timetz ORDER BY i; +SELECT * FROM test_timetz WHERE i>'08:55:08 GMT+2'::timetz ORDER BY i; diff --git a/contrib/btree_gin/sql/uuid.sql b/contrib/btree_gin/sql/uuid.sql new file mode 100644 index 0000000..3c141bd --- /dev/null +++ b/contrib/btree_gin/sql/uuid.sql @@ -0,0 +1,28 @@ +set enable_seqscan=off; + +CREATE TABLE test_uuid ( + i uuid +); + +INSERT INTO test_uuid VALUES + ( '00000000-0000-0000-0000-000000000000' ), + ( '299bc99f-2f79-4e3e-bfea-2cbfd62a7c27' ), + ( '6264af33-0d43-4337-bf4e-43509b8a4be8' ), + ( 'ce41c936-6acb-4feb-8c91-852a673e5a5c' ), + ( 'd2ce731f-f2a8-4a2b-be37-8f0ba637427f' ), + ( 'ffffffff-ffff-ffff-ffff-ffffffffffff' ) +; + +CREATE INDEX idx_uuid ON test_uuid USING gin (i); + +SELECT * FROM test_uuid WHERE i<'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; +SELECT * FROM test_uuid WHERE i<='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; +SELECT * FROM test_uuid WHERE i='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; +SELECT * FROM test_uuid WHERE i>='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; +SELECT * FROM test_uuid WHERE i>'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; + +EXPLAIN (COSTS OFF) SELECT * FROM test_uuid WHERE i<'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_uuid WHERE i<='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_uuid WHERE i='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_uuid WHERE i>='ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; +EXPLAIN (COSTS OFF) SELECT * FROM test_uuid WHERE i>'ce41c936-6acb-4feb-8c91-852a673e5a5c'::uuid ORDER BY i; diff --git a/contrib/btree_gin/sql/varbit.sql b/contrib/btree_gin/sql/varbit.sql new file mode 100644 index 0000000..4ad2d0c --- /dev/null +++ b/contrib/btree_gin/sql/varbit.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_varbit ( + i varbit +); + +INSERT INTO test_varbit VALUES ('001'),('010'),('011'),('100'),('101'),('110'); + +CREATE INDEX idx_varbit ON test_varbit USING gin (i); + +SELECT * FROM test_varbit WHERE i<'100'::varbit ORDER BY i; +SELECT * FROM test_varbit WHERE i<='100'::varbit ORDER BY i; +SELECT * FROM test_varbit WHERE i='100'::varbit ORDER BY i; +SELECT * FROM test_varbit WHERE i>='100'::varbit ORDER BY i; +SELECT * FROM test_varbit WHERE i>'100'::varbit ORDER BY i; diff --git a/contrib/btree_gin/sql/varchar.sql b/contrib/btree_gin/sql/varchar.sql new file mode 100644 index 0000000..dbdacab --- /dev/null +++ b/contrib/btree_gin/sql/varchar.sql @@ -0,0 +1,15 @@ +set enable_seqscan=off; + +CREATE TABLE test_varchar ( + i varchar +); + +INSERT INTO test_varchar VALUES ('a'),('ab'),('abc'),('abb'),('axy'),('xyz'); + +CREATE INDEX idx_varchar ON test_varchar USING gin (i); + +SELECT * FROM test_varchar WHERE i<'abc'::varchar ORDER BY i; +SELECT * FROM test_varchar WHERE i<='abc'::varchar ORDER BY i; +SELECT * FROM test_varchar WHERE i='abc'::varchar ORDER BY i; +SELECT * FROM test_varchar WHERE i>='abc'::varchar ORDER BY i; +SELECT * FROM test_varchar WHERE i>'abc'::varchar ORDER BY i; diff --git a/contrib/btree_gist/.gitignore b/contrib/btree_gist/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/btree_gist/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/btree_gist/Makefile b/contrib/btree_gist/Makefile new file mode 100644 index 0000000..48997c7 --- /dev/null +++ b/contrib/btree_gist/Makefile @@ -0,0 +1,54 @@ +# contrib/btree_gist/Makefile + +MODULE_big = btree_gist + +OBJS = \ + $(WIN32RES) \ + btree_bit.o \ + btree_bool.o \ + btree_bytea.o \ + btree_cash.o \ + btree_date.o \ + btree_enum.o \ + btree_float4.o \ + btree_float8.o \ + btree_gist.o \ + btree_inet.o \ + btree_int2.o \ + btree_int4.o \ + btree_int8.o \ + btree_interval.o \ + btree_macaddr.o \ + btree_macaddr8.o \ + btree_numeric.o \ + btree_oid.o \ + btree_text.o \ + btree_time.o \ + btree_ts.o \ + btree_utils_num.o \ + btree_utils_var.o \ + btree_uuid.o + +EXTENSION = btree_gist +DATA = btree_gist--1.0--1.1.sql \ + btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \ + btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \ + btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql +PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes" + +REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \ + time timetz date interval macaddr macaddr8 inet cidr text varchar char \ + bytea bit varbit numeric uuid not_equal enum bool + +SHLIB_LINK += $(filter -lm, $(LIBS)) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/btree_gist +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/btree_gist/btree_bit.c b/contrib/btree_gist/btree_bit.c new file mode 100644 index 0000000..6790f22 --- /dev/null +++ b/contrib/btree_gist/btree_bit.c @@ -0,0 +1,210 @@ +/* + * contrib/btree_gist/btree_bit.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_var.h" +#include "utils/builtins.h" +#include "utils/bytea.h" +#include "utils/varbit.h" + + +/* +** Bit ops +*/ +PG_FUNCTION_INFO_V1(gbt_bit_compress); +PG_FUNCTION_INFO_V1(gbt_bit_union); +PG_FUNCTION_INFO_V1(gbt_bit_picksplit); +PG_FUNCTION_INFO_V1(gbt_bit_consistent); +PG_FUNCTION_INFO_V1(gbt_bit_penalty); +PG_FUNCTION_INFO_V1(gbt_bit_same); + + +/* define for comparison */ + +static bool +gbt_bitgt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(bitgt, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bitge(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(bitge, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_biteq(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(biteq, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bitle(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(bitle, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bitlt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(bitlt, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static int32 +gbt_bitcmp(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetInt32(DirectFunctionCall2(byteacmp, + PointerGetDatum(a), + PointerGetDatum(b))); +} + + +static bytea * +gbt_bit_xfrm(bytea *leaf) +{ + bytea *out = leaf; + int sz = VARBITBYTES(leaf) + VARHDRSZ; + int padded_sz = INTALIGN(sz); + + out = (bytea *) palloc(padded_sz); + /* initialize the padding bytes to zero */ + while (sz < padded_sz) + ((char *) out)[sz++] = 0; + SET_VARSIZE(out, padded_sz); + memcpy(VARDATA(out), VARBITS(leaf), VARBITBYTES(leaf)); + return out; +} + + + + +static GBT_VARKEY * +gbt_bit_l2n(GBT_VARKEY *leaf, FmgrInfo *flinfo) +{ + GBT_VARKEY *out = leaf; + GBT_VARKEY_R r = gbt_var_key_readable(leaf); + bytea *o; + + o = gbt_bit_xfrm(r.lower); + r.upper = r.lower = o; + out = gbt_var_key_copy(&r); + pfree(o); + + return out; +} + +static const gbtree_vinfo tinfo = +{ + gbt_t_bit, + 0, + true, + gbt_bitgt, + gbt_bitge, + gbt_biteq, + gbt_bitle, + gbt_bitlt, + gbt_bitcmp, + gbt_bit_l2n +}; + + +/************************************************** + * Bit ops + **************************************************/ + +Datum +gbt_bit_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_var_compress(entry, &tinfo)); +} + +Datum +gbt_bit_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + void *query = (void *) DatumGetByteaP(PG_GETARG_DATUM(1)); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + bool retval; + GBT_VARKEY *key = (GBT_VARKEY *) DatumGetPointer(entry->key); + GBT_VARKEY_R r = gbt_var_key_readable(key); + + /* All cases served by this function are exact */ + *recheck = false; + + if (GIST_LEAF(entry)) + retval = gbt_var_consistent(&r, query, strategy, PG_GET_COLLATION(), + true, &tinfo, fcinfo->flinfo); + else + { + bytea *q = gbt_bit_xfrm((bytea *) query); + + retval = gbt_var_consistent(&r, q, strategy, PG_GET_COLLATION(), + false, &tinfo, fcinfo->flinfo); + } + PG_RETURN_BOOL(retval); +} + + + +Datum +gbt_bit_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int32 *size = (int *) PG_GETARG_POINTER(1); + + PG_RETURN_POINTER(gbt_var_union(entryvec, size, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_bit_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + + gbt_var_picksplit(entryvec, v, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(v); +} + +Datum +gbt_bit_same(PG_FUNCTION_ARGS) +{ + Datum d1 = PG_GETARG_DATUM(0); + Datum d2 = PG_GETARG_DATUM(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_var_same(d1, d2, PG_GET_COLLATION(), &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} + + +Datum +gbt_bit_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *o = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *n = (GISTENTRY *) PG_GETARG_POINTER(1); + float *result = (float *) PG_GETARG_POINTER(2); + + PG_RETURN_POINTER(gbt_var_penalty(result, o, n, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo)); +} diff --git a/contrib/btree_gist/btree_bool.c b/contrib/btree_gist/btree_bool.c new file mode 100644 index 0000000..8b2af12 --- /dev/null +++ b/contrib/btree_gist/btree_bool.c @@ -0,0 +1,169 @@ +/* + * contrib/btree_gist/btree_bool.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "common/int.h" + +typedef struct boolkey +{ + bool lower; + bool upper; +} boolKEY; + +/* +** bool ops +*/ +PG_FUNCTION_INFO_V1(gbt_bool_compress); +PG_FUNCTION_INFO_V1(gbt_bool_fetch); +PG_FUNCTION_INFO_V1(gbt_bool_union); +PG_FUNCTION_INFO_V1(gbt_bool_picksplit); +PG_FUNCTION_INFO_V1(gbt_bool_consistent); +PG_FUNCTION_INFO_V1(gbt_bool_penalty); +PG_FUNCTION_INFO_V1(gbt_bool_same); + +static bool +gbt_boolgt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const bool *) a) > *((const bool *) b)); +} +static bool +gbt_boolge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const bool *) a) >= *((const bool *) b)); +} +static bool +gbt_booleq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const bool *) a) == *((const bool *) b)); +} +static bool +gbt_boolle(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const bool *) a) <= *((const bool *) b)); +} +static bool +gbt_boollt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const bool *) a) < *((const bool *) b)); +} + +static int +gbt_boolkey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + boolKEY *ia = (boolKEY *) (((const Nsrt *) a)->t); + boolKEY *ib = (boolKEY *) (((const Nsrt *) b)->t); + + if (ia->lower == ib->lower) + { + if (ia->upper == ib->upper) + return 0; + + return (ia->upper > ib->upper) ? 1 : -1; + } + + return (ia->lower > ib->lower) ? 1 : -1; +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_bool, + sizeof(bool), + 2, /* sizeof(gbtreekey2) */ + gbt_boolgt, + gbt_boolge, + gbt_booleq, + gbt_boolle, + gbt_boollt, + gbt_boolkey_cmp, +}; + + +/************************************************** + * bool ops + **************************************************/ + + +Datum +gbt_bool_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_bool_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_bool_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + bool query = PG_GETARG_INT16(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + boolKEY *kkk = (boolKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_bool_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(boolKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(boolKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_bool_penalty(PG_FUNCTION_ARGS) +{ + boolKEY *origentry = (boolKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + boolKEY *newentry = (boolKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_bool_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_bool_same(PG_FUNCTION_ARGS) +{ + boolKEY *b1 = (boolKEY *) PG_GETARG_POINTER(0); + boolKEY *b2 = (boolKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_bytea.c b/contrib/btree_gist/btree_bytea.c new file mode 100644 index 0000000..6b005f0 --- /dev/null +++ b/contrib/btree_gist/btree_bytea.c @@ -0,0 +1,170 @@ +/* + * contrib/btree_gist/btree_bytea.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_var.h" +#include "utils/builtins.h" +#include "utils/bytea.h" + + +/* +** Bytea ops +*/ +PG_FUNCTION_INFO_V1(gbt_bytea_compress); +PG_FUNCTION_INFO_V1(gbt_bytea_union); +PG_FUNCTION_INFO_V1(gbt_bytea_picksplit); +PG_FUNCTION_INFO_V1(gbt_bytea_consistent); +PG_FUNCTION_INFO_V1(gbt_bytea_penalty); +PG_FUNCTION_INFO_V1(gbt_bytea_same); + + +/* define for comparison */ + +static bool +gbt_byteagt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(byteagt, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_byteage(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(byteage, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_byteaeq(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(byteaeq, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_byteale(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(byteale, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bytealt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(bytealt, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static int32 +gbt_byteacmp(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetInt32(DirectFunctionCall2(byteacmp, + PointerGetDatum(a), + PointerGetDatum(b))); +} + + +static const gbtree_vinfo tinfo = +{ + gbt_t_bytea, + 0, + true, + gbt_byteagt, + gbt_byteage, + gbt_byteaeq, + gbt_byteale, + gbt_bytealt, + gbt_byteacmp, + NULL +}; + + +/************************************************** + * Text ops + **************************************************/ + + +Datum +gbt_bytea_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_var_compress(entry, &tinfo)); +} + + + +Datum +gbt_bytea_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + void *query = (void *) DatumGetByteaP(PG_GETARG_DATUM(1)); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + bool retval; + GBT_VARKEY *key = (GBT_VARKEY *) DatumGetPointer(entry->key); + GBT_VARKEY_R r = gbt_var_key_readable(key); + + /* All cases served by this function are exact */ + *recheck = false; + + retval = gbt_var_consistent(&r, query, strategy, PG_GET_COLLATION(), + GIST_LEAF(entry), &tinfo, fcinfo->flinfo); + PG_RETURN_BOOL(retval); +} + + + +Datum +gbt_bytea_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int32 *size = (int *) PG_GETARG_POINTER(1); + + PG_RETURN_POINTER(gbt_var_union(entryvec, size, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_bytea_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + + gbt_var_picksplit(entryvec, v, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(v); +} + +Datum +gbt_bytea_same(PG_FUNCTION_ARGS) +{ + Datum d1 = PG_GETARG_DATUM(0); + Datum d2 = PG_GETARG_DATUM(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_var_same(d1, d2, PG_GET_COLLATION(), &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} + + +Datum +gbt_bytea_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *o = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *n = (GISTENTRY *) PG_GETARG_POINTER(1); + float *result = (float *) PG_GETARG_POINTER(2); + + PG_RETURN_POINTER(gbt_var_penalty(result, o, n, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo)); +} diff --git a/contrib/btree_gist/btree_cash.c b/contrib/btree_gist/btree_cash.c new file mode 100644 index 0000000..546b948 --- /dev/null +++ b/contrib/btree_gist/btree_cash.c @@ -0,0 +1,217 @@ +/* + * contrib/btree_gist/btree_cash.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "common/int.h" +#include "utils/cash.h" + +typedef struct +{ + Cash lower; + Cash upper; +} cashKEY; + +/* +** Cash ops +*/ +PG_FUNCTION_INFO_V1(gbt_cash_compress); +PG_FUNCTION_INFO_V1(gbt_cash_fetch); +PG_FUNCTION_INFO_V1(gbt_cash_union); +PG_FUNCTION_INFO_V1(gbt_cash_picksplit); +PG_FUNCTION_INFO_V1(gbt_cash_consistent); +PG_FUNCTION_INFO_V1(gbt_cash_distance); +PG_FUNCTION_INFO_V1(gbt_cash_penalty); +PG_FUNCTION_INFO_V1(gbt_cash_same); + +static bool +gbt_cashgt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Cash *) a) > *((const Cash *) b)); +} +static bool +gbt_cashge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Cash *) a) >= *((const Cash *) b)); +} +static bool +gbt_casheq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Cash *) a) == *((const Cash *) b)); +} +static bool +gbt_cashle(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Cash *) a) <= *((const Cash *) b)); +} +static bool +gbt_cashlt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Cash *) a) < *((const Cash *) b)); +} + +static int +gbt_cashkey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + cashKEY *ia = (cashKEY *) (((const Nsrt *) a)->t); + cashKEY *ib = (cashKEY *) (((const Nsrt *) b)->t); + + if (ia->lower == ib->lower) + { + if (ia->upper == ib->upper) + return 0; + + return (ia->upper > ib->upper) ? 1 : -1; + } + + return (ia->lower > ib->lower) ? 1 : -1; +} + +static float8 +gbt_cash_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + return GET_FLOAT_DISTANCE(Cash, a, b); +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_cash, + sizeof(Cash), + 16, /* sizeof(gbtreekey16) */ + gbt_cashgt, + gbt_cashge, + gbt_casheq, + gbt_cashle, + gbt_cashlt, + gbt_cashkey_cmp, + gbt_cash_dist +}; + + +PG_FUNCTION_INFO_V1(cash_dist); +Datum +cash_dist(PG_FUNCTION_ARGS) +{ + Cash a = PG_GETARG_CASH(0); + Cash b = PG_GETARG_CASH(1); + Cash r; + Cash ra; + + if (pg_sub_s64_overflow(a, b, &r) || + r == PG_INT64_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("money out of range"))); + + ra = i64abs(r); + + PG_RETURN_CASH(ra); +} + +/************************************************** + * Cash ops + **************************************************/ + + +Datum +gbt_cash_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_cash_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_cash_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Cash query = PG_GETARG_CASH(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + cashKEY *kkk = (cashKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, + fcinfo->flinfo)); +} + + +Datum +gbt_cash_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Cash query = PG_GETARG_CASH(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + cashKEY *kkk = (cashKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_cash_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(cashKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(cashKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_cash_penalty(PG_FUNCTION_ARGS) +{ + cashKEY *origentry = (cashKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + cashKEY *newentry = (cashKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_cash_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_cash_same(PG_FUNCTION_ARGS) +{ + cashKEY *b1 = (cashKEY *) PG_GETARG_POINTER(0); + cashKEY *b2 = (cashKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_date.c b/contrib/btree_gist/btree_date.c new file mode 100644 index 0000000..68a4107 --- /dev/null +++ b/contrib/btree_gist/btree_date.c @@ -0,0 +1,259 @@ +/* + * contrib/btree_gist/btree_date.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "utils/builtins.h" +#include "utils/date.h" + +typedef struct +{ + DateADT lower; + DateADT upper; +} dateKEY; + +/* +** date ops +*/ +PG_FUNCTION_INFO_V1(gbt_date_compress); +PG_FUNCTION_INFO_V1(gbt_date_fetch); +PG_FUNCTION_INFO_V1(gbt_date_union); +PG_FUNCTION_INFO_V1(gbt_date_picksplit); +PG_FUNCTION_INFO_V1(gbt_date_consistent); +PG_FUNCTION_INFO_V1(gbt_date_distance); +PG_FUNCTION_INFO_V1(gbt_date_penalty); +PG_FUNCTION_INFO_V1(gbt_date_same); + +static bool +gbt_dategt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(date_gt, + DateADTGetDatum(*((const DateADT *) a)), + DateADTGetDatum(*((const DateADT *) b)))); +} + +static bool +gbt_datege(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(date_ge, + DateADTGetDatum(*((const DateADT *) a)), + DateADTGetDatum(*((const DateADT *) b)))); +} + +static bool +gbt_dateeq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(date_eq, + DateADTGetDatum(*((const DateADT *) a)), + DateADTGetDatum(*((const DateADT *) b))) + ); +} + +static bool +gbt_datele(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(date_le, + DateADTGetDatum(*((const DateADT *) a)), + DateADTGetDatum(*((const DateADT *) b)))); +} + +static bool +gbt_datelt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(date_lt, + DateADTGetDatum(*((const DateADT *) a)), + DateADTGetDatum(*((const DateADT *) b)))); +} + + + +static int +gbt_datekey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + dateKEY *ia = (dateKEY *) (((const Nsrt *) a)->t); + dateKEY *ib = (dateKEY *) (((const Nsrt *) b)->t); + int res; + + res = DatumGetInt32(DirectFunctionCall2(date_cmp, + DateADTGetDatum(ia->lower), + DateADTGetDatum(ib->lower))); + if (res == 0) + return DatumGetInt32(DirectFunctionCall2(date_cmp, + DateADTGetDatum(ia->upper), + DateADTGetDatum(ib->upper))); + + return res; +} + +static float8 +gdb_date_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + /* we assume the difference can't overflow */ + Datum diff = DirectFunctionCall2(date_mi, + DateADTGetDatum(*((const DateADT *) a)), + DateADTGetDatum(*((const DateADT *) b))); + + return (float8) abs(DatumGetInt32(diff)); +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_date, + sizeof(DateADT), + 8, /* sizeof(gbtreekey8) */ + gbt_dategt, + gbt_datege, + gbt_dateeq, + gbt_datele, + gbt_datelt, + gbt_datekey_cmp, + gdb_date_dist +}; + + +PG_FUNCTION_INFO_V1(date_dist); +Datum +date_dist(PG_FUNCTION_ARGS) +{ + /* we assume the difference can't overflow */ + Datum diff = DirectFunctionCall2(date_mi, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1)); + + PG_RETURN_INT32(abs(DatumGetInt32(diff))); +} + + +/************************************************** + * date ops + **************************************************/ + + + +Datum +gbt_date_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_date_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_date_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + DateADT query = PG_GETARG_DATEADT(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + dateKEY *kkk = (dateKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, + fcinfo->flinfo)); +} + + +Datum +gbt_date_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + DateADT query = PG_GETARG_DATEADT(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + dateKEY *kkk = (dateKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_date_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(dateKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(dateKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_date_penalty(PG_FUNCTION_ARGS) +{ + dateKEY *origentry = (dateKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + dateKEY *newentry = (dateKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + int32 diff, + res; + + diff = DatumGetInt32(DirectFunctionCall2(date_mi, + DateADTGetDatum(newentry->upper), + DateADTGetDatum(origentry->upper))); + + res = Max(diff, 0); + + diff = DatumGetInt32(DirectFunctionCall2(date_mi, + DateADTGetDatum(origentry->lower), + DateADTGetDatum(newentry->lower))); + + res += Max(diff, 0); + + *result = 0.0; + + if (res > 0) + { + diff = DatumGetInt32(DirectFunctionCall2(date_mi, + DateADTGetDatum(origentry->upper), + DateADTGetDatum(origentry->lower))); + *result += FLT_MIN; + *result += (float) (res / ((double) (res + diff))); + *result *= (FLT_MAX / (((GISTENTRY *) PG_GETARG_POINTER(0))->rel->rd_att->natts + 1)); + } + + PG_RETURN_POINTER(result); +} + + +Datum +gbt_date_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_date_same(PG_FUNCTION_ARGS) +{ + dateKEY *b1 = (dateKEY *) PG_GETARG_POINTER(0); + dateKEY *b2 = (dateKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_enum.c b/contrib/btree_gist/btree_enum.c new file mode 100644 index 0000000..d4dc38a --- /dev/null +++ b/contrib/btree_gist/btree_enum.c @@ -0,0 +1,185 @@ +/* + * contrib/btree_gist/btree_enum.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "fmgr.h" +#include "utils/builtins.h" + +/* enums are really Oids, so we just use the same structure */ + +typedef struct +{ + Oid lower; + Oid upper; +} oidKEY; + +/* +** enum ops +*/ +PG_FUNCTION_INFO_V1(gbt_enum_compress); +PG_FUNCTION_INFO_V1(gbt_enum_fetch); +PG_FUNCTION_INFO_V1(gbt_enum_union); +PG_FUNCTION_INFO_V1(gbt_enum_picksplit); +PG_FUNCTION_INFO_V1(gbt_enum_consistent); +PG_FUNCTION_INFO_V1(gbt_enum_penalty); +PG_FUNCTION_INFO_V1(gbt_enum_same); + + +static bool +gbt_enumgt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(CallerFInfoFunctionCall2(enum_gt, flinfo, InvalidOid, + ObjectIdGetDatum(*((const Oid *) a)), + ObjectIdGetDatum(*((const Oid *) b)))); +} +static bool +gbt_enumge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(CallerFInfoFunctionCall2(enum_ge, flinfo, InvalidOid, + ObjectIdGetDatum(*((const Oid *) a)), + ObjectIdGetDatum(*((const Oid *) b)))); +} +static bool +gbt_enumeq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Oid *) a) == *((const Oid *) b)); +} +static bool +gbt_enumle(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(CallerFInfoFunctionCall2(enum_le, flinfo, InvalidOid, + ObjectIdGetDatum(*((const Oid *) a)), + ObjectIdGetDatum(*((const Oid *) b)))); +} +static bool +gbt_enumlt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(CallerFInfoFunctionCall2(enum_lt, flinfo, InvalidOid, + ObjectIdGetDatum(*((const Oid *) a)), + ObjectIdGetDatum(*((const Oid *) b)))); +} + +static int +gbt_enumkey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + oidKEY *ia = (oidKEY *) (((const Nsrt *) a)->t); + oidKEY *ib = (oidKEY *) (((const Nsrt *) b)->t); + + if (ia->lower == ib->lower) + { + if (ia->upper == ib->upper) + return 0; + + return DatumGetInt32(CallerFInfoFunctionCall2(enum_cmp, flinfo, InvalidOid, + ObjectIdGetDatum(ia->upper), + ObjectIdGetDatum(ib->upper))); + } + + return DatumGetInt32(CallerFInfoFunctionCall2(enum_cmp, flinfo, InvalidOid, + ObjectIdGetDatum(ia->lower), + ObjectIdGetDatum(ib->lower))); +} + +static const gbtree_ninfo tinfo = +{ + gbt_t_enum, + sizeof(Oid), + 8, /* sizeof(gbtreekey8) */ + gbt_enumgt, + gbt_enumge, + gbt_enumeq, + gbt_enumle, + gbt_enumlt, + gbt_enumkey_cmp, + NULL /* no KNN support at least for now */ +}; + + +/************************************************** + * Enum ops + **************************************************/ + + +Datum +gbt_enum_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_enum_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_enum_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Oid query = PG_GETARG_OID(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + oidKEY *kkk = (oidKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, + fcinfo->flinfo)); +} + +Datum +gbt_enum_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(oidKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(oidKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_enum_penalty(PG_FUNCTION_ARGS) +{ + oidKEY *origentry = (oidKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + oidKEY *newentry = (oidKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_enum_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_enum_same(PG_FUNCTION_ARGS) +{ + oidKEY *b1 = (oidKEY *) PG_GETARG_POINTER(0); + oidKEY *b2 = (oidKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_float4.c b/contrib/btree_gist/btree_float4.c new file mode 100644 index 0000000..84ca5ee --- /dev/null +++ b/contrib/btree_gist/btree_float4.c @@ -0,0 +1,212 @@ +/* + * contrib/btree_gist/btree_float4.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "utils/float.h" + +typedef struct float4key +{ + float4 lower; + float4 upper; +} float4KEY; + +/* +** float4 ops +*/ +PG_FUNCTION_INFO_V1(gbt_float4_compress); +PG_FUNCTION_INFO_V1(gbt_float4_fetch); +PG_FUNCTION_INFO_V1(gbt_float4_union); +PG_FUNCTION_INFO_V1(gbt_float4_picksplit); +PG_FUNCTION_INFO_V1(gbt_float4_consistent); +PG_FUNCTION_INFO_V1(gbt_float4_distance); +PG_FUNCTION_INFO_V1(gbt_float4_penalty); +PG_FUNCTION_INFO_V1(gbt_float4_same); + +static bool +gbt_float4gt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const float4 *) a) > *((const float4 *) b)); +} +static bool +gbt_float4ge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const float4 *) a) >= *((const float4 *) b)); +} +static bool +gbt_float4eq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const float4 *) a) == *((const float4 *) b)); +} +static bool +gbt_float4le(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const float4 *) a) <= *((const float4 *) b)); +} +static bool +gbt_float4lt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const float4 *) a) < *((const float4 *) b)); +} + +static int +gbt_float4key_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + float4KEY *ia = (float4KEY *) (((const Nsrt *) a)->t); + float4KEY *ib = (float4KEY *) (((const Nsrt *) b)->t); + + if (ia->lower == ib->lower) + { + if (ia->upper == ib->upper) + return 0; + + return (ia->upper > ib->upper) ? 1 : -1; + } + + return (ia->lower > ib->lower) ? 1 : -1; +} + +static float8 +gbt_float4_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + return GET_FLOAT_DISTANCE(float4, a, b); +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_float4, + sizeof(float4), + 8, /* sizeof(gbtreekey8) */ + gbt_float4gt, + gbt_float4ge, + gbt_float4eq, + gbt_float4le, + gbt_float4lt, + gbt_float4key_cmp, + gbt_float4_dist +}; + + +PG_FUNCTION_INFO_V1(float4_dist); +Datum +float4_dist(PG_FUNCTION_ARGS) +{ + float4 a = PG_GETARG_FLOAT4(0); + float4 b = PG_GETARG_FLOAT4(1); + float4 r; + + r = a - b; + if (unlikely(isinf(r)) && !isinf(a) && !isinf(b)) + float_overflow_error(); + + PG_RETURN_FLOAT4(fabsf(r)); +} + + +/************************************************** + * float4 ops + **************************************************/ + + +Datum +gbt_float4_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_float4_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_float4_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + float4 query = PG_GETARG_FLOAT4(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + float4KEY *kkk = (float4KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, + fcinfo->flinfo)); +} + + +Datum +gbt_float4_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + float4 query = PG_GETARG_FLOAT4(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + float4KEY *kkk = (float4KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_float4_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(float4KEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(float4KEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_float4_penalty(PG_FUNCTION_ARGS) +{ + float4KEY *origentry = (float4KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + float4KEY *newentry = (float4KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_float4_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_float4_same(PG_FUNCTION_ARGS) +{ + float4KEY *b1 = (float4KEY *) PG_GETARG_POINTER(0); + float4KEY *b2 = (float4KEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_float8.c b/contrib/btree_gist/btree_float8.c new file mode 100644 index 0000000..081a719 --- /dev/null +++ b/contrib/btree_gist/btree_float8.c @@ -0,0 +1,219 @@ +/* + * contrib/btree_gist/btree_float8.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "utils/float.h" + +typedef struct float8key +{ + float8 lower; + float8 upper; +} float8KEY; + +/* +** float8 ops +*/ +PG_FUNCTION_INFO_V1(gbt_float8_compress); +PG_FUNCTION_INFO_V1(gbt_float8_fetch); +PG_FUNCTION_INFO_V1(gbt_float8_union); +PG_FUNCTION_INFO_V1(gbt_float8_picksplit); +PG_FUNCTION_INFO_V1(gbt_float8_consistent); +PG_FUNCTION_INFO_V1(gbt_float8_distance); +PG_FUNCTION_INFO_V1(gbt_float8_penalty); +PG_FUNCTION_INFO_V1(gbt_float8_same); + + +static bool +gbt_float8gt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const float8 *) a) > *((const float8 *) b)); +} +static bool +gbt_float8ge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const float8 *) a) >= *((const float8 *) b)); +} +static bool +gbt_float8eq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const float8 *) a) == *((const float8 *) b)); +} +static bool +gbt_float8le(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const float8 *) a) <= *((const float8 *) b)); +} +static bool +gbt_float8lt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const float8 *) a) < *((const float8 *) b)); +} + +static int +gbt_float8key_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + float8KEY *ia = (float8KEY *) (((const Nsrt *) a)->t); + float8KEY *ib = (float8KEY *) (((const Nsrt *) b)->t); + + if (ia->lower == ib->lower) + { + if (ia->upper == ib->upper) + return 0; + + return (ia->upper > ib->upper) ? 1 : -1; + } + + return (ia->lower > ib->lower) ? 1 : -1; +} + +static float8 +gbt_float8_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + float8 arg1 = *(const float8 *) a; + float8 arg2 = *(const float8 *) b; + float8 r; + + r = arg1 - arg2; + if (unlikely(isinf(r)) && !isinf(arg1) && !isinf(arg2)) + float_overflow_error(); + return fabs(r); +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_float8, + sizeof(float8), + 16, /* sizeof(gbtreekey16) */ + gbt_float8gt, + gbt_float8ge, + gbt_float8eq, + gbt_float8le, + gbt_float8lt, + gbt_float8key_cmp, + gbt_float8_dist +}; + + +PG_FUNCTION_INFO_V1(float8_dist); +Datum +float8_dist(PG_FUNCTION_ARGS) +{ + float8 a = PG_GETARG_FLOAT8(0); + float8 b = PG_GETARG_FLOAT8(1); + float8 r; + + r = a - b; + if (unlikely(isinf(r)) && !isinf(a) && !isinf(b)) + float_overflow_error(); + + PG_RETURN_FLOAT8(fabs(r)); +} + +/************************************************** + * float8 ops + **************************************************/ + + +Datum +gbt_float8_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_float8_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_float8_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + float8 query = PG_GETARG_FLOAT8(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + float8KEY *kkk = (float8KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, + fcinfo->flinfo)); +} + + +Datum +gbt_float8_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + float8 query = PG_GETARG_FLOAT8(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + float8KEY *kkk = (float8KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_float8_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(float8KEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(float8KEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_float8_penalty(PG_FUNCTION_ARGS) +{ + float8KEY *origentry = (float8KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + float8KEY *newentry = (float8KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_float8_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_float8_same(PG_FUNCTION_ARGS) +{ + float8KEY *b1 = (float8KEY *) PG_GETARG_POINTER(0); + float8KEY *b2 = (float8KEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_gist--1.0--1.1.sql b/contrib/btree_gist/btree_gist--1.0--1.1.sql new file mode 100644 index 0000000..2633bea --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.0--1.1.sql @@ -0,0 +1,127 @@ +/* contrib/btree_gist/btree_gist--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.1'" to load this file. \quit + +-- Index-only scan support new in 9.5. +CREATE FUNCTION gbt_oid_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int2_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int4_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int8_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float4_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float8_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_ts_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_time_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_date_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_intv_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_cash_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_var_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD + FUNCTION 9 (oid, oid) gbt_oid_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD + FUNCTION 9 (int2, int2) gbt_int2_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD + FUNCTION 9 (int4, int4) gbt_int4_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD + FUNCTION 9 (int8, int8) gbt_int8_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD + FUNCTION 9 (float4, float4) gbt_float4_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD + FUNCTION 9 (float8, float8) gbt_float8_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD + FUNCTION 9 (timestamp, timestamp) gbt_ts_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD + FUNCTION 9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_time_ops USING gist ADD + FUNCTION 9 (time, time) gbt_time_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_date_ops USING gist ADD + FUNCTION 9 (date, date) gbt_date_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD + FUNCTION 9 (interval, interval) gbt_intv_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD + FUNCTION 9 (money, money) gbt_cash_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD + FUNCTION 9 (macaddr, macaddr) gbt_macad_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_text_ops USING gist ADD + FUNCTION 9 (text, text) gbt_var_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD + FUNCTION 9 (bpchar, bpchar) gbt_var_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD + FUNCTION 9 (bytea, bytea) gbt_var_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD + FUNCTION 9 (numeric, numeric) gbt_var_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD + FUNCTION 9 (bit, bit) gbt_var_fetch (internal) ; + +ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD + FUNCTION 9 (varbit, varbit) gbt_var_fetch (internal) ; diff --git a/contrib/btree_gist/btree_gist--1.1--1.2.sql b/contrib/btree_gist/btree_gist--1.1--1.2.sql new file mode 100644 index 0000000..d5a8c6c --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.1--1.2.sql @@ -0,0 +1,79 @@ +/* contrib/btree_gist/btree_gist--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.2'" to load this file. \quit + +-- Update procedure signatures the hard way. +-- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, +-- wherein the signatures have been updated already. In that case to_regprocedure() will +-- return NULL and no updates will happen. + +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) +FROM (VALUES +(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types +('gbt_oid_distance(internal,oid,int2,oid)', '{internal,oid,int2,oid,internal}'), +('gbt_oid_union(bytea,internal)', '{internal,internal}'), +('gbt_oid_same(internal,internal,internal)', '{SCH.gbtreekey8,SCH.gbtreekey8,internal}'), +('gbt_int2_distance(internal,int2,int2,oid)', '{internal,int2,int2,oid,internal}'), +('gbt_int2_union(bytea,internal)', '{internal,internal}'), +('gbt_int2_same(internal,internal,internal)', '{SCH.gbtreekey4,SCH.gbtreekey4,internal}'), +('gbt_int4_distance(internal,int4,int2,oid)', '{internal,int4,int2,oid,internal}'), +('gbt_int4_union(bytea,internal)', '{internal,internal}'), +('gbt_int4_same(internal,internal,internal)', '{SCH.gbtreekey8,SCH.gbtreekey8,internal}'), +('gbt_int8_distance(internal,int8,int2,oid)', '{internal,int8,int2,oid,internal}'), +('gbt_int8_union(bytea,internal)', '{internal,internal}'), +('gbt_int8_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), +('gbt_float4_distance(internal,float4,int2,oid)', '{internal,float4,int2,oid,internal}'), +('gbt_float4_union(bytea,internal)', '{internal,internal}'), +('gbt_float4_same(internal,internal,internal)', '{SCH.gbtreekey8,SCH.gbtreekey8,internal}'), +('gbt_float8_distance(internal,float8,int2,oid)', '{internal,float8,int2,oid,internal}'), +('gbt_float8_union(bytea,internal)', '{internal,internal}'), +('gbt_float8_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), +('gbt_ts_distance(internal,timestamp,int2,oid)', '{internal,timestamp,int2,oid,internal}'), +('gbt_tstz_distance(internal,timestamptz,int2,oid)', '{internal,timestamptz,int2,oid,internal}'), +('gbt_ts_union(bytea,internal)', '{internal,internal}'), +('gbt_ts_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), +('gbt_time_distance(internal,time,int2,oid)', '{internal,time,int2,oid,internal}'), +('gbt_time_union(bytea,internal)', '{internal,internal}'), +('gbt_time_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), +('gbt_date_distance(internal,date,int2,oid)', '{internal,date,int2,oid,internal}'), +('gbt_date_union(bytea,internal)', '{internal,internal}'), +('gbt_date_same(internal,internal,internal)', '{SCH.gbtreekey8,SCH.gbtreekey8,internal}'), +('gbt_intv_distance(internal,interval,int2,oid)', '{internal,interval,int2,oid,internal}'), +('gbt_intv_union(bytea,internal)', '{internal,internal}'), +('gbt_intv_same(internal,internal,internal)', '{SCH.gbtreekey32,SCH.gbtreekey32,internal}'), +('gbt_cash_distance(internal,money,int2,oid)', '{internal,money,int2,oid,internal}'), +('gbt_cash_union(bytea,internal)', '{internal,internal}'), +('gbt_cash_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), +('gbt_macad_union(bytea,internal)', '{internal,internal}'), +('gbt_macad_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}'), +('gbt_text_union(bytea,internal)', '{internal,internal}'), +('gbt_text_same(internal,internal,internal)', '{SCH.gbtreekey_var,SCH.gbtreekey_var,internal}'), +('gbt_bytea_union(bytea,internal)', '{internal,internal}'), +('gbt_bytea_same(internal,internal,internal)', '{SCH.gbtreekey_var,SCH.gbtreekey_var,internal}'), +('gbt_numeric_union(bytea,internal)', '{internal,internal}'), +('gbt_numeric_same(internal,internal,internal)', '{SCH.gbtreekey_var,SCH.gbtreekey_var,internal}'), +('gbt_bit_union(bytea,internal)', '{internal,internal}'), +('gbt_bit_same(internal,internal,internal)', '{SCH.gbtreekey_var,SCH.gbtreekey_var,internal}'), +('gbt_inet_union(bytea,internal)', '{internal,internal}'), +('gbt_inet_same(internal,internal,internal)', '{SCH.gbtreekey16,SCH.gbtreekey16,internal}') +) AS update_data (oldproc, newtypestext), +LATERAL ( + SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ +) ls +WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; diff --git a/contrib/btree_gist/btree_gist--1.2--1.3.sql b/contrib/btree_gist/btree_gist--1.2--1.3.sql new file mode 100644 index 0000000..726561e --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.2--1.3.sql @@ -0,0 +1,65 @@ +/* contrib/btree_gist/btree_gist--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.3'" to load this file. \quit + +-- Add support for indexing UUID columns + +-- define the GiST support methods +CREATE FUNCTION gbt_uuid_consistent(internal,uuid,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_uuid_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_uuid_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_uuid_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_uuid_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_uuid_union(internal, internal) +RETURNS gbtreekey32 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_uuid_same(gbtreekey32, gbtreekey32, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_uuid_ops +DEFAULT FOR TYPE uuid USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_uuid_consistent (internal, uuid, int2, oid, internal), + FUNCTION 2 gbt_uuid_union (internal, internal), + FUNCTION 3 gbt_uuid_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_uuid_penalty (internal, internal, internal), + FUNCTION 6 gbt_uuid_picksplit (internal, internal), + FUNCTION 7 gbt_uuid_same (gbtreekey32, gbtreekey32, internal), + STORAGE gbtreekey32; + +-- These are "loose" in the opfamily for consistency with the rest of btree_gist +ALTER OPERATOR FAMILY gist_uuid_ops USING gist ADD + OPERATOR 6 <> (uuid, uuid) , + FUNCTION 9 (uuid, uuid) gbt_uuid_fetch (internal) ; diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql new file mode 100644 index 0000000..1efe753 --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.2.sql @@ -0,0 +1,1570 @@ +/* contrib/btree_gist/btree_gist--1.2.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION btree_gist" to load this file. \quit + +CREATE FUNCTION gbtreekey4_in(cstring) +RETURNS gbtreekey4 +AS 'MODULE_PATHNAME', 'gbtreekey_in' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbtreekey4_out(gbtreekey4) +RETURNS cstring +AS 'MODULE_PATHNAME', 'gbtreekey_out' +LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE gbtreekey4 ( + INTERNALLENGTH = 4, + INPUT = gbtreekey4_in, + OUTPUT = gbtreekey4_out +); + +CREATE FUNCTION gbtreekey8_in(cstring) +RETURNS gbtreekey8 +AS 'MODULE_PATHNAME', 'gbtreekey_in' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbtreekey8_out(gbtreekey8) +RETURNS cstring +AS 'MODULE_PATHNAME', 'gbtreekey_out' +LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE gbtreekey8 ( + INTERNALLENGTH = 8, + INPUT = gbtreekey8_in, + OUTPUT = gbtreekey8_out +); + +CREATE FUNCTION gbtreekey16_in(cstring) +RETURNS gbtreekey16 +AS 'MODULE_PATHNAME', 'gbtreekey_in' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbtreekey16_out(gbtreekey16) +RETURNS cstring +AS 'MODULE_PATHNAME', 'gbtreekey_out' +LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE gbtreekey16 ( + INTERNALLENGTH = 16, + INPUT = gbtreekey16_in, + OUTPUT = gbtreekey16_out +); + +CREATE FUNCTION gbtreekey32_in(cstring) +RETURNS gbtreekey32 +AS 'MODULE_PATHNAME', 'gbtreekey_in' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbtreekey32_out(gbtreekey32) +RETURNS cstring +AS 'MODULE_PATHNAME', 'gbtreekey_out' +LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE gbtreekey32 ( + INTERNALLENGTH = 32, + INPUT = gbtreekey32_in, + OUTPUT = gbtreekey32_out +); + +CREATE FUNCTION gbtreekey_var_in(cstring) +RETURNS gbtreekey_var +AS 'MODULE_PATHNAME', 'gbtreekey_in' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbtreekey_var_out(gbtreekey_var) +RETURNS cstring +AS 'MODULE_PATHNAME', 'gbtreekey_out' +LANGUAGE C IMMUTABLE STRICT; + +CREATE TYPE gbtreekey_var ( + INTERNALLENGTH = VARIABLE, + INPUT = gbtreekey_var_in, + OUTPUT = gbtreekey_var_out, + STORAGE = EXTENDED +); + +--distance operators + +CREATE FUNCTION cash_dist(money, money) +RETURNS money +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = money, + RIGHTARG = money, + PROCEDURE = cash_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION date_dist(date, date) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = date, + RIGHTARG = date, + PROCEDURE = date_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION float4_dist(float4, float4) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = float4, + RIGHTARG = float4, + PROCEDURE = float4_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION float8_dist(float8, float8) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = float8, + RIGHTARG = float8, + PROCEDURE = float8_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION int2_dist(int2, int2) +RETURNS int2 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = int2, + RIGHTARG = int2, + PROCEDURE = int2_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION int4_dist(int4, int4) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = int4, + RIGHTARG = int4, + PROCEDURE = int4_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION int8_dist(int8, int8) +RETURNS int8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = int8, + RIGHTARG = int8, + PROCEDURE = int8_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION interval_dist(interval, interval) +RETURNS interval +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = interval, + RIGHTARG = interval, + PROCEDURE = interval_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION oid_dist(oid, oid) +RETURNS oid +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = oid, + RIGHTARG = oid, + PROCEDURE = oid_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION time_dist(time, time) +RETURNS interval +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = time, + RIGHTARG = time, + PROCEDURE = time_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION ts_dist(timestamp, timestamp) +RETURNS interval +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = timestamp, + RIGHTARG = timestamp, + PROCEDURE = ts_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION tstz_dist(timestamptz, timestamptz) +RETURNS interval +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR <-> ( + LEFTARG = timestamptz, + RIGHTARG = timestamptz, + PROCEDURE = tstz_dist, + COMMUTATOR = '<->' +); + + +-- +-- +-- +-- oid ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_oid_consistent(internal,oid,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_oid_distance(internal,oid,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_oid_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_oid_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_decompress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_var_decompress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_var_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_oid_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_oid_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_oid_union(internal, internal) +RETURNS gbtreekey8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_oid_ops +DEFAULT FOR TYPE oid USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_oid_consistent (internal, oid, int2, oid, internal), + FUNCTION 2 gbt_oid_union (internal, internal), + FUNCTION 3 gbt_oid_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_oid_penalty (internal, internal, internal), + FUNCTION 6 gbt_oid_picksplit (internal, internal), + FUNCTION 7 gbt_oid_same (gbtreekey8, gbtreekey8, internal), + STORAGE gbtreekey8; + +-- Add operators that are new in 9.1. We do it like this, leaving them +-- "loose" in the operator family rather than bound into the opclass, because +-- that's the only state that can be reproduced during an upgrade from 9.0. +ALTER OPERATOR FAMILY gist_oid_ops USING gist ADD + OPERATOR 6 <> (oid, oid) , + OPERATOR 15 <-> (oid, oid) FOR ORDER BY pg_catalog.oid_ops , + FUNCTION 8 (oid, oid) gbt_oid_distance (internal, oid, int2, oid, internal) , + -- Also add support function for index-only-scans, added in 9.5. + FUNCTION 9 (oid, oid) gbt_oid_fetch (internal) ; + + +-- +-- +-- +-- int2 ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_int2_consistent(internal,int2,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int2_distance(internal,int2,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int2_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int2_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int2_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int2_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int2_union(internal, internal) +RETURNS gbtreekey4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_int2_ops +DEFAULT FOR TYPE int2 USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_int2_consistent (internal, int2, int2, oid, internal), + FUNCTION 2 gbt_int2_union (internal, internal), + FUNCTION 3 gbt_int2_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_int2_penalty (internal, internal, internal), + FUNCTION 6 gbt_int2_picksplit (internal, internal), + FUNCTION 7 gbt_int2_same (gbtreekey4, gbtreekey4, internal), + STORAGE gbtreekey4; + +ALTER OPERATOR FAMILY gist_int2_ops USING gist ADD + OPERATOR 6 <> (int2, int2) , + OPERATOR 15 <-> (int2, int2) FOR ORDER BY pg_catalog.integer_ops , + FUNCTION 8 (int2, int2) gbt_int2_distance (internal, int2, int2, oid, internal) , + FUNCTION 9 (int2, int2) gbt_int2_fetch (internal) ; + +-- +-- +-- +-- int4 ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_int4_consistent(internal,int4,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int4_distance(internal,int4,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int4_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int4_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int4_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int4_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int4_union(internal, internal) +RETURNS gbtreekey8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_int4_ops +DEFAULT FOR TYPE int4 USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_int4_consistent (internal, int4, int2, oid, internal), + FUNCTION 2 gbt_int4_union (internal, internal), + FUNCTION 3 gbt_int4_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_int4_penalty (internal, internal, internal), + FUNCTION 6 gbt_int4_picksplit (internal, internal), + FUNCTION 7 gbt_int4_same (gbtreekey8, gbtreekey8, internal), + STORAGE gbtreekey8; + +ALTER OPERATOR FAMILY gist_int4_ops USING gist ADD + OPERATOR 6 <> (int4, int4) , + OPERATOR 15 <-> (int4, int4) FOR ORDER BY pg_catalog.integer_ops , + FUNCTION 8 (int4, int4) gbt_int4_distance (internal, int4, int2, oid, internal) , + FUNCTION 9 (int4, int4) gbt_int4_fetch (internal) ; + + +-- +-- +-- +-- int8 ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_int8_consistent(internal,int8,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int8_distance(internal,int8,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int8_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int8_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int8_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int8_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int8_union(internal, internal) +RETURNS gbtreekey16 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_int8_ops +DEFAULT FOR TYPE int8 USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_int8_consistent (internal, int8, int2, oid, internal), + FUNCTION 2 gbt_int8_union (internal, internal), + FUNCTION 3 gbt_int8_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_int8_penalty (internal, internal, internal), + FUNCTION 6 gbt_int8_picksplit (internal, internal), + FUNCTION 7 gbt_int8_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_int8_ops USING gist ADD + OPERATOR 6 <> (int8, int8) , + OPERATOR 15 <-> (int8, int8) FOR ORDER BY pg_catalog.integer_ops , + FUNCTION 8 (int8, int8) gbt_int8_distance (internal, int8, int2, oid, internal) , + FUNCTION 9 (int8, int8) gbt_int8_fetch (internal) ; + +-- +-- +-- +-- float4 ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_float4_consistent(internal,float4,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float4_distance(internal,float4,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float4_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float4_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float4_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float4_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float4_union(internal, internal) +RETURNS gbtreekey8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_float4_ops +DEFAULT FOR TYPE float4 USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_float4_consistent (internal, float4, int2, oid, internal), + FUNCTION 2 gbt_float4_union (internal, internal), + FUNCTION 3 gbt_float4_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_float4_penalty (internal, internal, internal), + FUNCTION 6 gbt_float4_picksplit (internal, internal), + FUNCTION 7 gbt_float4_same (gbtreekey8, gbtreekey8, internal), + STORAGE gbtreekey8; + +ALTER OPERATOR FAMILY gist_float4_ops USING gist ADD + OPERATOR 6 <> (float4, float4) , + OPERATOR 15 <-> (float4, float4) FOR ORDER BY pg_catalog.float_ops , + FUNCTION 8 (float4, float4) gbt_float4_distance (internal, float4, int2, oid, internal) , + FUNCTION 9 (float4, float4) gbt_float4_fetch (internal) ; + +-- +-- +-- +-- float8 ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_float8_consistent(internal,float8,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float8_distance(internal,float8,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float8_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float8_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float8_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float8_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float8_union(internal, internal) +RETURNS gbtreekey16 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_float8_ops +DEFAULT FOR TYPE float8 USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_float8_consistent (internal, float8, int2, oid, internal), + FUNCTION 2 gbt_float8_union (internal, internal), + FUNCTION 3 gbt_float8_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_float8_penalty (internal, internal, internal), + FUNCTION 6 gbt_float8_picksplit (internal, internal), + FUNCTION 7 gbt_float8_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_float8_ops USING gist ADD + OPERATOR 6 <> (float8, float8) , + OPERATOR 15 <-> (float8, float8) FOR ORDER BY pg_catalog.float_ops , + FUNCTION 8 (float8, float8) gbt_float8_distance (internal, float8, int2, oid, internal) , + FUNCTION 9 (float8, float8) gbt_float8_fetch (internal) ; + +-- +-- +-- +-- timestamp ops +-- +-- +-- + +CREATE FUNCTION gbt_ts_consistent(internal,timestamp,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_ts_distance(internal,timestamp,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_tstz_consistent(internal,timestamptz,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_tstz_distance(internal,timestamptz,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_ts_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_tstz_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_ts_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_ts_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_ts_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_ts_union(internal, internal) +RETURNS gbtreekey16 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_timestamp_ops +DEFAULT FOR TYPE timestamp USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_ts_consistent (internal, timestamp, int2, oid, internal), + FUNCTION 2 gbt_ts_union (internal, internal), + FUNCTION 3 gbt_ts_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_ts_penalty (internal, internal, internal), + FUNCTION 6 gbt_ts_picksplit (internal, internal), + FUNCTION 7 gbt_ts_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD + OPERATOR 6 <> (timestamp, timestamp) , + OPERATOR 15 <-> (timestamp, timestamp) FOR ORDER BY pg_catalog.interval_ops , + FUNCTION 8 (timestamp, timestamp) gbt_ts_distance (internal, timestamp, int2, oid, internal) , + FUNCTION 9 (timestamp, timestamp) gbt_ts_fetch (internal) ; + +-- Create the operator class +CREATE OPERATOR CLASS gist_timestamptz_ops +DEFAULT FOR TYPE timestamptz USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_tstz_consistent (internal, timestamptz, int2, oid, internal), + FUNCTION 2 gbt_ts_union (internal, internal), + FUNCTION 3 gbt_tstz_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_ts_penalty (internal, internal, internal), + FUNCTION 6 gbt_ts_picksplit (internal, internal), + FUNCTION 7 gbt_ts_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_timestamptz_ops USING gist ADD + OPERATOR 6 <> (timestamptz, timestamptz) , + OPERATOR 15 <-> (timestamptz, timestamptz) FOR ORDER BY pg_catalog.interval_ops , + FUNCTION 8 (timestamptz, timestamptz) gbt_tstz_distance (internal, timestamptz, int2, oid, internal) , + FUNCTION 9 (timestamptz, timestamptz) gbt_ts_fetch (internal) ; + +-- +-- +-- +-- time ops +-- +-- +-- + +CREATE FUNCTION gbt_time_consistent(internal,time,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_time_distance(internal,time,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_timetz_consistent(internal,timetz,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_time_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_timetz_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_time_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_time_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_time_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_time_union(internal, internal) +RETURNS gbtreekey16 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_time_ops +DEFAULT FOR TYPE time USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_time_consistent (internal, time, int2, oid, internal), + FUNCTION 2 gbt_time_union (internal, internal), + FUNCTION 3 gbt_time_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_time_penalty (internal, internal, internal), + FUNCTION 6 gbt_time_picksplit (internal, internal), + FUNCTION 7 gbt_time_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_time_ops USING gist ADD + OPERATOR 6 <> (time, time) , + OPERATOR 15 <-> (time, time) FOR ORDER BY pg_catalog.interval_ops , + FUNCTION 8 (time, time) gbt_time_distance (internal, time, int2, oid, internal) , + FUNCTION 9 (time, time) gbt_time_fetch (internal) ; + + +CREATE OPERATOR CLASS gist_timetz_ops +DEFAULT FOR TYPE timetz USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_timetz_consistent (internal, timetz, int2, oid, internal), + FUNCTION 2 gbt_time_union (internal, internal), + FUNCTION 3 gbt_timetz_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_time_penalty (internal, internal, internal), + FUNCTION 6 gbt_time_picksplit (internal, internal), + FUNCTION 7 gbt_time_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_timetz_ops USING gist ADD + OPERATOR 6 <> (timetz, timetz) ; + -- no 'fetch' function, as the compress function is lossy. + + +-- +-- +-- +-- date ops +-- +-- +-- + +CREATE FUNCTION gbt_date_consistent(internal,date,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_date_distance(internal,date,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_date_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_date_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_date_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_date_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_date_union(internal, internal) +RETURNS gbtreekey8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_date_ops +DEFAULT FOR TYPE date USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_date_consistent (internal, date, int2, oid, internal), + FUNCTION 2 gbt_date_union (internal, internal), + FUNCTION 3 gbt_date_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_date_penalty (internal, internal, internal), + FUNCTION 6 gbt_date_picksplit (internal, internal), + FUNCTION 7 gbt_date_same (gbtreekey8, gbtreekey8, internal), + STORAGE gbtreekey8; + +ALTER OPERATOR FAMILY gist_date_ops USING gist ADD + OPERATOR 6 <> (date, date) , + OPERATOR 15 <-> (date, date) FOR ORDER BY pg_catalog.integer_ops , + FUNCTION 8 (date, date) gbt_date_distance (internal, date, int2, oid, internal) , + FUNCTION 9 (date, date) gbt_date_fetch (internal) ; + + +-- +-- +-- +-- interval ops +-- +-- +-- + +CREATE FUNCTION gbt_intv_consistent(internal,interval,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_intv_distance(internal,interval,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_intv_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_intv_decompress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_intv_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_intv_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_intv_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_intv_union(internal, internal) +RETURNS gbtreekey32 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_interval_ops +DEFAULT FOR TYPE interval USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_intv_consistent (internal, interval, int2, oid, internal), + FUNCTION 2 gbt_intv_union (internal, internal), + FUNCTION 3 gbt_intv_compress (internal), + FUNCTION 4 gbt_intv_decompress (internal), + FUNCTION 5 gbt_intv_penalty (internal, internal, internal), + FUNCTION 6 gbt_intv_picksplit (internal, internal), + FUNCTION 7 gbt_intv_same (gbtreekey32, gbtreekey32, internal), + STORAGE gbtreekey32; + +ALTER OPERATOR FAMILY gist_interval_ops USING gist ADD + OPERATOR 6 <> (interval, interval) , + OPERATOR 15 <-> (interval, interval) FOR ORDER BY pg_catalog.interval_ops , + FUNCTION 8 (interval, interval) gbt_intv_distance (internal, interval, int2, oid, internal) , + FUNCTION 9 (interval, interval) gbt_intv_fetch (internal) ; + + +-- +-- +-- +-- cash ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_cash_consistent(internal,money,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_cash_distance(internal,money,int2,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_cash_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_cash_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_cash_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_cash_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_cash_union(internal, internal) +RETURNS gbtreekey16 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_cash_ops +DEFAULT FOR TYPE money USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_cash_consistent (internal, money, int2, oid, internal), + FUNCTION 2 gbt_cash_union (internal, internal), + FUNCTION 3 gbt_cash_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_cash_penalty (internal, internal, internal), + FUNCTION 6 gbt_cash_picksplit (internal, internal), + FUNCTION 7 gbt_cash_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_cash_ops USING gist ADD + OPERATOR 6 <> (money, money) , + OPERATOR 15 <-> (money, money) FOR ORDER BY pg_catalog.money_ops , + FUNCTION 8 (money, money) gbt_cash_distance (internal, money, int2, oid, internal) , + FUNCTION 9 (money, money) gbt_cash_fetch (internal) ; + + +-- +-- +-- +-- macaddr ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_macad_consistent(internal,macaddr,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad_union(internal, internal) +RETURNS gbtreekey16 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_macaddr_ops +DEFAULT FOR TYPE macaddr USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_macad_consistent (internal, macaddr, int2, oid, internal), + FUNCTION 2 gbt_macad_union (internal, internal), + FUNCTION 3 gbt_macad_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_macad_penalty (internal, internal, internal), + FUNCTION 6 gbt_macad_picksplit (internal, internal), + FUNCTION 7 gbt_macad_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_macaddr_ops USING gist ADD + OPERATOR 6 <> (macaddr, macaddr) , + FUNCTION 9 (macaddr, macaddr) gbt_macad_fetch (internal); + + +-- +-- +-- +-- text/ bpchar ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_text_consistent(internal,text,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bpchar_consistent(internal,bpchar,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_text_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bpchar_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_text_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_text_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_text_union(internal, internal) +RETURNS gbtreekey_var +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_text_ops +DEFAULT FOR TYPE text USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_text_consistent (internal, text, int2, oid, internal), + FUNCTION 2 gbt_text_union (internal, internal), + FUNCTION 3 gbt_text_compress (internal), + FUNCTION 4 gbt_var_decompress (internal), + FUNCTION 5 gbt_text_penalty (internal, internal, internal), + FUNCTION 6 gbt_text_picksplit (internal, internal), + FUNCTION 7 gbt_text_same (gbtreekey_var, gbtreekey_var, internal), + STORAGE gbtreekey_var; + +ALTER OPERATOR FAMILY gist_text_ops USING gist ADD + OPERATOR 6 <> (text, text) , + FUNCTION 9 (text, text) gbt_var_fetch (internal) ; + + +---- Create the operator class +CREATE OPERATOR CLASS gist_bpchar_ops +DEFAULT FOR TYPE bpchar USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_bpchar_consistent (internal, bpchar , int2, oid, internal), + FUNCTION 2 gbt_text_union (internal, internal), + FUNCTION 3 gbt_bpchar_compress (internal), + FUNCTION 4 gbt_var_decompress (internal), + FUNCTION 5 gbt_text_penalty (internal, internal, internal), + FUNCTION 6 gbt_text_picksplit (internal, internal), + FUNCTION 7 gbt_text_same (gbtreekey_var, gbtreekey_var, internal), + STORAGE gbtreekey_var; + +ALTER OPERATOR FAMILY gist_bpchar_ops USING gist ADD + OPERATOR 6 <> (bpchar, bpchar) , + FUNCTION 9 (bpchar, bpchar) gbt_var_fetch (internal) ; + +-- +-- +-- bytea ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_bytea_consistent(internal,bytea,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bytea_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bytea_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bytea_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bytea_union(internal, internal) +RETURNS gbtreekey_var +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_bytea_ops +DEFAULT FOR TYPE bytea USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_bytea_consistent (internal, bytea, int2, oid, internal), + FUNCTION 2 gbt_bytea_union (internal, internal), + FUNCTION 3 gbt_bytea_compress (internal), + FUNCTION 4 gbt_var_decompress (internal), + FUNCTION 5 gbt_bytea_penalty (internal, internal, internal), + FUNCTION 6 gbt_bytea_picksplit (internal, internal), + FUNCTION 7 gbt_bytea_same (gbtreekey_var, gbtreekey_var, internal), + STORAGE gbtreekey_var; + +ALTER OPERATOR FAMILY gist_bytea_ops USING gist ADD + OPERATOR 6 <> (bytea, bytea) , + FUNCTION 9 (bytea, bytea) gbt_var_fetch (internal) ; + + +-- +-- +-- +-- numeric ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_numeric_consistent(internal,numeric,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_numeric_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_numeric_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_numeric_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_numeric_union(internal, internal) +RETURNS gbtreekey_var +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_numeric_ops +DEFAULT FOR TYPE numeric USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_numeric_consistent (internal, numeric, int2, oid, internal), + FUNCTION 2 gbt_numeric_union (internal, internal), + FUNCTION 3 gbt_numeric_compress (internal), + FUNCTION 4 gbt_var_decompress (internal), + FUNCTION 5 gbt_numeric_penalty (internal, internal, internal), + FUNCTION 6 gbt_numeric_picksplit (internal, internal), + FUNCTION 7 gbt_numeric_same (gbtreekey_var, gbtreekey_var, internal), + STORAGE gbtreekey_var; + +ALTER OPERATOR FAMILY gist_numeric_ops USING gist ADD + OPERATOR 6 <> (numeric, numeric) , + FUNCTION 9 (numeric, numeric) gbt_var_fetch (internal) ; + + +-- +-- +-- bit ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_bit_consistent(internal,bit,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bit_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bit_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bit_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bit_union(internal, internal) +RETURNS gbtreekey_var +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_bit_ops +DEFAULT FOR TYPE bit USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_bit_consistent (internal, bit, int2, oid, internal), + FUNCTION 2 gbt_bit_union (internal, internal), + FUNCTION 3 gbt_bit_compress (internal), + FUNCTION 4 gbt_var_decompress (internal), + FUNCTION 5 gbt_bit_penalty (internal, internal, internal), + FUNCTION 6 gbt_bit_picksplit (internal, internal), + FUNCTION 7 gbt_bit_same (gbtreekey_var, gbtreekey_var, internal), + STORAGE gbtreekey_var; + +ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD + OPERATOR 6 <> (bit, bit) , + FUNCTION 9 (bit, bit) gbt_var_fetch (internal) ; + + +-- Create the operator class +CREATE OPERATOR CLASS gist_vbit_ops +DEFAULT FOR TYPE varbit USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_bit_consistent (internal, bit, int2, oid, internal), + FUNCTION 2 gbt_bit_union (internal, internal), + FUNCTION 3 gbt_bit_compress (internal), + FUNCTION 4 gbt_var_decompress (internal), + FUNCTION 5 gbt_bit_penalty (internal, internal, internal), + FUNCTION 6 gbt_bit_picksplit (internal, internal), + FUNCTION 7 gbt_bit_same (gbtreekey_var, gbtreekey_var, internal), + STORAGE gbtreekey_var; + +ALTER OPERATOR FAMILY gist_vbit_ops USING gist ADD + OPERATOR 6 <> (varbit, varbit) , + FUNCTION 9 (varbit, varbit) gbt_var_fetch (internal) ; + + +-- +-- +-- +-- inet/cidr ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_inet_consistent(internal,inet,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_inet_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_inet_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_inet_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_inet_union(internal, internal) +RETURNS gbtreekey16 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_inet_ops +DEFAULT FOR TYPE inet USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_inet_consistent (internal, inet, int2, oid, internal), + FUNCTION 2 gbt_inet_union (internal, internal), + FUNCTION 3 gbt_inet_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_inet_penalty (internal, internal, internal), + FUNCTION 6 gbt_inet_picksplit (internal, internal), + FUNCTION 7 gbt_inet_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD + OPERATOR 6 <> (inet, inet) ; + -- no fetch support, the compress function is lossy + +-- Create the operator class +CREATE OPERATOR CLASS gist_cidr_ops +DEFAULT FOR TYPE cidr USING gist +AS + OPERATOR 1 < (inet, inet) , + OPERATOR 2 <= (inet, inet) , + OPERATOR 3 = (inet, inet) , + OPERATOR 4 >= (inet, inet) , + OPERATOR 5 > (inet, inet) , + FUNCTION 1 gbt_inet_consistent (internal, inet, int2, oid, internal), + FUNCTION 2 gbt_inet_union (internal, internal), + FUNCTION 3 gbt_inet_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_inet_penalty (internal, internal, internal), + FUNCTION 6 gbt_inet_picksplit (internal, internal), + FUNCTION 7 gbt_inet_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_cidr_ops USING gist ADD + OPERATOR 6 <> (inet, inet) ; + -- no fetch support, the compress function is lossy diff --git a/contrib/btree_gist/btree_gist--1.3--1.4.sql b/contrib/btree_gist/btree_gist--1.3--1.4.sql new file mode 100644 index 0000000..f77f6c8 --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.3--1.4.sql @@ -0,0 +1,64 @@ +/* contrib/btree_gist/btree_gist--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.4'" to load this file. \quit + +-- Add support for indexing macaddr8 columns + +-- define the GiST support methods +CREATE FUNCTION gbt_macad8_consistent(internal,macaddr8,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad8_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad8_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad8_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad8_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad8_union(internal, internal) +RETURNS gbtreekey16 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_macad8_same(gbtreekey16, gbtreekey16, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_macaddr8_ops +DEFAULT FOR TYPE macaddr8 USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_macad8_consistent (internal, macaddr8, int2, oid, internal), + FUNCTION 2 gbt_macad8_union (internal, internal), + FUNCTION 3 gbt_macad8_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_macad8_penalty (internal, internal, internal), + FUNCTION 6 gbt_macad8_picksplit (internal, internal), + FUNCTION 7 gbt_macad8_same (gbtreekey16, gbtreekey16, internal), + STORAGE gbtreekey16; + +ALTER OPERATOR FAMILY gist_macaddr8_ops USING gist ADD + OPERATOR 6 <> (macaddr8, macaddr8) , + FUNCTION 9 (macaddr8, macaddr8) gbt_macad8_fetch (internal); diff --git a/contrib/btree_gist/btree_gist--1.4--1.5.sql b/contrib/btree_gist/btree_gist--1.4--1.5.sql new file mode 100644 index 0000000..cf974c2 --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.4--1.5.sql @@ -0,0 +1,69 @@ +/* contrib/btree_gist/btree_gist--1.4--1.5.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.5'" to load this file. \quit + +-- +-- +-- +-- enum ops +-- +-- +-- +-- define the GiST support methods +CREATE FUNCTION gbt_enum_consistent(internal,anyenum,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_enum_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_enum_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_enum_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_enum_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_enum_union(internal, internal) +RETURNS gbtreekey8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_enum_same(gbtreekey8, gbtreekey8, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_enum_ops +DEFAULT FOR TYPE anyenum USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 gbt_enum_consistent (internal, anyenum, int2, oid, internal), + FUNCTION 2 gbt_enum_union (internal, internal), + FUNCTION 3 gbt_enum_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_enum_penalty (internal, internal, internal), + FUNCTION 6 gbt_enum_picksplit (internal, internal), + FUNCTION 7 gbt_enum_same (gbtreekey8, gbtreekey8, internal), + STORAGE gbtreekey8; + +ALTER OPERATOR FAMILY gist_enum_ops USING gist ADD + OPERATOR 6 <> (anyenum, anyenum) , + FUNCTION 9 (anyenum, anyenum) gbt_enum_fetch (internal) ; diff --git a/contrib/btree_gist/btree_gist--1.5--1.6.sql b/contrib/btree_gist/btree_gist--1.5--1.6.sql new file mode 100644 index 0000000..5e1fcb4 --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.5--1.6.sql @@ -0,0 +1,191 @@ +/* contrib/btree_gist/btree_gist--1.5--1.6.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.6'" to load this file. \quit + +-- This upgrade script marks all btree_gist functions as parallel safe. + +-- Input/output functions for GiST key types (gbtreekey*) +ALTER FUNCTION gbtreekey4_in(cstring) PARALLEL SAFE; +ALTER FUNCTION gbtreekey4_out(gbtreekey4) PARALLEL SAFE; +ALTER FUNCTION gbtreekey8_in(cstring) PARALLEL SAFE; +ALTER FUNCTION gbtreekey8_out(gbtreekey8) PARALLEL SAFE; +ALTER FUNCTION gbtreekey16_in(cstring) PARALLEL SAFE; +ALTER FUNCTION gbtreekey16_out(gbtreekey16) PARALLEL SAFE; +ALTER FUNCTION gbtreekey32_in(cstring) PARALLEL SAFE; +ALTER FUNCTION gbtreekey32_out(gbtreekey32) PARALLEL SAFE; +ALTER FUNCTION gbtreekey_var_in(cstring) PARALLEL SAFE; +ALTER FUNCTION gbtreekey_var_out(gbtreekey_var) PARALLEL SAFE; + +-- Functions, which implement distance operators (<->) +ALTER FUNCTION cash_dist(money, money) PARALLEL SAFE; +ALTER FUNCTION date_dist(date, date) PARALLEL SAFE; +ALTER FUNCTION float4_dist(real, real) PARALLEL SAFE; +ALTER FUNCTION float8_dist(double precision, double precision) PARALLEL SAFE; +ALTER FUNCTION int2_dist(smallint, smallint) PARALLEL SAFE; +ALTER FUNCTION int4_dist(integer, integer) PARALLEL SAFE; +ALTER FUNCTION int8_dist(bigint, bigint) PARALLEL SAFE; +ALTER FUNCTION interval_dist(interval, interval) PARALLEL SAFE; +ALTER FUNCTION oid_dist(oid, oid) PARALLEL SAFE; +ALTER FUNCTION time_dist(time without time zone, time without time zone) PARALLEL SAFE; +ALTER FUNCTION ts_dist(timestamp without time zone, timestamp without time zone) PARALLEL SAFE; +ALTER FUNCTION tstz_dist(timestamp with time zone, timestamp with time zone) PARALLEL SAFE; + +-- GiST support methods +ALTER FUNCTION gbt_oid_consistent(internal, oid, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_oid_distance(internal, oid, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_oid_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_oid_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_decompress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_var_decompress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_var_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_oid_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_oid_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_oid_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_oid_same(gbtreekey8, gbtreekey8, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int2_consistent(internal, smallint, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int2_distance(internal, smallint, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int2_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int2_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int2_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int2_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int2_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int2_same(gbtreekey4, gbtreekey4, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int4_consistent(internal, integer, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int4_distance(internal, integer, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int4_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int4_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int4_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int4_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int4_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int4_same(gbtreekey8, gbtreekey8, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int8_consistent(internal, bigint, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int8_distance(internal, bigint, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int8_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int8_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int8_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int8_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int8_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_int8_same(gbtreekey16, gbtreekey16, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float4_consistent(internal, real, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float4_distance(internal, real, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float4_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float4_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float4_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float4_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float4_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float4_same(gbtreekey8, gbtreekey8, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float8_consistent(internal, double precision, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float8_distance(internal, double precision, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float8_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float8_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float8_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float8_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float8_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_float8_same(gbtreekey16, gbtreekey16, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_ts_consistent(internal, timestamp without time zone, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_ts_distance(internal, timestamp without time zone, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_tstz_consistent(internal, timestamp with time zone, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_tstz_distance(internal, timestamp with time zone, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_ts_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_tstz_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_ts_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_ts_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_ts_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_ts_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_ts_same(gbtreekey16, gbtreekey16, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_time_consistent(internal, time without time zone, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_time_distance(internal, time without time zone, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_timetz_consistent(internal, time with time zone, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_time_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_timetz_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_time_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_time_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_time_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_time_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_time_same(gbtreekey16, gbtreekey16, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_date_consistent(internal, date, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_date_distance(internal, date, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_date_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_date_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_date_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_date_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_date_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_date_same(gbtreekey8, gbtreekey8, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_intv_consistent(internal, interval, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_intv_distance(internal, interval, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_intv_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_intv_decompress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_intv_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_intv_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_intv_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_intv_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_intv_same(gbtreekey32, gbtreekey32, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_cash_consistent(internal, money, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_cash_distance(internal, money, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_cash_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_cash_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_cash_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_cash_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_cash_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_cash_same(gbtreekey16, gbtreekey16, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad_consistent(internal, macaddr, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad_same(gbtreekey16, gbtreekey16, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_text_consistent(internal, text, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bpchar_consistent(internal, character, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_text_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bpchar_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_text_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_text_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_text_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_text_same(gbtreekey_var, gbtreekey_var, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bytea_consistent(internal, bytea, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bytea_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bytea_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bytea_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bytea_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bytea_same(gbtreekey_var, gbtreekey_var, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_numeric_consistent(internal, numeric, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_numeric_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_numeric_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_numeric_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_numeric_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_numeric_same(gbtreekey_var, gbtreekey_var, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bit_consistent(internal, bit, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bit_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bit_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bit_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bit_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_bit_same(gbtreekey_var, gbtreekey_var, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_inet_consistent(internal, inet, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_inet_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_inet_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_inet_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_inet_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_inet_same(gbtreekey16, gbtreekey16, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_uuid_consistent(internal, uuid, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_uuid_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_uuid_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_uuid_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_uuid_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_uuid_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_uuid_same(gbtreekey32, gbtreekey32, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad8_consistent(internal, macaddr8, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad8_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad8_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad8_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad8_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad8_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_macad8_same(gbtreekey16, gbtreekey16, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_enum_consistent(internal, anyenum, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_enum_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_enum_fetch(internal) PARALLEL SAFE; +ALTER FUNCTION gbt_enum_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_enum_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_enum_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gbt_enum_same(gbtreekey8, gbtreekey8, internal) PARALLEL SAFE; diff --git a/contrib/btree_gist/btree_gist--1.6--1.7.sql b/contrib/btree_gist/btree_gist--1.6--1.7.sql new file mode 100644 index 0000000..1085216 --- /dev/null +++ b/contrib/btree_gist/btree_gist--1.6--1.7.sql @@ -0,0 +1,77 @@ +/* contrib/btree_gist/btree_gist--1.6--1.7.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.7'" to load this file. \quit + +-- This upgrade scripts add support for bool. +CREATE FUNCTION gbtreekey2_in(cstring) +RETURNS gbtreekey2 +AS 'MODULE_PATHNAME', 'gbtreekey_in' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gbtreekey2_out(gbtreekey2) +RETURNS cstring +AS 'MODULE_PATHNAME', 'gbtreekey_out' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE TYPE gbtreekey2 ( + INTERNALLENGTH = 2, + INPUT = gbtreekey2_in, + OUTPUT = gbtreekey2_out +); + +-- Define the GiST support methods +CREATE FUNCTION gbt_bool_consistent(internal,bool,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bool_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bool_fetch(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bool_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bool_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bool_union(internal, internal) +RETURNS gbtreekey2 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION gbt_bool_same(gbtreekey2, gbtreekey2, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +-- Create the operator class +CREATE OPERATOR CLASS gist_bool_ops +DEFAULT FOR TYPE bool USING gist +AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + OPERATOR 6 <> , + FUNCTION 1 gbt_bool_consistent (internal, bool, int2, oid, internal), + FUNCTION 2 gbt_bool_union (internal, internal), + FUNCTION 3 gbt_bool_compress (internal), + FUNCTION 4 gbt_decompress (internal), + FUNCTION 5 gbt_bool_penalty (internal, internal, internal), + FUNCTION 6 gbt_bool_picksplit (internal, internal), + FUNCTION 7 gbt_bool_same (gbtreekey2, gbtreekey2, internal), + FUNCTION 9 gbt_bool_fetch (internal), + STORAGE gbtreekey2; diff --git a/contrib/btree_gist/btree_gist.c b/contrib/btree_gist/btree_gist.c new file mode 100644 index 0000000..92520ae --- /dev/null +++ b/contrib/btree_gist/btree_gist.c @@ -0,0 +1,53 @@ +/* + * contrib/btree_gist/btree_gist.c + */ +#include "postgres.h" + +#include "utils/builtins.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(gbt_decompress); +PG_FUNCTION_INFO_V1(gbtreekey_in); +PG_FUNCTION_INFO_V1(gbtreekey_out); + +/************************************************** + * In/Out for keys + **************************************************/ + + +Datum +gbtreekey_in(PG_FUNCTION_ARGS) +{ + Oid typioparam = PG_GETARG_OID(1); + + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type %s", + format_type_extended(typioparam, -1, + FORMAT_TYPE_ALLOW_INVALID)))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +Datum +gbtreekey_out(PG_FUNCTION_ARGS) +{ + /* Sadly, we do not receive any indication of the specific type */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot display a value of type %s", "gbtreekey?"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + + +/* +** GiST DeCompress methods +** do not do anything. +*/ +Datum +gbt_decompress(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(PG_GETARG_POINTER(0)); +} diff --git a/contrib/btree_gist/btree_gist.control b/contrib/btree_gist/btree_gist.control new file mode 100644 index 0000000..fa9171a --- /dev/null +++ b/contrib/btree_gist/btree_gist.control @@ -0,0 +1,6 @@ +# btree_gist extension +comment = 'support for indexing common datatypes in GiST' +default_version = '1.7' +module_pathname = '$libdir/btree_gist' +relocatable = true +trusted = true diff --git a/contrib/btree_gist/btree_gist.h b/contrib/btree_gist/btree_gist.h new file mode 100644 index 0000000..f22f14a --- /dev/null +++ b/contrib/btree_gist/btree_gist.h @@ -0,0 +1,41 @@ +/* + * contrib/btree_gist/btree_gist.h + */ +#ifndef __BTREE_GIST_H__ +#define __BTREE_GIST_H__ + +#include "access/nbtree.h" +#include "fmgr.h" + +#define BtreeGistNotEqualStrategyNumber 6 + +/* indexed types */ + +enum gbtree_type +{ + gbt_t_var, + gbt_t_int2, + gbt_t_int4, + gbt_t_int8, + gbt_t_float4, + gbt_t_float8, + gbt_t_numeric, + gbt_t_ts, + gbt_t_cash, + gbt_t_oid, + gbt_t_time, + gbt_t_date, + gbt_t_intv, + gbt_t_macad, + gbt_t_macad8, + gbt_t_text, + gbt_t_bpchar, + gbt_t_bytea, + gbt_t_bit, + gbt_t_bool, + gbt_t_inet, + gbt_t_uuid, + gbt_t_enum +}; + +#endif diff --git a/contrib/btree_gist/btree_inet.c b/contrib/btree_gist/btree_inet.c new file mode 100644 index 0000000..2fb952d --- /dev/null +++ b/contrib/btree_gist/btree_inet.c @@ -0,0 +1,187 @@ +/* + * contrib/btree_gist/btree_inet.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h" +#include "utils/inet.h" + +typedef struct inetkey +{ + double lower; + double upper; +} inetKEY; + +/* +** inet ops +*/ +PG_FUNCTION_INFO_V1(gbt_inet_compress); +PG_FUNCTION_INFO_V1(gbt_inet_union); +PG_FUNCTION_INFO_V1(gbt_inet_picksplit); +PG_FUNCTION_INFO_V1(gbt_inet_consistent); +PG_FUNCTION_INFO_V1(gbt_inet_penalty); +PG_FUNCTION_INFO_V1(gbt_inet_same); + + +static bool +gbt_inetgt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const double *) a) > *((const double *) b)); +} +static bool +gbt_inetge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const double *) a) >= *((const double *) b)); +} +static bool +gbt_ineteq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const double *) a) == *((const double *) b)); +} +static bool +gbt_inetle(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const double *) a) <= *((const double *) b)); +} +static bool +gbt_inetlt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const double *) a) < *((const double *) b)); +} + +static int +gbt_inetkey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + inetKEY *ia = (inetKEY *) (((const Nsrt *) a)->t); + inetKEY *ib = (inetKEY *) (((const Nsrt *) b)->t); + + if (ia->lower == ib->lower) + { + if (ia->upper == ib->upper) + return 0; + + return (ia->upper > ib->upper) ? 1 : -1; + } + + return (ia->lower > ib->lower) ? 1 : -1; +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_inet, + sizeof(double), + 16, /* sizeof(gbtreekey16) */ + gbt_inetgt, + gbt_inetge, + gbt_ineteq, + gbt_inetle, + gbt_inetlt, + gbt_inetkey_cmp, + NULL +}; + + +/************************************************** + * inet ops + **************************************************/ + + +Datum +gbt_inet_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; + + if (entry->leafkey) + { + inetKEY *r = (inetKEY *) palloc(sizeof(inetKEY)); + bool failure = false; + + retval = palloc(sizeof(GISTENTRY)); + r->lower = convert_network_to_scalar(entry->key, INETOID, &failure); + Assert(!failure); + r->upper = r->lower; + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, + entry->offset, false); + } + else + retval = entry; + + PG_RETURN_POINTER(retval); +} + + +Datum +gbt_inet_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Datum dquery = PG_GETARG_DATUM(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + inetKEY *kkk = (inetKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + double query; + bool failure = false; + + query = convert_network_to_scalar(dquery, INETOID, &failure); + Assert(!failure); + + /* All cases served by this function are inexact */ + *recheck = true; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, + &strategy, GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_inet_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(inetKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(inetKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_inet_penalty(PG_FUNCTION_ARGS) +{ + inetKEY *origentry = (inetKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + inetKEY *newentry = (inetKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_inet_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_inet_same(PG_FUNCTION_ARGS) +{ + inetKEY *b1 = (inetKEY *) PG_GETARG_POINTER(0); + inetKEY *b2 = (inetKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_int2.c b/contrib/btree_gist/btree_int2.c new file mode 100644 index 0000000..fdbf156 --- /dev/null +++ b/contrib/btree_gist/btree_int2.c @@ -0,0 +1,216 @@ +/* + * contrib/btree_gist/btree_int2.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "common/int.h" + +typedef struct int16key +{ + int16 lower; + int16 upper; +} int16KEY; + +/* +** int16 ops +*/ +PG_FUNCTION_INFO_V1(gbt_int2_compress); +PG_FUNCTION_INFO_V1(gbt_int2_fetch); +PG_FUNCTION_INFO_V1(gbt_int2_union); +PG_FUNCTION_INFO_V1(gbt_int2_picksplit); +PG_FUNCTION_INFO_V1(gbt_int2_consistent); +PG_FUNCTION_INFO_V1(gbt_int2_distance); +PG_FUNCTION_INFO_V1(gbt_int2_penalty); +PG_FUNCTION_INFO_V1(gbt_int2_same); + +static bool +gbt_int2gt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int16 *) a) > *((const int16 *) b)); +} +static bool +gbt_int2ge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int16 *) a) >= *((const int16 *) b)); +} +static bool +gbt_int2eq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int16 *) a) == *((const int16 *) b)); +} +static bool +gbt_int2le(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int16 *) a) <= *((const int16 *) b)); +} +static bool +gbt_int2lt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int16 *) a) < *((const int16 *) b)); +} + +static int +gbt_int2key_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + int16KEY *ia = (int16KEY *) (((const Nsrt *) a)->t); + int16KEY *ib = (int16KEY *) (((const Nsrt *) b)->t); + + if (ia->lower == ib->lower) + { + if (ia->upper == ib->upper) + return 0; + + return (ia->upper > ib->upper) ? 1 : -1; + } + + return (ia->lower > ib->lower) ? 1 : -1; +} + +static float8 +gbt_int2_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + return GET_FLOAT_DISTANCE(int16, a, b); +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_int2, + sizeof(int16), + 4, /* sizeof(gbtreekey4) */ + gbt_int2gt, + gbt_int2ge, + gbt_int2eq, + gbt_int2le, + gbt_int2lt, + gbt_int2key_cmp, + gbt_int2_dist +}; + + +PG_FUNCTION_INFO_V1(int2_dist); +Datum +int2_dist(PG_FUNCTION_ARGS) +{ + int16 a = PG_GETARG_INT16(0); + int16 b = PG_GETARG_INT16(1); + int16 r; + int16 ra; + + if (pg_sub_s16_overflow(a, b, &r) || + r == PG_INT16_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("smallint out of range"))); + + ra = abs(r); + + PG_RETURN_INT16(ra); +} + + +/************************************************** + * int16 ops + **************************************************/ + + +Datum +gbt_int2_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_int2_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_int2_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int16 query = PG_GETARG_INT16(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + int16KEY *kkk = (int16KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_int2_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int16 query = PG_GETARG_INT16(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + int16KEY *kkk = (int16KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_int2_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(int16KEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(int16KEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_int2_penalty(PG_FUNCTION_ARGS) +{ + int16KEY *origentry = (int16KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + int16KEY *newentry = (int16KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_int2_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_int2_same(PG_FUNCTION_ARGS) +{ + int16KEY *b1 = (int16KEY *) PG_GETARG_POINTER(0); + int16KEY *b2 = (int16KEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_int4.c b/contrib/btree_gist/btree_int4.c new file mode 100644 index 0000000..8915fb5 --- /dev/null +++ b/contrib/btree_gist/btree_int4.c @@ -0,0 +1,217 @@ +/* + * contrib/btree_gist/btree_int4.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "common/int.h" + +typedef struct int32key +{ + int32 lower; + int32 upper; +} int32KEY; + +/* +** int32 ops +*/ +PG_FUNCTION_INFO_V1(gbt_int4_compress); +PG_FUNCTION_INFO_V1(gbt_int4_fetch); +PG_FUNCTION_INFO_V1(gbt_int4_union); +PG_FUNCTION_INFO_V1(gbt_int4_picksplit); +PG_FUNCTION_INFO_V1(gbt_int4_consistent); +PG_FUNCTION_INFO_V1(gbt_int4_distance); +PG_FUNCTION_INFO_V1(gbt_int4_penalty); +PG_FUNCTION_INFO_V1(gbt_int4_same); + + +static bool +gbt_int4gt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int32 *) a) > *((const int32 *) b)); +} +static bool +gbt_int4ge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int32 *) a) >= *((const int32 *) b)); +} +static bool +gbt_int4eq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int32 *) a) == *((const int32 *) b)); +} +static bool +gbt_int4le(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int32 *) a) <= *((const int32 *) b)); +} +static bool +gbt_int4lt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int32 *) a) < *((const int32 *) b)); +} + +static int +gbt_int4key_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + int32KEY *ia = (int32KEY *) (((const Nsrt *) a)->t); + int32KEY *ib = (int32KEY *) (((const Nsrt *) b)->t); + + if (ia->lower == ib->lower) + { + if (ia->upper == ib->upper) + return 0; + + return (ia->upper > ib->upper) ? 1 : -1; + } + + return (ia->lower > ib->lower) ? 1 : -1; +} + +static float8 +gbt_int4_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + return GET_FLOAT_DISTANCE(int32, a, b); +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_int4, + sizeof(int32), + 8, /* sizeof(gbtreekey8) */ + gbt_int4gt, + gbt_int4ge, + gbt_int4eq, + gbt_int4le, + gbt_int4lt, + gbt_int4key_cmp, + gbt_int4_dist +}; + + +PG_FUNCTION_INFO_V1(int4_dist); +Datum +int4_dist(PG_FUNCTION_ARGS) +{ + int32 a = PG_GETARG_INT32(0); + int32 b = PG_GETARG_INT32(1); + int32 r; + int32 ra; + + if (pg_sub_s32_overflow(a, b, &r) || + r == PG_INT32_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("integer out of range"))); + + ra = abs(r); + + PG_RETURN_INT32(ra); +} + + +/************************************************** + * int32 ops + **************************************************/ + + +Datum +gbt_int4_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_int4_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_int4_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int32 query = PG_GETARG_INT32(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + int32KEY *kkk = (int32KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_int4_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int32 query = PG_GETARG_INT32(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + int32KEY *kkk = (int32KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_int4_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(int32KEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(int32KEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_int4_penalty(PG_FUNCTION_ARGS) +{ + int32KEY *origentry = (int32KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + int32KEY *newentry = (int32KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_int4_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_int4_same(PG_FUNCTION_ARGS) +{ + int32KEY *b1 = (int32KEY *) PG_GETARG_POINTER(0); + int32KEY *b2 = (int32KEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_int8.c b/contrib/btree_gist/btree_int8.c new file mode 100644 index 0000000..7c63a5b --- /dev/null +++ b/contrib/btree_gist/btree_int8.c @@ -0,0 +1,217 @@ +/* + * contrib/btree_gist/btree_int8.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "common/int.h" + +typedef struct int64key +{ + int64 lower; + int64 upper; +} int64KEY; + +/* +** int64 ops +*/ +PG_FUNCTION_INFO_V1(gbt_int8_compress); +PG_FUNCTION_INFO_V1(gbt_int8_fetch); +PG_FUNCTION_INFO_V1(gbt_int8_union); +PG_FUNCTION_INFO_V1(gbt_int8_picksplit); +PG_FUNCTION_INFO_V1(gbt_int8_consistent); +PG_FUNCTION_INFO_V1(gbt_int8_distance); +PG_FUNCTION_INFO_V1(gbt_int8_penalty); +PG_FUNCTION_INFO_V1(gbt_int8_same); + + +static bool +gbt_int8gt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int64 *) a) > *((const int64 *) b)); +} +static bool +gbt_int8ge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int64 *) a) >= *((const int64 *) b)); +} +static bool +gbt_int8eq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int64 *) a) == *((const int64 *) b)); +} +static bool +gbt_int8le(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int64 *) a) <= *((const int64 *) b)); +} +static bool +gbt_int8lt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const int64 *) a) < *((const int64 *) b)); +} + +static int +gbt_int8key_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + int64KEY *ia = (int64KEY *) (((const Nsrt *) a)->t); + int64KEY *ib = (int64KEY *) (((const Nsrt *) b)->t); + + if (ia->lower == ib->lower) + { + if (ia->upper == ib->upper) + return 0; + + return (ia->upper > ib->upper) ? 1 : -1; + } + + return (ia->lower > ib->lower) ? 1 : -1; +} + +static float8 +gbt_int8_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + return GET_FLOAT_DISTANCE(int64, a, b); +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_int8, + sizeof(int64), + 16, /* sizeof(gbtreekey16) */ + gbt_int8gt, + gbt_int8ge, + gbt_int8eq, + gbt_int8le, + gbt_int8lt, + gbt_int8key_cmp, + gbt_int8_dist +}; + + +PG_FUNCTION_INFO_V1(int8_dist); +Datum +int8_dist(PG_FUNCTION_ARGS) +{ + int64 a = PG_GETARG_INT64(0); + int64 b = PG_GETARG_INT64(1); + int64 r; + int64 ra; + + if (pg_sub_s64_overflow(a, b, &r) || + r == PG_INT64_MIN) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("bigint out of range"))); + + ra = i64abs(r); + + PG_RETURN_INT64(ra); +} + + +/************************************************** + * int64 ops + **************************************************/ + + +Datum +gbt_int8_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_int8_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_int8_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int64 query = PG_GETARG_INT64(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + int64KEY *kkk = (int64KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_int8_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int64 query = PG_GETARG_INT64(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + int64KEY *kkk = (int64KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_int8_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(int64KEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(int64KEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_int8_penalty(PG_FUNCTION_ARGS) +{ + int64KEY *origentry = (int64KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + int64KEY *newentry = (int64KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_int8_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_int8_same(PG_FUNCTION_ARGS) +{ + int64KEY *b1 = (int64KEY *) PG_GETARG_POINTER(0); + int64KEY *b2 = (int64KEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_interval.c b/contrib/btree_gist/btree_interval.c new file mode 100644 index 0000000..b0afdf0 --- /dev/null +++ b/contrib/btree_gist/btree_interval.c @@ -0,0 +1,297 @@ +/* + * contrib/btree_gist/btree_interval.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "utils/builtins.h" +#include "utils/timestamp.h" + +typedef struct +{ + Interval lower, + upper; +} intvKEY; + + +/* +** Interval ops +*/ +PG_FUNCTION_INFO_V1(gbt_intv_compress); +PG_FUNCTION_INFO_V1(gbt_intv_fetch); +PG_FUNCTION_INFO_V1(gbt_intv_decompress); +PG_FUNCTION_INFO_V1(gbt_intv_union); +PG_FUNCTION_INFO_V1(gbt_intv_picksplit); +PG_FUNCTION_INFO_V1(gbt_intv_consistent); +PG_FUNCTION_INFO_V1(gbt_intv_distance); +PG_FUNCTION_INFO_V1(gbt_intv_penalty); +PG_FUNCTION_INFO_V1(gbt_intv_same); + + +static bool +gbt_intvgt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(interval_gt, IntervalPGetDatum(a), IntervalPGetDatum(b))); +} + +static bool +gbt_intvge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(interval_ge, IntervalPGetDatum(a), IntervalPGetDatum(b))); +} + +static bool +gbt_intveq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(interval_eq, IntervalPGetDatum(a), IntervalPGetDatum(b))); +} + +static bool +gbt_intvle(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(interval_le, IntervalPGetDatum(a), IntervalPGetDatum(b))); +} + +static bool +gbt_intvlt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(interval_lt, IntervalPGetDatum(a), IntervalPGetDatum(b))); +} + +static int +gbt_intvkey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + intvKEY *ia = (intvKEY *) (((const Nsrt *) a)->t); + intvKEY *ib = (intvKEY *) (((const Nsrt *) b)->t); + int res; + + res = DatumGetInt32(DirectFunctionCall2(interval_cmp, IntervalPGetDatum(&ia->lower), IntervalPGetDatum(&ib->lower))); + if (res == 0) + return DatumGetInt32(DirectFunctionCall2(interval_cmp, IntervalPGetDatum(&ia->upper), IntervalPGetDatum(&ib->upper))); + + return res; +} + + +static double +intr2num(const Interval *i) +{ + return INTERVAL_TO_SEC(i); +} + +static float8 +gbt_intv_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + return fabs(intr2num((const Interval *) a) - intr2num((const Interval *) b)); +} + +/* + * INTERVALSIZE should be the actual size-on-disk of an Interval, as shown + * in pg_type. This might be less than sizeof(Interval) if the compiler + * insists on adding alignment padding at the end of the struct. (Note: + * this concern is obsolete with the current definition of Interval, but + * was real before a separate "day" field was added to it.) + */ +#define INTERVALSIZE 16 + +static const gbtree_ninfo tinfo = +{ + gbt_t_intv, + sizeof(Interval), + 32, /* sizeof(gbtreekey32) */ + gbt_intvgt, + gbt_intvge, + gbt_intveq, + gbt_intvle, + gbt_intvlt, + gbt_intvkey_cmp, + gbt_intv_dist +}; + + +Interval * +abs_interval(Interval *a) +{ + static Interval zero = {0, 0, 0}; + + if (DatumGetBool(DirectFunctionCall2(interval_lt, + IntervalPGetDatum(a), + IntervalPGetDatum(&zero)))) + a = DatumGetIntervalP(DirectFunctionCall1(interval_um, + IntervalPGetDatum(a))); + + return a; +} + +PG_FUNCTION_INFO_V1(interval_dist); +Datum +interval_dist(PG_FUNCTION_ARGS) +{ + Datum diff = DirectFunctionCall2(interval_mi, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1)); + + PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff))); +} + + +/************************************************** + * interval ops + **************************************************/ + + +Datum +gbt_intv_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval = entry; + + if (entry->leafkey || INTERVALSIZE != sizeof(Interval)) + { + char *r = (char *) palloc(2 * INTERVALSIZE); + + retval = palloc(sizeof(GISTENTRY)); + + if (entry->leafkey) + { + Interval *key = DatumGetIntervalP(entry->key); + + memcpy(r, key, INTERVALSIZE); + memcpy(r + INTERVALSIZE, key, INTERVALSIZE); + } + else + { + intvKEY *key = (intvKEY *) DatumGetPointer(entry->key); + + memcpy(r, &key->lower, INTERVALSIZE); + memcpy(r + INTERVALSIZE, &key->upper, INTERVALSIZE); + } + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, + entry->offset, false); + } + + PG_RETURN_POINTER(retval); +} + +Datum +gbt_intv_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_intv_decompress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval = entry; + + if (INTERVALSIZE != sizeof(Interval)) + { + intvKEY *r = palloc(sizeof(intvKEY)); + char *key = DatumGetPointer(entry->key); + + retval = palloc(sizeof(GISTENTRY)); + memcpy(&r->lower, key, INTERVALSIZE); + memcpy(&r->upper, key + INTERVALSIZE, INTERVALSIZE); + + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, + entry->offset, false); + } + PG_RETURN_POINTER(retval); +} + + +Datum +gbt_intv_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Interval *query = PG_GETARG_INTERVAL_P(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + intvKEY *kkk = (intvKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) query, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_intv_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Interval *query = PG_GETARG_INTERVAL_P(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + intvKEY *kkk = (intvKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_intv_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(intvKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(intvKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_intv_penalty(PG_FUNCTION_ARGS) +{ + intvKEY *origentry = (intvKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + intvKEY *newentry = (intvKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + double iorg[2], + inew[2]; + + iorg[0] = intr2num(&origentry->lower); + iorg[1] = intr2num(&origentry->upper); + inew[0] = intr2num(&newentry->lower); + inew[1] = intr2num(&newentry->upper); + + penalty_num(result, iorg[0], iorg[1], inew[0], inew[1]); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_intv_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_intv_same(PG_FUNCTION_ARGS) +{ + intvKEY *b1 = (intvKEY *) PG_GETARG_POINTER(0); + intvKEY *b2 = (intvKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_macaddr.c b/contrib/btree_gist/btree_macaddr.c new file mode 100644 index 0000000..1729052 --- /dev/null +++ b/contrib/btree_gist/btree_macaddr.c @@ -0,0 +1,196 @@ +/* + * contrib/btree_gist/btree_macaddr.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "utils/builtins.h" +#include "utils/inet.h" + +typedef struct +{ + macaddr lower; + macaddr upper; + char pad[4]; /* make struct size = sizeof(gbtreekey16) */ +} macKEY; + +/* +** OID ops +*/ +PG_FUNCTION_INFO_V1(gbt_macad_compress); +PG_FUNCTION_INFO_V1(gbt_macad_fetch); +PG_FUNCTION_INFO_V1(gbt_macad_union); +PG_FUNCTION_INFO_V1(gbt_macad_picksplit); +PG_FUNCTION_INFO_V1(gbt_macad_consistent); +PG_FUNCTION_INFO_V1(gbt_macad_penalty); +PG_FUNCTION_INFO_V1(gbt_macad_same); + + +static bool +gbt_macadgt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(macaddr_gt, PointerGetDatum(a), PointerGetDatum(b))); +} +static bool +gbt_macadge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(macaddr_ge, PointerGetDatum(a), PointerGetDatum(b))); +} + +static bool +gbt_macadeq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(macaddr_eq, PointerGetDatum(a), PointerGetDatum(b))); +} + +static bool +gbt_macadle(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(macaddr_le, PointerGetDatum(a), PointerGetDatum(b))); +} + +static bool +gbt_macadlt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(macaddr_lt, PointerGetDatum(a), PointerGetDatum(b))); +} + + +static int +gbt_macadkey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + macKEY *ia = (macKEY *) (((const Nsrt *) a)->t); + macKEY *ib = (macKEY *) (((const Nsrt *) b)->t); + int res; + + res = DatumGetInt32(DirectFunctionCall2(macaddr_cmp, MacaddrPGetDatum(&ia->lower), MacaddrPGetDatum(&ib->lower))); + if (res == 0) + return DatumGetInt32(DirectFunctionCall2(macaddr_cmp, MacaddrPGetDatum(&ia->upper), MacaddrPGetDatum(&ib->upper))); + + return res; +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_macad, + sizeof(macaddr), + 16, /* sizeof(gbtreekey16) */ + gbt_macadgt, + gbt_macadge, + gbt_macadeq, + gbt_macadle, + gbt_macadlt, + gbt_macadkey_cmp, + NULL +}; + + +/************************************************** + * macaddr ops + **************************************************/ + + + +static uint64 +mac_2_uint64(macaddr *m) +{ + unsigned char *mi = (unsigned char *) m; + uint64 res = 0; + int i; + + for (i = 0; i < 6; i++) + res += (((uint64) mi[i]) << ((uint64) ((5 - i) * 8))); + return res; +} + + + +Datum +gbt_macad_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_macad_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_macad_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + macaddr *query = (macaddr *) PG_GETARG_POINTER(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + macKEY *kkk = (macKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) query, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_macad_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc0(sizeof(macKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(macKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_macad_penalty(PG_FUNCTION_ARGS) +{ + macKEY *origentry = (macKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + macKEY *newentry = (macKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + uint64 iorg[2], + inew[2]; + + iorg[0] = mac_2_uint64(&origentry->lower); + iorg[1] = mac_2_uint64(&origentry->upper); + inew[0] = mac_2_uint64(&newentry->lower); + inew[1] = mac_2_uint64(&newentry->upper); + + penalty_num(result, iorg[0], iorg[1], inew[0], inew[1]); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_macad_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_macad_same(PG_FUNCTION_ARGS) +{ + macKEY *b1 = (macKEY *) PG_GETARG_POINTER(0); + macKEY *b2 = (macKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_macaddr8.c b/contrib/btree_gist/btree_macaddr8.c new file mode 100644 index 0000000..796cc4e --- /dev/null +++ b/contrib/btree_gist/btree_macaddr8.c @@ -0,0 +1,196 @@ +/* + * contrib/btree_gist/btree_macaddr8.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "utils/builtins.h" +#include "utils/inet.h" + +typedef struct +{ + macaddr8 lower; + macaddr8 upper; + /* make struct size = sizeof(gbtreekey16) */ +} mac8KEY; + +/* +** OID ops +*/ +PG_FUNCTION_INFO_V1(gbt_macad8_compress); +PG_FUNCTION_INFO_V1(gbt_macad8_fetch); +PG_FUNCTION_INFO_V1(gbt_macad8_union); +PG_FUNCTION_INFO_V1(gbt_macad8_picksplit); +PG_FUNCTION_INFO_V1(gbt_macad8_consistent); +PG_FUNCTION_INFO_V1(gbt_macad8_penalty); +PG_FUNCTION_INFO_V1(gbt_macad8_same); + + +static bool +gbt_macad8gt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(macaddr8_gt, PointerGetDatum(a), PointerGetDatum(b))); +} +static bool +gbt_macad8ge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(macaddr8_ge, PointerGetDatum(a), PointerGetDatum(b))); +} + +static bool +gbt_macad8eq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(macaddr8_eq, PointerGetDatum(a), PointerGetDatum(b))); +} + +static bool +gbt_macad8le(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(macaddr8_le, PointerGetDatum(a), PointerGetDatum(b))); +} + +static bool +gbt_macad8lt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(macaddr8_lt, PointerGetDatum(a), PointerGetDatum(b))); +} + + +static int +gbt_macad8key_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + mac8KEY *ia = (mac8KEY *) (((const Nsrt *) a)->t); + mac8KEY *ib = (mac8KEY *) (((const Nsrt *) b)->t); + int res; + + res = DatumGetInt32(DirectFunctionCall2(macaddr8_cmp, Macaddr8PGetDatum(&ia->lower), Macaddr8PGetDatum(&ib->lower))); + if (res == 0) + return DatumGetInt32(DirectFunctionCall2(macaddr8_cmp, Macaddr8PGetDatum(&ia->upper), Macaddr8PGetDatum(&ib->upper))); + + return res; +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_macad8, + sizeof(macaddr8), + 16, /* sizeof(gbtreekey16) */ + gbt_macad8gt, + gbt_macad8ge, + gbt_macad8eq, + gbt_macad8le, + gbt_macad8lt, + gbt_macad8key_cmp, + NULL +}; + + +/************************************************** + * macaddr ops + **************************************************/ + + + +static uint64 +mac8_2_uint64(macaddr8 *m) +{ + unsigned char *mi = (unsigned char *) m; + uint64 res = 0; + int i; + + for (i = 0; i < 8; i++) + res += (((uint64) mi[i]) << ((uint64) ((7 - i) * 8))); + return res; +} + + + +Datum +gbt_macad8_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_macad8_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_macad8_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + macaddr8 *query = (macaddr8 *) PG_GETARG_POINTER(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + mac8KEY *kkk = (mac8KEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) query, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_macad8_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc0(sizeof(mac8KEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(mac8KEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_macad8_penalty(PG_FUNCTION_ARGS) +{ + mac8KEY *origentry = (mac8KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + mac8KEY *newentry = (mac8KEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + uint64 iorg[2], + inew[2]; + + iorg[0] = mac8_2_uint64(&origentry->lower); + iorg[1] = mac8_2_uint64(&origentry->upper); + inew[0] = mac8_2_uint64(&newentry->lower); + inew[1] = mac8_2_uint64(&newentry->upper); + + penalty_num(result, iorg[0], iorg[1], inew[0], inew[1]); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_macad8_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_macad8_same(PG_FUNCTION_ARGS) +{ + mac8KEY *b1 = (mac8KEY *) PG_GETARG_POINTER(0); + mac8KEY *b2 = (mac8KEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_numeric.c b/contrib/btree_gist/btree_numeric.c new file mode 100644 index 0000000..35e466c --- /dev/null +++ b/contrib/btree_gist/btree_numeric.c @@ -0,0 +1,229 @@ +/* + * contrib/btree_gist/btree_numeric.c + */ +#include "postgres.h" + +#include +#include + +#include "btree_gist.h" +#include "btree_utils_var.h" +#include "utils/builtins.h" +#include "utils/numeric.h" +#include "utils/rel.h" + +/* +** Bytea ops +*/ +PG_FUNCTION_INFO_V1(gbt_numeric_compress); +PG_FUNCTION_INFO_V1(gbt_numeric_union); +PG_FUNCTION_INFO_V1(gbt_numeric_picksplit); +PG_FUNCTION_INFO_V1(gbt_numeric_consistent); +PG_FUNCTION_INFO_V1(gbt_numeric_penalty); +PG_FUNCTION_INFO_V1(gbt_numeric_same); + + +/* define for comparison */ + +static bool +gbt_numeric_gt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(numeric_gt, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_numeric_ge(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(numeric_ge, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_numeric_eq(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(numeric_eq, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_numeric_le(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(numeric_le, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_numeric_lt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2(numeric_lt, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static int32 +gbt_numeric_cmp(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetInt32(DirectFunctionCall2(numeric_cmp, + PointerGetDatum(a), + PointerGetDatum(b))); +} + + +static const gbtree_vinfo tinfo = +{ + gbt_t_numeric, + 0, + false, + gbt_numeric_gt, + gbt_numeric_ge, + gbt_numeric_eq, + gbt_numeric_le, + gbt_numeric_lt, + gbt_numeric_cmp, + NULL +}; + + +/************************************************** + * Text ops + **************************************************/ + + +Datum +gbt_numeric_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_var_compress(entry, &tinfo)); +} + + + +Datum +gbt_numeric_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + void *query = (void *) DatumGetNumeric(PG_GETARG_DATUM(1)); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + bool retval; + GBT_VARKEY *key = (GBT_VARKEY *) DatumGetPointer(entry->key); + GBT_VARKEY_R r = gbt_var_key_readable(key); + + /* All cases served by this function are exact */ + *recheck = false; + + retval = gbt_var_consistent(&r, query, strategy, PG_GET_COLLATION(), + GIST_LEAF(entry), &tinfo, fcinfo->flinfo); + PG_RETURN_BOOL(retval); +} + + + +Datum +gbt_numeric_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int32 *size = (int *) PG_GETARG_POINTER(1); + + PG_RETURN_POINTER(gbt_var_union(entryvec, size, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_numeric_same(PG_FUNCTION_ARGS) +{ + Datum d1 = PG_GETARG_DATUM(0); + Datum d2 = PG_GETARG_DATUM(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_var_same(d1, d2, PG_GET_COLLATION(), &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} + + +Datum +gbt_numeric_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *o = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *n = (GISTENTRY *) PG_GETARG_POINTER(1); + float *result = (float *) PG_GETARG_POINTER(2); + + Numeric us, + os, + ds; + + GBT_VARKEY *org = (GBT_VARKEY *) DatumGetPointer(o->key); + GBT_VARKEY *newe = (GBT_VARKEY *) DatumGetPointer(n->key); + Datum uni; + GBT_VARKEY_R rk, + ok, + uk; + + rk = gbt_var_key_readable(org); + uni = PointerGetDatum(gbt_var_key_copy(&rk)); + gbt_var_bin_union(&uni, newe, PG_GET_COLLATION(), &tinfo, fcinfo->flinfo); + ok = gbt_var_key_readable(org); + uk = gbt_var_key_readable((GBT_VARKEY *) DatumGetPointer(uni)); + + us = DatumGetNumeric(DirectFunctionCall2(numeric_sub, + PointerGetDatum(uk.upper), + PointerGetDatum(uk.lower))); + + os = DatumGetNumeric(DirectFunctionCall2(numeric_sub, + PointerGetDatum(ok.upper), + PointerGetDatum(ok.lower))); + + ds = DatumGetNumeric(DirectFunctionCall2(numeric_sub, + NumericGetDatum(us), + NumericGetDatum(os))); + + if (numeric_is_nan(us)) + { + if (numeric_is_nan(os)) + *result = 0.0; + else + *result = 1.0; + } + else + { + Numeric nul = int64_to_numeric(0); + + *result = 0.0; + + if (DirectFunctionCall2(numeric_gt, NumericGetDatum(ds), NumericGetDatum(nul))) + { + *result += FLT_MIN; + os = DatumGetNumeric(DirectFunctionCall2(numeric_div, + NumericGetDatum(ds), + NumericGetDatum(us))); + *result += (float4) DatumGetFloat8(DirectFunctionCall1(numeric_float8_no_overflow, NumericGetDatum(os))); + } + } + + if (*result > 0) + *result *= (FLT_MAX / (((GISTENTRY *) PG_GETARG_POINTER(0))->rel->rd_att->natts + 1)); + + PG_RETURN_POINTER(result); +} + + + +Datum +gbt_numeric_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + + gbt_var_picksplit(entryvec, v, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(v); +} diff --git a/contrib/btree_gist/btree_oid.c b/contrib/btree_gist/btree_oid.c new file mode 100644 index 0000000..3cc7d42 --- /dev/null +++ b/contrib/btree_gist/btree_oid.c @@ -0,0 +1,217 @@ +/* + * contrib/btree_gist/btree_oid.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" + +typedef struct +{ + Oid lower; + Oid upper; +} oidKEY; + +/* +** OID ops +*/ +PG_FUNCTION_INFO_V1(gbt_oid_compress); +PG_FUNCTION_INFO_V1(gbt_oid_fetch); +PG_FUNCTION_INFO_V1(gbt_oid_union); +PG_FUNCTION_INFO_V1(gbt_oid_picksplit); +PG_FUNCTION_INFO_V1(gbt_oid_consistent); +PG_FUNCTION_INFO_V1(gbt_oid_distance); +PG_FUNCTION_INFO_V1(gbt_oid_penalty); +PG_FUNCTION_INFO_V1(gbt_oid_same); + + +static bool +gbt_oidgt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Oid *) a) > *((const Oid *) b)); +} +static bool +gbt_oidge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Oid *) a) >= *((const Oid *) b)); +} +static bool +gbt_oideq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Oid *) a) == *((const Oid *) b)); +} +static bool +gbt_oidle(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Oid *) a) <= *((const Oid *) b)); +} +static bool +gbt_oidlt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return (*((const Oid *) a) < *((const Oid *) b)); +} + +static int +gbt_oidkey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + oidKEY *ia = (oidKEY *) (((const Nsrt *) a)->t); + oidKEY *ib = (oidKEY *) (((const Nsrt *) b)->t); + + if (ia->lower == ib->lower) + { + if (ia->upper == ib->upper) + return 0; + + return (ia->upper > ib->upper) ? 1 : -1; + } + + return (ia->lower > ib->lower) ? 1 : -1; +} + +static float8 +gbt_oid_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + Oid aa = *(const Oid *) a; + Oid bb = *(const Oid *) b; + + if (aa < bb) + return (float8) (bb - aa); + else + return (float8) (aa - bb); +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_oid, + sizeof(Oid), + 8, /* sizeof(gbtreekey8) */ + gbt_oidgt, + gbt_oidge, + gbt_oideq, + gbt_oidle, + gbt_oidlt, + gbt_oidkey_cmp, + gbt_oid_dist +}; + + +PG_FUNCTION_INFO_V1(oid_dist); +Datum +oid_dist(PG_FUNCTION_ARGS) +{ + Oid a = PG_GETARG_OID(0); + Oid b = PG_GETARG_OID(1); + Oid res; + + if (a < b) + res = b - a; + else + res = a - b; + PG_RETURN_OID(res); +} + + +/************************************************** + * Oid ops + **************************************************/ + + +Datum +gbt_oid_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + +Datum +gbt_oid_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_oid_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Oid query = PG_GETARG_OID(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + oidKEY *kkk = (oidKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_oid_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Oid query = PG_GETARG_OID(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + oidKEY *kkk = (oidKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_oid_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(oidKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(oidKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_oid_penalty(PG_FUNCTION_ARGS) +{ + oidKEY *origentry = (oidKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + oidKEY *newentry = (oidKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_oid_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_oid_same(PG_FUNCTION_ARGS) +{ + oidKEY *b1 = (oidKEY *) PG_GETARG_POINTER(0); + oidKEY *b2 = (oidKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_text.c b/contrib/btree_gist/btree_text.c new file mode 100644 index 0000000..be0eac7 --- /dev/null +++ b/contrib/btree_gist/btree_text.c @@ -0,0 +1,289 @@ +/* + * contrib/btree_gist/btree_text.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_var.h" +#include "utils/builtins.h" + +/* +** Text ops +*/ +PG_FUNCTION_INFO_V1(gbt_text_compress); +PG_FUNCTION_INFO_V1(gbt_bpchar_compress); +PG_FUNCTION_INFO_V1(gbt_text_union); +PG_FUNCTION_INFO_V1(gbt_text_picksplit); +PG_FUNCTION_INFO_V1(gbt_text_consistent); +PG_FUNCTION_INFO_V1(gbt_bpchar_consistent); +PG_FUNCTION_INFO_V1(gbt_text_penalty); +PG_FUNCTION_INFO_V1(gbt_text_same); + + +/* define for comparison */ + +static bool +gbt_textgt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(text_gt, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_textge(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(text_ge, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_texteq(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(texteq, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_textle(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(text_le, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_textlt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(text_lt, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static int32 +gbt_textcmp(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetInt32(DirectFunctionCall2Coll(bttextcmp, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static gbtree_vinfo tinfo = +{ + gbt_t_text, + 0, + false, + gbt_textgt, + gbt_textge, + gbt_texteq, + gbt_textle, + gbt_textlt, + gbt_textcmp, + NULL +}; + +/* bpchar needs its own comparison rules */ + +static bool +gbt_bpchargt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(bpchargt, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bpcharge(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(bpcharge, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bpchareq(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(bpchareq, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bpcharle(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(bpcharle, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static bool +gbt_bpcharlt(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetBool(DirectFunctionCall2Coll(bpcharlt, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static int32 +gbt_bpcharcmp(const void *a, const void *b, Oid collation, FmgrInfo *flinfo) +{ + return DatumGetInt32(DirectFunctionCall2Coll(bpcharcmp, + collation, + PointerGetDatum(a), + PointerGetDatum(b))); +} + +static gbtree_vinfo bptinfo = +{ + gbt_t_bpchar, + 0, + false, + gbt_bpchargt, + gbt_bpcharge, + gbt_bpchareq, + gbt_bpcharle, + gbt_bpcharlt, + gbt_bpcharcmp, + NULL +}; + + +/************************************************** + * Text ops + **************************************************/ + + +Datum +gbt_text_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + if (tinfo.eml == 0) + { + tinfo.eml = pg_database_encoding_max_length(); + } + + PG_RETURN_POINTER(gbt_var_compress(entry, &tinfo)); +} + +Datum +gbt_bpchar_compress(PG_FUNCTION_ARGS) +{ + /* This should never have been distinct from gbt_text_compress */ + return gbt_text_compress(fcinfo); +} + + + +Datum +gbt_text_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + void *query = (void *) DatumGetTextP(PG_GETARG_DATUM(1)); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + bool retval; + GBT_VARKEY *key = (GBT_VARKEY *) DatumGetPointer(entry->key); + GBT_VARKEY_R r = gbt_var_key_readable(key); + + /* All cases served by this function are exact */ + *recheck = false; + + if (tinfo.eml == 0) + { + tinfo.eml = pg_database_encoding_max_length(); + } + + retval = gbt_var_consistent(&r, query, strategy, PG_GET_COLLATION(), + GIST_LEAF(entry), &tinfo, fcinfo->flinfo); + + PG_RETURN_BOOL(retval); +} + + +Datum +gbt_bpchar_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + void *query = (void *) DatumGetTextP(PG_GETARG_DATUM(1)); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + bool retval; + GBT_VARKEY *key = (GBT_VARKEY *) DatumGetPointer(entry->key); + GBT_VARKEY_R r = gbt_var_key_readable(key); + + /* All cases served by this function are exact */ + *recheck = false; + + if (bptinfo.eml == 0) + { + bptinfo.eml = pg_database_encoding_max_length(); + } + + retval = gbt_var_consistent(&r, query, strategy, PG_GET_COLLATION(), + GIST_LEAF(entry), &bptinfo, fcinfo->flinfo); + PG_RETURN_BOOL(retval); +} + + +Datum +gbt_text_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int32 *size = (int *) PG_GETARG_POINTER(1); + + PG_RETURN_POINTER(gbt_var_union(entryvec, size, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_text_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + + gbt_var_picksplit(entryvec, v, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(v); +} + +Datum +gbt_text_same(PG_FUNCTION_ARGS) +{ + Datum d1 = PG_GETARG_DATUM(0); + Datum d2 = PG_GETARG_DATUM(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_var_same(d1, d2, PG_GET_COLLATION(), &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} + + +Datum +gbt_text_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *o = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *n = (GISTENTRY *) PG_GETARG_POINTER(1); + float *result = (float *) PG_GETARG_POINTER(2); + + PG_RETURN_POINTER(gbt_var_penalty(result, o, n, PG_GET_COLLATION(), + &tinfo, fcinfo->flinfo)); +} diff --git a/contrib/btree_gist/btree_time.c b/contrib/btree_gist/btree_time.c new file mode 100644 index 0000000..d89401c --- /dev/null +++ b/contrib/btree_gist/btree_time.c @@ -0,0 +1,334 @@ +/* + * contrib/btree_gist/btree_time.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/timestamp.h" + +typedef struct +{ + TimeADT lower; + TimeADT upper; +} timeKEY; + +/* +** time ops +*/ +PG_FUNCTION_INFO_V1(gbt_time_compress); +PG_FUNCTION_INFO_V1(gbt_timetz_compress); +PG_FUNCTION_INFO_V1(gbt_time_fetch); +PG_FUNCTION_INFO_V1(gbt_time_union); +PG_FUNCTION_INFO_V1(gbt_time_picksplit); +PG_FUNCTION_INFO_V1(gbt_time_consistent); +PG_FUNCTION_INFO_V1(gbt_time_distance); +PG_FUNCTION_INFO_V1(gbt_timetz_consistent); +PG_FUNCTION_INFO_V1(gbt_time_penalty); +PG_FUNCTION_INFO_V1(gbt_time_same); + + +#ifdef USE_FLOAT8_BYVAL +#define TimeADTGetDatumFast(X) TimeADTGetDatum(X) +#else +#define TimeADTGetDatumFast(X) PointerGetDatum(&(X)) +#endif + + +static bool +gbt_timegt(const void *a, const void *b, FmgrInfo *flinfo) +{ + const TimeADT *aa = (const TimeADT *) a; + const TimeADT *bb = (const TimeADT *) b; + + return DatumGetBool(DirectFunctionCall2(time_gt, + TimeADTGetDatumFast(*aa), + TimeADTGetDatumFast(*bb))); +} + +static bool +gbt_timege(const void *a, const void *b, FmgrInfo *flinfo) +{ + const TimeADT *aa = (const TimeADT *) a; + const TimeADT *bb = (const TimeADT *) b; + + return DatumGetBool(DirectFunctionCall2(time_ge, + TimeADTGetDatumFast(*aa), + TimeADTGetDatumFast(*bb))); +} + +static bool +gbt_timeeq(const void *a, const void *b, FmgrInfo *flinfo) +{ + const TimeADT *aa = (const TimeADT *) a; + const TimeADT *bb = (const TimeADT *) b; + + return DatumGetBool(DirectFunctionCall2(time_eq, + TimeADTGetDatumFast(*aa), + TimeADTGetDatumFast(*bb))); +} + +static bool +gbt_timele(const void *a, const void *b, FmgrInfo *flinfo) +{ + const TimeADT *aa = (const TimeADT *) a; + const TimeADT *bb = (const TimeADT *) b; + + return DatumGetBool(DirectFunctionCall2(time_le, + TimeADTGetDatumFast(*aa), + TimeADTGetDatumFast(*bb))); +} + +static bool +gbt_timelt(const void *a, const void *b, FmgrInfo *flinfo) +{ + const TimeADT *aa = (const TimeADT *) a; + const TimeADT *bb = (const TimeADT *) b; + + return DatumGetBool(DirectFunctionCall2(time_lt, + TimeADTGetDatumFast(*aa), + TimeADTGetDatumFast(*bb))); +} + + + +static int +gbt_timekey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + timeKEY *ia = (timeKEY *) (((const Nsrt *) a)->t); + timeKEY *ib = (timeKEY *) (((const Nsrt *) b)->t); + int res; + + res = DatumGetInt32(DirectFunctionCall2(time_cmp, TimeADTGetDatumFast(ia->lower), TimeADTGetDatumFast(ib->lower))); + if (res == 0) + return DatumGetInt32(DirectFunctionCall2(time_cmp, TimeADTGetDatumFast(ia->upper), TimeADTGetDatumFast(ib->upper))); + + return res; +} + +static float8 +gbt_time_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + const TimeADT *aa = (const TimeADT *) a; + const TimeADT *bb = (const TimeADT *) b; + Interval *i; + + i = DatumGetIntervalP(DirectFunctionCall2(time_mi_time, + TimeADTGetDatumFast(*aa), + TimeADTGetDatumFast(*bb))); + return fabs(INTERVAL_TO_SEC(i)); +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_time, + sizeof(TimeADT), + 16, /* sizeof(gbtreekey16) */ + gbt_timegt, + gbt_timege, + gbt_timeeq, + gbt_timele, + gbt_timelt, + gbt_timekey_cmp, + gbt_time_dist +}; + + +PG_FUNCTION_INFO_V1(time_dist); +Datum +time_dist(PG_FUNCTION_ARGS) +{ + Datum diff = DirectFunctionCall2(time_mi_time, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1)); + + PG_RETURN_INTERVAL_P(abs_interval(DatumGetIntervalP(diff))); +} + + +/************************************************** + * time ops + **************************************************/ + + + +Datum +gbt_time_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + + +Datum +gbt_timetz_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; + + if (entry->leafkey) + { + timeKEY *r = (timeKEY *) palloc(sizeof(timeKEY)); + TimeTzADT *tz = DatumGetTimeTzADTP(entry->key); + TimeADT tmp; + + retval = palloc(sizeof(GISTENTRY)); + + /* We are using the time + zone only to compress */ + tmp = tz->time + (tz->zone * INT64CONST(1000000)); + r->lower = r->upper = tmp; + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, + entry->offset, false); + } + else + retval = entry; + PG_RETURN_POINTER(retval); +} + +Datum +gbt_time_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_time_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + TimeADT query = PG_GETARG_TIMEADT(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + timeKEY *kkk = (timeKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_time_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + TimeADT query = PG_GETARG_TIMEADT(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + timeKEY *kkk = (timeKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_timetz_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + TimeTzADT *query = PG_GETARG_TIMETZADT_P(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + timeKEY *kkk = (timeKEY *) DatumGetPointer(entry->key); + TimeADT qqq; + GBT_NUMKEY_R key; + + /* All cases served by this function are inexact */ + *recheck = true; + + qqq = query->time + (query->zone * INT64CONST(1000000)); + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &qqq, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_time_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(timeKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(timeKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_time_penalty(PG_FUNCTION_ARGS) +{ + timeKEY *origentry = (timeKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + timeKEY *newentry = (timeKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + Interval *intr; + double res; + double res2; + + intr = DatumGetIntervalP(DirectFunctionCall2(time_mi_time, + TimeADTGetDatumFast(newentry->upper), + TimeADTGetDatumFast(origentry->upper))); + res = INTERVAL_TO_SEC(intr); + res = Max(res, 0); + + intr = DatumGetIntervalP(DirectFunctionCall2(time_mi_time, + TimeADTGetDatumFast(origentry->lower), + TimeADTGetDatumFast(newentry->lower))); + res2 = INTERVAL_TO_SEC(intr); + res2 = Max(res2, 0); + + res += res2; + + *result = 0.0; + + if (res > 0) + { + intr = DatumGetIntervalP(DirectFunctionCall2(time_mi_time, + TimeADTGetDatumFast(origentry->upper), + TimeADTGetDatumFast(origentry->lower))); + *result += FLT_MIN; + *result += (float) (res / (res + INTERVAL_TO_SEC(intr))); + *result *= (FLT_MAX / (((GISTENTRY *) PG_GETARG_POINTER(0))->rel->rd_att->natts + 1)); + } + + PG_RETURN_POINTER(result); +} + + +Datum +gbt_time_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_time_same(PG_FUNCTION_ARGS) +{ + timeKEY *b1 = (timeKEY *) PG_GETARG_POINTER(0); + timeKEY *b2 = (timeKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c new file mode 100644 index 0000000..3f5ba91 --- /dev/null +++ b/contrib/btree_gist/btree_ts.c @@ -0,0 +1,400 @@ +/* + * contrib/btree_gist/btree_ts.c + */ +#include "postgres.h" + +#include + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "utils/builtins.h" +#include "utils/datetime.h" +#include "utils/float.h" + +typedef struct +{ + Timestamp lower; + Timestamp upper; +} tsKEY; + +/* +** timestamp ops +*/ +PG_FUNCTION_INFO_V1(gbt_ts_compress); +PG_FUNCTION_INFO_V1(gbt_tstz_compress); +PG_FUNCTION_INFO_V1(gbt_ts_fetch); +PG_FUNCTION_INFO_V1(gbt_ts_union); +PG_FUNCTION_INFO_V1(gbt_ts_picksplit); +PG_FUNCTION_INFO_V1(gbt_ts_consistent); +PG_FUNCTION_INFO_V1(gbt_ts_distance); +PG_FUNCTION_INFO_V1(gbt_tstz_consistent); +PG_FUNCTION_INFO_V1(gbt_tstz_distance); +PG_FUNCTION_INFO_V1(gbt_ts_penalty); +PG_FUNCTION_INFO_V1(gbt_ts_same); + + +#ifdef USE_FLOAT8_BYVAL +#define TimestampGetDatumFast(X) TimestampGetDatum(X) +#else +#define TimestampGetDatumFast(X) PointerGetDatum(&(X)) +#endif + + +static bool +gbt_tsgt(const void *a, const void *b, FmgrInfo *flinfo) +{ + const Timestamp *aa = (const Timestamp *) a; + const Timestamp *bb = (const Timestamp *) b; + + return DatumGetBool(DirectFunctionCall2(timestamp_gt, + TimestampGetDatumFast(*aa), + TimestampGetDatumFast(*bb))); +} + +static bool +gbt_tsge(const void *a, const void *b, FmgrInfo *flinfo) +{ + const Timestamp *aa = (const Timestamp *) a; + const Timestamp *bb = (const Timestamp *) b; + + return DatumGetBool(DirectFunctionCall2(timestamp_ge, + TimestampGetDatumFast(*aa), + TimestampGetDatumFast(*bb))); +} + +static bool +gbt_tseq(const void *a, const void *b, FmgrInfo *flinfo) +{ + const Timestamp *aa = (const Timestamp *) a; + const Timestamp *bb = (const Timestamp *) b; + + return DatumGetBool(DirectFunctionCall2(timestamp_eq, + TimestampGetDatumFast(*aa), + TimestampGetDatumFast(*bb))); +} + +static bool +gbt_tsle(const void *a, const void *b, FmgrInfo *flinfo) +{ + const Timestamp *aa = (const Timestamp *) a; + const Timestamp *bb = (const Timestamp *) b; + + return DatumGetBool(DirectFunctionCall2(timestamp_le, + TimestampGetDatumFast(*aa), + TimestampGetDatumFast(*bb))); +} + +static bool +gbt_tslt(const void *a, const void *b, FmgrInfo *flinfo) +{ + const Timestamp *aa = (const Timestamp *) a; + const Timestamp *bb = (const Timestamp *) b; + + return DatumGetBool(DirectFunctionCall2(timestamp_lt, + TimestampGetDatumFast(*aa), + TimestampGetDatumFast(*bb))); +} + + +static int +gbt_tskey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + tsKEY *ia = (tsKEY *) (((const Nsrt *) a)->t); + tsKEY *ib = (tsKEY *) (((const Nsrt *) b)->t); + int res; + + res = DatumGetInt32(DirectFunctionCall2(timestamp_cmp, TimestampGetDatumFast(ia->lower), TimestampGetDatumFast(ib->lower))); + if (res == 0) + return DatumGetInt32(DirectFunctionCall2(timestamp_cmp, TimestampGetDatumFast(ia->upper), TimestampGetDatumFast(ib->upper))); + + return res; +} + +static float8 +gbt_ts_dist(const void *a, const void *b, FmgrInfo *flinfo) +{ + const Timestamp *aa = (const Timestamp *) a; + const Timestamp *bb = (const Timestamp *) b; + Interval *i; + + if (TIMESTAMP_NOT_FINITE(*aa) || TIMESTAMP_NOT_FINITE(*bb)) + return get_float8_infinity(); + + i = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi, + TimestampGetDatumFast(*aa), + TimestampGetDatumFast(*bb))); + return fabs(INTERVAL_TO_SEC(i)); +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_ts, + sizeof(Timestamp), + 16, /* sizeof(gbtreekey16) */ + gbt_tsgt, + gbt_tsge, + gbt_tseq, + gbt_tsle, + gbt_tslt, + gbt_tskey_cmp, + gbt_ts_dist +}; + + +PG_FUNCTION_INFO_V1(ts_dist); +Datum +ts_dist(PG_FUNCTION_ARGS) +{ + Timestamp a = PG_GETARG_TIMESTAMP(0); + Timestamp b = PG_GETARG_TIMESTAMP(1); + Interval *r; + + if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b)) + { + Interval *p = palloc(sizeof(Interval)); + + p->day = INT_MAX; + p->month = INT_MAX; + p->time = PG_INT64_MAX; + PG_RETURN_INTERVAL_P(p); + } + else + r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + PG_RETURN_INTERVAL_P(abs_interval(r)); +} + +PG_FUNCTION_INFO_V1(tstz_dist); +Datum +tstz_dist(PG_FUNCTION_ARGS) +{ + TimestampTz a = PG_GETARG_TIMESTAMPTZ(0); + TimestampTz b = PG_GETARG_TIMESTAMPTZ(1); + Interval *r; + + if (TIMESTAMP_NOT_FINITE(a) || TIMESTAMP_NOT_FINITE(b)) + { + Interval *p = palloc(sizeof(Interval)); + + p->day = INT_MAX; + p->month = INT_MAX; + p->time = PG_INT64_MAX; + PG_RETURN_INTERVAL_P(p); + } + + r = DatumGetIntervalP(DirectFunctionCall2(timestamp_mi, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + PG_RETURN_INTERVAL_P(abs_interval(r)); +} + + +/************************************************** + * timestamp ops + **************************************************/ + + +static inline Timestamp +tstz_to_ts_gmt(TimestampTz ts) +{ + /* No timezone correction is needed, since GMT is offset 0 by definition */ + return (Timestamp) ts; +} + + +Datum +gbt_ts_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo)); +} + + +Datum +gbt_tstz_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; + + if (entry->leafkey) + { + tsKEY *r = (tsKEY *) palloc(sizeof(tsKEY)); + TimestampTz ts = DatumGetTimestampTz(entry->key); + Timestamp gmt; + + gmt = tstz_to_ts_gmt(ts); + + retval = palloc(sizeof(GISTENTRY)); + r->lower = r->upper = gmt; + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, + entry->offset, false); + } + else + retval = entry; + + PG_RETURN_POINTER(retval); +} + +Datum +gbt_ts_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_ts_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Timestamp query = PG_GETARG_TIMESTAMP(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + tsKEY *kkk = (tsKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_ts_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Timestamp query = PG_GETARG_TIMESTAMP(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + tsKEY *kkk = (tsKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &query, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_tstz_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + TimestampTz query = PG_GETARG_TIMESTAMPTZ(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + char *kkk = (char *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + Timestamp qqq; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk[0]; + key.upper = (GBT_NUMKEY *) &kkk[MAXALIGN(tinfo.size)]; + qqq = tstz_to_ts_gmt(query); + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &qqq, &strategy, + GIST_LEAF(entry), &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_tstz_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + TimestampTz query = PG_GETARG_TIMESTAMPTZ(1); + + /* Oid subtype = PG_GETARG_OID(3); */ + char *kkk = (char *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + Timestamp qqq; + + key.lower = (GBT_NUMKEY *) &kkk[0]; + key.upper = (GBT_NUMKEY *) &kkk[MAXALIGN(tinfo.size)]; + qqq = tstz_to_ts_gmt(query); + + PG_RETURN_FLOAT8(gbt_num_distance(&key, (void *) &qqq, GIST_LEAF(entry), + &tinfo, fcinfo->flinfo)); +} + + +Datum +gbt_ts_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(tsKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(tsKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + + +#define penalty_check_max_float(val) \ + do { \ + if ( val > FLT_MAX ) \ + val = FLT_MAX; \ + if ( val < -FLT_MAX ) \ + val = -FLT_MAX; \ + } while (0) + + +Datum +gbt_ts_penalty(PG_FUNCTION_ARGS) +{ + tsKEY *origentry = (tsKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + tsKEY *newentry = (tsKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + + double orgdbl[2], + newdbl[2]; + + /* + * We are always using "double" timestamps here. Precision should be good + * enough. + */ + orgdbl[0] = ((double) origentry->lower); + orgdbl[1] = ((double) origentry->upper); + newdbl[0] = ((double) newentry->lower); + newdbl[1] = ((double) newentry->upper); + + penalty_check_max_float(orgdbl[0]); + penalty_check_max_float(orgdbl[1]); + penalty_check_max_float(newdbl[0]); + penalty_check_max_float(newdbl[1]); + + penalty_num(result, orgdbl[0], orgdbl[1], newdbl[0], newdbl[1]); + + PG_RETURN_POINTER(result); +} + + +Datum +gbt_ts_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_ts_same(PG_FUNCTION_ARGS) +{ + tsKEY *b1 = (tsKEY *) PG_GETARG_POINTER(0); + tsKEY *b2 = (tsKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/btree_utils_num.c b/contrib/btree_gist/btree_utils_num.c new file mode 100644 index 0000000..346ee83 --- /dev/null +++ b/contrib/btree_gist/btree_utils_num.c @@ -0,0 +1,384 @@ +/* + * contrib/btree_gist/btree_utils_num.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "utils/cash.h" +#include "utils/date.h" +#include "utils/timestamp.h" + + +GISTENTRY * +gbt_num_compress(GISTENTRY *entry, const gbtree_ninfo *tinfo) +{ + GISTENTRY *retval; + + if (entry->leafkey) + { + union + { + bool bo; + int16 i2; + int32 i4; + int64 i8; + float4 f4; + float8 f8; + DateADT dt; + TimeADT tm; + Timestamp ts; + Cash ch; + } v; + + GBT_NUMKEY *r = (GBT_NUMKEY *) palloc0(tinfo->indexsize); + void *leaf = NULL; + + switch (tinfo->t) + { + case gbt_t_bool: + v.bo = DatumGetBool(entry->key); + leaf = &v.bo; + break; + case gbt_t_int2: + v.i2 = DatumGetInt16(entry->key); + leaf = &v.i2; + break; + case gbt_t_int4: + v.i4 = DatumGetInt32(entry->key); + leaf = &v.i4; + break; + case gbt_t_int8: + v.i8 = DatumGetInt64(entry->key); + leaf = &v.i8; + break; + case gbt_t_oid: + case gbt_t_enum: + v.i4 = DatumGetObjectId(entry->key); + leaf = &v.i4; + break; + case gbt_t_float4: + v.f4 = DatumGetFloat4(entry->key); + leaf = &v.f4; + break; + case gbt_t_float8: + v.f8 = DatumGetFloat8(entry->key); + leaf = &v.f8; + break; + case gbt_t_date: + v.dt = DatumGetDateADT(entry->key); + leaf = &v.dt; + break; + case gbt_t_time: + v.tm = DatumGetTimeADT(entry->key); + leaf = &v.tm; + break; + case gbt_t_ts: + v.ts = DatumGetTimestamp(entry->key); + leaf = &v.ts; + break; + case gbt_t_cash: + v.ch = DatumGetCash(entry->key); + leaf = &v.ch; + break; + default: + leaf = DatumGetPointer(entry->key); + } + + Assert(tinfo->indexsize >= 2 * tinfo->size); + + memcpy(&r[0], leaf, tinfo->size); + memcpy(&r[tinfo->size], leaf, tinfo->size); + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(r), entry->rel, entry->page, + entry->offset, false); + } + else + retval = entry; + + return retval; +} + +/* + * Convert a compressed leaf item back to the original type, for index-only + * scans. + */ +GISTENTRY * +gbt_num_fetch(GISTENTRY *entry, const gbtree_ninfo *tinfo) +{ + GISTENTRY *retval; + Datum datum; + + Assert(tinfo->indexsize >= 2 * tinfo->size); + + /* + * Get the original Datum from the stored datum. On leaf entries, the + * lower and upper bound are the same. We just grab the lower bound and + * return it. + */ + switch (tinfo->t) + { + case gbt_t_bool: + datum = BoolGetDatum(*(bool *) entry->key); + break; + case gbt_t_int2: + datum = Int16GetDatum(*(int16 *) entry->key); + break; + case gbt_t_int4: + datum = Int32GetDatum(*(int32 *) entry->key); + break; + case gbt_t_int8: + datum = Int64GetDatum(*(int64 *) entry->key); + break; + case gbt_t_oid: + case gbt_t_enum: + datum = ObjectIdGetDatum(*(Oid *) entry->key); + break; + case gbt_t_float4: + datum = Float4GetDatum(*(float4 *) entry->key); + break; + case gbt_t_float8: + datum = Float8GetDatum(*(float8 *) entry->key); + break; + case gbt_t_date: + datum = DateADTGetDatum(*(DateADT *) entry->key); + break; + case gbt_t_time: + datum = TimeADTGetDatum(*(TimeADT *) entry->key); + break; + case gbt_t_ts: + datum = TimestampGetDatum(*(Timestamp *) entry->key); + break; + case gbt_t_cash: + datum = CashGetDatum(*(Cash *) entry->key); + break; + default: + datum = entry->key; + } + + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, datum, entry->rel, entry->page, entry->offset, + false); + return retval; +} + + + +/* +** The GiST union method for numerical values +*/ + +void * +gbt_num_union(GBT_NUMKEY *out, const GistEntryVector *entryvec, const gbtree_ninfo *tinfo, FmgrInfo *flinfo) +{ + int i, + numranges; + GBT_NUMKEY *cur; + GBT_NUMKEY_R o, + c; + + numranges = entryvec->n; + cur = (GBT_NUMKEY *) DatumGetPointer((entryvec->vector[0].key)); + + + o.lower = &((GBT_NUMKEY *) out)[0]; + o.upper = &((GBT_NUMKEY *) out)[tinfo->size]; + + memcpy(out, cur, 2 * tinfo->size); + + for (i = 1; i < numranges; i++) + { + cur = (GBT_NUMKEY *) DatumGetPointer((entryvec->vector[i].key)); + c.lower = &cur[0]; + c.upper = &cur[tinfo->size]; + /* if out->lower > cur->lower, adopt cur as lower */ + if (tinfo->f_gt(o.lower, c.lower, flinfo)) + memcpy(unconstify(GBT_NUMKEY *, o.lower), c.lower, tinfo->size); + /* if out->upper < cur->upper, adopt cur as upper */ + if (tinfo->f_lt(o.upper, c.upper, flinfo)) + memcpy(unconstify(GBT_NUMKEY *, o.upper), c.upper, tinfo->size); + } + + return out; +} + + + +/* +** The GiST same method for numerical values +*/ + +bool +gbt_num_same(const GBT_NUMKEY *a, const GBT_NUMKEY *b, const gbtree_ninfo *tinfo, FmgrInfo *flinfo) +{ + GBT_NUMKEY_R b1, + b2; + + b1.lower = &(a[0]); + b1.upper = &(a[tinfo->size]); + b2.lower = &(b[0]); + b2.upper = &(b[tinfo->size]); + + return (tinfo->f_eq(b1.lower, b2.lower, flinfo) && + tinfo->f_eq(b1.upper, b2.upper, flinfo)); +} + + +void +gbt_num_bin_union(Datum *u, GBT_NUMKEY *e, const gbtree_ninfo *tinfo, FmgrInfo *flinfo) +{ + GBT_NUMKEY_R rd; + + rd.lower = &e[0]; + rd.upper = &e[tinfo->size]; + + if (!DatumGetPointer(*u)) + { + *u = PointerGetDatum(palloc0(tinfo->indexsize)); + memcpy(&(((GBT_NUMKEY *) DatumGetPointer(*u))[0]), rd.lower, tinfo->size); + memcpy(&(((GBT_NUMKEY *) DatumGetPointer(*u))[tinfo->size]), rd.upper, tinfo->size); + } + else + { + GBT_NUMKEY_R ur; + + ur.lower = &(((GBT_NUMKEY *) DatumGetPointer(*u))[0]); + ur.upper = &(((GBT_NUMKEY *) DatumGetPointer(*u))[tinfo->size]); + if (tinfo->f_gt(ur.lower, rd.lower, flinfo)) + memcpy(unconstify(GBT_NUMKEY *, ur.lower), rd.lower, tinfo->size); + if (tinfo->f_lt(ur.upper, rd.upper, flinfo)) + memcpy(unconstify(GBT_NUMKEY *, ur.upper), rd.upper, tinfo->size); + } +} + + + +/* + * The GiST consistent method + * + * Note: we currently assume that no datatypes that use this routine are + * collation-aware; so we don't bother passing collation through. + */ +bool +gbt_num_consistent(const GBT_NUMKEY_R *key, + const void *query, + const StrategyNumber *strategy, + bool is_leaf, + const gbtree_ninfo *tinfo, + FmgrInfo *flinfo) +{ + bool retval; + + switch (*strategy) + { + case BTLessEqualStrategyNumber: + retval = tinfo->f_ge(query, key->lower, flinfo); + break; + case BTLessStrategyNumber: + if (is_leaf) + retval = tinfo->f_gt(query, key->lower, flinfo); + else + retval = tinfo->f_ge(query, key->lower, flinfo); + break; + case BTEqualStrategyNumber: + if (is_leaf) + retval = tinfo->f_eq(query, key->lower, flinfo); + else + retval = (tinfo->f_le(key->lower, query, flinfo) && + tinfo->f_le(query, key->upper, flinfo)); + break; + case BTGreaterStrategyNumber: + if (is_leaf) + retval = tinfo->f_lt(query, key->upper, flinfo); + else + retval = tinfo->f_le(query, key->upper, flinfo); + break; + case BTGreaterEqualStrategyNumber: + retval = tinfo->f_le(query, key->upper, flinfo); + break; + case BtreeGistNotEqualStrategyNumber: + retval = (!(tinfo->f_eq(query, key->lower, flinfo) && + tinfo->f_eq(query, key->upper, flinfo))); + break; + default: + retval = false; + } + + return retval; +} + + +/* +** The GiST distance method (for KNN-Gist) +*/ + +float8 +gbt_num_distance(const GBT_NUMKEY_R *key, + const void *query, + bool is_leaf, + const gbtree_ninfo *tinfo, + FmgrInfo *flinfo) +{ + float8 retval; + + if (tinfo->f_dist == NULL) + elog(ERROR, "KNN search is not supported for btree_gist type %d", + (int) tinfo->t); + if (tinfo->f_le(query, key->lower, flinfo)) + retval = tinfo->f_dist(query, key->lower, flinfo); + else if (tinfo->f_ge(query, key->upper, flinfo)) + retval = tinfo->f_dist(query, key->upper, flinfo); + else + retval = 0.0; + + return retval; +} + + +GIST_SPLITVEC * +gbt_num_picksplit(const GistEntryVector *entryvec, GIST_SPLITVEC *v, + const gbtree_ninfo *tinfo, FmgrInfo *flinfo) +{ + OffsetNumber i, + maxoff = entryvec->n - 1; + Nsrt *arr; + int nbytes; + + arr = (Nsrt *) palloc((maxoff + 1) * sizeof(Nsrt)); + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + v->spl_ldatum = PointerGetDatum(0); + v->spl_rdatum = PointerGetDatum(0); + v->spl_nleft = 0; + v->spl_nright = 0; + + /* Sort entries */ + + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + arr[i].t = (GBT_NUMKEY *) DatumGetPointer((entryvec->vector[i].key)); + arr[i].i = i; + } + qsort_arg(&arr[FirstOffsetNumber], maxoff - FirstOffsetNumber + 1, sizeof(Nsrt), (qsort_arg_comparator) tinfo->f_cmp, flinfo); + + /* We do simply create two parts */ + + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + if (i <= (maxoff - FirstOffsetNumber + 1) / 2) + { + gbt_num_bin_union(&v->spl_ldatum, arr[i].t, tinfo, flinfo); + v->spl_left[v->spl_nleft] = arr[i].i; + v->spl_nleft++; + } + else + { + gbt_num_bin_union(&v->spl_rdatum, arr[i].t, tinfo, flinfo); + v->spl_right[v->spl_nright] = arr[i].i; + v->spl_nright++; + } + } + + return v; +} diff --git a/contrib/btree_gist/btree_utils_num.h b/contrib/btree_gist/btree_utils_num.h new file mode 100644 index 0000000..11e8923 --- /dev/null +++ b/contrib/btree_gist/btree_utils_num.h @@ -0,0 +1,118 @@ +/* + * contrib/btree_gist/btree_utils_num.h + */ +#ifndef __BTREE_UTILS_NUM_H__ +#define __BTREE_UTILS_NUM_H__ + +#include +#include + +#include "access/gist.h" +#include "btree_gist.h" +#include "utils/rel.h" + +typedef char GBT_NUMKEY; + +/* Better readable key */ +typedef struct +{ + const GBT_NUMKEY *lower, + *upper; +} GBT_NUMKEY_R; + + +/* for sorting */ +typedef struct +{ + int i; + GBT_NUMKEY *t; +} Nsrt; + + +/* type description */ + +typedef struct +{ + + /* Attribs */ + + enum gbtree_type t; /* data type */ + int32 size; /* size of type, 0 means variable */ + int32 indexsize; /* size of datums stored in index */ + + /* Methods */ + + bool (*f_gt) (const void *, const void *, FmgrInfo *); /* greater than */ + bool (*f_ge) (const void *, const void *, FmgrInfo *); /* greater or equal */ + bool (*f_eq) (const void *, const void *, FmgrInfo *); /* equal */ + bool (*f_le) (const void *, const void *, FmgrInfo *); /* less or equal */ + bool (*f_lt) (const void *, const void *, FmgrInfo *); /* less than */ + int (*f_cmp) (const void *, const void *, FmgrInfo *); /* key compare function */ + float8 (*f_dist) (const void *, const void *, FmgrInfo *); /* key distance function */ +} gbtree_ninfo; + + +/* + * Numeric btree functions + */ + + + +/* + * Note: The factor 0.49 in following macro avoids floating point overflows + */ +#define penalty_num(result,olower,oupper,nlower,nupper) do { \ + double tmp = 0.0F; \ + (*(result)) = 0.0F; \ + if ( (nupper) > (oupper) ) \ + tmp += ( ((double)nupper)*0.49F - ((double)oupper)*0.49F ); \ + if ( (olower) > (nlower) ) \ + tmp += ( ((double)olower)*0.49F - ((double)nlower)*0.49F ); \ + if (tmp > 0.0F) \ + { \ + (*(result)) += FLT_MIN; \ + (*(result)) += (float) ( ((double)(tmp)) / ( (double)(tmp) + ( ((double)(oupper))*0.49F - ((double)(olower))*0.49F ) ) ); \ + (*(result)) *= (FLT_MAX / (((GISTENTRY *) PG_GETARG_POINTER(0))->rel->rd_att->natts + 1)); \ + } \ +} while (0) + + +/* + * Convert an Interval to an approximate equivalent number of seconds + * (as a double). Here because we need it for time/timetz as well as + * interval. See interval_cmp_internal for comparison. + */ +#define INTERVAL_TO_SEC(ivp) \ + (((double) (ivp)->time) / ((double) USECS_PER_SEC) + \ + (ivp)->day * (24.0 * SECS_PER_HOUR) + \ + (ivp)->month * (30.0 * SECS_PER_DAY)) + +#define GET_FLOAT_DISTANCE(t, arg1, arg2) fabs( ((float8) *((const t *) (arg1))) - ((float8) *((const t *) (arg2))) ) + + +extern Interval *abs_interval(Interval *a); + +extern bool gbt_num_consistent(const GBT_NUMKEY_R *key, const void *query, + const StrategyNumber *strategy, bool is_leaf, + const gbtree_ninfo *tinfo, FmgrInfo *flinfo); + +extern float8 gbt_num_distance(const GBT_NUMKEY_R *key, const void *query, + bool is_leaf, const gbtree_ninfo *tinfo, FmgrInfo *flinfo); + +extern GIST_SPLITVEC *gbt_num_picksplit(const GistEntryVector *entryvec, GIST_SPLITVEC *v, + const gbtree_ninfo *tinfo, FmgrInfo *flinfo); + +extern GISTENTRY *gbt_num_compress(GISTENTRY *entry, const gbtree_ninfo *tinfo); + +extern GISTENTRY *gbt_num_fetch(GISTENTRY *entry, const gbtree_ninfo *tinfo); + +extern void *gbt_num_union(GBT_NUMKEY *out, const GistEntryVector *entryvec, + const gbtree_ninfo *tinfo, FmgrInfo *flinfo); + +extern bool gbt_num_same(const GBT_NUMKEY *a, const GBT_NUMKEY *b, + const gbtree_ninfo *tinfo, FmgrInfo *flinfo); + +extern void gbt_num_bin_union(Datum *u, GBT_NUMKEY *e, + const gbtree_ninfo *tinfo, FmgrInfo *flinfo); + +#endif diff --git a/contrib/btree_gist/btree_utils_var.c b/contrib/btree_gist/btree_utils_var.c new file mode 100644 index 0000000..0c0e952 --- /dev/null +++ b/contrib/btree_gist/btree_utils_var.c @@ -0,0 +1,612 @@ +/* + * contrib/btree_gist/btree_utils_var.c + */ +#include "postgres.h" + +#include +#include +#include + +#include "btree_gist.h" +#include "btree_utils_var.h" +#include "utils/builtins.h" +#include "utils/pg_locale.h" +#include "utils/rel.h" + +/* used for key sorting */ +typedef struct +{ + int i; + GBT_VARKEY *t; +} Vsrt; + +typedef struct +{ + const gbtree_vinfo *tinfo; + Oid collation; + FmgrInfo *flinfo; +} gbt_vsrt_arg; + + +PG_FUNCTION_INFO_V1(gbt_var_decompress); +PG_FUNCTION_INFO_V1(gbt_var_fetch); + + +Datum +gbt_var_decompress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GBT_VARKEY *key = (GBT_VARKEY *) PG_DETOAST_DATUM(entry->key); + + if (key != (GBT_VARKEY *) DatumGetPointer(entry->key)) + { + GISTENTRY *retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + + gistentryinit(*retval, PointerGetDatum(key), + entry->rel, entry->page, + entry->offset, false); + + PG_RETURN_POINTER(retval); + } + + PG_RETURN_POINTER(entry); +} + +/* Returns a better readable representation of variable key ( sets pointer ) */ +GBT_VARKEY_R +gbt_var_key_readable(const GBT_VARKEY *k) +{ + GBT_VARKEY_R r; + + r.lower = (bytea *) &(((char *) k)[VARHDRSZ]); + if (VARSIZE(k) > (VARHDRSZ + (VARSIZE(r.lower)))) + r.upper = (bytea *) &(((char *) k)[VARHDRSZ + INTALIGN(VARSIZE(r.lower))]); + else + r.upper = r.lower; + return r; +} + + +/* + * Create a leaf-entry to store in the index, from a single Datum. + */ +static GBT_VARKEY * +gbt_var_key_from_datum(const struct varlena *u) +{ + int32 lowersize = VARSIZE(u); + GBT_VARKEY *r; + + r = (GBT_VARKEY *) palloc(lowersize + VARHDRSZ); + memcpy(VARDATA(r), u, lowersize); + SET_VARSIZE(r, lowersize + VARHDRSZ); + + return r; +} + +/* + * Create an entry to store in the index, from lower and upper bound. + */ +GBT_VARKEY * +gbt_var_key_copy(const GBT_VARKEY_R *u) +{ + int32 lowersize = VARSIZE(u->lower); + int32 uppersize = VARSIZE(u->upper); + GBT_VARKEY *r; + + r = (GBT_VARKEY *) palloc0(INTALIGN(lowersize) + uppersize + VARHDRSZ); + memcpy(VARDATA(r), u->lower, lowersize); + memcpy(VARDATA(r) + INTALIGN(lowersize), u->upper, uppersize); + SET_VARSIZE(r, INTALIGN(lowersize) + uppersize + VARHDRSZ); + + return r; +} + + +static GBT_VARKEY * +gbt_var_leaf2node(GBT_VARKEY *leaf, const gbtree_vinfo *tinfo, FmgrInfo *flinfo) +{ + GBT_VARKEY *out = leaf; + + if (tinfo->f_l2n) + out = tinfo->f_l2n(leaf, flinfo); + + return out; +} + + +/* + * returns the common prefix length of a node key +*/ +static int32 +gbt_var_node_cp_len(const GBT_VARKEY *node, const gbtree_vinfo *tinfo) +{ + GBT_VARKEY_R r = gbt_var_key_readable(node); + int32 i = 0; + int32 l = 0; + int32 t1len = VARSIZE(r.lower) - VARHDRSZ; + int32 t2len = VARSIZE(r.upper) - VARHDRSZ; + int32 ml = Min(t1len, t2len); + char *p1 = VARDATA(r.lower); + char *p2 = VARDATA(r.upper); + + if (ml == 0) + return 0; + + while (i < ml) + { + if (tinfo->eml > 1 && l == 0) + { + if ((l = pg_mblen(p1)) != pg_mblen(p2)) + { + return i; + } + } + if (*p1 != *p2) + { + if (tinfo->eml > 1) + { + return (i - l + 1); + } + else + { + return i; + } + } + + p1++; + p2++; + l--; + i++; + } + return ml; /* lower == upper */ +} + + +/* + * returns true, if query matches prefix ( common prefix ) + */ +static bool +gbt_bytea_pf_match(const bytea *pf, const bytea *query, const gbtree_vinfo *tinfo) +{ + bool out = false; + int32 qlen = VARSIZE(query) - VARHDRSZ; + int32 nlen = VARSIZE(pf) - VARHDRSZ; + + if (nlen <= qlen) + { + char *q = VARDATA(query); + char *n = VARDATA(pf); + + out = (memcmp(q, n, nlen) == 0); + } + + return out; +} + + +/* + * returns true, if query matches node using common prefix + */ +static bool +gbt_var_node_pf_match(const GBT_VARKEY_R *node, const bytea *query, const gbtree_vinfo *tinfo) +{ + return (tinfo->trnc && + (gbt_bytea_pf_match(node->lower, query, tinfo) || + gbt_bytea_pf_match(node->upper, query, tinfo))); +} + + +/* +* truncates / compresses the node key +* cpf_length .. common prefix length +*/ +static GBT_VARKEY * +gbt_var_node_truncate(const GBT_VARKEY *node, int32 cpf_length, const gbtree_vinfo *tinfo) +{ + GBT_VARKEY *out = NULL; + GBT_VARKEY_R r = gbt_var_key_readable(node); + int32 len1 = VARSIZE(r.lower) - VARHDRSZ; + int32 len2 = VARSIZE(r.upper) - VARHDRSZ; + int32 si; + char *out2; + + len1 = Min(len1, (cpf_length + 1)); + len2 = Min(len2, (cpf_length + 1)); + + si = 2 * VARHDRSZ + INTALIGN(len1 + VARHDRSZ) + len2; + out = (GBT_VARKEY *) palloc0(si); + SET_VARSIZE(out, si); + + memcpy(VARDATA(out), r.lower, len1 + VARHDRSZ); + SET_VARSIZE(VARDATA(out), len1 + VARHDRSZ); + + out2 = VARDATA(out) + INTALIGN(len1 + VARHDRSZ); + memcpy(out2, r.upper, len2 + VARHDRSZ); + SET_VARSIZE(out2, len2 + VARHDRSZ); + + return out; +} + + + +void +gbt_var_bin_union(Datum *u, GBT_VARKEY *e, Oid collation, + const gbtree_vinfo *tinfo, FmgrInfo *flinfo) +{ + GBT_VARKEY_R eo = gbt_var_key_readable(e); + GBT_VARKEY_R nr; + + if (eo.lower == eo.upper) /* leaf */ + { + GBT_VARKEY *tmp; + + tmp = gbt_var_leaf2node(e, tinfo, flinfo); + if (tmp != e) + eo = gbt_var_key_readable(tmp); + } + + if (DatumGetPointer(*u)) + { + GBT_VARKEY_R ro = gbt_var_key_readable((GBT_VARKEY *) DatumGetPointer(*u)); + bool update = false; + + nr.lower = ro.lower; + nr.upper = ro.upper; + + if (tinfo->f_cmp(ro.lower, eo.lower, collation, flinfo) > 0) + { + nr.lower = eo.lower; + update = true; + } + + if (tinfo->f_cmp(ro.upper, eo.upper, collation, flinfo) < 0) + { + nr.upper = eo.upper; + update = true; + } + + if (update) + *u = PointerGetDatum(gbt_var_key_copy(&nr)); + } + else + { + nr.lower = eo.lower; + nr.upper = eo.upper; + *u = PointerGetDatum(gbt_var_key_copy(&nr)); + } +} + + +GISTENTRY * +gbt_var_compress(GISTENTRY *entry, const gbtree_vinfo *tinfo) +{ + GISTENTRY *retval; + + if (entry->leafkey) + { + struct varlena *leaf = PG_DETOAST_DATUM(entry->key); + GBT_VARKEY *r; + + r = gbt_var_key_from_datum(leaf); + + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, + entry->offset, true); + } + else + retval = entry; + + return retval; +} + + +Datum +gbt_var_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GBT_VARKEY *key = (GBT_VARKEY *) PG_DETOAST_DATUM(entry->key); + GBT_VARKEY_R r = gbt_var_key_readable(key); + GISTENTRY *retval; + + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(r.lower), + entry->rel, entry->page, + entry->offset, true); + + PG_RETURN_POINTER(retval); +} + + +GBT_VARKEY * +gbt_var_union(const GistEntryVector *entryvec, int32 *size, Oid collation, + const gbtree_vinfo *tinfo, FmgrInfo *flinfo) +{ + int i = 0, + numranges = entryvec->n; + GBT_VARKEY *cur; + Datum out; + GBT_VARKEY_R rk; + + *size = sizeof(GBT_VARKEY); + + cur = (GBT_VARKEY *) DatumGetPointer(entryvec->vector[0].key); + rk = gbt_var_key_readable(cur); + out = PointerGetDatum(gbt_var_key_copy(&rk)); + + for (i = 1; i < numranges; i++) + { + cur = (GBT_VARKEY *) DatumGetPointer(entryvec->vector[i].key); + gbt_var_bin_union(&out, cur, collation, tinfo, flinfo); + } + + + /* Truncate (=compress) key */ + if (tinfo->trnc) + { + int32 plen; + GBT_VARKEY *trc = NULL; + + plen = gbt_var_node_cp_len((GBT_VARKEY *) DatumGetPointer(out), tinfo); + trc = gbt_var_node_truncate((GBT_VARKEY *) DatumGetPointer(out), plen + 1, tinfo); + + out = PointerGetDatum(trc); + } + + return ((GBT_VARKEY *) DatumGetPointer(out)); +} + + +bool +gbt_var_same(Datum d1, Datum d2, Oid collation, + const gbtree_vinfo *tinfo, FmgrInfo *flinfo) +{ + GBT_VARKEY *t1 = (GBT_VARKEY *) DatumGetPointer(d1); + GBT_VARKEY *t2 = (GBT_VARKEY *) DatumGetPointer(d2); + GBT_VARKEY_R r1, + r2; + + r1 = gbt_var_key_readable(t1); + r2 = gbt_var_key_readable(t2); + + return (tinfo->f_cmp(r1.lower, r2.lower, collation, flinfo) == 0 && + tinfo->f_cmp(r1.upper, r2.upper, collation, flinfo) == 0); +} + + +float * +gbt_var_penalty(float *res, const GISTENTRY *o, const GISTENTRY *n, + Oid collation, const gbtree_vinfo *tinfo, FmgrInfo *flinfo) +{ + GBT_VARKEY *orge = (GBT_VARKEY *) DatumGetPointer(o->key); + GBT_VARKEY *newe = (GBT_VARKEY *) DatumGetPointer(n->key); + GBT_VARKEY_R ok, + nk; + + *res = 0.0; + + nk = gbt_var_key_readable(newe); + if (nk.lower == nk.upper) /* leaf */ + { + GBT_VARKEY *tmp; + + tmp = gbt_var_leaf2node(newe, tinfo, flinfo); + if (tmp != newe) + nk = gbt_var_key_readable(tmp); + } + ok = gbt_var_key_readable(orge); + + if ((VARSIZE(ok.lower) - VARHDRSZ) == 0 && (VARSIZE(ok.upper) - VARHDRSZ) == 0) + *res = 0.0; + else if (!((tinfo->f_cmp(nk.lower, ok.lower, collation, flinfo) >= 0 || + gbt_bytea_pf_match(ok.lower, nk.lower, tinfo)) && + (tinfo->f_cmp(nk.upper, ok.upper, collation, flinfo) <= 0 || + gbt_bytea_pf_match(ok.upper, nk.upper, tinfo)))) + { + Datum d = PointerGetDatum(0); + double dres; + int32 ol, + ul; + + gbt_var_bin_union(&d, orge, collation, tinfo, flinfo); + ol = gbt_var_node_cp_len((GBT_VARKEY *) DatumGetPointer(d), tinfo); + gbt_var_bin_union(&d, newe, collation, tinfo, flinfo); + ul = gbt_var_node_cp_len((GBT_VARKEY *) DatumGetPointer(d), tinfo); + + if (ul < ol) + { + dres = (ol - ul); /* reduction of common prefix len */ + } + else + { + GBT_VARKEY_R uk = gbt_var_key_readable((GBT_VARKEY *) DatumGetPointer(d)); + unsigned char tmp[4]; + + tmp[0] = (unsigned char) (((VARSIZE(ok.lower) - VARHDRSZ) <= ul) ? 0 : (VARDATA(ok.lower)[ul])); + tmp[1] = (unsigned char) (((VARSIZE(uk.lower) - VARHDRSZ) <= ul) ? 0 : (VARDATA(uk.lower)[ul])); + tmp[2] = (unsigned char) (((VARSIZE(ok.upper) - VARHDRSZ) <= ul) ? 0 : (VARDATA(ok.upper)[ul])); + tmp[3] = (unsigned char) (((VARSIZE(uk.upper) - VARHDRSZ) <= ul) ? 0 : (VARDATA(uk.upper)[ul])); + dres = abs(tmp[0] - tmp[1]) + abs(tmp[3] - tmp[2]); + dres /= 256.0; + } + + *res += FLT_MIN; + *res += (float) (dres / ((double) (ol + 1))); + *res *= (FLT_MAX / (o->rel->rd_att->natts + 1)); + } + + return res; +} + + +static int +gbt_vsrt_cmp(const void *a, const void *b, void *arg) +{ + GBT_VARKEY_R ar = gbt_var_key_readable(((const Vsrt *) a)->t); + GBT_VARKEY_R br = gbt_var_key_readable(((const Vsrt *) b)->t); + const gbt_vsrt_arg *varg = (const gbt_vsrt_arg *) arg; + int res; + + res = varg->tinfo->f_cmp(ar.lower, br.lower, varg->collation, varg->flinfo); + if (res == 0) + return varg->tinfo->f_cmp(ar.upper, br.upper, varg->collation, varg->flinfo); + + return res; +} + +GIST_SPLITVEC * +gbt_var_picksplit(const GistEntryVector *entryvec, GIST_SPLITVEC *v, + Oid collation, const gbtree_vinfo *tinfo, FmgrInfo *flinfo) +{ + OffsetNumber i, + maxoff = entryvec->n - 1; + Vsrt *arr; + int svcntr = 0, + nbytes; + char *cur; + GBT_VARKEY **sv = NULL; + gbt_vsrt_arg varg; + + arr = (Vsrt *) palloc((maxoff + 1) * sizeof(Vsrt)); + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + v->spl_ldatum = PointerGetDatum(0); + v->spl_rdatum = PointerGetDatum(0); + v->spl_nleft = 0; + v->spl_nright = 0; + + sv = palloc(sizeof(bytea *) * (maxoff + 1)); + + /* Sort entries */ + + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + GBT_VARKEY_R ro; + + cur = (char *) DatumGetPointer(entryvec->vector[i].key); + ro = gbt_var_key_readable((GBT_VARKEY *) cur); + if (ro.lower == ro.upper) /* leaf */ + { + sv[svcntr] = gbt_var_leaf2node((GBT_VARKEY *) cur, tinfo, flinfo); + arr[i].t = sv[svcntr]; + if (sv[svcntr] != (GBT_VARKEY *) cur) + svcntr++; + } + else + arr[i].t = (GBT_VARKEY *) cur; + arr[i].i = i; + } + + /* sort */ + varg.tinfo = tinfo; + varg.collation = collation; + varg.flinfo = flinfo; + qsort_arg(&arr[FirstOffsetNumber], + maxoff - FirstOffsetNumber + 1, + sizeof(Vsrt), + gbt_vsrt_cmp, + &varg); + + /* We do simply create two parts */ + + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + if (i <= (maxoff - FirstOffsetNumber + 1) / 2) + { + gbt_var_bin_union(&v->spl_ldatum, arr[i].t, collation, tinfo, flinfo); + v->spl_left[v->spl_nleft] = arr[i].i; + v->spl_nleft++; + } + else + { + gbt_var_bin_union(&v->spl_rdatum, arr[i].t, collation, tinfo, flinfo); + v->spl_right[v->spl_nright] = arr[i].i; + v->spl_nright++; + } + } + + /* Truncate (=compress) key */ + if (tinfo->trnc) + { + int32 ll = gbt_var_node_cp_len((GBT_VARKEY *) DatumGetPointer(v->spl_ldatum), tinfo); + int32 lr = gbt_var_node_cp_len((GBT_VARKEY *) DatumGetPointer(v->spl_rdatum), tinfo); + GBT_VARKEY *dl; + GBT_VARKEY *dr; + + ll = Max(ll, lr); + ll++; + + dl = gbt_var_node_truncate((GBT_VARKEY *) DatumGetPointer(v->spl_ldatum), ll, tinfo); + dr = gbt_var_node_truncate((GBT_VARKEY *) DatumGetPointer(v->spl_rdatum), ll, tinfo); + v->spl_ldatum = PointerGetDatum(dl); + v->spl_rdatum = PointerGetDatum(dr); + } + + return v; +} + + +/* + * The GiST consistent method + */ +bool +gbt_var_consistent(GBT_VARKEY_R *key, + const void *query, + StrategyNumber strategy, + Oid collation, + bool is_leaf, + const gbtree_vinfo *tinfo, + FmgrInfo *flinfo) +{ + bool retval = false; + + switch (strategy) + { + case BTLessEqualStrategyNumber: + if (is_leaf) + retval = tinfo->f_ge(query, key->lower, collation, flinfo); + else + retval = tinfo->f_cmp(query, key->lower, collation, flinfo) >= 0 + || gbt_var_node_pf_match(key, query, tinfo); + break; + case BTLessStrategyNumber: + if (is_leaf) + retval = tinfo->f_gt(query, key->lower, collation, flinfo); + else + retval = tinfo->f_cmp(query, key->lower, collation, flinfo) >= 0 + || gbt_var_node_pf_match(key, query, tinfo); + break; + case BTEqualStrategyNumber: + if (is_leaf) + retval = tinfo->f_eq(query, key->lower, collation, flinfo); + else + retval = + (tinfo->f_cmp(key->lower, query, collation, flinfo) <= 0 && + tinfo->f_cmp(query, key->upper, collation, flinfo) <= 0) || + gbt_var_node_pf_match(key, query, tinfo); + break; + case BTGreaterStrategyNumber: + if (is_leaf) + retval = tinfo->f_lt(query, key->upper, collation, flinfo); + else + retval = tinfo->f_cmp(query, key->upper, collation, flinfo) <= 0 + || gbt_var_node_pf_match(key, query, tinfo); + break; + case BTGreaterEqualStrategyNumber: + if (is_leaf) + retval = tinfo->f_le(query, key->upper, collation, flinfo); + else + retval = tinfo->f_cmp(query, key->upper, collation, flinfo) <= 0 + || gbt_var_node_pf_match(key, query, tinfo); + break; + case BtreeGistNotEqualStrategyNumber: + retval = !(tinfo->f_eq(query, key->lower, collation, flinfo) && + tinfo->f_eq(query, key->upper, collation, flinfo)); + break; + default: + retval = false; + } + + return retval; +} diff --git a/contrib/btree_gist/btree_utils_var.h b/contrib/btree_gist/btree_utils_var.h new file mode 100644 index 0000000..2f8def6 --- /dev/null +++ b/contrib/btree_gist/btree_utils_var.h @@ -0,0 +1,72 @@ +/* + * contrib/btree_gist/btree_utils_var.h + */ +#ifndef __BTREE_UTILS_VAR_H__ +#define __BTREE_UTILS_VAR_H__ + +#include "access/gist.h" +#include "btree_gist.h" +#include "mb/pg_wchar.h" + +/* Variable length key */ +typedef bytea GBT_VARKEY; + +/* Better readable key */ +typedef struct +{ + bytea *lower, + *upper; +} GBT_VARKEY_R; + +/* + * type description + */ +typedef struct +{ + + /* Attribs */ + + enum gbtree_type t; /* data type */ + int32 eml; /* cached pg_database_encoding_max_length (0: + * undefined) */ + bool trnc; /* truncate (=compress) key */ + + /* Methods */ + + bool (*f_gt) (const void *, const void *, Oid, FmgrInfo *); /* greater than */ + bool (*f_ge) (const void *, const void *, Oid, FmgrInfo *); /* greater equal */ + bool (*f_eq) (const void *, const void *, Oid, FmgrInfo *); /* equal */ + bool (*f_le) (const void *, const void *, Oid, FmgrInfo *); /* less equal */ + bool (*f_lt) (const void *, const void *, Oid, FmgrInfo *); /* less than */ + int32 (*f_cmp) (const void *, const void *, Oid, FmgrInfo *); /* compare */ + GBT_VARKEY *(*f_l2n) (GBT_VARKEY *, FmgrInfo *flinfo); /* convert leaf to node */ +} gbtree_vinfo; + + + +extern GBT_VARKEY_R gbt_var_key_readable(const GBT_VARKEY *k); + +extern GBT_VARKEY *gbt_var_key_copy(const GBT_VARKEY_R *u); + +extern GISTENTRY *gbt_var_compress(GISTENTRY *entry, const gbtree_vinfo *tinfo); + +extern GBT_VARKEY *gbt_var_union(const GistEntryVector *entryvec, int32 *size, + Oid collation, const gbtree_vinfo *tinfo, FmgrInfo *flinfo); + +extern bool gbt_var_same(Datum d1, Datum d2, Oid collation, + const gbtree_vinfo *tinfo, FmgrInfo *flinfo); + +extern float *gbt_var_penalty(float *res, const GISTENTRY *o, const GISTENTRY *n, + Oid collation, const gbtree_vinfo *tinfo, FmgrInfo *flinfo); + +extern bool gbt_var_consistent(GBT_VARKEY_R *key, const void *query, + StrategyNumber strategy, Oid collation, bool is_leaf, + const gbtree_vinfo *tinfo, FmgrInfo *flinfo); + +extern GIST_SPLITVEC *gbt_var_picksplit(const GistEntryVector *entryvec, GIST_SPLITVEC *v, + Oid collation, const gbtree_vinfo *tinfo, FmgrInfo *flinfo); + +extern void gbt_var_bin_union(Datum *u, GBT_VARKEY *e, Oid collation, + const gbtree_vinfo *tinfo, FmgrInfo *flinfo); + +#endif diff --git a/contrib/btree_gist/btree_uuid.c b/contrib/btree_gist/btree_uuid.c new file mode 100644 index 0000000..fe8c679 --- /dev/null +++ b/contrib/btree_gist/btree_uuid.c @@ -0,0 +1,235 @@ +/* + * contrib/btree_gist/btree_uuid.c + */ +#include "postgres.h" + +#include "btree_gist.h" +#include "btree_utils_num.h" +#include "port/pg_bswap.h" +#include "utils/uuid.h" + +typedef struct +{ + pg_uuid_t lower, + upper; +} uuidKEY; + + +/* + * UUID ops + */ +PG_FUNCTION_INFO_V1(gbt_uuid_compress); +PG_FUNCTION_INFO_V1(gbt_uuid_fetch); +PG_FUNCTION_INFO_V1(gbt_uuid_union); +PG_FUNCTION_INFO_V1(gbt_uuid_picksplit); +PG_FUNCTION_INFO_V1(gbt_uuid_consistent); +PG_FUNCTION_INFO_V1(gbt_uuid_penalty); +PG_FUNCTION_INFO_V1(gbt_uuid_same); + + +static int +uuid_internal_cmp(const pg_uuid_t *arg1, const pg_uuid_t *arg2) +{ + return memcmp(arg1->data, arg2->data, UUID_LEN); +} + +static bool +gbt_uuidgt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return uuid_internal_cmp((const pg_uuid_t *) a, (const pg_uuid_t *) b) > 0; +} + +static bool +gbt_uuidge(const void *a, const void *b, FmgrInfo *flinfo) +{ + return uuid_internal_cmp((const pg_uuid_t *) a, (const pg_uuid_t *) b) >= 0; +} + +static bool +gbt_uuideq(const void *a, const void *b, FmgrInfo *flinfo) +{ + return uuid_internal_cmp((const pg_uuid_t *) a, (const pg_uuid_t *) b) == 0; +} + +static bool +gbt_uuidle(const void *a, const void *b, FmgrInfo *flinfo) +{ + return uuid_internal_cmp((const pg_uuid_t *) a, (const pg_uuid_t *) b) <= 0; +} + +static bool +gbt_uuidlt(const void *a, const void *b, FmgrInfo *flinfo) +{ + return uuid_internal_cmp((const pg_uuid_t *) a, (const pg_uuid_t *) b) < 0; +} + +static int +gbt_uuidkey_cmp(const void *a, const void *b, FmgrInfo *flinfo) +{ + uuidKEY *ia = (uuidKEY *) (((const Nsrt *) a)->t); + uuidKEY *ib = (uuidKEY *) (((const Nsrt *) b)->t); + int res; + + res = uuid_internal_cmp(&ia->lower, &ib->lower); + if (res == 0) + res = uuid_internal_cmp(&ia->upper, &ib->upper); + return res; +} + + +static const gbtree_ninfo tinfo = +{ + gbt_t_uuid, + UUID_LEN, + 32, /* sizeof(gbtreekey32) */ + gbt_uuidgt, + gbt_uuidge, + gbt_uuideq, + gbt_uuidle, + gbt_uuidlt, + gbt_uuidkey_cmp, + NULL +}; + + +/************************************************** + * uuid ops + **************************************************/ + + +Datum +gbt_uuid_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; + + if (entry->leafkey) + { + char *r = (char *) palloc(2 * UUID_LEN); + pg_uuid_t *key = DatumGetUUIDP(entry->key); + + retval = palloc(sizeof(GISTENTRY)); + + memcpy(r, key, UUID_LEN); + memcpy(r + UUID_LEN, key, UUID_LEN); + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, + entry->offset, false); + } + else + retval = entry; + + PG_RETURN_POINTER(retval); +} + +Datum +gbt_uuid_fetch(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + + PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo)); +} + +Datum +gbt_uuid_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + pg_uuid_t *query = PG_GETARG_UUID_P(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + uuidKEY *kkk = (uuidKEY *) DatumGetPointer(entry->key); + GBT_NUMKEY_R key; + + /* All cases served by this function are exact */ + *recheck = false; + + key.lower = (GBT_NUMKEY *) &kkk->lower; + key.upper = (GBT_NUMKEY *) &kkk->upper; + + PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) query, &strategy, + GIST_LEAF(entry), &tinfo, + fcinfo->flinfo)); +} + +Datum +gbt_uuid_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + void *out = palloc(sizeof(uuidKEY)); + + *(int *) PG_GETARG_POINTER(1) = sizeof(uuidKEY); + PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo)); +} + +/* + * Convert a uuid to a "double" value for estimating sizes of ranges. + */ +static double +uuid_2_double(const pg_uuid_t *u) +{ + uint64 uu[2]; + const double two64 = 18446744073709551616.0; /* 2^64 */ + + /* Source data may not be suitably aligned, so copy */ + memcpy(uu, u->data, UUID_LEN); + + /* + * uuid values should be considered as big-endian numbers, since that + * corresponds to how memcmp will compare them. On a little-endian + * machine, byte-swap each half so we can use native uint64 arithmetic. + */ +#ifndef WORDS_BIGENDIAN + uu[0] = pg_bswap64(uu[0]); + uu[1] = pg_bswap64(uu[1]); +#endif + + /* + * 2^128 is about 3.4e38, which in theory could exceed the range of + * "double" (POSIX only requires 1e37). To avoid any risk of overflow, + * put the decimal point between the two halves rather than treating the + * uuid value as a 128-bit integer. + */ + return (double) uu[0] + (double) uu[1] / two64; +} + +Datum +gbt_uuid_penalty(PG_FUNCTION_ARGS) +{ + uuidKEY *origentry = (uuidKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + uuidKEY *newentry = (uuidKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *result = (float *) PG_GETARG_POINTER(2); + double olower, + oupper, + nlower, + nupper; + + olower = uuid_2_double(&origentry->lower); + oupper = uuid_2_double(&origentry->upper); + nlower = uuid_2_double(&newentry->lower); + nupper = uuid_2_double(&newentry->upper); + + penalty_num(result, olower, oupper, nlower, nupper); + + PG_RETURN_POINTER(result); +} + +Datum +gbt_uuid_picksplit(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0), + (GIST_SPLITVEC *) PG_GETARG_POINTER(1), + &tinfo, fcinfo->flinfo)); +} + +Datum +gbt_uuid_same(PG_FUNCTION_ARGS) +{ + uuidKEY *b1 = (uuidKEY *) PG_GETARG_POINTER(0); + uuidKEY *b2 = (uuidKEY *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo); + PG_RETURN_POINTER(result); +} diff --git a/contrib/btree_gist/data/bit.data b/contrib/btree_gist/data/bit.data new file mode 100644 index 0000000..0d07719 --- /dev/null +++ b/contrib/btree_gist/data/bit.data @@ -0,0 +1,612 @@ +010000100100001100101100011110110 +010110001111011000001101000000101 +101100011110110000011010000001010 +001101100101000001110001000001101 +000110100111111111101001011111010 +001100000100111101000011111100000 +001100010100010001010010000011010 +000101001000001101000111100100010 +110110101100010001000101001111000 +110110110110100010000110100000011 +110101011101101111110111110010101 +010001111000000110010101010011100 +111010010111110101101100110011010 +010000100100001100101100011110110 +010101001101100101000001110001000 +110111101011110001111110011001011 +110001011110101000011101101011100 +011001011010100000000100000111100 +101000011101100110000111010100111 +011000111000011011110101010001110 +000111001110101000110000011000000 +110101000010001101010111011000001 +010100100000010110010011011111111 +110000110011011010111101010001100 +101010100110000010011110100001111 +\N +100000111100001111011001001011101 +111100100011001110101000010010111 +011010001111111111011100001011110 +110010001100111010100001001011101 +110110111111010010100111110010001 +101110011110000110000000000110110 +100100110000001101010111100111010 +001001010111100011101101100001100 +010000001100010100010001010010000 +111010000100100110101011011101010 +110100001000001100111000111101100 +111011001111111001111000111010110 +111111100000010110100111101011011 +011011111111101110110011111110011 +110100001000001100111000111101100 +001011101101010100101010010010001 +101010011100100011011000001101011 +000000110100000111011101010011101 +110100101111101011011001100110100 +011111010110110011001101000000110 +101010001100000110000000111000101 +101111111011001001001000000000011 +011000101100111000010011000100001 +000110101011110011101000000110001 +000111110101111010110000111011011 +100010001100101110011000110010110 +010000011010001111001000101000001 +111010001111010110101111010110111 +100110011011011100001001001010101 +110010010110011101110111101011110 +110010101101000011111111101110100 +011101011111111001100010010000011 +010100010011011011001100011100001 +110110111111010010100111110010001 +000011101110111100110111100111011 +101011110101101111111110111011001 +001100010001110100010010100101101 +111101011011001100110100000011000 +000000100000101101010100000110000 +101010110100010000001111010010010 +010111010011000101010110111000011 +100101101101101111001100111010000 +000001100110111000101101100001110 +\N +110110111101010000011111100010000 +100010001010010000011010001111001 +110010100010110100100110000111010 +101101101101111001100111010000100 +010111101011000011101101110001011 +100000000000010101110011011100010 +100001101000010101111101001010100 +101000110001011001110000100110001 +110011011111011100010000010100011 +010011011100110000100001001100111 +001100010101110001110101111001001 +001110000010000001100011000110001 +000110001100011010111111101011100 +100010001111110100101100110010110 +000001001100110110111000010010010 +111101100100101110101100111001100 +010101011101100001111111110011011 +001011111111001100101101110101001 +110111111111111110111000010011001 +010101111001101011011111011111000 +011110111100101001000111111001100 +010111001001111010011000101000011 +010001110111111100000011100100000 +100001111111001110011001110010101 +011000010110001010011011011101111 +000000001010111001101110001011010 +100011100000001111011010011010110 +000011100100110111000001010001111 +100101010111110110000011001010101 +110000101100110010000010011001011 +110010000001011101011110110011011 +100010111100001111111001110011001 +111111101111101000000100000011111 +110100101011110110101110001101011 +011110111111010101011000001000111 +011111111110011001110011101100000 +101101000110001011001110000100110 +010101001001011011110110100010100 +011111111100000010110100111101011 +011000111101111110011000000010011 +011000010001000000000110101011001 +011011000100010111011000110000100 +100101100011111101110100011101000 +011100000011111110111111010011010 +010010111001111100010110011011111 +001100101111111101111111111111101 +111111111010000010101110100011011 +111010000110101001111001000101100 +100101100000000000010101110011011 +010001011000010010101100010001100 +010010011110010010111001100101111 +111010011101110110101110011110000 +000011100111010011100001111110110 +101110010010010010101100001001000 +100100011001010000011100100011101 +010111110001111111100101000100100 +110100000100101001110000111100011 +011111000111001100101111111101011 +110010000111111011111001001110111 +010111001111000011000000000011011 +110000110101111010101010110111000 +000111101001100011111110011000011 +000001001010011100001111000110000 +010010001101001110010001111111000 +010111101011000011101101110001011 +\N +001001111001011100001010100101110 +110001110011100000000010000001001 +101111111111110101101111000110010 +011101111110011101110000001001111 +011010110001101010110010110101001 +101100110001100101011011001001111 +000000010010010111110001010001110 +110101110100110000100010011000100 +100101000100111110111001101101111 +101110000111100000110110101111001 +011111111001010001001001001001111 +111110011001011110111111010101011 +011100100000111101110001101000101 +011100100011001100011001010100000 +001010110101101100000100001101011 +101011000101101010000110110100000 +010000000011011000111010010001011 +001110100100010110110010010000001 +110111001101011111110000101100110 +101110001010111110110111111101011 +001111001010011010101101110011101 +000001001100000110100001111011001 +111011101010011101001100011011010 +101111000101101101101100100100011 +011011110011101101000111111111101 +000011101101101000001000111001111 +001101001110010001111111000110101 +101001010100000100011101110010101 +111001010010001111110011000100010 +001011110000110111001000010100010 +001110010010111110110111001111100 +000100111100100111010100010110001 +101000100111110111001101101111001 +111001011110111111101000111011011 +001011111111010111011001110101010 +000110101001110010100110000100000 +111000000101001000110111010011101 +011011000100000001010111111111101 +011011100001110101100100111100101 +111101101010011001110011001110000 +011000101001000010010101110101001 +010101001110100011110101101011110 +011011101001110100001000100011011 +110101111110011101101110100101111 +011011010101001110010000011000111 +100010110000000001111111001110011 +100100101110011001011110111100011 +001001100010001110001000000111000 +010000001101010001010011001100001 +011110011010110111110111110001011 +111111110100110111010010111011000 +101111111011001001001000000000011 +010110111101101000101001010010101 +001101010101100100100011001010001 +000011101100110000111010100111001 +111010001111010110101111010110111 +000110101111000101010101010100010 +111001100111011100000100011000110 +011110111111011100111010000100010 +000111010100111001001010100010001 +111010011111010100100101011110101 +110100100101011010100001010010010 +001111110001110100100101001001110 +100101011111001100101010011110101 +011110010111101001010011000110000 +\N +011101010001011000110110001000101 +010100100100111000000011000110000 +100110111100110110010011111010110 +\N +001000000010011011010000010001100 +010010101010101001000001111001010 +111100100111010100010110001101100 +111100000110010010101010101001000 +100001001000110101000110110101101 +110100000011011101011010101000101 +110111000010000001110000110011001 +011110001010101100110100000000111 +000100100111011010000000010100100 +010001000000000110101011001111100 +100110010101001110000100110101111 +001111100010011010000011000100101 +010010011000100111111101000011001 +000010001110101101111111010000010 +001100001101111000010100100110111 +100011011010000111000011111000011 +000110110101000010101001111110100 +110100101010010100101100101101111 +111111001110111000000100111110001 +101100101011101101001111011100100 +011100110011001001101000100101000 +101000110100100101011101110110100 +010100010111000111010100101100111 +011011101001100000011000011011100 +110011001100101111101010001011111 +001101110000011111001010010101010 +000001111010000111101011111110000 +000111000001010111110111000001010 +000000001111111001110011000100100 +100111110011111100011001100110110 +111001011000100001001001101101001 +000111010011100101101001110001010 +010011101101000100111101110010011 +000010101110100011011011011010101 +001001101111010001101101001111010 +011011110111110010110100000110110 +110011000010000100110011101111010 +010101110101101000000110010010011 +011000001101000011110110011000110 +101001111010100010000000110101011 +100110101111010100000000100101101 +010110000100110010110101111001111 +011000010101111101001110111000011 +011110010110100100100010110100101 +010101001001011011110110100010100 +010001110011100000010000111110001 +100101011010100001010010010011100 +100000100011000000010010010111110 +000101100110000011000110101010010 +010110100011010001001000011011000 +000010100100100111000000011000110 +101000011110001101000110110100000 +101011110100110000010011101110011 +011001010111111000100010101001001 +001100010111101100011011110101001 +110010001111010010111000101000100 +110111000010111101110101011100110 +100010011110010011100011011010000 +010010011010100000011000110010010 +001111011111100110000000100111011 +101100101001001010000000010100000 +111110100101011010100010110001100 +010110010111101001100010101110001 +011101110000000000001001010101100 +100011111001010101110010000001111 +100001011000111001011011011010100 +010001110010010000101010001100010 +100100000110000111001111110000010 +011101110011111100001001010010010 +011101111010111101101000001101100 +110101100100110001110111111010010 +011101011001010101000010010101101 +000111100101111010010100110001100 +111110101110101100111011110001001 +100001111011100011110100110001111 +001100001010101111101001111101100 +001100000010010010110110110001111 +111110110110011000111011001110010 +101011000111011111011000110010111 +001111011111100110000000100111011 +100001100000111111110110101010100 +101011111011000100011110110001101 +111010111111110011000100100000111 +100100101100010111010000000011110 +111100000110010010101010101001000 +110101111111111100100111010000000 +100101001010010110111000100000111 +010000011111100010000110111111110 +011001010011000110111110100000001 +101101001010011101101111111010111 +111000110011001101101010001111010 +110001011100101100011100000101011 +010010110011001011000001010110100 +101111101110001001110111100100010 +000011001101110110101101010111011 +101010000010100100111111000111001 +101110101111010101000101110001110 +101110000001001101010101100000111 +000101001010010101100100001111110 +111011100110001100010000001011001 +101011011011111010000101001100000 +101011101110000001010010011010100 +111101010011000111100001101011000 +001001000010101110111110001101011 +101110110011000011111000010101011 +100110000011011111010010010001100 +000010101000110110111110101000101 +110010001110000110000101111100001 +011000110010110010110101000000001 +001111000111000001100100011001111 +011001101100000011000101100111101 +110000101101101110001110010101011 +100101011011111110011011001000010 +101111111001111111101010101100010 +111111110110101010100101111010101 +010101101000011011001110100010101 +101000001101001111101111001110010 +011100000100100100000000100010111 +010011000110111110100000001110001 +111101011111011000001011000110110 +101010000011100110110111011011101 +100110110111010111110111100101010 +011111001101001001110110000011011 +101001101010010010000001011010100 +101101111111001011011111000010111 +001100111000000011100111010011100 +101001001111001001011100110010111 +101011110010000110110101100110011 +011001010101010001111001101100011 +010111111001111111011110000000001 +010010001111001011101001111001000 +\N +000111010010001100010010101001101 +010110100111000000101001010110011 +111010000100100000011111110010110 +111000000111110111100010110110110 +110111101000100000011101110011111 +011110110100111000101110010000010 +011111011010100100011000011011010 +010001011000001001110100010101110 +100101000100111110101000001100110 +001011100001010111001011101011101 +101110001110011001100101111101010 +110100101001100011111011100100011 +101001000110000110110101000010101 +111010000110101001111001000101100 +100011110000011001001010101010100 +101111010110100110100010001111011 +000010110000101101100011001011001 +001011001000010011001111011110000 +110011110111110000001011011101101 +000101000110110100000100110100100 +000100010111011111011100100110100 +\N +101011101110111111010000000100010 +000100100100001100010111010101010 +\N +000100011001010101010001111001101 +111101010100010000011100101011000 +110000000110001011101000000101110 +111101111100110011010110100001000 +000011011101011010101000101001010 +010110000000001001010101001001111 +100110111111100011110011111111010 +010110100110000111000011100001111 +001000111100110111111010000101110 +100100010000101110011101011101110 +011111111110001100110110001100001 +010000100001110001100010101100101 +000001110111001001001001110000110 +101101001110001010000101101001011 +110011101010000100101110110101010 +100100100011000000001111100011001 +001000111111001100010001011000001 +111001100101010011110101000100000 +111001001100100110010001000000111 +110001111110010111111100010110100 +000111110001111010011011100110000 +001001011110001111111011010000100 +101110110011000000100111000111000 +101111000100000111011011111110001 +000011111101111100100111011111110 +001110100110101101111011110101001 +011000101001011110000011110100001 +100000001001111110101110000101100 +100010000110010101001010010000110 +111101010111001101101011000000111 +101010100000100101010101111010000 +011101111101110010011010010010110 +100010001010001101110011000010010 +101101100101101111000110110000010 +100111010011000110110101010111000 +000101101011111110110000111111010 +001100101110000011000110000111000 +100010010110111011001111000100000 +011000000000011110100100110100101 +101001100011111011100100011011010 +001111010111100101111100000010001 +111000000001101011110001010101010 +101101011011000001000011010110111 +\N +000000000111110111111011110000110 +110010001010000100001001100100011 +001100011001110011101010011100000 +111111101010010001000000100100111 +010000000011101111000010000100011 +001011010101110011111100000001100 +101010000010100011010001111000110 +101010111100111010000001100010111 +011001110111110111110010001100010 +110010101100000010110110101111110 +111000001001001000000001000101111 +000001101000001100101001000101001 +010111101011011111111101110110011 +010001101101111101010001011011001 +100101100000111100000110101101000 +011110000001011100011110110110100 +000100101100101110101001000000001 +011011101110011011110001111011101 +000110101011001100010101111010110 +011111000000000010011001101101110 +100110001011110110001101111010100 +111100100100100111110001001001011 +100110110100111110100011000000000 +011000001001101110101110110110011 +111100110101011000001110110110011 +101001101100110111110111100100101 +000001111100010110110101100011000 +001100011100001000000101110110010 +011001011010101000110011010011111 +110010100010110001000001001111011 +100001001000011001011000111101100 +001010011110011101001000010011000 +000000101101001011001100100001101 +011100000010100110001010110100000 +011011001101111101010001110011001 +000001000111100110111111010000101 +110101110010010011011010111111000 +100111010111001110010000100001110 +101101001100011010001001010011000 +010111011011001110100110111011001 +001001001011100010011010100111010 +000000011001110101001110000100100 +011110101001010011110101101000001 +000101011101000000011101010100111 +001100111111011001000100101110100 +010101100011000101111011110000101 +111101010111001101101011000000111 +111000110100101011010010100111011 +111010011010111101000010101110000 +100111100110001110100100101001100 +111010010000100000101000000100001 +011000011110101111101100010001111 +100000110110010111010011101110010 +010100011111011000111100001100100 +010101010110001011011001110111101 +010101110100001010000000000010111 +111001011110010010010000001010000 +000100011110111010010101011000110 +111011000100000001100010100011010 +011111100010010111101010010000101 +001010111111111101110111101011000 +010111111010100100101010000101101 +111101010100100000100000101011100 +000001101100101110100111011100101 +111100100011110100101111010101101 +\N +111111000010000001001100010110000 +111110100000010101001110010011110 +110000011110100001111010111111100 +111001101110001011010110001100100 +000100000010110010100100110011100 +111010001001011011101100111100010 +011001101101011110101000110001101 +100100110000001101010111100111010 +111000001010000010011111111000000 +011111001101011101101110111011100 +000000011011110100000011000100011 +000100110101100010010001101001000 +101000110001101011000100010001001 +110111101110011000110101101010101 +001101110010000101000101101101000 +100001001000011001011000111101100 +111110111010010111000011010011111 +001110100001000100010111101110110 +000101000111010000010111010010100 +110101010100101100110100111001110 +011110101011101111100101010000000 +000101110100100011000011111110000 +001011101110001111101110101101100 +110100010010001111111100100011010 +101010001100010000011101011111111 +101000010000100010101000011111111 +000000010101011110111110100110111 +110111001001101111100011010000101 +100010000000001100000111000010100 +011110101010110101010000011001000 +110110110111100010000111000110000 +110001111110010100010100011111100 +000001001100110100001010110011000 +010001111111011100101110000011001 +100100111000101101000111101101111 +000111111100011111001010101110010 +101100010000010011110111001100000 +110111100001110000000111001000110 +100000111011110001010010111101111 +100001001100100100000001011100111 +001010101111101000111000011010011 +001100100011110001101101011100001 +110011001011110111111010101011000 +001010101110010010101100010101011 +001011011000010110110100110011000 +100011000011110110000010011111001 +000000000000110010110000110100111 +011111111011111010001101110100111 +110111111010101011000001000111100 +000001110110011011000111010101001 +000100000010101001000010001001110 +101110100010010110111011001111000 +100010111010000000011110111011000 +001100110100101010000010001110111 +110110010011101011100011010101010 +101100000100011110000001100101010 +111010000000011001011010011001000 +010010011011000101111100000011101 +100110101101011111001111001110000 +110101100011010100011110000101111 +010101111010000101011010011101110 +111101100011110100100111000001110 +100010000000001101010110011111000 +100111010010001100100010100100010 +011100110101111010100111101001011 +\N +001111101101000110000010001110011 +101110110000010100001000101100111 +101111110011010100111101100101001 +111111110011101011000110011001101 +110101100100010100100100010100001 +010101010000111001010001010000000 +101010010110111010011100011011110 +100010111111110000001110001101010 +001000110001101110010110101001001 +010111000111001000101001111111000 +100100101011001111001110100110110 +011101001101101000110010001010101 +001110011000100101001010011111010 +001000011101010010110111010011100 +111100110110000000101101101001010 +110010101010000111001010001010000 +000000011011110100001110110011000 +101010001110110100100100010101100 +101000101001111010111000100011100 +001000001100111000111101100111000 +000001110011111011001111100000001 +011110111001011000111101100010010 +100110110110010010111110111000110 +111101011010111101000001011111111 +010000111110010011010001110000001 +011110111100111001011010001000000 +101001100100011101111011100000011 +000011110010111110000000110001011 +000101111011100100100100101011000 +011101110111011011001011110010100 +011000110100101100001111010111110 +010011000000011011111100101010111 +100000101101101110000010010111111 +000111110100010011110010011000001 +001010001001110110011100110000001 +010000001011001001101111111111000 +000001000100011001011111111011111 +110111000101000100011101101011010 +010011111001010101101011001001000 +000100111100001010000010010010100 +011110101111101010100011000111111 +001011001101101000011011111000110 +011100100001001010011000001000101 +100101101100011111010100111001010 +111111100100101101001001111000101 +111110010011111001010101101011001 +011000011100001110110001001100100 +011011110101000001111110001000011 +111100000000110011001111011111000 +100101001011110100110011001110010 +100000101100011100111011000110001 +110010010110010001011101100100001 +110110010000000010100000101111100 +100011100010000101001101100010101 +001101111000011010100101010010101 +000100100011110001110101110010000 +100011110100101011111000110101100 +100011100011000011100000010001000 +100000110101110010101001001011000 +011110101101100100001000011100110 +101111111011011101111100110100000 +001110010111000001101001011110100 +000110011011101101011010101110110 +010101100101111111010001110001010 +000111100101111001111100010011010 +010110110001000101111111001101101 +010000111001001000011101111011001 +110100000110001011100110001111100 +000011110011110001011010111100001 +111011111100101011111100101000110 +\N +101000111010000101111111000111000 +000111000111001111011011001001001 diff --git a/contrib/btree_gist/data/cash.data b/contrib/btree_gist/data/cash.data new file mode 100644 index 0000000..385d01e --- /dev/null +++ b/contrib/btree_gist/data/cash.data @@ -0,0 +1,600 @@ +-166122.30 +-432746.26 +\N +-282683.84 +454558.76 +-446358.85 +374604.84 +-207772.37 +-447987.84 +326562.77 +-269813.11 +-191115.13 +-70048.27 +150896.02 +-476375.00 +-390399.77 +212177.03 +-204062.17 +97619.20 +310057.22 +-39178.09 +-369182.60 +-149433.85 +-410022.92 +-373445.06 +108900.38 +-357486.45 +380320.26 +\N +-483777.58 +-384690.86 +-115424.90 +-300012.64 +-261623.29 +-5423.17 +-159664.48 +-45458.63 +-379324.56 +280086.82 +491889.08 +117994.57 +-87318.07 +\N +-247918.60 +-407705.26 +290100.50 +-120064.52 +-61386.63 +-256384.21 +205928.38 +453688.97 +63433.87 +489091.31 +454693.57 +\N +-241552.75 +-18629.07 +399128.14 +421657.16 +\N +-153046.41 +\N +-259814.14 +351443.36 +112822.79 +207894.26 +-120775.66 +-454270.13 +-488281.40 +-332881.24 +\N +113333.51 +-311200.38 +355731.15 +155154.56 +469754.45 +-168023.72 +-479427.28 +243797.84 +323948.38 +425953.89 +-119177.18 +180678.00 +426571.91 +65894.05 +\N +-389103.14 +426557.67 +-403492.88 +292354.04 +195771.55 +-348533.49 +-2206.12 +425633.57 +-453156.39 +15382.26 +404980.85 +-401394.64 +\N +-241207.65 +342900.81 +-171471.67 +81593.45 +-458067.19 +-306441.87 +\N +-411424.46 +-318165.32 +30912.14 +\N +49720.90 +351963.89 +238010.43 +404837.38 +275021.74 +126672.84 +439889.24 +-324251.18 +133075.86 +\N +-198536.50 +206972.33 +170209.43 +-483750.81 +\N +327444.80 +75763.63 +9840.43 +15700.78 +-51623.99 +-218733.26 +-36204.85 +60701.28 +-190014.00 +150597.22 +13545.90 +-314080.96 +-427964.35 +15262.72 +461174.33 +169218.74 +-74740.49 +-384598.83 +-483728.30 +287537.47 +234918.04 +-433550.83 +-230734.17 +323698.65 +-146197.04 +-255563.61 +83532.71 +-469513.46 +-36592.36 +-73146.17 +285947.07 +150015.40 +\N +\N +-209659.65 +355722.96 +243539.28 +-32803.88 +477435.65 +\N +417338.19 +\N +261176.47 +267675.86 +-229013.85 +-116379.58 +-476925.33 +-20226.29 +-143918.96 +143456.45 +306561.97 +290244.64 +119367.95 +252541.23 +-345895.39 +163329.59 +-113415.56 +-444697.34 +406232.17 +\N +-100528.56 +-29727.60 +-432245.57 +-78154.99 +-40228.56 +388482.70 +-360892.31 +397567.58 +-61500.50 +197420.99 +-53405.45 +-10035.31 +307734.63 +-476264.93 +-187896.84 +74967.90 +-96352.04 +-204654.09 +-314590.02 +-498658.17 +\N +459916.44 +-294815.05 +20507.27 +\N +473415.90 +150774.51 +185491.97 +-74069.01 +\N +46254.06 +-367453.99 +-168238.39 +-329399.97 +359048.54 +422343.40 +-380964.05 +-470561.40 +496663.18 +380409.42 +44918.95 +324580.02 +479079.72 +177174.87 +\N +457126.55 +-112441.99 +-430676.25 +-135129.13 +336449.85 +487338.40 +7167.97 +-1800.51 +-291559.23 +304700.54 +-120904.61 +114795.52 +-430214.95 +408084.32 +-191474.36 +450118.77 +-352847.81 +437584.98 +156887.65 +\N +-342586.53 +-79521.47 +190267.42 +463657.84 +-405475.24 +151640.70 +-415959.15 +107777.66 +285276.61 +341585.38 +-388847.32 +\N +-18905.90 +\N +-51094.80 +-398920.32 +-86554.74 +-239996.08 +-159324.69 +98208.93 +127700.52 +495535.34 +344071.85 +348527.75 +-159520.00 +\N +341431.83 +33083.75 +-391284.15 +-192247.31 +-472981.85 +-205808.33 +-138646.73 +\N +\N +-310383.84 +-120621.38 +-67352.75 +434226.84 +460706.11 +-447900.16 +21469.25 +\N +218925.44 +-413275.69 +-207067.87 +39712.27 +67008.72 +456794.17 +-379796.06 +261767.80 +-466546.12 +-305730.53 +\N +-364597.47 +\N +458787.41 +81602.89 +-271359.05 +-361870.59 +-28626.33 +481330.97 +-454201.68 +\N +-287344.91 +-321038.68 +-449334.66 +-327779.24 +488133.53 +-329401.54 +316435.73 +60517.74 +-233468.01 +354078.72 +475270.56 +-276973.48 +2820.02 +-305523.48 +-119945.40 +-29204.15 +11065.46 +\N +\N +-349221.53 +402110.01 +-454995.32 +-830.00 +-463136.68 +\N +-300999.19 +370635.37 +-292020.51 +-239004.63 +340136.68 +-262815.89 +-184908.59 +\N +\N +141849.58 +-498941.55 +95873.74 +-285016.93 +-107089.11 +-13721.30 +457191.81 +155699.77 +86327.14 +395889.04 +-229107.82 +324853.91 +-498107.17 +386767.59 +219841.13 +-27583.14 +-149702.20 +146393.57 +-437989.04 +159841.53 +32498.50 +396403.30 +-315063.83 +\N +22649.64 +215343.21 +-175059.30 +-1847.18 +102497.28 +-247295.07 +332109.98 +-235782.85 +-295096.68 +\N +498125.83 +-333733.57 +-433530.70 +449518.21 +316400.65 +280480.98 +180453.94 +457876.49 +412833.95 +428460.60 +-10056.74 +-214948.53 +37814.95 +102477.82 +-225564.43 +-92130.07 +-155741.02 +78450.91 +343105.39 +-130527.09 +33079.19 +329362.42 +200577.60 +212415.40 +-311348.62 +-21477.69 +333801.57 +-353712.01 +-433106.23 +-26081.60 +\N +-163820.36 +151893.43 +205173.27 +-393163.22 +277938.44 +-276107.66 +-304649.71 +171386.20 +-306944.28 +370017.46 +249675.98 +-72023.48 +-372187.26 +-238104.04 +354749.41 +389572.21 +365556.59 +235734.34 +\N +-280573.81 +128725.64 +71656.14 +400415.05 +-130616.30 +244866.96 +-415742.86 +-211304.73 +-267780.95 +-425028.29 +170832.02 +253488.32 +193520.19 +-25218.72 +-277658.68 +-167409.34 +-474770.31 +74591.72 +\N +366618.43 +-171394.63 +329032.65 +-328557.90 +211902.62 +60210.12 +178715.93 +\N +-456832.44 +344825.40 +64195.06 +-370332.57 +155903.15 +-265256.14 +15323.04 +\N +233809.17 +411862.69 +6551.58 +-461516.27 +420681.92 +233340.82 +-373891.60 +440798.68 +-319872.00 +435051.22 +377502.70 +197215.84 +273887.91 +-360415.44 +-41466.42 +-335436.28 +441898.09 +1302.43 +144990.34 +188585.87 +\N +174623.04 +322047.54 +230563.87 +-300142.22 +-120404.31 +21472.79 +444194.03 +80999.42 +83236.09 +-414005.44 +-181501.32 +48402.05 +181956.91 +-261939.44 +-155431.06 +388392.94 +337512.20 +-492902.69 +\N +481390.20 +\N +494812.78 +477359.18 +\N +208828.94 +-51946.00 +378432.41 +137447.10 +-235678.90 +-260524.65 +286759.89 +86014.42 +303893.81 +-185836.92 +-339715.80 +474384.49 +-487805.30 +\N +-135660.84 +\N +-12158.99 +-24671.78 +104450.53 +\N +-201014.92 +-430860.23 +-132855.90 +-339856.08 +431791.82 +406070.27 +-80184.35 +-330535.00 +371020.10 +446960.92 +-461880.13 +-399502.34 +221025.82 +-332205.06 +-394278.43 +-361844.93 +\N +35170.25 +466405.94 +124030.12 +-412309.14 +\N +-351146.89 +51070.28 +-322829.73 +233996.37 +482796.36 +-355095.23 +-489800.21 +21915.01 +282777.96 +169811.79 +301306.60 +\N +-399859.75 +154814.04 +65509.24 +27086.81 +31231.96 +-304619.93 +494051.91 +-474057.76 +-263425.16 +6217.86 +-341118.74 +\N +-306167.25 +410631.12 +-232238.14 +-316015.14 +-61724.14 +\N +176961.97 +277381.68 +190797.28 +-457747.77 +-449873.37 +-484344.98 +-367413.81 +-43132.61 +121522.02 +87962.33 +\N +370495.18 +191540.22 +-76890.93 +-232322.04 +-111047.08 diff --git a/contrib/btree_gist/data/char.data b/contrib/btree_gist/data/char.data new file mode 100644 index 0000000..99bb6ab --- /dev/null +++ b/contrib/btree_gist/data/char.data @@ -0,0 +1,1000 @@ +b026324c6904b2a9cb4b88d6d +26ab0db90d72e28ad0ba1e22ee +6d7fce9fe +48a24b70a0b376 +1dcc +9ae0ea9 +84bc3da1b3e33 +c30f7472766d25af1dc80 +7c5aba41f53293b712fd +3 +166d77ac1b46a1ec38aa +2737 +aa6ed9e0f26a6eba784aae8267df19 +3677643294 +8c9eb6 +5b6b41ed9b343fed9cd05a66d366 +4d095eeac8ed659b1ce69dcef32ed0 +cf4278314ef8e +3bb50ff8eeb +dbbf82208 +fe9d26c3e62 +2fc57d6f63a9ee7e2f21a26fa5 +2a53da1a6fb +7c67493bd +2a52a5e65fc3c43f409550dfa +b0771132ab2531a40c99413 +66a7c1 +5 + +d5b4c7d9b06b60a784 +4f89 +bb743fc2a7213949f2 +4fbafd6948b6529caa2b78e476359 +fd1bc138d22 +64 +fa84f696e31d07f55cd45cc3c +87aa6 +bda81ba88c634b46394 +59885ebc737617addaaf0cb809 +90e2a51705594d033a3a +21fa2e +50a2fabfdd276f57 +f0287f33e +e76 +93e +d3a57c7e953913944c760093574695 +4a5c26d88c5 +08c61f3fd48f12fa7c88a +a75f5a8337c6699916182 +6eb5cefde6fcb8463cea70880 +1aa55 +7efd8e +c92841e00db +2d999528ef1dd9ae433698abf3fe7f43 +8d7e35631f8 +a074c5929fb8088 +ec8b36fb3489 +f0810a +6a +ecf27a776cdfc +1153ebfe1f3 +9a01df2f09a7bf076c0013 +d +9ca +767abe +73b4c20 +b21dfa3fea +59 +105be3ebd0677ec739afd851a6d8 +30b00ce4c04ddd6 +721327bfc0e276a87156 +8faff61bc119 +f634226e +1ff1 +9f +\N +1709106f7c5dbb0539104f4ea3c17a7 +2b44a59db15a3ee2f1605ac82b028 +46107b341ed115c03ac287f00 +54a24082cfebcd031a +cea4eca +37a9aea1b +a114f532e70502 +60f3db995d0676b576 +091b847d9660c +d02a55da8edcc7ec95a59 +db0aa8bee7afe5a6e363ddcd890 +071 +9e01624ab7743bcdbd8cb316 +4b1709eb0990e993c3125c15 +1b28e946f4a8ffd5bdf939779c420 +80ad60f95 +ed31ca2c02fe2af071ee0089 +3368d7171580c61644211e59574674b +c1cc2eeb27a86f85032b21 +9672a5e9c09fb71edf28 +27e4d3594a232b37951be232 +90fafcc60c7ee9f7a1 +18be9375e5a +b68495714b0e1cad8ebaa1599766a94 +\N +919d117956d3135 +cf6a52053ff904bca9d96f +5aa8301da6367 +ff7f2c85af133d +929ad9494d911e47cd54 +4df9699916 +48e3b +0fa41cbcef9d3f +f9926b3 +ca4047ce3d85454b067b93 +2fe51daae840593fb0f4076 +1181c1834012245d785120e350 +f3527f99 +754ee2a8de2d24e4e7 +83050114704e61f1c58d81d9b607 +980327c +0a5e +969049573085c71a5d6dc034e34b56 +12fc5425 +353a +617119c2d67402b +83de9f8ad800dc61db78100 +87ff3a697a3847ea7f3d +ba1f2511fc30 +df8b712c4fe20a0df93381966 +f98 +f83be66 +52ee216e7ea72db74e87cc1 +650a1c9c9baa20730b4fcfdbe +221a057e91928a3651f31d37 + +3be +e051e54a58c467f7e3e23e648bf7ab8 +02e6b43d34792 +7162e6c8 +615010a6 +be8032a3ea812eeb288df3a +557ff88fbe890cf23ab4c4b6526ebd +26e41e2047e361fd54e4d19ec3 +6067e932002 +f52f16 +3c71a3f3482184ceca06acb7859 +b17bf99d69b88017 +f498cc +8cf478ccdecd74e358a73857922d0bc +95a283596833b3edcd +9ae1400 +fca +e6d604c19ab3dad +\N +b +176e +409cd9f3 +0f0254a +699598531905f632234819bfa19c34 +f9c12 +df05072 +9f430be862c6c636d25 +2 +dc35b9fd881cc59bf54338af5 +7 +52154e9782f4ab05f7f77fc4abe6161 +cd3164cb1a8780dd +0c0a5b8 +c5bb33d902ffd764f88adb1036c +e2f06df03efbe62eae471 +12fd838288a9836dcb33694479b +31c76c1c43008d0a9d396 +027f19cca4a7119da720f7f60314ce +63cf7c8db392ec220067 +a187f54f5661d23769a5ab76651d1f8f +36f3 +2 +21b97fc756c8732ba5cbf021792257 +3d1108469b16342a97c5b60b3 +41075c479e3066c +261104cbe0eceac00eaf4bf5d +ed6 +edeaea17b7bb5f989e8879a33c11 +7b7731085d04d325b618f8f83281d5e +f584bd6f9cff10166a302a2ab5bc6e7 +e42002e140b29fc4 +8bd50a3a0da6 +993647e71651a205aba +948cd06ca76467 +3b9be5cbb65a3 +ae8d333327fdf610b2e985 +1e1da348199896d5d +0372c098401a1 +e6ce069c60d8e681ccd45b06ff3d47ad +34c6e1feaf7f5084f3014d5d11fb72 +bd918538adde6 +035032ee8 +6be7de648b +31 +939444082f2f746b655 +6f3898aad606 +3bcd6e8775 +cb +5321951a8 +\N +053ade8bc0514fd9f +c1ba58b05f6245f221ad653 +1454 +b6e693b66d2003e1d08cb4d +e7d14fa651db7754aff22716bfccb +cd0a54e70051990d13f1fec +1166083fc +1041ba7e06b +076decdd037ccb810d0e9e1756a9 +7c6139e8fb296b24 +cbb8413057f9176473225d +301416f843b954c1f +49a6957c6e2e1c5ce89cde8898949 +1ad002f8a +6f36dfd82a1b64f6 +f +c5db628ee33ef57f9e6aa +8f9654eb7b0c528ed7edc173b692173e +c64ee80b +da4f2c32a51ddfa1 +545d8fdb508d46e +f5d8eac62167a471f95928c +48341ed4f1304fb01623d8f456 +348bd3ce10ec0 +9b779599da6 +f +2485938 +4a6d02b7fc61f162e24ea +dae34b +a84fb2 +55fc63933ba0c92102a8e +8226197b889d86f722471772777cc +fac87b8618ebb0dc9ec3f1b557 +3d26e13f5daf5e +9f3d9739b11c2a4b08ea +e4 +bcb35cddc47f3df844ff2 +caec6c5b9f4424ac0b44c2cf +427e8576c702d4d715 +4f61029d752be4d45c5e +938a35 +ffbfc1313c9c85 +3482bfb20c3f103718e +214311510e3d55d0291ad717f +909596ec6cc7db +16b70e +1471e2c8fff7ddcce1acd0210c +20007c50aa9258083faf59d69a0 +4479235f75efaad02357cbff +\N +\N +fbaa030eee9a34cefd5248d7c9005 +4d685096123bcc72d0923df5c +3c0efa1ba676ecbc802c3717f291ee +bdeaa2482 +60ec2d50e99cbb811e8 +90922c +64dbc39ab9cf +de030a3d4e727eb9 +26b4cb0930a +ac6a20237 +ee06d5a17b757b231d004946 +aeb75a20c5efa12e33eb54f1f7599 +0c4a96bf0bc4 +2bf1686588c741e0f407 +9f0ed6cee42549a57 +6b7e26a +8c +259a568c9e5262afa3bb020 +d9296b +6f6fcb8d7 +457126a29df4c8 +cdd52d64d4c86b2807dfd31c5ee0 +d4d67ff03c419e3fafc227628690 +453 +2c028a63bb4a9b99dc57e +b912d22e1bdb0027532bb318 +a3ab82e89ac30fdc459c5d +bcd69f8c3 +94 +07ca93ea45d55d28894b8664 +3910a4fc69883 +fc28261e2becf14ad69ba +af69b1990 +5dbd27ed12e6ca549c958d61db5 +9db1725416 +70d37da46913c0157ee +33e2 +5131e6710ad926457f97 +849c49cb79d556969520a7ba510fa7 +ff4da50c05473ef35e9f3442d39f + +4693b22f920e040c716c +6c2a40def9f5197fdf4a9fbb98 +bc88b78c1fca882c19a55bb725134e +352ad14d71a6069544a8592da +a43e268d90bf08407 +d70d0dd90a6c3 +6ba01cd31a75dd3cab2b6800a7e +5a592088ddbd5c96f25e13edd9 +fd1a71f57514f3d96aab5b +4268c65769504e17a0bcd91407e23 +3de +62b594 +1445f5c59eb6ef023569de +06b904260b0d751caf00 +31b0 +c687a39f +d31ba3c1c +828007765429cc8470834c483ab625 +64a +4eaed852e6857e6c196f20cfa49 +2dd746 +fe +8373fa500 +39 +f1bbcefb43f8731a660091a +642606e93992a316419e +0d96f49fdbb +47bcdd11e1cd38abf97 +098ddacdd558720b +0116a06b76 +509a69c52a4bf +6b23c0ac4415 +9492fe88f263d58e0b686885e8c9 +5c80dc6aeb95 +2ab5372e91b61384741a2 +\N +36ef14 +7c1b3b869434ddf685bc503e3067 +2ba9cf6 +9a215d19e54bc4b2282e30c33214e3 +c2cff429a10edd8 +e049b6ef70e237d8fa +96e6cdd58a6ef8e66f5188835 +080002b079e1c8f0c993 +aab4cdfa017 +40b134a +7dbfc1a44c7463d842d9adc6481 +8a318bde4768cb68f23fc193354 +a9700592 +d1d39407e2dc464252ef76d6f +7488b677139de1cfcff7c7 +30d310 +9ac1dce6537ab +125dca018389717c78efafb +7b95e765 +ca19fff8824c2 +71aeb19c +4692d489b0638e49682df4f46dacd3c +f37053cfdff777709610fe4e +51c36ccac +2eb2c961c1cbf6 +\N +cf8d7b68cb9a2f36 +7bbedb4ae7 +06ec46c55611a466eb7e3edcc009ca6 +e +5ed9cd0ea5a4e55d601027c56a + +64cacf3a42afc +90e63000c34506993345355640 +79bce +173bb7 +c5 +574ea7c921cb0f25 +089d56d16dff24f336e4740 +6870470f1f9afcb4f7c56c9f +b97e117fc965 +7013029 +e48f6dd481 +7d00e1e227beef84a9 +904d4c34241f +cb5c0f14 +3a8a70 +f51a73164e92052fbb53b4cc2f1fed +3c3fecaa0270175 +2521ef03594 +fa05756812648f450fb +13c2f +b39a0729d6182e9 +15b5ea204fe73 +d8991afd72d21acd188df1 +a29fff57ab897338 +de549b3ed5a024534c007125c +2fcf3e5c3e3 +7427b6daec5c3f +473 +8 +a5d9 +840410976ac2eeab58e1ca8bf46c2b7 +1db9cc85a336f1291ea19922 +db808f3548cda91 +2e379ce80af12bd7ed56d0338c +a +ea67a7c847f6620fc894f0ba10044 +0e +52e97d975af7201d8 +d95e6f08184d8ff +19762476fa +42f278f3534f3f2be0abaed71 +f0aba11835e4e1d94 +e8534cf677046eafb8f5f761865 +ffbee273c7bb +2bb77f6e780 +c77e81851c491 +e +a9f45d765b01a030d5d317 +ff7345a22bc360 +c87363ba121297b063e83 +13ea32e9618d +40304f6c2a7e92c1c66ff4208e +a781b4a21419abfdf5eb467e4d48908 +8a65656e514b2b3ef8f86310aaf85 +4 +90b7b2862e3dbc8f0eef3dfc6075bfa +eb94a1c +a58abb5def4fa43840e6e2716 +260e6eaebb +42415d712bf83944dcd1204e +305254fc3b849150b5 +5bbd7f8471dcd3621 +2ae0548115a250 +0c1988e9 +76f98bef45639b7 +0d5a28f01dc +b71 +c046576faa4d49eff8 +\N +c1e8d01c +10c86c457ea050455a742da4f8 +ea7676af85c71c7eeca635 +6a07137227404d +a4 +7186172 +8150f31c9a15401c +f1bb9057a9938bfa +22b482be08f424ec4 +21daea994293589 +15bff393f6b17fef24786dd6f9 +d5a2d +4b3b5dd9370543e +b4a93b2ac4341945d06 +d384447812e0 +4e3c97e9b8f7 +f7d4d644b2a1d373 +5102c +b9531f725674b28 +1aa16e7e34285797c1439 +51aa762ea14b40fb8876c887eea6 +45a62d3d5d3e946250904697486591 +b3f1a8 +243524767bf846d +\N +8 +95 +45a922872 +dd2497eb1e3da8d513d2 +7821db9e14d4f +24c4f085de60d7c0c6ea3fc6bc +e4c9f8c68596d7d +afd6c8cb0f2516b87f24bbd8 +61d2e457c70949 +d2d362cdc657 +3605f9d27fd6d72 +32de91d66fe5bf537530 +859e1a08b65 +9b5a55f +4116cda9fddeb843964002 +e81f3b2c0ca566ad3dbbc6e234 +0d3b1d54 +10c440be5c0bca95 +7dad841f +a61f041967972e805ccfee55c +deee9cc16e92ab197 +7627554073c1f56b9e +21bebcbfd2e2282f84 +7b121a83eeb91db8 +\N +4668b2019ebff30b970ccde7026e779 +2aa +eeca4d01996bdc8af7e4dbd01 + +a3431c0758a8c +52c07a135df1dda388 +6ee649b5a +c4884 +dab3abb06371c8a32dda2231e6 +e0 +92 +8fdb98 +cfbb1 +96acd3e9 +73be4a4f277862904a52 +fa +051b8f9c0d6d2b2 +bde +23d51c87b6c931d314c88e44edcbc06 +e822a0497153 +1963a88d14ac0c9b7fe2 +3ffaff39b37650c5cf +dc0 +aed3d3de6c6d9 +2347412d3332140012f +0 +\N +7b311463334cd1a +973a0eb +d1cc8f07f56a62d1386a +905f2bb7e8852fb96cb9aeff95 +b026324c6904b2a9cb4b88d6d +26ab0db90d72e28ad0ba1e22ee +6d7fce9fe +48a24b70a0b376 +1dcc +9ae0ea9 +84bc3da1b3e33 +c30f7472766d25af1dc80 +7c5aba41f53293b712fd +3 +166d77ac1b46a1ec38aa +b026324c6904b2a9cb4b88d6d +26ab0db90d72e28ad0ba1e22ee +6d7fce9fe +48a24b70a0b376 +1dcc +9ae0ea9 +84bc3da1b3e33 +c30f7472766d25af1dc80 +7c5aba41f53293b712fd +3 +166d77ac1b46a1ec38aa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/btree_gist/data/date.data b/contrib/btree_gist/data/date.data new file mode 100644 index 0000000..69221b4 --- /dev/null +++ b/contrib/btree_gist/data/date.data @@ -0,0 +1,600 @@ +2019-11-22 +1995-05-20 +1989-04-12 +\N +2023-10-21 +2028-11-27 +2024-05-24 +2018-08-31 +1988-07-16 +\N +2019-05-21 +1972-12-12 +1975-05-07 +2022-08-11 +1975-06-27 +2004-10-26 +1983-05-14 +2015-06-20 +1978-02-17 +\N +1970-12-04 +1977-03-22 +2032-10-28 +2004-03-02 +1992-10-29 +2021-05-05 +2025-06-25 +2036-07-15 +1981-03-10 +2012-12-04 +1996-02-02 +1992-12-31 +2003-02-03 +2008-12-15 +1994-11-19 +2036-09-20 +2034-10-21 +2019-11-16 +1995-01-23 +1974-10-11 +2027-02-26 +2027-08-08 +2037-08-30 +2026-01-08 +2023-09-18 +2012-03-07 +2011-08-29 +1979-11-06 +2033-09-25 +1986-06-08 +2020-01-13 +2015-09-19 +2035-11-16 +1985-11-12 +2002-05-26 +2002-01-30 +1980-01-26 +2037-03-23 +2016-02-23 +2023-04-21 +2033-04-11 +1985-09-09 +1993-01-29 +2015-06-28 +2036-04-06 +2031-12-08 +2014-02-26 +1994-01-23 +2019-01-27 +1978-08-01 +1979-05-21 +2022-12-12 +\N +\N +2007-10-16 +\N +2037-05-17 +2034-08-14 +2014-12-20 +2033-03-13 +2010-10-03 +\N +1992-12-23 +2029-05-15 +1993-07-17 +2007-11-06 +2024-10-22 +2019-03-02 +2026-07-24 +1973-03-29 +2006-06-02 +2032-06-11 +1990-01-28 +2019-06-16 +1970-04-21 +2012-09-17 +1993-10-06 +1993-02-24 +2014-12-07 +2018-09-17 +2015-05-22 +1983-01-19 +1978-11-08 +\N +2021-06-01 +1970-11-19 +\N +2030-06-12 +2035-08-11 +2021-01-31 +\N +1996-06-25 +1974-11-28 +1989-04-28 +1998-07-17 +2029-09-14 +2010-01-06 +2013-05-04 +\N +2023-10-03 +1998-02-06 +1984-07-16 +2008-01-11 +2015-06-23 +2026-04-02 +2021-11-26 +2012-07-09 +1975-01-07 +2022-12-04 +2013-01-22 +2001-02-13 +2001-11-20 +2008-12-27 +2030-07-05 +1970-07-22 +2037-07-11 +2034-12-24 +2022-04-04 +2018-03-24 +2037-03-25 +1972-06-24 +2034-08-15 +\N +2030-01-12 +2000-08-15 +\N +2034-07-25 +2030-04-06 +2003-08-22 +2012-04-17 +\N +1992-11-30 +2017-03-31 +2004-06-19 +1974-02-07 +1992-11-02 +2006-07-03 +2030-09-03 +1989-12-15 +1992-05-03 +1981-05-05 +1983-06-04 +2010-08-13 +\N +2022-06-15 +2011-03-10 +1973-11-06 +1998-03-22 +1992-07-20 +2022-01-15 +2022-05-03 +1972-06-08 +1976-10-27 +2023-12-13 +2027-08-01 +2009-05-04 +2021-03-19 +2025-04-09 +1988-07-28 +2023-07-31 +2003-07-25 +\N +1995-12-12 +1992-04-19 +1997-08-20 +2028-01-13 +1980-04-18 +\N +2030-03-08 +2000-03-22 +1987-03-17 +2008-03-10 +1982-04-02 +1970-01-29 +2032-03-28 +1991-10-03 +\N +\N +\N +2035-07-02 +1988-11-27 +2034-08-29 +2004-11-29 +\N +2013-08-28 +1997-04-06 +1994-10-12 +2027-06-18 +1971-01-10 +2035-02-23 +2036-10-25 +2033-06-29 +\N +2028-07-29 +2003-03-18 +1994-11-29 +2001-10-08 +2019-03-10 +2022-05-19 +1971-07-13 +1979-07-25 +1980-11-27 +2030-02-20 +1973-01-02 +1992-12-20 +2034-03-04 +\N +2036-11-27 +\N +2023-09-22 +2023-07-13 +1996-02-13 +1974-02-11 +1996-08-07 +1998-06-13 +2035-04-21 +1996-03-04 +2019-05-04 +2024-06-01 +2007-02-20 +2003-07-29 +2000-07-13 +2011-09-08 +2019-06-10 +1975-02-25 +2002-01-16 +\N +1986-04-08 +1986-03-08 +2019-06-21 +1988-09-19 +2035-11-30 +1986-11-27 +2037-03-10 +2030-07-15 +1988-06-29 +\N +1980-04-23 +2020-01-19 +2012-09-14 +1983-12-28 +1987-02-10 +2029-09-21 +1972-03-12 +2035-11-20 +1996-10-25 +1981-02-05 +2031-09-10 +1986-04-24 +1987-04-26 +2017-06-10 +1980-07-26 +2009-10-18 +1981-05-24 +2026-07-17 +2015-09-30 +2032-05-02 +1987-08-17 +2034-10-20 +2008-11-20 +1986-11-06 +1980-04-06 +1982-11-12 +1975-01-24 +2015-04-25 +1980-03-14 +1998-04-07 +2020-12-16 +2004-10-31 +1975-02-26 +2024-10-29 +1974-10-29 +1986-09-21 +2037-11-06 +\N +1979-06-02 +2001-06-28 +\N +2024-10-14 +2031-03-22 +1983-11-27 +1989-01-01 +\N +2037-03-05 +2002-12-28 +\N +2031-12-20 +2002-08-30 +1997-11-16 +2023-07-17 +2010-04-02 +2019-09-21 +1978-03-04 +1992-11-08 +2002-02-11 +2012-06-06 +2012-11-23 +1997-09-27 +1972-12-18 +1985-10-25 +2028-09-29 +1976-03-31 +1996-06-26 +2014-07-06 +2032-05-21 +2018-12-18 +\N +1974-06-02 +\N +2024-09-29 +1996-07-03 +1973-02-11 +2019-12-02 +2035-09-04 +2008-10-10 +2018-05-16 +2020-04-17 +2030-05-04 +2002-10-27 +2036-03-04 +2002-10-29 +2000-06-22 +1981-01-01 +2001-12-31 +2003-09-25 +2020-12-07 +2016-11-04 +2026-02-23 +1990-12-16 +2033-10-15 +2003-07-29 +1998-10-09 +2006-04-02 +1970-06-06 +2015-02-06 +2004-06-23 +2015-12-08 +2024-06-26 +1997-11-07 +2023-05-09 +1992-11-16 +2016-04-17 +1996-11-09 +2010-09-12 +2012-10-17 +\N +1998-07-19 +1973-11-11 +1992-11-15 +2032-04-22 +2031-09-14 +2005-03-25 +1993-03-17 +2004-03-01 +2009-10-06 +\N +1993-06-07 +1994-07-31 +1987-08-07 +2034-05-01 +2025-06-19 +2019-10-13 +1977-03-25 +1978-01-16 +2003-09-29 +2002-04-07 +2024-01-12 +1984-08-29 +2036-08-16 +1972-05-14 +2019-01-24 +2009-12-19 +1982-07-26 +2012-01-22 +1983-07-22 +1978-05-27 +1974-04-02 +2008-02-15 +2003-01-14 +1989-03-30 +2009-12-14 +2030-07-05 +2032-07-22 +2016-05-17 +2031-07-02 +\N +1980-07-28 +2031-09-06 +1975-01-20 +2019-05-05 +2025-05-02 +2001-12-27 +1991-09-27 +2021-12-14 +\N +2027-03-24 +2009-01-07 +1985-02-04 +\N +\N +1996-12-23 +2012-05-22 +1980-02-29 +2018-06-14 +1981-07-04 +1993-04-25 +2027-03-09 +1981-01-10 +\N +1974-06-16 +1985-10-15 +1988-01-11 +\N +1997-03-15 +1982-02-26 +1991-05-13 +2015-09-03 +\N +1987-06-05 +1971-03-29 +2002-03-28 +2032-03-25 +1983-07-23 +\N +1999-01-15 +2014-01-31 +2021-09-02 +1999-01-02 +2001-03-24 +2002-01-21 +2007-08-06 +\N +2032-09-07 +1984-11-22 +2035-06-18 +1994-07-29 +2023-04-20 +2010-02-17 +2014-08-23 +2017-02-08 +2036-10-24 +1994-11-14 +1979-04-19 +1973-10-09 +1999-01-17 +2012-07-08 +2012-12-03 +1980-11-22 +2002-02-28 +\N +\N +2023-03-01 +\N +1981-05-13 +2026-01-29 +2034-02-14 +2031-09-22 +1986-11-13 +2031-10-25 +2010-10-13 +2036-09-11 +2027-09-30 +\N +1990-07-28 +2001-05-05 +2029-01-10 +1972-10-09 +2012-03-25 +1987-04-06 +1998-06-04 +\N +2008-12-26 +\N +1979-04-23 +2010-01-16 +1982-07-23 +2013-08-21 +1982-12-23 +1997-04-03 +1986-02-20 +2034-09-08 +\N +1999-05-14 +1989-11-18 +1987-09-17 +\N +1978-05-03 +2005-05-22 +2001-02-11 +1980-07-28 +1984-04-11 +2010-06-02 +2023-09-05 +2020-12-29 +1997-06-06 +2005-10-01 +2023-01-17 +2013-04-27 +2018-05-23 +2022-11-23 +1971-05-09 +1997-12-05 +2008-08-22 +2025-05-23 +1989-02-18 +\N +2035-08-21 +1995-02-09 +1994-11-28 +1980-07-20 +1998-09-02 +1993-01-20 +1979-12-21 +2010-03-05 +\N +1989-01-07 +2015-10-09 +2006-08-23 +\N +2003-04-27 +2029-03-21 +2030-09-08 +2022-05-05 +1993-10-22 +2031-04-01 +2015-03-10 +1988-12-08 +2004-07-07 +1978-01-11 +2013-09-28 +2030-02-25 +2036-09-15 +2019-07-16 +2034-09-05 +2003-11-07 +2000-10-02 +2002-09-19 +2016-03-16 +1986-02-06 +2018-07-07 +1972-02-07 +2001-05-06 +1996-10-05 +1995-12-26 +1990-05-13 +1983-03-10 +2011-08-03 +2029-01-26 +2022-04-24 +1973-09-03 +2002-09-19 +1982-04-30 +2023-11-30 +1992-05-13 +\N +1986-05-11 +1976-12-31 +2009-10-23 +2022-02-08 +1999-02-24 +2007-09-30 +1991-11-03 +1985-06-14 +1973-06-15 +1972-07-18 +2012-07-05 +1989-01-17 +1982-02-13 +1994-11-23 +2008-08-01 +2032-11-22 +1976-01-04 +2037-04-11 +2011-02-24 +1992-11-16 +2025-11-25 +2013-02-08 +\N +2002-05-13 +1983-10-15 diff --git a/contrib/btree_gist/data/enum.data b/contrib/btree_gist/data/enum.data new file mode 100644 index 0000000..d03bfce --- /dev/null +++ b/contrib/btree_gist/data/enum.data @@ -0,0 +1,595 @@ +r +v +i +b +r +\N +y +v +g +o +y +b +o +o +o +o +v +r +i +o +b +r +g +b +i +o +r +r +r +\N +o +b +v +y +o +\N +i +o +o +g +g +b +y +v +g +g +\N +v +g +i +i +\N +v +y +i +r +\N +r +\N +g +\N +g +\N +v +g +y +v +r +v +r +v +y +i +i +v +y +v +i +b +i +i +r +r +\N +\N +y +r +g +i +y +i +i +r +g +y +\N +i +o +r +y +y +g +o +o +g +y +r +g +v +r +i +r +i +r +y +v +b +i +o +r +\N +o +i +v +o +b +\N +b +g +y +o +v +b +i +v +v +o +y +i +i +i +g +b +b +g +r +i +y +o +\N +r +\N +i +i +g +v +o +y +y +o +i +b +r +y +y +o +g +g +g +\N +y +o +v +g +y +g +v +\N +i +o +v +b +b +\N +y +v +\N +v +\N +i +\N +r +b +r +o +r +b +o +g +i +r +b +g +g +y +b +b +g +y +g +v +v +b +\N +i +v +y +b +b +o +g +b +v +g +g +b +\N +y +r +r +b +\N +r +g +i +o +v +\N +o +r +b +o +b +i +\N +\N +y +b +y +\N +i +i +i +o +y +o +i +b +o +g +r +\N +b +y +\N +g +b +y +y +o +o +b +g +i +i +v +b +o +o +v +i +g +i +o +r +o +i +i +r +b +g +o +o +y +v +g +g +g +r +o +i +i +g +\N +o +v +b +b +v +i +g +y +i +i +g +r +y +i +b +\N +g +y +o +\N +i +i +b +v +o +b +v +r +g +o +v +v +y +r +v +g +\N +v +v +b +y +o +g +i +o +b +r +y +r +v +b +b +\N +i +v +y +r +b +i +y +g +\N +g +r +y +y +g +b +o +v +r +i +g +r +b +b +b +\N +y +y +y +i +o +r +g +g +i +y +g +y +v +o +o +g +\N +b +v +o +y +r +\N +o +i +g +\N +i +i +i +o +b +\N +\N +b +\N +v +v +r +\N +o +b +r +o +b +o +r +y +\N +r +i +b +b +y +v +r +g +r +r +\N +g +\N +v +v +y +r +o +r +o +i +o +\N +r +\N +i +v +b +v +\N +b +r +v +o +\N +i +r +b +g +o +\N +o +g +r +v +y +g +v +r +b +r +v +o +g +i +i +g +i +y +b +i +y +r +y +o +r +b +y +y +b +y +g +b +\N +r +g +b +o +y +o +g +r +g +b +\N +v +v +v +g +b +y +v +o +v +g +o +g +i +b +v +i +r +r +i +b +i +b +o +\N +\N +y +r +g +v +o +y +\N +g +v +o +b +v +v +\N +r +v +y +g +b +o +v +b +v +b +r +r +i +r +v +y +v +y +o +v +g +i +r +o +o +i +y +r +\N +y +r +b +y +y +\N +b +\N +\N +i +v diff --git a/contrib/btree_gist/data/float4.data b/contrib/btree_gist/data/float4.data new file mode 100644 index 0000000..947955e --- /dev/null +++ b/contrib/btree_gist/data/float4.data @@ -0,0 +1,600 @@ +3926.686551 +-3078.593513 +-1523.429224 +-669.684166 +234.904986 +2226.918930 +-4718.612498 +2093.397950 +-1914.453566 +4027.226232 +391.864393 +4525.522995 +-158.177421 +-4801.751557 +3410.161728 +2481.256122 +-1640.183123 +1611.826046 +-2448.451169 +-1845.197308 +2683.842745 +-3542.172810 +3089.066696 +-2517.697386 +1892.967423 +727.945774 +-3513.884856 +-179.0 +\N +-1469.088605 +-4739.206973 +145.945556 +4549.133936 +-4208.637634 +-1936.179384 +4602.395105 +-1804.145345 +2053.716787 +\N +1456.927004 +4702.396402 +3268.547085 +-745.573619 +4625.357450 +-4765.915573 +-2790.475971 +-3403.123686 +-2817.878573 +-3274.289481 +-2413.766411 +-1374.753772 +-1924.610286 +475.397257 +3510.523368 +3814.010913 +-1859.390939 +168.847235 +2264.721811 +-2223.366707 +-1753.785013 +-3945.658993 +-4315.231992 +2746.639442 +-2342.824418 +-1044.805914 +3584.727458 +-3822.565833 +-2102.212746 +\N +1818.932987 +-3585.541038 +1452.891425 +1165.643202 +2412.440259 +-1608.959230 +-2272.316845 +3352.859341 +\N +1067.661511 +373.127556 +2907.255277 +\N +\N +-4119.844655 +425.751771 +-4652.598253 +3178.567043 +-2664.539180 +-1099.284997 +-2144.177909 +1805.341405 +\N +2945.853662 +-3351.437225 +-4875.769736 +\N +-412.211551 +-3334.087165 +1695.085786 +2535.912119 +\N +374.050255 +-4771.851972 +2954.420418 +1929.919923 +-3434.967221 +\N +2700.864965 +-2783.682392 +4985.334217 +-2062.958207 +4153.150501 +3012.590980 +-3011.189139 +\N +2721.497588 +-4169.312617 +-64.988860 +-4188.005384 +1744.599224 +1067.803538 +-1660.431575 +2804.799493 +3088.508415 +-4646.231299 +4578.547883 +-294.892171 +-561.912989 +1867.265677 +11.839394 +582.688896 +1502.454824 +\N +\N +4301.881413 +\N +-2194.430268 +-614.422083 +-690.354777 +4779.886957 +4531.156281 +4944.867236 +3858.390189 +3506.107763 +-4894.456207 +\N +1322.857973 +2578.630265 +-2175.318379 +-4086.425476 +4630.773663 +-3800.461310 +3753.750072 +\N +647.435317 +\N +4110.477110 +514.193226 +-3441.902748 +2573.758703 +664.845479 +-384.002535 +2023.851111 +-4615.566879 +\N +521.022754 +446.504990 +-3517.434806 +\N +1710.468068 +-988.509024 +-93.124302 +-2974.066063 +-1466.562229 +4606.999513 +291.009348 +771.456014 +172.233984 +-1228.758016 +-1273.510068 +3472.468191 +3783.157877 +-260.342455 +-4133.938523 +-1558.345359 +818.740365 +2345.368051 +4818.590693 +\N +-152.159210 +1395.434275 +-3107.620263 +4746.555020 +-189.023868 +3178.467591 +4922.804367 +-4963.738942 +\N +-2933.352626 +-3894.560951 +-1721.494834 +\N +-2491.926382 +-1659.638708 +3626.190726 +-1804.918329 +455.648226 +2244.824659 +2443.616389 +1022.869542 +3580.157235 +3348.054857 +-1537.855671 +1607.360266 +-1692.376280 +2292.201299 +3400.898151 +1889.594163 +239.448960 +4596.602567 +766.064066 +-1906.189177 +752.355324 +-2388.240838 +248.493708 +-4383.772933 +-3099.040067 +4912.247853 +2740.401308 +1334.028196 +-6.110885 +4208.217412 +2685.419516 +1947.814527 +4270.682796 +746.828278 +917.931886 +\N +-1105.016263 +-406.957355 +-2023.670192 +-477.481347 +-1056.222565 +-4039.906966 +3609.872045 +-3297.475124 +1972.339566 +-1621.688887 +-1030.170236 +-4464.104576 +-2638.849336 +1175.210125 +-483.963827 +-3362.699658 +456.088565 +-4952.708059 +4982.336056 +801.148906 +-1533.282710 +-1739.269000 +\N +-4390.957509 +4222.786482 +-4279.669281 +\N +4937.803242 +\N +2036.653081 +3409.729758 +3247.885061 +817.041977 +-822.372779 +2189.003680 +-3551.808846 +325.503762 +-2688.361261 +3833.177084 +2890.005446 +1325.965340 +-4132.875092 +-3011.022842 +1666.958698 +-4889.208372 +-157.283687 +-1516.214021 +3080.057571 +-2355.486070 +-539.260584 +-453.194364 +3574.346313 +-1012.181736 +1419.900389 +-3090.709731 +-811.664223 +1598.174198 +-1224.255663 +-1394.494598 +\N +2972.381398 +220.199877 +3542.561032 +-2168.024176 +-3305.714558 +\N +1754.880537 +3633.414739 +729.945999 +-2427.865606 +1648.939232 +-495.744751 +108.475352 +513.797465 +-3036.007973 +-3596.329479 +-3672.066260 +2261.791098 +-2077.391440 +-4873.215449 +4907.530537 +-3355.449754 +-3356.602051 +1232.493090 +-2822.598325 +1335.803283 +-1816.035000 +-479.029122 +\N +\N +4116.023187 +-259.211424 +-1636.301636 +151.567927 +\N +615.774279 +4981.348277 +-4279.760471 +2122.969975 +2331.666647 +-1270.180538 +-2166.953835 +4085.841327 +2906.495024 +3656.341992 +2993.227550 +2151.115144 +-2275.655664 +-3203.291138 +2257.053474 +-4566.818042 +-4214.319326 +2730.795260 +-4194.037830 +116.563907 +-3036.016760 +1681.412271 +-3805.177421 +-1747.152073 +-4864.378627 +-3938.731704 +4232.011228 +20.740053 +-1645.529264 +2544.557875 +\N +4777.722624 +-3356.018294 +1307.984314 +-1166.544462 +-1620.147366 +-137.408513 +724.484001 +-1343.453124 +4804.629749 +4522.789567 +2328.926240 +-1317.294201 +-4298.136513 +3398.895482 +3464.478505 +4943.133318 +3232.351173 +2893.212860 +1198.575174 +1121.555146 +-3552.881646 +4365.806104 +\N +2352.417529 +\N +4680.141932 +-2668.895399 +-238.973845 +-285.805471 +-3520.289230 +4878.743007 +1780.984535 +2832.673354 +4298.407435 +\N +-1608.563991 +1684.308879 +4737.577396 +4101.170539 +\N +-1464.015649 +\N +-3738.782163 +-3621.216579 +2780.429035 +2384.467403 +\N +552.970942 +3547.166413 +4900.754164 +4495.504335 +1607.961290 +4286.663369 +988.747024 +-2356.908041 +-1623.399549 +3724.461980 +-1540.581932 +561.640885 +3054.001451 +2224.754615 +3311.427291 +\N +351.611318 +4176.146704 +-2049.963552 +2267.505572 +-4955.461980 +1276.636327 +-1508.073735 +-1598.198477 +1602.926844 +2157.874181 +1669.786780 +-3082.299418 +\N +-1788.620850 +2555.355424 +-4870.338212 +-2851.518662 +3882.485213 +4641.214218 +2506.994706 +\N +-1108.634810 +-4278.677501 +-2160.498210 +-806.103356 +\N +880.145694 +1368.632529 +-2048.461217 +2899.266336 +-870.758219 +-2863.581156 +\N +1682.631744 +3266.096445 +2626.991285 +1248.276847 +-4262.887118 +2777.053997 +1327.535555 +-426.769580 +-314.249841 +729.344234 +3891.552519 +-4918.473703 +4922.770997 +\N +2679.172460 +\N +4879.287118 +-4635.792221 +4496.176890 +2007.650308 +3821.565699 +-500.003884 +-1126.098170 +4487.603870 +3855.490996 +-56.857325 +1215.173530 +-1892.555336 +-1632.647536 +1942.954095 +64.593039 +-4396.942081 +-1643.806415 +363.886503 +3916.018470 +-2067.613754 +-1717.717134 +-3333.186669 +-3027.546004 +968.184544 +1210.732165 +2708.135698 +1052.369823 +-4434.389802 +-853.166476 +-1686.386083 +2647.478668 +2690.547401 +-4642.268889 +-3997.579827 +2927.765217 +3461.773129 +781.051482 +2525.672604 +3121.858727 +-2270.808321 +2873.250371 +\N +4843.809511 +-1187.515681 +\N +\N +3528.188564 +-3487.236154 +845.094270 +3843.943537 +-558.746078 +-2471.493491 +611.999359 +-3199.954144 +-4988.404294 +4145.897291 +-1803.093537 +2876.372318 +-2474.919272 +4497.979139 +4625.114016 +-2221.654210 +4501.765724 +130.449252 +4534.131102 +1415.347330 +-993.720547 +\N +3443.961678 +-4849.518817 +4573.353608 +4818.748920 +4330.041823 +3161.612855 +3607.737095 +-119.757296 +-1339.207751 +-331.384328 +2228.158823 +2697.192313 +-4775.355388 +-3594.790915 +-3397.374660 +3837.461611 +-4558.641468 +-1624.755387 +4430.022700 +-1940.296994 +4832.148761 +-2888.874695 +2894.719014 +2866.099508 +-1161.805185 +-4395.337603 +-1091.088679 +-819.707958 +4596.183826 +-1154.086625 +2249.981569 +2996.321776 +457.294320 +-66.348014 +-513.412089 +1125.862285 +\N +-4243.177180 +-291.472275 +\N +-2609.517713 +-2621.961719 +2269.398848 +-2249.532369 +4686.007011 +\N +4873.354365 +3895.563571 +-2282.372172 +3784.030075 +-3485.082555 +2354.903229 +\N +-2944.317869 +-2485.837190 +3776.108724 diff --git a/contrib/btree_gist/data/float8.data b/contrib/btree_gist/data/float8.data new file mode 100644 index 0000000..ff21226 --- /dev/null +++ b/contrib/btree_gist/data/float8.data @@ -0,0 +1,600 @@ +-39017.848619 +17104.680681 +-9885.090245 +-931.243017 +-29740.660628 +-14665.622294 +46069.995126 +2910.093476 +7714.560143 +1722.339844 +-12287.580158 +-12735.100679 +\N +37831.578771 +-2603.424551 +-41339.385226 +-15583.453591 +8187.403650 +\N +48185.906931 +34727.898466 +-1521.592099 +13954.342745 +-31076.202626 +47465.550200 +-1890.0 +31784.675914 +\N +\N +\N +-29333.526259 +-38945.609512 +-17214.948343 +6871.406388 +\N +-16596.387078 +36261.907260 +-18049.183287 +4556.482264 +22448.246587 +24436.163890 +10228.695423 +35801.572346 +\N +-15378.556710 +16073.602655 +-16923.762796 +22922.012989 +34008.981513 +18895.941632 +2394.489596 +45966.025673 +7660.640663 +-19061.891767 +7523.553240 +-23882.408381 +2484.937083 +\N +-30990.400669 +\N +27404.013079 +13340.281957 +-61.108847 +42082.174119 +26854.195156 +19478.145274 +42706.827956 +7468.282784 +9179.318864 +-19064.482427 +-11050.162633 +-4069.573551 +-20236.701924 +-4774.813473 +-10562.225646 +-40399.069661 +36098.720453 +\N +19723.395663 +-16216.888868 +-10301.702358 +-44641.045758 +-26388.493356 +11752.101249 +-4839.638274 +-33626.996578 +4560.885650 +\N +49823.360564 +8011.489063 +-15332.827096 +-17392.689999 +-4894.502114 +-43909.575089 +42227.864821 +-42796.692808 +46124.794239 +49378.032423 +-14683.174325 +20366.530805 +34097.297575 +32478.850606 +8170.419772 +-8223.727792 +21890.036795 +-35518.088464 +3255.037616 +-26883.612609 +38331.770845 +28900.054460 +13259.653404 +-41328.750919 +-30110.228425 +16669.586984 +-48892.083717 +-1572.836866 +-15162.140208 +30800.575707 +-23554.860695 +-5392.605837 +-4531.943637 +35743.463126 +-10121.817356 +14199.003887 +-30907.097310 +\N +15981.741979 +-12242.556625 +\N +20565.261632 +29723.813981 +2201.998770 +35425.610321 +\N +-33057.145580 +18323.757159 +\N +36334.147391 +7299.459985 +-24278.656055 +16489.392317 +-4957.447506 +1084.753522 +5137.974655 +-30360.079734 +-35963.294788 +-36720.662604 +22617.910976 +-20773.914396 +-48732.154490 +49075.305368 +-33554.497540 +-33566.020515 +12324.930896 +-28225.983250 +13358.032826 +-18160.350001 +\N +-34552.779135 +\N +41160.231871 +-2592.114244 +-16363.016361 +1515.679272 +-25992.747897 +6157.742793 +49813.482771 +-42797.604712 +21229.699753 +23316.666471 +-12701.805384 +-21669.538352 +40858.413275 +29064.950244 +36563.419917 +29932.275498 +21511.151442 +-22756.556642 +-32032.911378 +22570.534736 +-45668.180425 +-42143.193256 +27307.952604 +-41940.378301 +1165.639074 +-30360.167604 +16814.122706 +-38051.774208 +-17471.520727 +-48643.786273 +-39387.317043 +42320.112275 +207.400532 +-16455.292640 +25445.578748 +31528.860601 +\N +-33560.182938 +13079.843141 +-11665.444617 +-16201.473664 +-1374.085132 +7244.840012 +-13434.531243 +48046.297486 +45227.895675 +\N +-13172.942010 +-42981.365134 +33988.954818 +34644.785051 +49431.333178 +32323.511728 +28932.128604 +11985.751736 +11215.551459 +-35528.816462 +43658.061043 +-25850.170886 +23524.175293 +17178.292092 +46801.419322 +-26688.953990 +-2389.738454 +-2858.054709 +-35202.892304 +48787.430068 +17809.845352 +28326.733540 +42984.074351 +-26945.552452 +-16085.639906 +16843.088794 +47375.773963 +41011.705385 +14885.293303 +\N +22111.838158 +-37387.821631 +-36212.165787 +27804.290353 +23844.674031 +-39495.544038 +5529.709419 +35471.664130 +\N +44955.043353 +16079.612899 +42866.633689 +\N +\N +-16233.995495 +37244.619796 +-15405.819316 +5616.408845 +30540.014515 +22247.546153 +33114.272907 +-45238.189304 +\N +41761.467043 +-20499.635521 +22675.055718 +\N +12766.363268 +-15080.737353 +-15981.984775 +16029.268441 +\N +16697.867805 +-30822.994179 +-36990.879004 +-17886.208495 +25553.554239 +\N +-28515.186616 +38824.852132 +46412.142178 +25069.947063 +14387.938736 +-11086.348100 +-42786.775014 +\N +-8061.033561 +37733.424496 +8801.456941 +13686.325291 +-20484.612170 +28992.663361 +-8707.582186 +-28635.811563 +5881.871775 +16826.317444 +32660.964449 +26269.912848 +12482.768466 +-42628.871180 +27770.539968 +13275.355549 +-4267.695804 +\N +\N +38915.525185 +\N +49227.709975 +45488.034233 +26791.724598 +20869.828235 +48792.871180 +-46357.922208 +44961.768899 +20076.503078 +38215.656992 +\N +-11260.981700 +44876.038700 +38554.909960 +-568.573247 +12151.735305 +-18925.553360 +-16326.475361 +\N +645.930390 +\N +-16438.064150 +3638.865032 +39160.184697 +-20676.137540 +-17177.171338 +-33331.866694 +-30275.460044 +9681.845438 +\N +27081.356979 +10523.698228 +-44343.898024 +\N +-16863.860826 +26474.786679 +26905.474009 +-46422.688894 +-39975.798265 +29277.652166 +34617.731294 +7810.514820 +25256.726041 +31218.587272 +-22708.083211 +28732.503708 +-679.955515 +48438.095114 +-11875.156808 +-17232.257113 +-9389.997907 +35281.885641 +-34872.361545 +\N +38439.435367 +-5587.460778 +-24714.934907 +6119.993588 +-31999.541438 +-49884.042935 +41458.972912 +-18030.935371 +\N +-24749.192723 +44979.791387 +46251.140161 +\N +45017.657240 +1304.492518 +45341.311020 +14153.473295 +-9937.205473 +-13588.917751 +34439.616783 +-48495.188168 +45733.536079 +48187.489201 +43300.418227 +31616.128553 +36077.370954 +-1197.572961 +-13392.077509 +-3313.843279 +22281.588229 +26971.923130 +-47753.553883 +-35947.909153 +-33973.746600 +38374.616107 +-45586.414680 +-16247.553875 +44300.227004 +-19402.969940 +48321.487614 +-28888.746947 +28947.190139 +28660.995084 +-11618.051846 +-43953.376028 +-10910.886787 +-8197.079579 +45961.838260 +\N +22499.815688 +29963.217759 +4572.943204 +-663.480140 +-5134.120889 +11258.622846 +22652.566979 +-42431.771798 +-2914.722754 +-26206.211502 +-26095.177129 +-26219.617192 +22693.988482 +-22495.323686 +46860.070106 +-25156.352611 +48733.543651 +38955.635712 +-22823.721717 +37840.300746 +-34850.825549 +23549.032292 +\N +-29443.178689 +-24858.371902 +37761.087244 +\N +46819.624164 +\N +-33693.144090 +40653.983453 +-26031.282486 +-24553.259497 +19714.503628 +-34474.345196 +8479.883456 +-33258.695939 +33087.950890 +17223.332481 +41600.595923 +-24101.627559 +45225.386273 +7144.885816 +-25243.179209 +-34920.445683 +-31098.240605 +-42560.678764 +16588.400754 +-35012.138861 +-8465.407211 +24885.269056 +1185.531892 +-42427.891210 +30568.514010 +-42910.065638 +-25427.667506 +49703.152850 +15339.563585 +-36162.816587 +\N +25849.401427 +30508.323238 +39964.989009 +-29568.012561 +-22074.867353 +\N +48713.154625 +-1520.744968 +36336.974374 +41062.088679 +-2003.634512 +-9037.801465 +28679.307540 +9146.625390 +23064.029018 +-37993.003516 +-16414.231186 +-2807.518818 +12352.072057 +13036.899205 +-9238.011185 +-45646.203680 +-26760.931302 +36327.588086 +-40820.465465 +-11081.759474 +15407.617374 +41678.071204 +\N +\N +-43507.700410 +24709.613796 +30447.305730 +-11054.192745 +\N +23353.155969 +46508.126145 +6981.355956 +21083.087321 +23906.590032 +38666.058652 +-1769.736340 +47240.657405 +-1764.098532 +-5218.443393 +-33834.449008 +-2977.604676 +-432.298309 +24849.356606 +18835.457959 +32506.030883 +-19205.680149 +43736.106527 +-665.804581 +-7721.326434 +3269.641033 +-49370.767409 +16273.904297 +662.390678 +17499.127457 +30066.082524 +-9073.769189 +28401.596043 +-16385.018437 +18027.110150 +\N +9804.530004 +12884.736463 +46511.284540 +-8051.955215 +-44326.810536 +-16389.214185 +41559.840176 +40672.067921 +1770.764334 +-15898.218176 +204.035430 +8434.125203 +-38309.302967 +-15565.175780 +-13882.281661 +-24141.443695 +44535.343952 +31507.292987 +23152.546758 +-39375.797840 +-38180.703478 +-415.027351 +\N +29397.380994 +\N +47904.062270 +-46523.514994 +22100.205660 +8730.225525 +-31533.990419 +11804.385093 +-30083.302585 +-37654.674513 +-43752.615104 +\N +-1452.872507 +-21721.060142 +8709.210511 +38917.112904 +\N +18148.277545 +\N +-2368.107416 +-34462.417841 +40637.684702 +-42576.995163 +\N +31314.380645 +-2993.921634 +-18056.906931 +26346.102998 +-31676.115180 +34094.465563 +7338.040814 +-27818.175767 +46722.334715 +-31033.022181 +-10355.819231 +12291.897909 +-35065.719851 +21197.209494 +-33092.215417 +\N +34034.608716 +-33796.447601 +2756.714939 +-43451.564896 +-26799.277904 diff --git a/contrib/btree_gist/data/inet.data b/contrib/btree_gist/data/inet.data new file mode 100644 index 0000000..687fcba --- /dev/null +++ b/contrib/btree_gist/data/inet.data @@ -0,0 +1,675 @@ +205.48.101.94 +64.191.16.251 +104.18.168.108 +32.163.254.222 +95.221.129.147 +183.253.140.85 +70.165.215.123 +84.170.107.43 +79.144.216.22 +\N +165.90.225.162 +238.233.177.15 +88.24.114.39 +\N +155.255.145.81 +83.13.81.117 +31.236.39.111 +31.223.45.140 +204.136.128.221 +\N +160.69.78.40 +88.170.171.22 +27.205.158.253 +121.179.104.153 +225.15.14.165 +1.180.121.239 +83.5.70.6 +\N +237.24.51.229 +120.151.214.171 +62.124.72.116 +253.74.141.202 +237.188.81.187 +61.252.190.144 +57.206.2.191 +\N +240.82.109.101 +209.125.201.244 +93.213.169.237 +139.112.18.173 +82.154.56.140 +\N +227.137.163.196 +69.77.51.75 +30.194.154.142 +193.185.41.198 +92.173.29.28 +103.28.183.154 +220.205.180.198 +74.216.214.72 +213.87.102.109 +240.47.114.57 +123.231.125.27 +134.239.185.20 +\N +113.195.56.93 +24.40.244.54 +172.109.167.148 +231.44.133.66 +44.67.142.219 +239.181.165.233 +124.235.41.48 +190.73.207.202 +74.159.254.108 +153.37.38.182 +189.99.7.199 +37.164.159.15 +105.9.31.250 +\N +4.16.24.165 +195.21.199.159 +162.106.77.88 +239.95.217.230 +150.197.150.14 +79.223.250.16 +65.207.143.228 +135.165.49.229 +91.1.57.212 +194.161.198.219 +\N +1.163.185.97 +131.96.207.198 +\N +216.88.243.136 +126.254.48.253 +\N +56.199.135.75 +165.11.118.48 +247.7.198.248 +106.96.249.227 +96.14.187.22 +\N +209.33.227.131 +136.206.43.175 +213.39.115.236 +\N +124.100.183.145 +2.254.243.185 +80.111.117.99 +200.56.244.221 +232.45.235.183 +92.190.136.92 +\N +194.45.213.168 +189.80.217.147 +221.149.51.2 +203.143.183.21 +10.76.215.130 +231.240.22.160 +228.107.124.145 +122.159.54.211 +249.175.223.152 +206.78.173.162 +176.177.135.225 +112.159.227.116 +\N +140.34.214.128 +60.215.174.18 +120.23.162.179 +\N +60.88.199.80 +\N +190.199.234.228 +167.52.107.219 +163.230.62.220 +114.126.128.119 +28.212.246.115 +\N +35.24.185.39 +74.11.153.183 +128.18.38.32 +56.38.113.145 +118.200.90.79 +90.216.40.68 +\N +184.157.233.95 +247.216.240.149 +201.160.3.208 +121.229.71.154 +197.172.114.23 +147.134.141.252 +63.69.81.68 +172.15.14.208 +74.66.194.128 +102.73.67.147 +147.202.215.148 +40.253.212.235 +222.168.227.51 +193.171.47.212 +254.123.253.233 +13.238.20.95 +6.240.85.220 +63.50.72.59 +138.149.213.250 +\N +204.155.97.217 +25.64.68.108 +175.95.119.68 +136.242.20.94 +218.65.176.89 +194.204.44.77 +147.246.187.105 +62.207.123.111 +\N +128.90.38.245 +213.206.241.70 +143.101.67.30 +155.201.184.79 +205.190.209.57 +44.237.228.229 +222.109.77.139 +32.140.24.250 +36.125.139.29 +149.166.225.18 +172.242.93.116 +215.147.44.173 +230.46.69.48 +4.184.53.45 +241.179.116.11 +220.179.63.168 +193.4.38.153 +148.229.44.205 +213.60.22.146 +59.133.135.50 +198.49.80.122 +45.252.129.164 +161.123.162.124 +112.30.20.29 +58.133.184.67 +9.201.58.3 +146.112.143.36 +143.157.113.68 +147.14.52.62 +205.165.6.112 +29.89.113.154 +66.17.234.63 +52.41.89.181 +241.211.1.109 +177.36.163.207 +13.161.5.32 +125.114.169.247 +8.152.34.248 +20.31.119.242 +234.86.171.182 +59.226.121.144 +157.156.134.72 +143.41.246.125 +244.148.162.224 +161.221.171.40 +128.12.105.10 +\N +211.96.181.118 +132.98.248.99 +128.151.39.43 +3.192.152.232 +206.13.203.250 +220.239.170.173 +149.215.24.9 +18.182.145.36 +179.212.151.153 +68.95.24.250 +223.255.215.176 +207.71.249.41 +60.90.154.16 +173.116.151.18 +121.111.63.82 +111.221.237.4 +238.209.54.62 +183.236.220.28 +126.186.123.78 +123.43.92.163 +89.23.85.100 +89.225.196.191 +85.136.41.16 +155.170.87.73 +31.13.161.188 +137.30.169.129 +78.32.92.76 +129.121.108.107 +78.239.221.76 +36.242.173.3 +151.134.174.87 +79.94.194.177 +\N +9.108.86.70 +5.65.207.234 +84.59.213.76 +20.230.161.43 +247.180.220.136 +67.151.49.171 +47.147.80.252 +74.190.254.29 +\N +111.24.200.106 +90.3.213.132 +110.101.207.168 +143.77.140.198 +\N +236.62.95.154 +56.251.21.190 +231.154.66.237 +169.30.40.6 +94.91.100.20 +113.49.232.34 +215.47.246.82 +169.224.7.29 +\N +37.231.196.152 +47.63.95.236 +181.49.112.52 +243.161.244.167 +175.242.48.116 +169.213.125.67 +196.130.108.140 +239.250.45.132 +35.136.41.79 +111.112.42.173 +29.151.75.38 +38.137.224.147 +64.101.177.59 +55.13.87.142 +131.53.181.224 +199.167.12.86 +168.11.48.234 +34.123.154.188 +213.4.129.9 +\N +101.134.51.130 +193.64.107.205 +49.43.91.47 +104.238.95.198 +138.189.159.157 +120.251.32.52 +153.214.200.197 +243.134.30.100 +135.52.111.34 +\N +112.42.87.159 +40.69.66.232 +207.81.62.124 +193.28.195.69 +55.96.199.235 +167.101.253.115 +\N +246.147.199.115 +193.79.112.101 +241.244.120.200 +\N +167.116.157.80 +102.31.171.101 +16.44.204.182 +34.17.92.190 +84.72.45.155 +193.109.167.147 +80.181.11.243 +130.181.212.219 +9.144.1.64 +246.224.132.58 +62.195.56.251 +142.66.251.66 +194.106.77.154 +\N +90.221.121.253 +15.163.194.138 +230.46.78.158 +46.105.50.131 +119.50.45.238 +248.225.135.21 +\N +124.214.84.154 +21.180.109.92 +115.101.89.130 +95.207.181.191 +125.235.193.182 +181.218.105.217 +133.89.141.43 +106.183.231.192 +115.35.116.107 +60.97.101.50 +\N +169.250.64.192 +120.241.254.238 +137.194.100.209 +48.16.35.136 +182.211.204.114 +40.99.67.49 +89.125.172.183 +104.228.203.245 +81.84.155.227 +1.112.197.117 +59.117.175.134 +58.214.124.144 +33.129.223.81 +126.143.252.69 +195.211.137.176 +208.14.45.76 +74.96.74.146 +\N +229.64.51.77 +65.21.152.189 +\N +114.101.237.200 +175.166.116.210 +87.134.226.114 +213.95.222.202 +30.2.239.190 +\N +159.81.159.223 +228.187.227.90 +\N +67.251.123.95 +162.251.195.17 +96.240.115.112 +233.87.71.43 +161.114.80.142 +140.113.203.25 +22.40.68.5 +180.139.2.40 +\N +111.38.231.216 +228.234.207.123 +\N +250.176.79.79 +59.107.193.142 +161.218.191.212 +96.37.54.203 +46.192.107.103 +71.197.52.178 +111.105.63.26 +139.58.62.200 +72.105.233.160 +239.87.14.72 +171.229.121.185 +240.220.164.57 +149.13.111.63 +163.49.238.5 +7.149.70.239 +248.242.205.103 +17.229.150.23 +134.55.46.252 +98.238.40.42 +\N +31.36.115.199 +64.234.158.9 +\N +32.69.44.86 +186.204.118.229 +\N +20.35.78.52 +132.47.83.153 +226.69.230.4 +33.33.156.254 +152.70.244.236 +247.180.160.113 +211.221.104.110 +129.124.231.41 +54.190.14.163 +49.180.34.117 +124.77.160.15 +52.3.82.192 +89.149.87.98 +67.71.146.173 +182.61.251.67 +14.180.19.120 +\N +66.218.5.209 +188.58.131.244 +128.157.228.197 +\N +223.221.76.172 +101.115.226.156 +229.17.33.101 +151.3.214.189 +37.180.117.157 +242.106.122.78 +30.95.165.92 +132.52.246.117 +\N +173.52.188.128 +118.223.229.41 +132.231.133.56 +135.235.133.171 +78.200.1.131 +\N +115.146.120.61 +20.96.157.214 +152.229.92.114 +109.190.145.204 +243.82.98.207 +\N +184.107.160.144 +39.2.129.97 +48.192.2.91 +221.151.237.221 +4.246.15.78 +210.161.249.39 +255.75.10.97 +228.249.129.27 +30.115.201.232 +246.215.8.102 +\N +63.16.75.23 +94.123.36.30 +132.61.79.239 +\N +105.151.204.126 +\N +243.229.8.172 +26.195.227.35 +219.206.181.101 +165.12.89.14 +62.24.41.190 +119.79.245.119 +202.197.197.152 +109.202.220.212 +35.183.214.65 +53.7.220.159 +\N +55.184.109.15 +\N +15.112.129.183 +44.124.131.125 +35.89.161.4 +220.242.200.101 +123.60.59.238 +211.223.96.183 +74.61.70.183 +\N +209.150.35.249 +240.232.193.155 +194.231.101.62 +\N +2.104.84.243 +221.162.167.181 +119.166.8.33 +40.72.241.71 +\N +159.208.215.103 +\N +61.22.131.30 +\N +41.119.175.142 +117.85.224.118 +\N +148.167.101.4 +45.106.131.138 +\N +94.189.41.3 +108.55.214.7 +\N +35.171.168.47 +90.252.21.131 +27.220.123.246 +20.78.135.63 +166.27.102.106 +142.222.1.91 +11.88.28.225 +38.175.101.188 +163.37.35.66 +12.97.128.208 +106.97.208.4 +\N +152.139.250.11 +66.153.27.211 +102.132.218.38 +199.142.41.164 +18.231.165.111 +138.109.241.13 +118.10.77.46 +146.27.170.90 +168.77.102.159 +226.198.128.192 +66.92.232.222 +47.27.194.20 +164.182.228.118 +105.131.236.121 +234.46.48.100 +118.34.237.203 +175.160.139.46 +163.208.222.249 +9.166.171.40 +227.230.225.180 +244.160.119.181 +126.211.169.225 +72.112.141.239 +220.198.141.154 +197.173.63.107 +229.208.36.32 +132.26.237.169 +203.241.185.28 +191.42.250.138 +\N +132.180.213.190 +190.210.77.219 +49.168.123.181 +78.189.91.119 +\N +195.144.143.245 +85.75.58.30 +148.26.97.224 +65.174.36.247 +203.110.226.93 +6.176.17.101 +\N +99.2.116.45 +203.207.156.164 +6.205.71.174 +146.246.43.100 +235.93.237.116 +158.220.15.72 +94.113.101.124 +51.194.52.42 +162.80.213.241 +\N +23.1.97.65 +133.240.185.226 +7.27.121.41 +192.28.209.195 +179.208.158.65 +145.159.157.167 +173.41.74.199 +96.106.28.103 +244.63.22.62 +\N +96.163.254.226 +58.221.131.199 +31.86.179.136 +127.219.60.48 +87.134.167.151 +135.52.126.134 +\N +47.109.125.45 +41.170.113.98 +165.216.170.67 +252.176.159.106 +69.227.163.227 +225.251.187.1 +40.202.43.19 +4.104.139.43 +249.245.11.156 +93.180.123.182 +113.67.34.90 +142.211.245.230 +63.6.54.114 +77.65.223.214 +59.233.170.32 +131.172.204.238 +234.156.241.152 +\N +8.91.22.29 +117.141.48.215 +79.171.208.203 +146.229.67.176 +66.85.44.114 +241.194.191.85 +63.255.71.88 +60.73.67.41 +48.149.137.56 +60.33.119.210 +220.121.61.208 +147.151.1.144 +\N +184.155.244.115 +97.151.107.25 +249.167.212.72 +142.137.230.31 +24.86.8.16 +28.117.109.25 +149.148.184.221 +106.99.191.123 +\N +65.14.167.10 +164.183.36.228 +52.175.120.249 +42.0.8.134 +223.54.80.64 +62.203.105.165 +144.148.207.249 +236.35.62.35 +\N +21.116.77.153 +114.242.119.6 +136.30.45.211 +153.121.70.120 +\N +216.116.169.127 +230.106.70.22 +96.164.134.139 +193.7.187.21 +35.121.231.194 +105.62.79.80 +224.235.238.193 +238.75.226.70 +176.162.92.173 +214.188.162.23 +162.58.151.104 +6.119.102.63 +56.63.61.114 +6.171.110.128 +51.155.66.187 +\N +107.50.227.215 +219.149.74.222 +23.110.108.137 +244.190.60.52 +21.210.197.77 +249.88.71.119 +\N +62.251.140.171 +62.118.73.196 +58.77.130.172 +233.131.155.245 +59.164.211.253 +218.33.169.7 +\N diff --git a/contrib/btree_gist/data/int2.data b/contrib/btree_gist/data/int2.data new file mode 100644 index 0000000..b5d1157 --- /dev/null +++ b/contrib/btree_gist/data/int2.data @@ -0,0 +1,600 @@ +3093 +4550 +-3556 +-3363 +324 +-149 +-265 +-3432 +1122 +-2534 +-4263 +660 +-2263 +-468 +-3605 +-663 +-4713 +\N +1361 +\N +\N +2681 +-550 +\N +1935 +\N +2575 +-171 +-1557 +3897 +\N +3732 +3315 +237 +4012 +-254 +2564 +-4842 +620 +4255 +3282 +-4427 +802 +845 +\N +-62 +-4461 +2569 +4086 +-3787 +-2087 +2765 +2746 +\N +-1826 +4876 +1981 +-297 +-469 +168 +-3594 +2289 +\N +-3682 +2553 +2462 +3596 +-1129 +-4537 +\N +-3733 +2268 +2876 +-2178 +3610 +-969 +-3189 +142 +2196 +-1401 +\N +1827 +-2279 +2810 +-1459 +\N +798 +-1397 +2246 +266 +1489 +-4491 +566 +\N +\N +\N +-165 +\N +-36 +1695 +-2135 +1563 +3973 +-2041 +-1513 +-4820 +-702 +\N +3401 +\N +-3200 +3674 +-118 +-3972 +3888 +-1619 +3231 +-276 +-2622 +-2011 +-3959 +-1561 +1120 +2912 +-593 +-1062 +1368 +3198 +-1471 +4271 +2054 +2468 +-4511 +1104 +-287 +-4836 +1065 +\N +-3437 +-2120 +-2238 +1023 +-4333 +-412 +998 +1641 +3612 +3564 +4220 +\N +\N +4226 +-3195 +-3571 +-1505 +-3115 +494 +\N +-368 +-3459 +-2643 +\N +-620 +-210 +-4658 +2523 +-2482 +-3778 +1833 +509 +-4993 +\N +3208 +4760 +\N +-4898 +4324 +-3045 +-4555 +-3364 +1192 +4268 +-2170 +4229 +2952 +-3249 +-950 +2566 +\N +-2026 +4611 +\N +3042 +\N +1671 +-4874 +1518 +-1875 +1600 +-4570 +-295 +-2710 +-4290 +101 +2180 +-1385 +1438 +\N +-4992 +721 +1137 +1424 +4064 +1424 +842 +4073 +-4554 +1501 +-1886 +3087 +-3732 +1560 +2136 +\N +986 +-3083 +1062 +1557 +-465 +-2744 +-130 +3663 +1220 +-3390 +1491 +-652 +-3981 +-233 +3559 +4461 +-3630 +1080 +\N +-1289 +4821 +3670 +-965 +-969 +-784 +2877 +-772 +-1881 +-270 +-1645 +-1618 +-450 +-1353 +351 +-4492 +-1721 +-7 +-4545 +\N +2080 +987 +1645 +-4774 +-1215 +-2215 +-2675 +3747 +1664 +-4609 +1177 +1368 +-4010 +-3768 +2435 +-825 +-963 +-442 +\N +-991 +-3702 +2751 +1388 +808 +-2548 +3015 +-338 +811 +485 +-4178 +-1229 +\N +3246 +-3976 +-1296 +-3479 +-2069 +4391 +\N +-1462 +325 +4877 +2559 +-3954 +3456 +-476 +-2739 +-2737 +568 +-60 +-4371 +-4966 +-329 +4131 +\N +-1841 +-2219 +-2604 +776 +-4635 +1972 +-3333 +-4281 +-3046 +-1781 +2941 +-3399 +-938 +-4728 +-517 +\N +-3832 +-1096 +3094 +1949 +2494 +4686 +-3597 +-4346 +-437 +2490 +1625 +-4445 +752 +-3302 +2966 +-3870 +\N +-2594 +3754 +-186 +-3611 +3738 +473 +3706 +-4756 +1184 +-927 +-984 +2760 +\N +-456 +3202 +-1251 +894 +\N +-2776 +2545 +1566 +-4043 +-4577 +-1113 +-4865 +-4459 +188 +232 +4559 +2355 +1747 +4140 +1051 +3989 +-430 +-3136 +-2172 +-1734 +927 +-1278 +1051 +2750 +18 +-2888 +-273 +-2380 +-3709 +-2889 +-4733 +1064 +-3281 +-4176 +-427 +-2789 +-4779 +\N +-4472 +538 +3726 +3873 +-2263 +2414 +\N +3628 +-4210 +-1475 +2938 +859 +\N +-3648 +3644 +3967 +835 +1922 +-2946 +4903 +1090 +\N +4057 +-2531 +2858 +558 +4570 +-1384 +362 +-135 +2864 +\N +-1880 +1697 +-103 +980 +-2721 +2015 +490 +2561 +-1806 +-2533 +-672 +-108 +-2162 +613 +-2769 +618 +-4370 +3117 +-1692 +-1547 +-1034 +-4782 +502 +3117 +4913 +-4492 +188 +917 +465 +962 +-3792 +-1346 +-3899 +848 +-4530 +-3025 +1265 +3701 +2046 +3451 +\N +-2628 +1758 +\N +4732 +983 +\N +370 +-3513 +-147 +2705 +-1167 +-4962 +1453 +1196 +-1890 +648 +-3137 +-158 +-499 +4982 +-2287 +-1767 +3892 +-3490 +\N +-4331 +3110 +1613 +4379 +-2047 +3137 +713 +\N +-2956 +2247 +2580 +3198 +-2453 +4420 +3192 +-658 +-3912 +3018 +-234 +-3049 +\N +-2705 +2225 +-455 +4140 +145 +2198 +-4912 +-2327 +-2366 +-1114 +-172 +-748 +-2851 +-1596 +-4257 +-4451 +2070 +2161 +535 +1714 +\N +4808 +-924 +-1990 +3560 +-1490 +-3458 +310 +1562 +-4423 +-1375 +1169 +2452 +-2944 +-1091 +-2156 +3378 +-4723 +-4549 +4893 +-4295 +4103 +-1213 +4542 +4192 +228 +\N +566 +-2829 +750 +-2819 +\N +54 +-745 +-2611 +-464 +2098 +255 +-2791 +110 +-1020 +1313 +3705 +-3461 +511 +2079 +-3852 +1177 +2751 +1023 +-3970 +-264 +-148 +2085 diff --git a/contrib/btree_gist/data/int4.data b/contrib/btree_gist/data/int4.data new file mode 100644 index 0000000..66c4206 --- /dev/null +++ b/contrib/btree_gist/data/int4.data @@ -0,0 +1,600 @@ +-278383369 +-747067264 +-444347454 +943059602 +760126298 +-313957087 +69871373 +1047228793 +549563771 +-849172813 +742152707 +-102126707 +-588216468 +-587679276 +121951908 +-12908316 +-938566787 +-1066376866 +-70757527 +887181957 +-416180675 +-395333602 +-476539199 +-559145553 +166603938 +-995460982 +423400029 +-715704678 +-919392688 +-654070990 +-382421072 +631560114 +-729854703 +-201487528 +-1015231681 +-111081607 +-150879882 +-822966764 +-235383789 +664472573 +418592852 +535580213 +1006220943 +-772410787 +-933257976 +-93888809 +534823075 +348910859 +-954633892 +161536478 +-709184521 +636988950 +-831019781 +\N +-557156033 +806151247 +-39951046 +-775508567 +\N +101482645 +795803474 +-1021371457 +254190941 +-198991605 +-211220357 +592642976 +159564926 +-97980046 +687623361 +-268671335 +192044603 +133023521 +-596127139 +546530680 +336306348 +-868276189 +-982853141 +-239020404 +-1044842390 +-957480672 +40377556 +49864481 +\N +505742335 +375096111 +\N +225695129 +856735725 +-92417405 +-673413352 +-466398428 +-372271859 +199069838 +-274470034 +225807793 +590462307 +3775258 +-620113195 +-58682105 +-511020053 +-796515704 +-620432149 +-1016328347 +228460677 +-704579869 +-896860429 +\N +\N +-1026387226 +-974499141 +-960283687 +115614636 +800148518 +831654092 +\N +518360994 +\N +779114556 +-904139163 +-316769082 +630950752 +\N +-55878393 +-783306330 +\N +852002029 +179256342 +412777993 +-632735583 +1052807112 +234006613 +528787810 +871273145 +-543437833 +613656227 +119746596 +981397980 +-297248487 +77649335 +-28913402 +614934531 +929940199 +-403660500 +364477031 +-22124387 +210404268 +\N +432660770 +105294608 +\N +-387805780 +-543871814 +-144364887 +-23259185 +-464225973 +131691359 +-594587949 +132792222 +-938482467 +669399965 +-363432139 +-332135297 +-222020946 +-1026899728 +107886324 +669465840 +1055107642 +-964572059 +\N +196967371 +99878136 +206583116 +-814267157 +-288969105 +-837297170 +182146005 +-972850964 +-649642105 +271750475 +794876066 +439409907 +741102414 +-896013712 +-564452917 +377584215 +785128489 +1016266473 +211032352 +-860051372 +79557251 +-754415346 +-31656520 +580909957 +-250670377 +-1065498478 +312055768 +256848844 +-405941467 +139226727 +-673719257 +-33907454 +-107071600 +\N +-491151352 +-379374978 +835717683 +-749442365 +391779355 +-930175254 +667789784 +346287223 +940296232 +-439643120 +673648096 +153155023 +761570957 +-634729660 +482454328 +553985095 +686753002 +-526797052 +949281215 +685507933 +-141218391 +-840157851 +648180473 +-50333869 +-654708416 +1040352328 +-580798126 +477781101 +-97755360 +889044981 +31130520 +472121821 +-1054910894 +-499784982 +-508051907 +-239312888 +-36874490 +-160560442 +-612146051 +\N +-914254016 +-955919451 +444572750 +464011997 +114922279 +368088103 +\N +1032465403 +-198412817 +-427355634 +764561021 +-319968927 +-742495989 +66602125 +335474465 +\N +-295255877 +250999059 +526539904 +-632305975 +\N +-462933479 +725444821 +-1014220649 +-976950894 +1050779420 +-922337893 +881175599 +-260490744 +975381286 +900263262 +48922968 +-598347542 +121514864 +-607528559 +\N +-605421206 +-1033560416 +11538600 +-160000018 +-560613615 +-99553157 +450510072 +54811495 +-599428633 +23616163 +-218984524 +281874162 +795632049 +-743284993 +109773364 +446413166 +-827212018 +\N +590703606 +219585528 +-852527230 +-56743535 +-31888159 +447756894 +81597117 +-514906619 +829002820 +194782415 +-861939426 +789235257 +1002760541 +77986645 +124541179 +839847275 +688643973 +\N +-818330059 +\N +\N +571483597 +1043237102 +\N +-260795164 +\N +-80635223 +883946580 +-997239646 +417377536 +\N +905148727 +75086673 +240863213 +-974803948 +-514042316 +780396140 +-773002241 +-839043143 +-1050961764 +-944793472 +971511639 +862816002 +-87163815 +\N +601763342 +409313850 +-376132854 +-104608772 +793131268 +-402748367 +103010521 +589866409 +\N +-806207129 +-92467487 +382080273 +-537053065 +296991677 +450543728 +36073823 +-518334030 +\N +-823745191 +567487602 +82546145 +-388387055 +862364138 +1063492312 +\N +-758175718 +910291639 +-161138360 +-397892038 +-543779825 +-678530933 +-245281302 +1038476033 +992074486 +248548658 +\N +98497175 +-171252301 +18217558 +682347077 +\N +310612886 +-118028644 +337185821 +-210791469 +301448323 +-906803508 +-356807299 +\N +\N +\N +559848005 +\N +31316502 +-952852111 +-906201820 +86880549 +-1067074828 +-554439888 +-372251154 +767868870 +485181743 +\N +234322120 +\N +469476289 +120818420 +973859819 +553938979 +666244197 +-1034134335 +-643847604 +-928942436 +-40098720 +913202598 +-634013637 +-124514124 +88422182 +362344229 +-955696569 +-883518062 +-589118966 +-917271271 +-789577219 +-1055370400 +653530707 +751509060 +-923377500 +-896739670 +1049674425 +713830537 +-919850486 +\N +464008848 +-500611805 +1052904894 +1005514228 +-464230048 +940348010 +-521487154 +196319826 +-827331032 +606501605 +419601479 +\N +\N +-763299772 +-551662126 +-996151300 +656870503 +569347075 +-768904837 +\N +-364654847 +-352793070 +-560639559 +\N +69488952 +-236949153 +8643035 +722046549 +772759204 +934327163 +952426653 +-992250784 +-688516267 +\N +23371654 +-277401737 +\N +392817327 +-455495167 +208331771 +76220225 +-965469751 +-117604765 +134109425 +-286846823 +-976521495 +359070260 +-1033796401 +397275542 +928779613 +\N +-479010011 +-408187297 +\N +-544847539 +\N +-328541470 +862677577 +-742793122 +-221057238 +-743659309 +-165126079 +-241769542 +-634439156 +228542599 +70091032 +763329904 +-269836367 +-86158045 +\N +279028361 +-575426235 +-726811335 +260613141 +176220403 +-722725936 +800015615 +-223367075 +338624364 +195939828 +-751945238 +348787257 +-997923024 +-8605001 +-568010250 +\N +-962777966 +870581366 +-713036908 +-307366031 +1058650551 +-8016540 +604533525 +774656817 +-148155805 +194042034 +944121304 +502530876 +-728423603 +567787452 +1051834880 +-1050394394 +843329917 +-583580255 +\N +814196699 +1041013209 +-532683489 +644480699 +-1064568183 +-302358011 +66224442 +-779159623 +373241073 +652059193 +153158612 +1019561792 +-159367912 +764825007 +277019219 +539033342 +-99541768 +-748934480 +683758505 +\N +110932412 +875195202 +154556209 +109603003 +32090837 +-763742564 +-76548222 +-910116085 +675149363 +-813726843 +-366033349 +995685874 +-987030706 +-135458917 +985423987 +-293582113 +88167527 +962971281 +\N +345458787 +244011716 +530986493 +-985258624 +-972350494 +84247136 +854078794 +377796622 +-324275021 +-878489620 +864262985 +180124912 +\N +-793083147 +-352435244 +-462586860 +-74556106 +28209937 +222627005 +\N +-760794594 diff --git a/contrib/btree_gist/data/int8.data b/contrib/btree_gist/data/int8.data new file mode 100644 index 0000000..9deb47b --- /dev/null +++ b/contrib/btree_gist/data/int8.data @@ -0,0 +1,600 @@ +571162582773129 +-968633726566092 +-84469929177915858 +4162557330816307 +37260777124104530 +-4777143170215921 +-18226963197866476 +-8625828876661828 +7656239724464679 +-5489288588377948 +-63386438189169149 +75567289191112039 +-41526404145192890 +-4533895872892689 +-82739169202936497 +6911236575041352 +75842816214481185 +\N +7669153338841222 +-37026243121878430 +-3175877126637642 +-16007995163334251 +-3395795664631244 +63935989156878460 +-16954091159640632 +5318858710978884 +-3108641393657255 +55102218209821952 +-8798896163938228 +-2840321381342671 +73738755119374157 +59644096131825710 +-84322607121172895 +-60180910135826388 +-2124570924863354 +-10333657186851396 +58703296194197255 +56676327154787999 +8481545099866909 +464571291354841 +\N +-32049226128340060 +\N +62491046169211228 +-4178902379487935 +-58704035165707105 +-4370744691117233 +-1145699597209467 +71898480208016040 +40210785139855015 +30111868125808287 +3442459016537352 +\N +-9847254040823009 +28204892100790341 +58222430175280391 +-5250734726525414 +\N +7331215572768482 +\N +7855555216013852 +-8445953483741985 +-553872842685130 +3758966176486019 +\N +68254106110727666 +-779085030760061 +9530941396584628 +17210298132952098 +15654503142207692 +-12642726199954453 +95074133209723437 +-44100539106198634 +-42897443120872206 +-70803370174642981 +53824237192501978 +14091118778760 +-72030601214124822 +27500286125861367 +-4058285263735350 +\N +850104221271283 +101571367179586565 +-105758499180483916 +14231743102597792 +9115311735961929 +-5264208557722402 +1462317488951483 +88960681204192702 +79197578144317932 +32261563115032881 +-19665360148210462 +-94405000169758831 +\N +5681300149925625 +5558458639524735 +-81538564141087644 +5508723459108571 +-29815658164026313 +46416288118123076 +-2080732169659869 +\N +6778478169280557 +-51815253164990834 +-5552057233004262 +-3530123878334630 +\N +-79781393192459088 +81091470135265063 +-3174546872057439 +-43491832133972989 +-26029833130989740 +3551109553373570 +\N +-982183979826234 +13323171191699130 +-3271539392992822 +478227196042750 +-41112576193858203 +97184864103977346 +73232664160658748 +3461370054794093 +5763508331306600 +-54067138148025345 +-75460101103530266 +-315484375600487 +9769190516994377 +\N +-72400967138661448 +\N +-10234725547449166 +-6051348037059350 +-2952261266043490 +75387914189279439 +3625967156008709 +-3766224622641800 +5898363257062410 +7698143425285206 +66546389146435468 +31200605132406177 +-3455213658576548 +47528730130302036 +62432830201286045 +-88472988116517129 +6825920650153638 +-46045869146143591 +6326172435402615 +-5919362998522005 +36401689161832477 +80326684899441 +4144471433608822 +5800063247595057 +-44301689196562406 +-6466479342162519 +-89535305105978555 +37464982130305086 +60232609173699392 +98323565101041419 +40099224107628429 +3226497172413119 +9238219853212754 +-1319461392548924 +97305838213564393 +752930892266536 +28408158162749842 +-87755317206819285 +80611167141629044 +-94055745195646002 +-73914297162645226 +-8246391150836051 +48938555118563058 +\N +-21228069105374351 +-31494183206308739 +\N +\N +-559081218598528 +17582315157740574 +74577592104106586 +-66735635209305671 +68257070213090596 +-6488749444380913 +-36968819122130413 +-35640469185246031 +9784971155581421 +21965956184257469 +-33025198141891978 +49224647180407910 +5142127206085466 +-40935100123530887 +533636213233375 +105489717166223849 +-131230197744959 +41828998199086393 +1971243766433517 +-873934263916195 +-2268220620617840 +\N +-2212273711508266 +2523744596981136 +97944271015587 +1720454174447185 +-1051086313079088 +-91905196206426419 +-31531876151110971 +69747799124920022 +4700849531099668 +-57732117189691029 +2847488818621365 +357976652379232 +-32560447173517911 +-1158053397641905 +-21736437137866308 +-17430356141694709 +-29946543151537742 +4728756183450097 +-70989678146724148 +78026986123049651 +3541070096728143 +1103371642176407 +-78857021155945773 +-104651503212762593 +-72082479133841767 +2868615668375126 +-7420152739539637 +-556652372234871 +-55819000120597832 +-91907154152964612 +-2727691960839202 +62416504185893525 +4619484558504848 +484698539302511 +5864338017307905 +-65197962143482233 +\N +90881747107819569 +54643963175081891 +\N +-34792399104423355 +-28850436210552816 +5001330979085503 +72990723181773288 +69414211169505452 +\N +-55512818157891961 +10050528050060089 +-613762631776546 +38246351168205376 +-5786513272830532 +101738698195446145 +\N +-77765032167083438 +-84816033119249140 +105242892203914398 +92055393128607360 +-34862239187356390 +12061146172958360 +7111235810225910 +8968206663351549 +-106417733134789736 +-34321050141796771 +3585839741182305 +-38410339162250019 +-61235896190749913 +53837300138272042 +-9188389860977835 +81031910126275164 +-43990369169635449 +-61494936120005403 +70138886163788387 +-91544802167010959 +-9164807100625716 +835704521750764 +97684808164908969 +1047818917821302 +43113961189441677 +-24182774203744737 +-1221002133469832 +-35060838149098801 +\N +8409585462972514 +-7157963742358126 +26000275165530950 +-9522779489052570 +56854170165153244 +-85847371170247458 +16772952161612585 +-48765236169076760 +10402001581872476 +\N +18148261189922237 +-53074917120516766 +\N +6176962454225695 +9932356559664520 +\N +-2133998678192202 +-104142621205586199 +92986938175269298 +-257176878614913 +47849345165295943 +-7719754534416116 +-9789607872482825 +-41667560211143782 +62163616168923197 +-9438915483943230 +9870229482590359 +64345519117194501 +-11025440131551888 +-91121534101114861 +-5603896551067982 +\N +104654486191030769 +8126142532532604 +2739168944145437 +810913166922484 +-2275160635018706 +-5590175254646458 +-74033091125584591 +71055832144361004 +-51757850204494955 +-5420931332383096 +-91398360142997499 +-18179323160814887 +-91113201173019563 +-54605499214110886 +-7765905699369496 +65516124193198338 +-47405416136137551 +\N +-430277287965650 +19642228156903804 +-35249292101345079 +2799652787535702 +-57468661185387080 +-23797897140461786 +4713853950884486 +53063490172759270 +-97546793157524699 +14992347152649764 +83034727103573700 +-378837396167658 +-6394357106445826 +40448837177180348 +93922571105944376 +70215001351267 +1422473144953269 +-19485771168366142 +3871292384756271 +27669760207256401 +-9519109972178611 +87342599111176867 +438163125486326 +-3342596077562208 +95638921175035575 +-8455888025381745 +-5553812170504574 +1028731887465695 +1874801639655453 +-6460339926511384 +12935207104254160 +18702887190948042 +38973128194072985 +-74007477194642941 +48329901174621299 +-38776912163952004 +73217306123132502 +10033544840731274 +\N +-7106499073575381 +-72577317113294180 +-5755101056886868 +-2153332538359850 +3675308624690700 +-103461942101729940 +-64592601205133764 +31745072163056028 +-8827303101164453 +\N +9920716477549680 +19269113140876068 +10346764378482915 +-9547067291656372 +\N +68899277111163150 +-5154067635858711 +94977075194786589 +87693228128699438 +7485821479753669 +-8472668186271245 +2589559354466271 +107349961123032263 +-78000859126368058 +3033692340944986 +-56442118204131253 +\N +8313139926300583 +\N +20174427169387802 +-20817519112818564 +29328370152711004 +-10310696988128501 +6742185960379702 +9975388579236356 +\N +-75913155126775951 +-47356034144438153 +64337514105141385 +84133515165177048 +85904591142596001 +154102825333184 +82448968210511128 +\N +-4132665145813622 +457257666629329 +\N +-65756686131235045 +5770121211587146 +\N +-681329651615480 +18252938164428001 +\N +-964719458030678 +-4727044638245516 +14382487198474686 +104931686129854363 +\N +-524490443508808 +-5409828867122403 +-9181546297405233 +148961126577670 +-457861622113867 +\N +-8291535678315181 +31193366146847993 +-29045667151925792 +76199591212655385 +101708151208313969 +\N +7611981289809249 +-4063202412293642 +93382613193471632 +-66793190128641761 +2119337550577124 +50006559150465155 +-37785078129030470 +67070256141072548 +-57427591153091745 +\N +56930159168628907 +6262653086116434 +1812020175980923 +-60824628201994696 +-41445956104770511 +-98561174197889445 +65081378137952117 +-4709913937816332 +15168293135976771 +-79444556211593680 +54524573183207193 +\N +80599107103738466 +-15080292202625850 +3599922292681710 +10548113488081741 +-16095559175518178 +77523877187189428 +-1425168149826835 +3224427236896674 +-35199492127195051 +-6069164398850959 +9796493012969240 +1655940150099642 +5593028453315162 +54543795101679613 +-4901907019878396 +-16277768201853340 +42702008211664372 +100465864162056788 +\N +-7259772955295969 +-43344869168379475 +2387994273960108 +6813412213220101 +-35045975201264603 +-4169039115935033 +-3465320744627564 +\N +-8774505320366729 +-10234221080903233 +\N +-33431287210348996 +-18686684154709649 +11065021143131398 +98202891135320415 +-4804117047782001 +-8602172936734534 +1510532842674793 +\N +-48895594123983692 +1551674012705189 +-9340844216200742 +45730748190013165 +94523139162337073 +2609069738447319 +7695297236865360 +-18562157167827878 +-1388967693233129 +-3838287274649635 +36110661195956474 +-5738342872254390 +-5116252556595312 +7682319991347229 +55542449158580255 +-3007003760543423 +7296692443452118 +1155927925107625 +7543653043525059 +\N +-94909898165568234 +41218109154904764 +6633352313198796 +-15778782124332590 +103491985174098803 +-1784322577952292 +-9096880634185423 +5075356316566121 +5795491194207193 +70413333187654805 +60851035164462435 +-81411679171424293 +89150284196809557 +-8141710960632445 +21739482143854904 +104172284182307094 +-9010915383273451 +102243779193394851 +1446591350291789 +20672486141595277 +-10451707129953600 +3808314557084451 +4202888939347042 +61647148205322240 +\N +79376379132868914 +8760919795930088 +-45661711132605083 +-970917635875382 +\N +-105361387199605165 +-4047373221704350 +50014571178988278 +\N +-1945986026058284 +-70697134125038763 +968299419395944 +-54310692168796488 +-57071775143358867 +634196190032528 +87012477120875722 +1524418494420369 +4675805785402153 +-6649246646077956 +29400170100479605 +-88735135555188 +958687447945394 +36322738110509385 +-4530787153175354 +7246095930595264 +25762424121462081 +-101889593195744622 +-12299006125085795 +-8970808434914172 +-6316433271930092 +-4071744674545863 +12598555199787281 +64522791141639046 +91810857119110814 +-63801044209819006 +-8216052623004959 +\N +-43255707123485242 +4483587649977559 +-7266625765593156 +-3085636132347609 +-72544126194422740 +-42452203161494667 +\N +-65498988106251024 +-53102696163635269 +6547188826700461 +13150951201167246 +10110681961565834 +-73380538100200918 +98881899210477509 +21804516135251682 +-78292833188851738 +-31304191599521 +-21287065172320779 +3221074184065080 diff --git a/contrib/btree_gist/data/interval.data b/contrib/btree_gist/data/interval.data new file mode 100644 index 0000000..af418c2 --- /dev/null +++ b/contrib/btree_gist/data/interval.data @@ -0,0 +1,612 @@ +1748 days 11:26:21 +-2771 days -11:53:23 +-6230 days -20:26:04 +8945 days 14:56:37 +3581 days 23:33:20 +199 days 21:21:23 +\N +\N +-3568 days -06:07:25 +-3738 days -10:16:35 +1798 days 04:44:12 +10847 days 22:03:35 +-9527 days -15:38:39 +9648 days 10:24:38 +2256 days 12:07:24 +-4327 days -18:09:39 +-10857 days -13:31:07 +-5953 days -22:31:57 +6040 days 10:40:15 +10316 days 08:23:28 +-6639 days -19:43:47 +-8066 days -00:23:40 +2249 days 15:40:53 +10792 days 15:10:06 +-6302 days -16:08:38 +-12058 days -02:55:05 +-5738 days -10:39:26 +-10108 days -21:09:51 +-58 days -02:15:25 +-12240 days -03:38:13 +-59 days -11:53:16 +799 days 02:04:53 +7911 days 23:12:01 +242 days 03:57:29 +-390 days -13:23:08 +-5179 days -18:01:34 +-8577 days -17:10:05 +-11369 days -02:10:38 +-332 days -12:27:59 +1421 days 05:21:36 +-1310 days -17:18:48 +11773 days 16:56:50 +-4543 days -00:45:49 +-662 days -15:27:34 +-4847 days -13:30:15 +-7971 days -06:18:34 +1757 days 09:59:42 +3574 days 17:17:41 +2119 days 23:22:46 +-2935 days -00:26:37 +-3700 days -15:35:25 +-12058 days -21:45:31 +1503 days 09:38:15 +10519 days 23:34:24 +3575 days 18:19:24 +-10828 days -04:14:35 +-10607 days -12:28:31 +-938 days -02:09:57 +3453 days 17:25:05 +\N +-548 days -16:15:50 +-2594 days -09:39:39 +9024 days 05:44:35 +3370 days 21:44:34 +5502 days 15:10:21 +-11567 days -18:48:51 +-8329 days -12:27:53 +-6252 days -20:27:48 +9289 days 07:37:58 +8992 days 06:37:03 +-394 days -20:24:39 +-7805 days -00:20:59 +-12405 days -02:00:04 +11065 days 23:25:32 +-5991 days -05:00:15 +-2667 days -22:58:04 +7340 days 03:25:50 +-3432 days -02:02:37 +6979 days 12:44:26 +8861 days 12:43:57 +10377 days 06:21:51 +-1995 days -03:16:36 +-136 days -20:25:24 +4901 days 00:01:21 +-2662 days -17:36:30 +-10533 days -00:09:32 +90 days 15:09:55 +8068 days 00:10:48 +-10598 days -23:22:17 +10085 days 00:31:05 +-8281 days -13:41:52 +3990 days 05:29:52 +-8972 days -12:32:59 +-2321 days -22:15:46 +6416 days 13:43:59 +7215 days 15:02:19 +2076 days 01:43:17 +-3553 days -07:10:15 +8218 days 00:01:00 +-5766 days -19:25:21 +7727 days 10:36:16 +3414 days 01:24:30 +-8957 days -15:29:24 +7902 days 12:30:47 +-1183 days -04:35:24 +10302 days 09:31:47 +6163 days 06:46:15 +-10350 days -16:27:57 +1994 days 09:38:44 +7490 days 03:19:36 +4127 days 01:50:41 +\N +-4752 days -00:03:05 +9072 days 06:11:14 +5565 days 12:40:05 +11477 days 07:08:16 +155 days 08:17:54 +-10799 days -21:17:31 +6978 days 06:44:44 +-11813 days -04:32:46 +1322 days 23:25:33 +-4172 days -07:54:00 +-6942 days -21:56:44 +1335 days 21:19:11 +523 days 18:08:28 +220 days 19:05:42 +-1801 days -17:12:44 +9763 days 05:23:55 +-10965 days -20:16:30 +6490 days 09:05:30 +-11980 days -18:36:02 +2954 days 00:41:14 +-7538 days -14:38:35 +-2728 days -06:27:30 +-4375 days -16:56:38 +-5275 days -17:22:29 +-12473 days -22:15:51 +\N +2846 days 01:22:08 +8534 days 23:36:42 +10552 days 08:10:01 +1321 days 07:26:00 +-3600 days -00:48:35 +7951 days 17:51:17 +183 days 06:52:48 +-8472 days -18:01:29 +-936 days -12:03:46 +5467 days 14:23:59 +-8553 days -02:53:57 +-11491 days -03:21:12 +-10648 days -02:26:43 +-2966 days -06:58:23 +-12431 days -06:22:30 +-9162 days -11:37:47 +2698 days 22:29:45 +4416 days 08:44:27 +482 days 16:02:41 +-9629 days -22:39:29 +10917 days 12:49:36 +-3495 days -07:20:56 +5625 days 20:16:12 +1548 days 10:21:09 +-12923 days -05:28:48 +-2310 days -07:49:09 +7677 days 13:21:01 +8530 days 06:54:22 +-2696 days -09:55:01 +7343 days 01:40:45 +-4009 days -08:22:33 +7190 days 21:23:41 +1974 days 22:02:10 +6034 days 22:30:57 +-11697 days -04:47:36 +10012 days 08:11:09 +1204 days 14:58:21 +4667 days 23:00:42 +-10120 days -01:41:49 +8885 days 22:17:23 +-995 days -14:46:27 +565 days 02:23:32 +8068 days 19:50:34 +6715 days 00:51:20 +-2833 days -02:13:10 +-7675 days -19:12:28 +-6696 days -06:23:39 +1911 days 12:17:48 +-1128 days -04:26:05 +-7445 days -08:21:53 +-3892 days -14:50:27 +-5745 days -12:46:01 +-7389 days -21:27:09 +11436 days 05:06:06 +-2183 days -17:44:21 +-8301 days -19:05:16 +4941 days 10:04:16 +-1342 days -00:42:15 +-12044 days -01:16:13 +2758 days 20:28:33 +2832 days 18:05:22 +8076 days 00:20:56 +1457 days 19:01:58 +3509 days 06:02:22 +8288 days 14:43:44 +-7354 days -22:38:15 +-1453 days -23:14:42 +-7944 days -18:27:40 +-239 days -13:56:15 +-9699 days -18:33:50 +-10236 days -01:21:09 +-7188 days -09:39:59 +3234 days 15:52:22 +-10441 days -02:00:33 +-4574 days -17:32:16 +5013 days 04:00:31 +-4047 days -11:12:00 +\N +-10041 days -01:03:19 +-12435 days -15:40:06 +-11694 days -07:11:46 +-434 days -05:39:07 +-19 days -16:31:13 +-4947 days -03:22:20 +6049 days 18:30:28 +-5790 days -13:09:39 +480 days 23:23:21 +2155 days 05:03:12 +2438 days 18:45:31 +7113 days 01:18:52 +-3259 days -19:19:31 +-9646 days -06:50:26 +-7176 days -15:17:24 +10588 days 22:57:01 +7427 days 09:25:16 +-7762 days -14:43:54 +3761 days 15:53:03 +290 days 15:40:32 +5955 days 19:07:54 +-7101 days -15:58:50 +1283 days 15:54:51 +-1856 days -20:27:35 +3953 days 03:13:19 +\N +6286 days 00:33:36 +10668 days 05:28:51 +11000 days 14:01:35 +4973 days 08:38:59 +7503 days 08:55:14 +2837 days 06:51:40 +-2162 days -17:29:50 +9735 days 22:31:27 +-12643 days -09:03:02 +-3099 days -23:05:58 +3574 days 14:34:48 +-8487 days -16:33:27 +-3814 days -13:46:46 +-247 days -13:57:10 +109 days 02:01:52 +6894 days 03:39:53 +2791 days 23:24:16 +5763 days 15:52:42 +4160 days 22:20:52 +-3047 days -15:41:28 +10129 days 05:48:14 +655 days 21:22:30 +-1249 days -13:46:55 +-670 days -09:57:10 +8853 days 20:36:22 +31 days 23:14:00 +9370 days 13:03:26 +3914 days 00:30:54 +5921 days 22:43:34 +-3753 days -06:14:50 +9099 days 02:17:12 +2865 days 11:17:43 +2057 days 15:01:53 +-4108 days -10:23:00 +-5066 days -21:37:35 +2863 days 22:15:47 +-5654 days -14:37:25 +-11724 days -08:14:41 +-9317 days -17:40:37 +-12683 days -19:56:10 +-10364 days -01:28:57 +-12270 days -05:47:08 +-12181 days -06:26:48 +8378 days 09:56:31 +-9114 days -01:00:41 +-1174 days -22:58:50 +-9308 days -12:04:06 +1535 days 01:19:45 +-7855 days -05:25:15 +4205 days 00:35:17 +10507 days 04:27:33 +\N +-566 days -01:36:09 +-11306 days -14:17:28 +-10463 days -09:44:02 +-2679 days -12:29:05 +7233 days 00:29:37 +-2959 days -14:45:29 +7409 days 12:12:27 +-2951 days -00:37:54 +6076 days 00:27:14 +1989 days 12:23:54 +-6286 days -00:15:17 +-5485 days -14:17:50 +-8739 days -17:42:24 +3875 days 11:24:17 +-3004 days -13:30:19 +10383 days 10:08:29 +-8261 days -01:24:41 +-4398 days -10:43:25 +8738 days 00:40:22 +6148 days 23:56:32 +-5789 days -14:22:50 +4747 days 18:58:16 +11555 days 21:52:56 +7034 days 06:35:38 +-12510 days -12:02:30 +-7087 days -08:28:34 +-7587 days -23:09:54 +4831 days 16:01:13 +-7746 days -16:43:47 +1962 days 11:08:54 +-7050 days -07:09:35 +-2595 days -08:02:15 +3135 days 16:20:43 +4114 days 06:42:47 +6542 days 05:09:18 +-11449 days -20:54:48 +9299 days 12:41:02 +4299 days 05:30:41 +-196 days -14:43:53 +9134 days 16:01:15 +10910 days 00:53:35 +-10974 days -17:26:16 +2952 days 07:37:10 +-8030 days -20:45:05 +-4037 days -05:51:34 +7553 days 01:05:58 +-2253 days -15:17:36 +-3808 days -13:47:12 +-5893 days -02:28:14 +8697 days 09:10:41 +-12560 days -21:45:19 +\N +-6808 days -23:17:31 +-9275 days -06:24:17 +-4576 days -04:40:00 +-926 days -01:54:23 +-516 days -05:17:15 +4180 days 07:04:20 +-9826 days -23:33:11 +-77 days -03:56:25 +5599 days 05:02:56 +1247 days 11:33:16 +-11522 days -22:07:08 +1513 days 19:37:30 +-4664 days -17:59:08 +1202 days 14:16:15 +-5539 days -05:18:35 +-12706 days -23:17:47 +1735 days 00:57:48 +3471 days 20:40:18 +1365 days 00:44:23 +-8385 days -09:01:45 +7335 days 06:33:44 +-9736 days -06:09:59 +-8423 days -16:05:19 +-5411 days -17:11:17 +4304 days 01:12:07 +-4230 days -03:34:05 +-6570 days -22:06:27 +3576 days 05:13:19 +-2792 days -04:07:12 +-11528 days -13:42:31 +6526 days 19:45:07 +488 days 12:28:03 +2206 days 11:11:23 +10428 days 23:47:56 +-11168 days -16:23:59 +-689 days -15:58:43 +-5443 days -06:59:01 +-8505 days -05:59:07 +-6971 days -16:02:43 +5759 days 02:47:33 +-8460 days -12:54:34 +-6811 days -04:41:10 +161 days 04:05:43 +-3793 days -19:54:41 +5175 days 16:56:13 +9629 days 06:41:59 +-1957 days -20:57:17 +1386 days 02:52:34 +-11433 days -12:15:19 +6995 days 08:16:54 +-5440 days -13:50:07 +\N +-12569 days -02:07:24 +-745 days -23:01:35 +-12126 days -09:59:26 +-6827 days -06:16:45 +5415 days 05:31:17 +-8860 days -05:00:30 +4263 days 22:01:16 +5529 days 19:33:03 +-9951 days -04:31:38 +4518 days 23:16:10 +-7559 days -02:17:48 +-3262 days -00:19:03 +3369 days 12:43:16 +7394 days 01:50:49 +-8384 days -05:35:48 +10399 days 22:57:43 +-3315 days -07:19:47 +3558 days 10:47:06 +-11365 days -08:52:35 +7162 days 05:55:08 +-15 days -11:28:55 +-1488 days -21:17:32 +1888 days 11:33:40 +6764 days 16:00:41 +3693 days 20:33:29 +3903 days 20:46:46 +4438 days 19:02:47 +10668 days 19:53:49 +9658 days 03:06:51 +9572 days 18:13:11 +11185 days 19:57:35 +11347 days 15:46:14 +2565 days 21:15:51 +-1429 days -16:46:30 +-7691 days -15:31:38 +-199 days -22:25:06 +-2118 days -16:03:13 +3371 days 12:32:24 +-12416 days -21:18:04 +11799 days 18:51:26 +-5976 days -19:33:37 +5980 days 13:44:23 +-1778 days -17:18:48 +-4636 days -17:45:06 +-8502 days -15:11:20 +-3732 days -14:34:28 +-7282 days -18:28:40 +5062 days 13:33:02 +6399 days 13:41:08 +2291 days 09:09:56 +9458 days 16:06:07 +7678 days 05:05:33 +-12054 days -13:31:00 +-3362 days -07:41:50 +-5693 days -21:32:36 +6597 days 19:10:20 +5161 days 09:56:55 +-9860 days -17:18:35 +-2293 days -23:12:04 +10459 days 13:16:26 +2265 days 13:04:27 +10313 days 14:45:01 +10054 days 22:24:22 +2984 days 07:57:12 +-5791 days -03:00:07 +6534 days 08:35:43 +-11438 days -14:25:10 +-6859 days -16:50:55 +-8909 days -10:22:34 +-3517 days -21:18:12 +7480 days 00:21:27 +-795 days -15:06:11 +-9404 days -23:24:01 +7663 days 01:41:12 +-713 days -01:01:42 +1650 days 12:24:05 +-7495 days -05:04:58 +4000 days 21:11:36 +1138 days 18:33:07 +3944 days 15:56:41 +3289 days 17:22:03 +-8837 days -21:25:58 +-4677 days -15:32:13 +-10750 days -23:04:22 +10140 days 05:58:50 +6143 days 21:16:02 +3566 days 23:49:41 +5887 days 06:12:51 +5423 days 20:58:45 +5379 days 11:15:53 +2123 days 03:34:50 +-9389 days -22:55:12 +1357 days 02:09:47 +10142 days 04:25:36 +-6926 days -18:43:31 +-6424 days -21:14:28 +2982 days 10:44:03 +4922 days 11:17:28 +-2045 days -18:22:51 +-8943 days -13:02:39 +6900 days 17:08:21 +2510 days 06:39:59 +-1835 days -22:15:12 +\N +9024 days 00:03:49 +-2045 days -06:37:43 +6892 days 06:31:32 +-9844 days -06:17:20 +2870 days 23:46:12 +-5579 days -06:20:34 +-5641 days -22:13:10 +10487 days 10:27:35 +-7233 days -17:17:43 +-11226 days -05:05:14 +-5268 days -18:53:33 +-5999 days -09:32:42 +11656 days 06:29:49 +-8175 days -17:55:50 +10402 days 10:08:40 +-6480 days -11:33:32 +1328 days 08:48:12 +-2460 days -20:50:25 +-4343 days -20:26:12 +-12476 days -16:52:49 +-8221 days -00:19:12 +10934 days 04:29:47 +1567 days 06:22:11 +2907 days 10:19:15 +9512 days 13:17:14 +\N +10031 days 05:58:55 +7973 days 06:57:28 +9828 days 05:58:27 +-6372 days -11:15:50 +10012 days 15:27:44 +-9097 days -03:49:51 +-7206 days -00:00:34 +1026 days 00:49:11 +-1597 days -12:20:05 +-8648 days -00:44:16 +-9790 days -02:52:49 +-981 days -04:10:39 +-7541 days -05:47:56 +-7105 days -15:37:54 +-1748 days -11:26:21 +-4519 days -23:19:44 +-7979 days -07:52:25 +7197 days 03:30:16 +1833 days 12:06:59 +-1548 days -14:04:58 +-5316 days -17:33:46 +-5486 days -21:42:56 +49 days 17:17:51 +9099 days 10:37:14 +-11276 days -03:05:00 +7899 days 22:58:17 +508 days 00:41:03 +-6076 days -05:36:00 +-12606 days -00:57:28 +-7702 days -09:58:18 +4291 days 23:13:54 +8567 days 20:57:07 +-8388 days -07:10:08 +-9814 days -11:50:01 +501 days 04:14:32 +9044 days 03:43:45 +-8051 days -03:34:59 +-13806 days -14:21:26 +-7486 days -22:05:47 +-11857 days -08:36:12 +-1806 days -13:41:46 +-13988 days -15:04:34 +-1807 days -23:19:37 +-949 days -09:21:28 +6163 days 11:45:40 +-1506 days -07:28:52 +-2139 days -00:49:29 +-6928 days -05:27:55 +-10326 days -04:36:26 +-13117 days -13:36:59 +-2080 days -23:54:20 +-327 days -06:04:45 +-3059 days -04:45:09 +10025 days 05:30:29 +-6291 days -12:12:10 +-2411 days -02:53:55 +-6596 days -00:56:36 +-9719 days -17:44:55 +8 days 22:33:21 +1826 days 05:51:20 +371 days 11:56:25 +-4683 days -11:52:58 +-5449 days -03:01:46 +-13807 days -09:11:52 +-245 days -01:48:06 +8771 days 12:08:03 +1827 days 06:53:03 +-12576 days -15:40:56 +-12355 days -23:54:52 +-2686 days -13:36:18 +1705 days 05:58:44 +-2297 days -03:42:11 +-4342 days -21:06:00 +7275 days 18:18:14 +1622 days 10:18:13 +3754 days 03:44:00 +-13316 days -06:15:12 +-10077 days -23:54:14 +-8001 days -07:54:09 +7540 days 20:11:37 +7243 days 19:10:42 +-2143 days -07:51:00 +-9553 days -11:47:20 +-14153 days -13:26:25 diff --git a/contrib/btree_gist/data/macaddr.data b/contrib/btree_gist/data/macaddr.data new file mode 100644 index 0000000..12ef530 --- /dev/null +++ b/contrib/btree_gist/data/macaddr.data @@ -0,0 +1,644 @@ +4a:31:57:23:9b:54 +e2:cc:11:0d:3a:a5 +a9:2b:32:79:3e:60 +53:1c:b9:1d:db:c7 +\N +70:8a:83:2b:c0:23 +4c:55:af:1e:79:f9 +b9:4c:7c:39:c1:ee +c5:83:ab:17:63:50 +b4:cf:fc:c4:fe:db +12:33:a4:22:4e:a4 +99:db:22:10:3f:ae +f4:31:5b:3a:26:c4 +\N +db:a5:1a:67:bf:b5 +9c:c6:3f:59:ec:96 +8f:52:b2:83:41:b4 +49:ac:2a:44:a4:28 +0c:51:53:4e:10:79 +e5:54:31:9d:51:d9 +d0:8d:79:bd:4f:a1 +ee:ba:f1:4f:a1:bc +76:25:97:80:8b:a7 +3a:dd:50:3b:82:d0 +34:8d:ca:fd:24:fc +2f:f9:aa:93:0e:70 +93:29:d0:c2:7a:cb +3a:a8:a7:c9:9c:5b +8f:6c:0b:a1:1a:22 +ce:4a:b7:a6:59:9e +\N +\N +e5:bd:83:62:cb:59 +ba:d9:ed:2f:68:58 +48:18:7a:6c:cc:2f +76:03:b0:d3:ae:9d +ef:3c:db:66:d9:82 +2d:73:91:dd:fb:69 +33:35:c3:7d:50:49 +50:a3:e9:24:18:32 +66:eb:ab:3a:14:2e +a9:13:53:36:ee:95 +1f:4f:b4:57:34:0a +60:26:2c:17:5c:d8 +01:43:b5:79:eb:0f +c0:94:5d:b5:41:2c +fb:e6:51:d6:49:9c +87:7a:23:90:e9:df +78:52:28:a5:f9:1f +38:b2:03:d7:5d:b3 +bf:b5:5e:99:80:9f +7c:8f:73:5c:07:95 +ba:b8:6b:d2:ef:ab +9f:25:81:eb:33:0a +82:cb:7c:44:6a:5f +ca:46:2b:3c:cf:7f +17:64:2a:89:cf:59 +\N +35:59:4c:9d:5a:0d +c9:5e:a4:dd:bb:ae +d0:90:56:ef:30:05 +bd:2d:22:41:42:5e +be:49:c2:de:66:22 +21:cb:55:17:01:c8 +22:00:5c:e5:9b:0d +23:c8:5f:22:33:ca +01:02:37:05:4f:36 +91:ba:08:fd:9e:ed +bf:ad:23:1f:3e:4d +c4:a6:c5:bd:89:dc +b9:13:9f:85:d9:fa +7d:b2:c9:07:4f:bc +\N +7a:84:d0:53:46:30 +f7:32:0c:87:f0:2f +77:15:22:cd:68:a0 +17:f9:26:35:6e:73 +d7:21:4d:fe:43:22 +63:13:3a:af:94:e9 +f3:11:3c:68:ef:5e +76:5a:06:33:31:fe +63:d9:b8:ef:ec:36 +3b:f3:4d:c4:22:e6 +d0:a2:cb:f9:f1:0b +95:0e:76:bc:99:ee +ae:e7:64:d9:ce:5b +ff:5a:6f:a2:26:9a +75:96:d4:72:db:49 +a8:ac:2a:2a:23:67 +db:4e:14:4d:b1:7c +45:fd:c8:52:05:cf +91:7b:e7:6e:23:07 +71:50:0c:46:78:3d +a9:90:9b:c0:b4:fc +47:0e:9a:82:af:48 +e8:1c:cf:c0:16:d1 +e1:6a:42:25:89:c3 +\N +55:8e:63:3b:44:c8 +93:8a:78:62:38:70 +29:81:d2:3d:12:80 +3e:d1:1e:94:2f:5d +fb:8c:c4:ee:84:7c +\N +cc:ad:28:56:b2:4a +52:f6:13:94:b7:03 +a2:8d:f8:f9:f8:c0 +57:68:00:9c:1b:f8 +a6:e2:31:88:8a:b4 +41:f7:0a:bd:47:08 +bf:5c:7d:64:08:73 +33:cc:60:43:05:e1 +cd:a5:a8:b0:99:14 +ef:fd:75:f1:3e:99 +b8:b0:b8:85:80:53 +7b:1c:fc:5c:b2:c8 +92:38:0c:df:81:57 +1e:ab:d4:18:c5:3a +b0:a1:c6:88:a3:29 +c7:37:ec:30:2b:7c +5e:5b:32:94:60:f7 +9d:ab:93:1f:de:1d +ff:32:af:ef:10:2e +f8:54:03:77:0d:e0 +49:e4:f8:f7:63:41 +18:f7:95:42:ea:32 +f8:a2:40:af:9e:af +1d:e2:71:c3:be:6e +\N +6a:38:d7:a5:b7:cd +df:51:52:74:c6:d4 +f2:05:5e:d2:1e:f3 +85:a7:b9:dd:80:72 +ae:ad:f5:1f:d1:7e +cf:71:9a:9c:b4:4d +\N +ac:8d:0f:5b:63:21 +c4:f0:3e:23:9e:7d +45:91:19:00:06:fa +c6:b6:30:24:e2:c5 +48:e1:e1:4a:8f:99 +cd:74:2c:39:14:e3 +9b:bb:32:10:ec:14 +a5:3c:a6:c9:64:14 +bc:ea:9c:03:76:75 +d5:29:75:05:8a:5d +\N +37:92:8f:cb:f1:e5 +e5:eb:1b:0e:02:58 +9f:9c:a2:22:a4:25 +26:f4:f4:e6:b8:cb +dd:79:87:70:0b:e7 +80:87:95:20:cd:ce +0d:28:2a:d1:0f:e2 +05:b8:34:36:55:b0 +89:03:74:cd:43:a9 +74:10:93:21:1e:04 +27:bb:a9:3a:7b:f0 +73:3c:06:b1:51:ad +47:0e:59:c0:5d:38 +13:13:e1:1c:aa:25 +4a:31:57:23:9b:54 +e2:cc:11:0d:3a:a5 +a9:2b:32:79:3e:60 +53:1c:b9:1d:db:c7 +\N +70:8a:83:2b:c0:23 +4c:55:af:1e:79:f9 +b9:4c:7c:39:c1:ee +c5:83:ab:17:63:50 +b4:cf:fc:c4:fe:db +12:33:a4:22:4e:a4 +99:db:22:10:3f:ae +f4:31:5b:3a:26:c4 +\N +db:a5:1a:67:bf:b5 +9c:c6:3f:59:ec:96 +8f:52:b2:83:41:b4 +49:ac:2a:44:a4:28 +0c:51:53:4e:10:79 +e5:54:31:9d:51:d9 +d0:8d:79:bd:4f:a1 +ee:ba:f1:4f:a1:bc +76:25:97:80:8b:a7 +3a:dd:50:3b:82:d0 +34:8d:ca:fd:24:fc +2f:f9:aa:93:0e:70 +93:29:d0:c2:7a:cb +3a:a8:a7:c9:9c:5b +8f:6c:0b:a1:1a:22 +ce:4a:b7:a6:59:9e +\N +\N +e5:bd:83:62:cb:59 +ba:d9:ed:2f:68:58 +48:18:7a:6c:cc:2f +76:03:b0:d3:ae:9d +ef:3c:db:66:d9:82 +2d:73:91:dd:fb:69 +33:35:c3:7d:50:49 +50:a3:e9:24:18:32 +66:eb:ab:3a:14:2e +a9:13:53:36:ee:95 +1f:4f:b4:57:34:0a +60:26:2c:17:5c:d8 +01:43:b5:79:eb:0f +c0:94:5d:b5:41:2c +fb:e6:51:d6:49:9c +87:7a:23:90:e9:df +78:52:28:a5:f9:1f +38:b2:03:d7:5d:b3 +bf:b5:5e:99:80:9f +7c:8f:73:5c:07:95 +ba:b8:6b:d2:ef:ab +9f:25:81:eb:33:0a +82:cb:7c:44:6a:5f +ca:46:2b:3c:cf:7f +17:64:2a:89:cf:59 +\N +35:59:4c:9d:5a:0d +c9:5e:a4:dd:bb:ae +d0:90:56:ef:30:05 +bd:2d:22:41:42:5e +be:49:c2:de:66:22 +21:cb:55:17:01:c8 +22:00:5c:e5:9b:0d +23:c8:5f:22:33:ca +01:02:37:05:4f:36 +91:ba:08:fd:9e:ed +bf:ad:23:1f:3e:4d +c4:a6:c5:bd:89:dc +b9:13:9f:85:d9:fa +7d:b2:c9:07:4f:bc +\N +7a:84:d0:53:46:30 +f7:32:0c:87:f0:2f +77:15:22:cd:68:a0 +17:f9:26:35:6e:73 +d7:21:4d:fe:43:22 +63:13:3a:af:94:e9 +f3:11:3c:68:ef:5e +76:5a:06:33:31:fe +63:d9:b8:ef:ec:36 +3b:f3:4d:c4:22:e6 +d0:a2:cb:f9:f1:0b +95:0e:76:bc:99:ee +ae:e7:64:d9:ce:5b +ff:5a:6f:a2:26:9a +75:96:d4:72:db:49 +a8:ac:2a:2a:23:67 +db:4e:14:4d:b1:7c +45:fd:c8:52:05:cf +91:7b:e7:6e:23:07 +71:50:0c:46:78:3d +a9:90:9b:c0:b4:fc +47:0e:9a:82:af:48 +e8:1c:cf:c0:16:d1 +e1:6a:42:25:89:c3 +\N +55:8e:63:3b:44:c8 +93:8a:78:62:38:70 +29:81:d2:3d:12:80 +3e:d1:1e:94:2f:5d +fb:8c:c4:ee:84:7c +\N +cc:ad:28:56:b2:4a +52:f6:13:94:b7:03 +a2:8d:f8:f9:f8:c0 +57:68:00:9c:1b:f8 +a6:e2:31:88:8a:b4 +41:f7:0a:bd:47:08 +bf:5c:7d:64:08:73 +33:cc:60:43:05:e1 +cd:a5:a8:b0:99:14 +ef:fd:75:f1:3e:99 +b8:b0:b8:85:80:53 +7b:1c:fc:5c:b2:c8 +92:38:0c:df:81:57 +1e:ab:d4:18:c5:3a +b0:a1:c6:88:a3:29 +c7:37:ec:30:2b:7c +5e:5b:32:94:60:f7 +9d:ab:93:1f:de:1d +ff:32:af:ef:10:2e +f8:54:03:77:0d:e0 +49:e4:f8:f7:63:41 +18:f7:95:42:ea:32 +f8:a2:40:af:9e:af +1d:e2:71:c3:be:6e +\N +6a:38:d7:a5:b7:cd +df:51:52:74:c6:d4 +f2:05:5e:d2:1e:f3 +85:a7:b9:dd:80:72 +ae:ad:f5:1f:d1:7e +cf:71:9a:9c:b4:4d +\N +ac:8d:0f:5b:63:21 +c4:f0:3e:23:9e:7d +45:91:19:00:06:fa +c6:b6:30:24:e2:c5 +48:e1:e1:4a:8f:99 +cd:74:2c:39:14:e3 +9b:bb:32:10:ec:14 +a5:3c:a6:c9:64:14 +bc:ea:9c:03:76:75 +d5:29:75:05:8a:5d +\N +37:92:8f:cb:f1:e5 +e5:eb:1b:0e:02:58 +9f:9c:a2:22:a4:25 +26:f4:f4:e6:b8:cb +dd:79:87:70:0b:e7 +80:87:95:20:cd:ce +0d:28:2a:d1:0f:e2 +05:b8:34:36:55:b0 +89:03:74:cd:43:a9 +74:10:93:21:1e:04 +27:bb:a9:3a:7b:f0 +73:3c:06:b1:51:ad +47:0e:59:c0:5d:38 +13:13:e1:1c:aa:25 +4a:31:57:23:9b:54 +e2:cc:11:0d:3a:a5 +a9:2b:32:79:3e:60 +53:1c:b9:1d:db:c7 +\N +70:8a:83:2b:c0:23 +4c:55:af:1e:79:f9 +b9:4c:7c:39:c1:ee +c5:83:ab:17:63:50 +b4:cf:fc:c4:fe:db +12:33:a4:22:4e:a4 +99:db:22:10:3f:ae +f4:31:5b:3a:26:c4 +\N +db:a5:1a:67:bf:b5 +9c:c6:3f:59:ec:96 +8f:52:b2:83:41:b4 +49:ac:2a:44:a4:28 +0c:51:53:4e:10:79 +e5:54:31:9d:51:d9 +d0:8d:79:bd:4f:a1 +ee:ba:f1:4f:a1:bc +76:25:97:80:8b:a7 +3a:dd:50:3b:82:d0 +34:8d:ca:fd:24:fc +2f:f9:aa:93:0e:70 +93:29:d0:c2:7a:cb +3a:a8:a7:c9:9c:5b +8f:6c:0b:a1:1a:22 +ce:4a:b7:a6:59:9e +\N +\N +e5:bd:83:62:cb:59 +ba:d9:ed:2f:68:58 +48:18:7a:6c:cc:2f +76:03:b0:d3:ae:9d +ef:3c:db:66:d9:82 +2d:73:91:dd:fb:69 +33:35:c3:7d:50:49 +50:a3:e9:24:18:32 +66:eb:ab:3a:14:2e +a9:13:53:36:ee:95 +1f:4f:b4:57:34:0a +60:26:2c:17:5c:d8 +01:43:b5:79:eb:0f +c0:94:5d:b5:41:2c +fb:e6:51:d6:49:9c +87:7a:23:90:e9:df +78:52:28:a5:f9:1f +38:b2:03:d7:5d:b3 +bf:b5:5e:99:80:9f +7c:8f:73:5c:07:95 +ba:b8:6b:d2:ef:ab +9f:25:81:eb:33:0a +82:cb:7c:44:6a:5f +ca:46:2b:3c:cf:7f +17:64:2a:89:cf:59 +\N +35:59:4c:9d:5a:0d +c9:5e:a4:dd:bb:ae +d0:90:56:ef:30:05 +bd:2d:22:41:42:5e +be:49:c2:de:66:22 +21:cb:55:17:01:c8 +22:00:5c:e5:9b:0d +23:c8:5f:22:33:ca +01:02:37:05:4f:36 +91:ba:08:fd:9e:ed +bf:ad:23:1f:3e:4d +c4:a6:c5:bd:89:dc +b9:13:9f:85:d9:fa +7d:b2:c9:07:4f:bc +\N +7a:84:d0:53:46:30 +f7:32:0c:87:f0:2f +77:15:22:cd:68:a0 +17:f9:26:35:6e:73 +d7:21:4d:fe:43:22 +63:13:3a:af:94:e9 +f3:11:3c:68:ef:5e +76:5a:06:33:31:fe +63:d9:b8:ef:ec:36 +3b:f3:4d:c4:22:e6 +d0:a2:cb:f9:f1:0b +95:0e:76:bc:99:ee +ae:e7:64:d9:ce:5b +ff:5a:6f:a2:26:9a +75:96:d4:72:db:49 +a8:ac:2a:2a:23:67 +db:4e:14:4d:b1:7c +45:fd:c8:52:05:cf +91:7b:e7:6e:23:07 +71:50:0c:46:78:3d +a9:90:9b:c0:b4:fc +47:0e:9a:82:af:48 +e8:1c:cf:c0:16:d1 +e1:6a:42:25:89:c3 +\N +55:8e:63:3b:44:c8 +93:8a:78:62:38:70 +29:81:d2:3d:12:80 +3e:d1:1e:94:2f:5d +fb:8c:c4:ee:84:7c +\N +cc:ad:28:56:b2:4a +52:f6:13:94:b7:03 +a2:8d:f8:f9:f8:c0 +57:68:00:9c:1b:f8 +a6:e2:31:88:8a:b4 +41:f7:0a:bd:47:08 +bf:5c:7d:64:08:73 +33:cc:60:43:05:e1 +cd:a5:a8:b0:99:14 +ef:fd:75:f1:3e:99 +b8:b0:b8:85:80:53 +7b:1c:fc:5c:b2:c8 +92:38:0c:df:81:57 +1e:ab:d4:18:c5:3a +b0:a1:c6:88:a3:29 +c7:37:ec:30:2b:7c +5e:5b:32:94:60:f7 +9d:ab:93:1f:de:1d +ff:32:af:ef:10:2e +f8:54:03:77:0d:e0 +49:e4:f8:f7:63:41 +18:f7:95:42:ea:32 +f8:a2:40:af:9e:af +1d:e2:71:c3:be:6e +\N +6a:38:d7:a5:b7:cd +df:51:52:74:c6:d4 +f2:05:5e:d2:1e:f3 +85:a7:b9:dd:80:72 +ae:ad:f5:1f:d1:7e +cf:71:9a:9c:b4:4d +\N +ac:8d:0f:5b:63:21 +c4:f0:3e:23:9e:7d +45:91:19:00:06:fa +c6:b6:30:24:e2:c5 +48:e1:e1:4a:8f:99 +cd:74:2c:39:14:e3 +9b:bb:32:10:ec:14 +a5:3c:a6:c9:64:14 +bc:ea:9c:03:76:75 +d5:29:75:05:8a:5d +\N +37:92:8f:cb:f1:e5 +e5:eb:1b:0e:02:58 +9f:9c:a2:22:a4:25 +26:f4:f4:e6:b8:cb +dd:79:87:70:0b:e7 +80:87:95:20:cd:ce +0d:28:2a:d1:0f:e2 +05:b8:34:36:55:b0 +89:03:74:cd:43:a9 +74:10:93:21:1e:04 +27:bb:a9:3a:7b:f0 +73:3c:06:b1:51:ad +47:0e:59:c0:5d:38 +13:13:e1:1c:aa:25 +4a:31:57:23:9b:54 +e2:cc:11:0d:3a:a5 +a9:2b:32:79:3e:60 +53:1c:b9:1d:db:c7 +\N +70:8a:83:2b:c0:23 +4c:55:af:1e:79:f9 +b9:4c:7c:39:c1:ee +c5:83:ab:17:63:50 +b4:cf:fc:c4:fe:db +12:33:a4:22:4e:a4 +99:db:22:10:3f:ae +f4:31:5b:3a:26:c4 +\N +db:a5:1a:67:bf:b5 +9c:c6:3f:59:ec:96 +8f:52:b2:83:41:b4 +49:ac:2a:44:a4:28 +0c:51:53:4e:10:79 +e5:54:31:9d:51:d9 +d0:8d:79:bd:4f:a1 +ee:ba:f1:4f:a1:bc +76:25:97:80:8b:a7 +3a:dd:50:3b:82:d0 +34:8d:ca:fd:24:fc +2f:f9:aa:93:0e:70 +93:29:d0:c2:7a:cb +3a:a8:a7:c9:9c:5b +8f:6c:0b:a1:1a:22 +ce:4a:b7:a6:59:9e +\N +\N +e5:bd:83:62:cb:59 +ba:d9:ed:2f:68:58 +48:18:7a:6c:cc:2f +76:03:b0:d3:ae:9d +ef:3c:db:66:d9:82 +2d:73:91:dd:fb:69 +33:35:c3:7d:50:49 +50:a3:e9:24:18:32 +66:eb:ab:3a:14:2e +a9:13:53:36:ee:95 +1f:4f:b4:57:34:0a +60:26:2c:17:5c:d8 +01:43:b5:79:eb:0f +c0:94:5d:b5:41:2c +fb:e6:51:d6:49:9c +87:7a:23:90:e9:df +78:52:28:a5:f9:1f +38:b2:03:d7:5d:b3 +bf:b5:5e:99:80:9f +7c:8f:73:5c:07:95 +ba:b8:6b:d2:ef:ab +9f:25:81:eb:33:0a +82:cb:7c:44:6a:5f +ca:46:2b:3c:cf:7f +17:64:2a:89:cf:59 +\N +35:59:4c:9d:5a:0d +c9:5e:a4:dd:bb:ae +d0:90:56:ef:30:05 +bd:2d:22:41:42:5e +be:49:c2:de:66:22 +21:cb:55:17:01:c8 +22:00:5c:e5:9b:0d +23:c8:5f:22:33:ca +01:02:37:05:4f:36 +91:ba:08:fd:9e:ed +bf:ad:23:1f:3e:4d +c4:a6:c5:bd:89:dc +b9:13:9f:85:d9:fa +7d:b2:c9:07:4f:bc +\N +7a:84:d0:53:46:30 +f7:32:0c:87:f0:2f +77:15:22:cd:68:a0 +17:f9:26:35:6e:73 +d7:21:4d:fe:43:22 +63:13:3a:af:94:e9 +f3:11:3c:68:ef:5e +76:5a:06:33:31:fe +63:d9:b8:ef:ec:36 +3b:f3:4d:c4:22:e6 +d0:a2:cb:f9:f1:0b +95:0e:76:bc:99:ee +ae:e7:64:d9:ce:5b +ff:5a:6f:a2:26:9a +75:96:d4:72:db:49 +a8:ac:2a:2a:23:67 +db:4e:14:4d:b1:7c +45:fd:c8:52:05:cf +91:7b:e7:6e:23:07 +71:50:0c:46:78:3d +a9:90:9b:c0:b4:fc +47:0e:9a:82:af:48 +e8:1c:cf:c0:16:d1 +e1:6a:42:25:89:c3 +\N +55:8e:63:3b:44:c8 +93:8a:78:62:38:70 +29:81:d2:3d:12:80 +3e:d1:1e:94:2f:5d +fb:8c:c4:ee:84:7c +\N +cc:ad:28:56:b2:4a +52:f6:13:94:b7:03 +a2:8d:f8:f9:f8:c0 +57:68:00:9c:1b:f8 +a6:e2:31:88:8a:b4 +41:f7:0a:bd:47:08 +bf:5c:7d:64:08:73 +33:cc:60:43:05:e1 +cd:a5:a8:b0:99:14 +ef:fd:75:f1:3e:99 +b8:b0:b8:85:80:53 +7b:1c:fc:5c:b2:c8 +92:38:0c:df:81:57 +1e:ab:d4:18:c5:3a +b0:a1:c6:88:a3:29 +c7:37:ec:30:2b:7c +5e:5b:32:94:60:f7 +9d:ab:93:1f:de:1d +ff:32:af:ef:10:2e +f8:54:03:77:0d:e0 +49:e4:f8:f7:63:41 +18:f7:95:42:ea:32 +f8:a2:40:af:9e:af +1d:e2:71:c3:be:6e +\N +6a:38:d7:a5:b7:cd +df:51:52:74:c6:d4 +f2:05:5e:d2:1e:f3 +85:a7:b9:dd:80:72 +ae:ad:f5:1f:d1:7e +cf:71:9a:9c:b4:4d +\N +ac:8d:0f:5b:63:21 +c4:f0:3e:23:9e:7d +45:91:19:00:06:fa +c6:b6:30:24:e2:c5 +48:e1:e1:4a:8f:99 +cd:74:2c:39:14:e3 +9b:bb:32:10:ec:14 +a5:3c:a6:c9:64:14 +bc:ea:9c:03:76:75 +d5:29:75:05:8a:5d +\N +37:92:8f:cb:f1:e5 +e5:eb:1b:0e:02:58 +9f:9c:a2:22:a4:25 +26:f4:f4:e6:b8:cb +dd:79:87:70:0b:e7 +80:87:95:20:cd:ce +0d:28:2a:d1:0f:e2 +05:b8:34:36:55:b0 +89:03:74:cd:43:a9 +74:10:93:21:1e:04 +27:bb:a9:3a:7b:f0 +73:3c:06:b1:51:ad +47:0e:59:c0:5d:38 +13:13:e1:1c:aa:25 diff --git a/contrib/btree_gist/data/numeric.data b/contrib/btree_gist/data/numeric.data new file mode 100644 index 0000000..58d76fa --- /dev/null +++ b/contrib/btree_gist/data/numeric.data @@ -0,0 +1,11 @@ +-1890.000000000000000000000000000000000000000000000000000000000000000000000000001 +-1889.999999999999999999999999999999999999999999999999999999999999999999999999999 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567892 +10000 +0 +0 +0 +NaN +NaN diff --git a/contrib/btree_gist/data/text.data b/contrib/btree_gist/data/text.data new file mode 100644 index 0000000..4494936 --- /dev/null +++ b/contrib/btree_gist/data/text.data @@ -0,0 +1 @@ +2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 diff --git a/contrib/btree_gist/data/time.data b/contrib/btree_gist/data/time.data new file mode 100644 index 0000000..6c567c7 --- /dev/null +++ b/contrib/btree_gist/data/time.data @@ -0,0 +1,599 @@ +23:56:29 +14:23:32 +15:46:58 +\N +18:47:20 +11:54:06 +08:35:07 +06:37:29 +11:27:58 +\N +14:05:32 +17:18:35 +20:40:36 +01:53:24 +06:30:54 +08:55:08 +14:09:40 +19:45:28 +18:56:29 +\N +03:07:47 +20:07:06 +08:54:31 +03:48:12 +14:58:09 +04:23:51 +04:35:11 +17:51:38 +04:19:35 +14:54:46 +03:31:30 +17:03:30 +01:52:27 +07:45:51 +10:30:45 +14:55:59 +02:30:42 +04:28:11 +10:57:11 +12:47:13 +14:37:11 +15:47:57 +20:59:33 +15:51:53 +18:28:37 +16:24:13 +06:58:48 +20:58:13 +22:05:01 +13:20:20 +13:46:46 +08:06:45 +03:56:45 +00:48:10 +16:15:59 +05:39:04 +21:11:01 +18:57:40 +06:28:28 +07:09:17 +09:35:47 +07:08:00 +12:02:01 +00:32:50 +23:06:16 +02:57:27 +08:50:33 +18:40:40 +12:51:38 +19:34:22 +00:37:59 +09:39:35 +\N +\N +01:36:43 +\N +12:58:39 +06:53:03 +05:14:10 +04:02:08 +04:40:02 +\N +15:03:29 +20:25:02 +12:19:17 +13:47:39 +09:53:23 +06:23:25 +05:56:20 +14:18:24 +14:02:34 +12:27:36 +09:31:29 +13:35:03 +17:47:47 +17:55:57 +06:30:39 +06:21:38 +16:55:11 +01:49:01 +22:16:13 +12:17:35 +11:25:40 +\N +00:29:46 +07:37:36 +\N +14:52:12 +10:49:02 +18:40:02 +\N +02:58:13 +21:33:44 +16:52:33 +01:40:26 +08:15:03 +05:20:44 +09:43:11 +\N +19:34:51 +07:51:41 +04:20:43 +13:20:32 +20:30:08 +13:50:55 +00:44:30 +06:13:26 +03:29:43 +00:19:51 +05:11:23 +20:48:53 +12:52:42 +09:41:18 +03:45:21 +16:13:59 +06:19:58 +17:17:55 +18:28:12 +19:58:59 +03:25:07 +06:50:23 +23:00:13 +\N +03:52:47 +21:13:06 +\N +15:36:11 +12:36:08 +07:56:23 +23:32:03 +\N +15:23:39 +21:20:05 +04:44:09 +22:48:19 +21:44:28 +02:17:10 +23:12:24 +12:38:57 +05:47:43 +23:17:09 +15:27:41 +02:04:03 +\N +18:11:40 +06:54:54 +20:57:42 +01:14:59 +02:45:54 +22:51:42 +01:00:42 +04:29:29 +13:41:28 +14:36:19 +22:25:02 +23:08:45 +04:50:50 +01:44:02 +12:37:17 +03:40:05 +15:31:07 +\N +00:32:35 +13:11:45 +18:05:35 +23:15:08 +01:52:35 +\N +18:46:10 +20:06:36 +16:17:06 +17:47:39 +02:54:56 +20:42:39 +08:00:44 +23:53:40 +\N +\N +10:24:10 +15:46:42 +20:23:26 +20:12:43 +\N +12:21:58 +17:52:22 +01:33:40 +15:55:22 +01:33:07 +17:46:27 +17:10:28 +00:39:40 +\N +17:30:25 +22:48:37 +23:45:32 +23:45:59 +17:27:33 +13:23:46 +09:34:22 +16:59:06 +09:19:23 +16:06:27 +01:03:41 +06:10:45 +11:45:10 +\N +23:30:22 +\N +12:19:21 +09:06:09 +22:14:02 +22:30:58 +17:38:16 +06:07:40 +12:38:59 +02:39:50 +00:14:12 +01:09:47 +05:16:45 +17:47:53 +13:23:16 +23:54:39 +23:26:01 +03:14:19 +03:36:47 +\N +15:49:11 +16:30:21 +06:31:21 +09:46:53 +17:48:40 +23:07:33 +09:30:58 +03:01:28 +18:48:39 +\N +10:20:37 +18:03:26 +23:38:14 +11:12:46 +00:39:00 +05:15:56 +06:07:20 +16:32:30 +08:42:21 +03:10:56 +06:03:47 +12:57:58 +21:42:59 +17:12:12 +00:08:11 +08:05:07 +03:45:59 +16:55:19 +12:47:20 +12:49:35 +03:48:21 +11:32:33 +11:30:51 +09:11:06 +03:02:36 +02:23:03 +02:16:44 +17:16:26 +07:04:20 +13:29:28 +21:01:12 +06:35:03 +02:23:14 +10:53:45 +06:06:18 +13:40:10 +09:27:36 +\N +07:00:44 +04:22:42 +\N +12:50:38 +23:03:05 +10:32:59 +20:54:04 +\N +07:53:27 +15:00:51 +\N +15:30:20 +15:27:57 +06:01:36 +22:20:26 +01:21:20 +05:54:01 +09:31:20 +19:54:37 +19:06:34 +18:35:22 +22:44:32 +12:43:45 +04:24:22 +03:26:29 +22:06:36 +15:03:19 +06:20:33 +05:37:36 +18:59:04 +10:59:54 +\N +17:04:29 +\N +08:51:37 +02:47:04 +11:17:51 +01:36:35 +07:23:02 +21:01:14 +21:07:23 +15:58:20 +03:37:48 +17:43:22 +08:06:10 +03:21:13 +14:23:04 +00:15:56 +12:30:28 +04:51:05 +22:47:38 +04:29:31 +00:38:02 +00:16:09 +07:38:35 +03:55:56 +12:06:21 +07:46:45 +10:31:07 +12:13:46 +06:55:31 +00:58:12 +14:46:31 +07:28:15 +21:43:37 +02:10:16 +07:30:34 +19:05:11 +13:54:34 +04:03:22 +\N +07:49:42 +02:53:33 +01:08:33 +09:42:32 +17:56:28 +17:37:54 +02:02:52 +07:43:47 +23:07:36 +\N +21:43:23 +04:34:38 +06:17:36 +23:16:48 +19:55:50 +05:55:45 +16:49:59 +17:50:54 +11:52:36 +20:21:19 +08:48:56 +11:18:58 +06:28:01 +03:02:25 +15:28:12 +12:26:01 +20:02:07 +14:30:34 +09:43:31 +11:44:00 +20:06:06 +17:44:33 +11:53:21 +20:26:36 +07:04:27 +13:13:37 +14:56:54 +10:18:04 +05:37:29 +\N +23:17:09 +04:50:14 +00:56:52 +09:20:10 +21:09:49 +11:10:24 +13:58:05 +23:14:01 +\N +20:00:56 +11:43:43 +08:43:58 +\N +\N +15:44:40 +19:38:12 +23:26:57 +05:53:18 +12:51:41 +18:03:27 +22:57:21 +10:57:10 +\N +15:56:10 +14:41:42 +04:04:38 +\N +07:49:13 +20:08:21 +17:37:16 +11:31:02 +\N +08:47:52 +20:19:41 +08:10:03 +02:45:28 +15:43:13 +\N +18:02:24 +19:21:57 +07:24:41 +20:08:46 +22:19:29 +21:22:15 +07:40:41 +\N +01:44:27 +13:22:27 +23:03:59 +17:46:43 +23:06:32 +19:55:27 +04:24:35 +03:50:26 +01:43:48 +17:05:49 +00:51:15 +18:17:56 +10:01:57 +13:16:32 +16:28:12 +05:36:40 +09:35:09 +\N +\N +04:36:40 +\N +09:31:47 +10:21:54 +07:49:09 +19:51:28 +14:02:25 +07:54:40 +20:26:20 +09:50:27 +19:05:44 +\N +11:43:30 +00:25:16 +05:07:26 +13:38:21 +19:48:53 +01:11:45 +07:06:48 +\N +21:17:06 +\N +17:33:35 +23:22:58 +21:47:12 +20:50:30 +01:04:16 +19:25:47 +22:57:00 +09:15:33 +\N +02:29:36 +21:27:15 +13:29:22 +\N +02:10:34 +06:14:24 +14:04:25 +03:37:23 +21:36:37 +15:41:07 +03:40:25 +15:51:36 +05:10:09 +18:54:02 +16:49:50 +03:18:24 +23:13:58 +05:55:06 +22:21:51 +02:23:28 +07:12:18 +02:33:13 +10:23:41 +\N +05:44:10 +21:59:24 +00:22:35 +23:07:01 +22:25:59 +13:25:35 +08:44:13 +04:25:14 +\N +14:04:18 +11:05:27 +13:42:39 +\N +05:24:12 +01:01:47 +07:49:06 +19:07:56 +03:35:35 +07:28:30 +05:00:13 +16:27:26 +00:14:42 +02:57:18 +23:39:57 +07:31:16 +19:08:03 +13:26:21 +11:39:43 +21:07:04 +03:30:59 +07:59:10 +14:50:17 +02:57:29 +23:37:36 +07:48:10 +17:04:36 +13:24:45 +23:42:26 +04:31:37 +21:09:05 +07:47:28 +13:18:23 +00:45:21 +03:30:56 +10:09:02 +14:02:41 +23:11:51 +03:34:54 +\N +02:20:03 +04:46:49 +06:12:58 +01:26:47 +01:33:06 +10:55:32 +16:14:38 +21:54:21 +20:59:06 +12:26:22 +19:34:40 +11:48:58 +14:32:43 +11:36:17 +06:57:47 +15:34:23 +02:56:30 +12:30:59 +11:33:55 +04:53:09 +00:01:24 +02:14:43 +\N +14:26:05 +18:48:04 diff --git a/contrib/btree_gist/data/timestamp.data b/contrib/btree_gist/data/timestamp.data new file mode 100644 index 0000000..065bc50 --- /dev/null +++ b/contrib/btree_gist/data/timestamp.data @@ -0,0 +1,624 @@ +2019-11-22 23:56:29 +1995-05-20 14:23:32 +1989-04-12 15:46:58 +\N +2023-10-21 18:47:20 +2028-11-27 11:54:06 +2024-05-24 08:35:07 +2018-08-31 06:37:29 +1988-07-16 11:27:58 +\N +2019-05-21 14:05:32 +1972-12-12 17:18:35 +1975-05-07 20:40:36 +2022-08-11 01:53:24 +1975-06-27 06:30:54 +2004-10-26 08:55:08 +1983-05-14 14:09:40 +2015-06-20 19:45:28 +1978-02-17 18:56:29 +\N +1970-12-04 03:07:47 +1977-03-22 20:07:06 +2032-10-28 08:54:31 +2004-03-02 03:48:12 +1992-10-29 14:58:09 +2021-05-05 04:23:51 +2025-06-25 04:35:11 +2036-07-15 17:51:38 +1981-03-10 04:19:35 +2012-12-04 14:54:46 +1996-02-02 03:31:30 +1992-12-31 17:03:30 +2003-02-03 01:52:27 +2008-12-15 07:45:51 +1994-11-19 10:30:45 +2036-09-20 14:55:59 +2034-10-21 02:30:42 +2019-11-16 04:28:11 +1995-01-23 10:57:11 +1974-10-11 12:47:13 +2027-02-26 14:37:11 +2027-08-08 15:47:57 +2037-08-30 20:59:33 +2026-01-08 15:51:53 +2023-09-18 18:28:37 +2012-03-07 16:24:13 +2011-08-29 06:58:48 +1979-11-06 20:58:13 +2033-09-25 22:05:01 +1986-06-08 13:20:20 +2020-01-13 13:46:46 +2015-09-19 08:06:45 +2035-11-16 03:56:45 +1985-11-12 00:48:10 +2002-05-26 16:15:59 +2002-01-30 05:39:04 +1980-01-26 21:11:01 +2037-03-23 18:57:40 +2016-02-23 06:28:28 +2023-04-21 07:09:17 +2033-04-11 09:35:47 +1985-09-09 07:08:00 +1993-01-29 12:02:01 +2015-06-28 00:32:50 +2036-04-06 23:06:16 +2031-12-08 02:57:27 +2014-02-26 08:50:33 +1994-01-23 18:40:40 +2019-01-27 12:51:38 +1978-08-01 19:34:22 +1979-05-21 00:37:59 +2022-12-12 09:39:35 +\N +\N +2007-10-16 01:36:43 +\N +2037-05-17 12:58:39 +2034-08-14 06:53:03 +2014-12-20 05:14:10 +2033-03-13 04:02:08 +2010-10-03 04:40:02 +\N +1992-12-23 15:03:29 +2029-05-15 20:25:02 +1993-07-17 12:19:17 +2007-11-06 13:47:39 +2024-10-22 09:53:23 +2019-03-02 06:23:25 +2026-07-24 05:56:20 +1973-03-29 14:18:24 +2006-06-02 14:02:34 +2032-06-11 12:27:36 +1990-01-28 09:31:29 +2019-06-16 13:35:03 +1970-04-21 17:47:47 +2012-09-17 17:55:57 +1993-10-06 06:30:39 +1993-02-24 06:21:38 +2014-12-07 16:55:11 +2018-09-17 01:49:01 +2015-05-22 22:16:13 +1983-01-19 12:17:35 +1978-11-08 11:25:40 +\N +2021-06-01 00:29:46 +1970-11-19 07:37:36 +\N +2030-06-12 14:52:12 +2035-08-11 10:49:02 +2021-01-31 18:40:02 +\N +1996-06-25 02:58:13 +1974-11-28 21:33:44 +1989-04-28 16:52:33 +1998-07-17 01:40:26 +2029-09-14 08:15:03 +2010-01-06 05:20:44 +2013-05-04 09:43:11 +\N +2023-10-03 19:34:51 +1998-02-06 07:51:41 +1984-07-16 04:20:43 +2008-01-11 13:20:32 +2015-06-23 20:30:08 +2026-04-02 13:50:55 +2021-11-26 00:44:30 +2012-07-09 06:13:26 +1975-01-07 03:29:43 +2022-12-04 00:19:51 +2013-01-22 05:11:23 +2001-02-13 20:48:53 +2001-11-20 12:52:42 +2008-12-27 09:41:18 +2030-07-05 03:45:21 +1970-07-22 16:13:59 +2037-07-11 06:19:58 +2034-12-24 17:17:55 +2022-04-04 18:28:12 +2018-03-24 19:58:59 +2037-03-25 03:25:07 +1972-06-24 06:50:23 +2034-08-15 23:00:13 +\N +2030-01-12 03:52:47 +2000-08-15 21:13:06 +\N +2034-07-25 15:36:11 +2030-04-06 12:36:08 +2003-08-22 07:56:23 +2012-04-17 23:32:03 +\N +1992-11-30 15:23:39 +2017-03-31 21:20:05 +2004-06-19 04:44:09 +1974-02-07 22:48:19 +1992-11-02 21:44:28 +2006-07-03 02:17:10 +2030-09-03 23:12:24 +1989-12-15 12:38:57 +1992-05-03 05:47:43 +1981-05-05 23:17:09 +1983-06-04 15:27:41 +2010-08-13 02:04:03 +\N +2022-06-15 18:11:40 +2011-03-10 06:54:54 +1973-11-06 20:57:42 +1998-03-22 01:14:59 +1992-07-20 02:45:54 +2022-01-15 22:51:42 +2022-05-03 01:00:42 +1972-06-08 04:29:29 +1976-10-27 13:41:28 +2023-12-13 14:36:19 +2027-08-01 22:25:02 +2009-05-04 23:08:45 +2021-03-19 04:50:50 +2025-04-09 01:44:02 +1988-07-28 12:37:17 +2023-07-31 03:40:05 +2003-07-25 15:31:07 +\N +1995-12-12 00:32:35 +1992-04-19 13:11:45 +1997-08-20 18:05:35 +2028-01-13 23:15:08 +1980-04-18 01:52:35 +\N +2030-03-08 18:46:10 +2000-03-22 20:06:36 +1987-03-17 16:17:06 +2008-03-10 17:47:39 +1982-04-02 02:54:56 +1970-01-29 20:42:39 +2032-03-28 08:00:44 +1991-10-03 23:53:40 +\N +\N +\N +2035-07-02 10:24:10 +1988-11-27 15:46:42 +2034-08-29 20:23:26 +2004-11-29 20:12:43 +\N +2013-08-28 12:21:58 +1997-04-06 17:52:22 +1994-10-12 01:33:40 +2027-06-18 15:55:22 +1971-01-10 01:33:07 +2035-02-23 17:46:27 +2036-10-25 17:10:28 +2033-06-29 00:39:40 +\N +2028-07-29 17:30:25 +2003-03-18 22:48:37 +1994-11-29 23:45:32 +2001-10-08 23:45:59 +2019-03-10 17:27:33 +2022-05-19 13:23:46 +1971-07-13 09:34:22 +1979-07-25 16:59:06 +1980-11-27 09:19:23 +2030-02-20 16:06:27 +1973-01-02 01:03:41 +1992-12-20 06:10:45 +2034-03-04 11:45:10 +\N +2036-11-27 23:30:22 +\N +2023-09-22 12:19:21 +2023-07-13 09:06:09 +1996-02-13 22:14:02 +1974-02-11 22:30:58 +1996-08-07 17:38:16 +1998-06-13 06:07:40 +2035-04-21 12:38:59 +1996-03-04 02:39:50 +2019-05-04 00:14:12 +2024-06-01 01:09:47 +2007-02-20 05:16:45 +2003-07-29 17:47:53 +2000-07-13 13:23:16 +2011-09-08 23:54:39 +2019-06-10 23:26:01 +1975-02-25 03:14:19 +2002-01-16 03:36:47 +\N +1986-04-08 15:49:11 +1986-03-08 16:30:21 +2019-06-21 06:31:21 +1988-09-19 09:46:53 +2035-11-30 17:48:40 +1986-11-27 23:07:33 +2037-03-10 09:30:58 +2030-07-15 03:01:28 +1988-06-29 18:48:39 +\N +1980-04-23 10:20:37 +2020-01-19 18:03:26 +2012-09-14 23:38:14 +1983-12-28 11:12:46 +1987-02-10 00:39:00 +2029-09-21 05:15:56 +1972-03-12 06:07:20 +2035-11-20 16:32:30 +1996-10-25 08:42:21 +1981-02-05 03:10:56 +2031-09-10 06:03:47 +1986-04-24 12:57:58 +1987-04-26 21:42:59 +2017-06-10 17:12:12 +1980-07-26 00:08:11 +2009-10-18 08:05:07 +1981-05-24 03:45:59 +2026-07-17 16:55:19 +2015-09-30 12:47:20 +2032-05-02 12:49:35 +1987-08-17 03:48:21 +2034-10-20 11:32:33 +2008-11-20 11:30:51 +1986-11-06 09:11:06 +1980-04-06 03:02:36 +1982-11-12 02:23:03 +1975-01-24 02:16:44 +2015-04-25 17:16:26 +1980-03-14 07:04:20 +1998-04-07 13:29:28 +2020-12-16 21:01:12 +2004-10-31 06:35:03 +1975-02-26 02:23:14 +2024-10-29 10:53:45 +1974-10-29 06:06:18 +1986-09-21 13:40:10 +2037-11-06 09:27:36 +\N +1979-06-02 07:00:44 +2001-06-28 04:22:42 +\N +2024-10-14 12:50:38 +2031-03-22 23:03:05 +1983-11-27 10:32:59 +1989-01-01 20:54:04 +\N +2037-03-05 07:53:27 +2002-12-28 15:00:51 +\N +2031-12-20 15:30:20 +2002-08-30 15:27:57 +1997-11-16 06:01:36 +2023-07-17 22:20:26 +2010-04-02 01:21:20 +2019-09-21 05:54:01 +1978-03-04 09:31:20 +1992-11-08 19:54:37 +2002-02-11 19:06:34 +2012-06-06 18:35:22 +2012-11-23 22:44:32 +1997-09-27 12:43:45 +1972-12-18 04:24:22 +1985-10-25 03:26:29 +2028-09-29 22:06:36 +1976-03-31 15:03:19 +1996-06-26 06:20:33 +2014-07-06 05:37:36 +2032-05-21 18:59:04 +2018-12-18 10:59:54 +\N +1974-06-02 17:04:29 +\N +2024-09-29 08:51:37 +1996-07-03 02:47:04 +1973-02-11 11:17:51 +2019-12-02 01:36:35 +2035-09-04 07:23:02 +2008-10-10 21:01:14 +2018-05-16 21:07:23 +2020-04-17 15:58:20 +2030-05-04 03:37:48 +2002-10-27 17:43:22 +2036-03-04 08:06:10 +2002-10-29 03:21:13 +2000-06-22 14:23:04 +1981-01-01 00:15:56 +infinity +infinity +infinity +infinity +infinity +infinity +infinity +infinity +2001-12-31 12:30:28 +2003-09-25 04:51:05 +2020-12-07 22:47:38 +2016-11-04 04:29:31 +2026-02-23 00:38:02 +1990-12-16 00:16:09 +2033-10-15 07:38:35 +2003-07-29 03:55:56 +1998-10-09 12:06:21 +2006-04-02 07:46:45 +1970-06-06 10:31:07 +2015-02-06 12:13:46 +2004-06-23 06:55:31 +2015-12-08 00:58:12 +2024-06-26 14:46:31 +1997-11-07 07:28:15 +2023-05-09 21:43:37 +1992-11-16 02:10:16 +2016-04-17 07:30:34 +1996-11-09 19:05:11 +2010-09-12 13:54:34 +2012-10-17 04:03:22 +\N +1998-07-19 07:49:42 +1973-11-11 02:53:33 +1992-11-15 01:08:33 +2032-04-22 09:42:32 +2031-09-14 17:56:28 +2005-03-25 17:37:54 +1993-03-17 02:02:52 +2004-03-01 07:43:47 +2009-10-06 23:07:36 +\N +1993-06-07 21:43:23 +1994-07-31 04:34:38 +1987-08-07 06:17:36 +2034-05-01 23:16:48 +2025-06-19 19:55:50 +2019-10-13 05:55:45 +1977-03-25 16:49:59 +1978-01-16 17:50:54 +2003-09-29 11:52:36 +2002-04-07 20:21:19 +2024-01-12 08:48:56 +1984-08-29 11:18:58 +2036-08-16 06:28:01 +1972-05-14 03:02:25 +2019-01-24 15:28:12 +2009-12-19 12:26:01 +1982-07-26 20:02:07 +2012-01-22 14:30:34 +1983-07-22 09:43:31 +1978-05-27 11:44:00 +1974-04-02 20:06:06 +2008-02-15 17:44:33 +2003-01-14 11:53:21 +1989-03-30 20:26:36 +2009-12-14 07:04:27 +2030-07-05 13:13:37 +2032-07-22 14:56:54 +2016-05-17 10:18:04 +2031-07-02 05:37:29 +\N +1980-07-28 23:17:09 +2031-09-06 04:50:14 +1975-01-20 00:56:52 +2019-05-05 09:20:10 +2025-05-02 21:09:49 +2001-12-27 11:10:24 +1991-09-27 13:58:05 +2021-12-14 23:14:01 +\N +2027-03-24 20:00:56 +2009-01-07 11:43:43 +1985-02-04 08:43:58 +\N +\N +1996-12-23 15:44:40 +2012-05-22 19:38:12 +1980-02-29 23:26:57 +2018-06-14 05:53:18 +1981-07-04 12:51:41 +1993-04-25 18:03:27 +2027-03-09 22:57:21 +1981-01-10 10:57:10 +\N +1974-06-16 15:56:10 +1985-10-15 14:41:42 +1988-01-11 04:04:38 +\N +1997-03-15 07:49:13 +1982-02-26 20:08:21 +1991-05-13 17:37:16 +2015-09-03 11:31:02 +\N +1987-06-05 08:47:52 +1971-03-29 20:19:41 +2002-03-28 08:10:03 +2032-03-25 02:45:28 +1983-07-23 15:43:13 +\N +1999-01-15 18:02:24 +2014-01-31 19:21:57 +2021-09-02 07:24:41 +1999-01-02 20:08:46 +2001-03-24 22:19:29 +2002-01-21 21:22:15 +2007-08-06 07:40:41 +\N +2032-09-07 01:44:27 +1984-11-22 13:22:27 +2035-06-18 23:03:59 +1994-07-29 17:46:43 +2023-04-20 23:06:32 +2010-02-17 19:55:27 +2014-08-23 04:24:35 +2017-02-08 03:50:26 +2036-10-24 01:43:48 +1994-11-14 17:05:49 +1979-04-19 00:51:15 +1973-10-09 18:17:56 +1999-01-17 10:01:57 +2012-07-08 13:16:32 +2012-12-03 16:28:12 +1980-11-22 05:36:40 +2002-02-28 09:35:09 +\N +\N +2023-03-01 04:36:40 +\N +1981-05-13 09:31:47 +2026-01-29 10:21:54 +2034-02-14 07:49:09 +2031-09-22 19:51:28 +1986-11-13 14:02:25 +2031-10-25 07:54:40 +2010-10-13 20:26:20 +2036-09-11 09:50:27 +2027-09-30 19:05:44 +\N +1990-07-28 11:43:30 +2001-05-05 00:25:16 +2029-01-10 05:07:26 +1972-10-09 13:38:21 +2012-03-25 19:48:53 +1987-04-06 01:11:45 +1998-06-04 07:06:48 +\N +2008-12-26 21:17:06 +\N +1979-04-23 17:33:35 +2010-01-16 23:22:58 +1982-07-23 21:47:12 +2013-08-21 20:50:30 +1982-12-23 01:04:16 +1997-04-03 19:25:47 +1986-02-20 22:57:00 +2034-09-08 09:15:33 +\N +1999-05-14 02:29:36 +1989-11-18 21:27:15 +1987-09-17 13:29:22 +\N +1978-05-03 02:10:34 +2005-05-22 06:14:24 +2001-02-11 14:04:25 +1980-07-28 03:37:23 +1984-04-11 21:36:37 +2010-06-02 15:41:07 +2023-09-05 03:40:25 +2020-12-29 15:51:36 +1997-06-06 05:10:09 +2005-10-01 18:54:02 +2023-01-17 16:49:50 +2013-04-27 03:18:24 +2018-05-23 23:13:58 +2022-11-23 05:55:06 +1971-05-09 22:21:51 +1997-12-05 02:23:28 +2008-08-22 07:12:18 +2025-05-23 02:33:13 +1989-02-18 10:23:41 +\N +2035-08-21 05:44:10 +1995-02-09 21:59:24 +1994-11-28 00:22:35 +1980-07-20 23:07:01 +1998-09-02 22:25:59 +1993-01-20 13:25:35 +1979-12-21 08:44:13 +2010-03-05 04:25:14 +\N +1989-01-07 14:04:18 +2015-10-09 11:05:27 +2006-08-23 13:42:39 +\N +2003-04-27 05:24:12 +2029-03-21 01:01:47 +2030-09-08 07:49:06 +2022-05-05 19:07:56 +1993-10-22 03:35:35 +2031-04-01 07:28:30 +2015-03-10 05:00:13 +1988-12-08 16:27:26 +2004-07-07 00:14:42 +1978-01-11 02:57:18 +2013-09-28 23:39:57 +2030-02-25 07:31:16 +2036-09-15 19:08:03 +2019-07-16 13:26:21 +2034-09-05 11:39:43 +2003-11-07 21:07:04 +2000-10-02 03:30:59 +2002-09-19 07:59:10 +2016-03-16 14:50:17 +1986-02-06 02:57:29 +-infinity +-infinity +-infinity +-infinity +-infinity +-infinity +-infinity +-infinity +2018-07-07 23:37:36 +1972-02-07 07:48:10 +2001-05-06 17:04:36 +1996-10-05 13:24:45 +1995-12-26 23:42:26 +1990-05-13 04:31:37 +1983-03-10 21:09:05 +2011-08-03 07:47:28 +2029-01-26 13:18:23 +2022-04-24 00:45:21 +1973-09-03 03:30:56 +2002-09-19 10:09:02 +1982-04-30 14:02:41 +2023-11-30 23:11:51 +1992-05-13 03:34:54 +\N +1986-05-11 02:20:03 +1976-12-31 04:46:49 +2009-10-23 06:12:58 +2022-02-08 01:26:47 +1999-02-24 01:33:06 +2007-09-30 10:55:32 +1991-11-03 16:14:38 +1985-06-14 21:54:21 +1973-06-15 20:59:06 +1972-07-18 12:26:22 +2012-07-05 19:34:40 +1989-01-17 11:48:58 +1982-02-13 14:32:43 +1994-11-23 11:36:17 +2008-08-01 06:57:47 +2032-11-22 15:34:23 +1976-01-04 02:56:30 +infinity +infinity +infinity +infinity +infinity +infinity +infinity +infinity +2037-04-11 12:30:59 +2011-02-24 11:33:55 +1992-11-16 04:53:09 +2025-11-25 00:01:24 +2013-02-08 02:14:43 +\N +2002-05-13 14:26:05 +1983-10-15 18:48:04 diff --git a/contrib/btree_gist/data/timestamptz.data b/contrib/btree_gist/data/timestamptz.data new file mode 100644 index 0000000..7f07cdc --- /dev/null +++ b/contrib/btree_gist/data/timestamptz.data @@ -0,0 +1,618 @@ +\N +2002-08-30 15:27:57 GMT +1997-11-16 06:01:36 GMT-2 +\N +2010-04-02 01:21:20 GMT+2 +2019-09-21 05:54:01 GMT+6 +1978-03-04 09:31:20 GMT-9 +1992-11-08 19:54:37 GMT-4 +2002-02-11 19:06:34 GMT-1 +2012-06-06 18:35:22 GMT+3 +2012-11-23 22:44:32 GMT+3 +1997-09-27 12:43:45 GMT-2 +1972-12-18 04:24:22 GMT-11 +\N +2028-09-29 22:06:36 GMT+9 +1976-03-31 15:03:19 GMT-10 +1996-06-26 06:20:33 GMT-3 +2014-07-06 05:37:36 GMT+4 +2032-05-21 18:59:04 GMT+10 +2018-12-18 10:59:54 GMT+3 +1986-02-15 10:47:42 GMT-6 +1974-06-02 17:04:29 GMT-10 +2020-11-03 05:11:44 GMT+6 +2024-09-29 08:51:37 GMT+7 +1996-07-03 02:47:04 GMT-3 +1973-02-11 11:17:51 GMT-11 +2019-12-02 01:36:35 GMT+6 +2035-09-04 07:23:02 GMT+11 +\N +2018-05-16 21:07:23 GMT+5 +\N +2030-05-04 03:37:48 GMT+9 +2002-10-27 17:43:22 GMT +2036-03-04 08:06:10 GMT+11 +2002-10-29 03:21:13 GMT +2000-06-22 14:23:04 GMT-1 +1981-01-01 00:15:56 GMT-8 +2001-12-31 12:30:28 GMT-1 +2003-09-25 04:51:05 GMT +\N +2016-11-04 04:29:31 GMT+5 +2026-02-23 00:38:02 GMT+8 +\N +2033-10-15 07:38:35 GMT+10 +2003-07-29 03:55:56 GMT +1998-10-09 12:06:21 GMT-2 +2006-04-02 07:46:45 GMT+1 +1970-06-06 10:31:07 GMT-12 +2015-02-06 12:13:46 GMT+4 +2004-06-23 06:55:31 GMT+0 +2015-12-08 00:58:12 GMT+4 +2024-06-26 14:46:31 GMT+7 +1997-11-07 07:28:15 GMT-2 +\N +1992-11-16 02:10:16 GMT-4 +\N +1996-11-09 19:05:11 GMT-3 +2010-09-12 13:54:34 GMT+2 +2012-10-17 04:03:22 GMT+3 +2035-09-05 02:13:28 GMT+11 +1998-07-19 07:49:42 GMT-2 +1973-11-11 02:53:33 GMT-11 +1992-11-15 01:08:33 GMT-4 +2032-04-22 09:42:32 GMT+10 +2031-09-14 17:56:28 GMT+10 +2005-03-25 17:37:54 GMT+0 +1993-03-17 02:02:52 GMT-4 +2004-03-01 07:43:47 GMT+0 +2009-10-06 23:07:36 GMT+2 +1977-12-15 18:43:22 GMT-9 +1993-06-07 21:43:23 GMT-4 +\N +1987-08-07 06:17:36 GMT-6 +2034-05-01 23:16:48 GMT+11 +2025-06-19 19:55:50 GMT+8 +2019-10-13 05:55:45 GMT+6 +1977-03-25 16:49:59 GMT-9 +1978-01-16 17:50:54 GMT-9 +2003-09-29 11:52:36 GMT +\N +2024-01-12 08:48:56 GMT+7 +\N +2036-08-16 06:28:01 GMT+11 +1972-05-14 03:02:25 GMT-11 +2019-01-24 15:28:12 GMT+5 +2009-12-19 12:26:01 GMT+2 +1982-07-26 20:02:07 GMT-8 +2012-01-22 14:30:34 GMT+3 +1983-07-22 09:43:31 GMT-7 +1978-05-27 11:44:00 GMT-9 +1974-04-02 20:06:06 GMT-11 +2008-02-15 17:44:33 GMT+1 +2003-01-14 11:53:21 GMT +1989-03-30 20:26:36 GMT-5 +2009-12-14 07:04:27 GMT+2 +\N +\N +\N +2031-07-02 05:37:29 GMT+10 +2002-06-01 01:18:02 GMT-1 +1980-07-28 23:17:09 GMT-8 +2031-09-06 04:50:14 GMT+10 +1975-01-20 00:56:52 GMT-10 +\N +2025-05-02 21:09:49 GMT+8 +\N +1991-09-27 13:58:05 GMT-4 +\N +\N +2027-03-24 20:00:56 GMT+8 +2009-01-07 11:43:43 GMT+2 +1985-02-04 08:43:58 GMT-7 +\N +1982-11-28 07:25:38 GMT-7 +1996-12-23 15:44:40 GMT-2 +2012-05-22 19:38:12 GMT+3 +1980-02-29 23:26:57 GMT-8 +2018-06-14 05:53:18 GMT+5 +1981-07-04 12:51:41 GMT-8 +1993-04-25 18:03:27 GMT-4 +2027-03-09 22:57:21 GMT+8 +1981-01-10 10:57:10 GMT-8 +2005-11-25 19:03:21 GMT+1 +1974-06-16 15:56:10 GMT-10 +1985-10-15 14:41:42 GMT-6 +\N +2030-12-31 21:55:54 GMT+10 +1997-03-15 07:49:13 GMT-2 +1982-02-26 20:08:21 GMT-8 +1991-05-13 17:37:16 GMT-4 +2015-09-03 11:31:02 GMT+4 +1977-10-28 18:16:43 GMT-9 +1987-06-05 08:47:52 GMT-6 +1971-03-29 20:19:41 GMT-12 +2002-03-28 08:10:03 GMT-1 +2032-03-25 02:45:28 GMT+10 +1983-07-23 15:43:13 GMT-7 +2035-01-02 09:00:43 GMT+11 +1999-01-15 18:02:24 GMT-2 +2014-01-31 19:21:57 GMT+4 +2021-09-02 07:24:41 GMT+6 +1999-01-02 20:08:46 GMT-2 +2001-03-24 22:19:29 GMT-1 +2002-01-21 21:22:15 GMT-1 +2007-08-06 07:40:41 GMT+1 +1975-12-07 20:04:02 GMT-10 +2032-09-07 01:44:27 GMT+10 +1984-11-22 13:22:27 GMT-7 +2035-06-18 23:03:59 GMT+11 +1994-07-29 17:46:43 GMT-3 +2023-04-20 23:06:32 GMT+7 +2010-02-17 19:55:27 GMT+2 +2014-08-23 04:24:35 GMT+4 +2017-02-08 03:50:26 GMT+5 +2036-10-24 01:43:48 GMT+12 +1994-11-14 17:05:49 GMT-3 +1979-04-19 00:51:15 GMT-9 +1973-10-09 18:17:56 GMT-11 +1999-01-17 10:01:57 GMT-2 +2012-07-08 13:16:32 GMT+3 +\N +1980-11-22 05:36:40 GMT-8 +2002-02-28 09:35:09 GMT-1 +2025-11-10 01:29:26 GMT+8 +2005-03-24 03:31:43 GMT+0 +\N +1987-09-11 07:03:58 GMT-6 +\N +2026-01-29 10:21:54 GMT+8 +2034-02-14 07:49:09 GMT+11 +\N +\N +2031-10-25 07:54:40 GMT+10 +2010-10-13 20:26:20 GMT+2 +2036-09-11 09:50:27 GMT+12 +2027-09-30 19:05:44 GMT+8 +1995-04-10 19:58:12 GMT-3 +1990-07-28 11:43:30 GMT-5 +2001-05-05 00:25:16 GMT-1 +2029-01-10 05:07:26 GMT+9 +1972-10-09 13:38:21 GMT-11 +2012-03-25 19:48:53 GMT+3 +1987-04-06 01:11:45 GMT-6 +1998-06-04 07:06:48 GMT-2 +2038-01-16 08:56:45 GMT+12 +2008-12-26 21:17:06 GMT+2 +1981-08-23 10:06:56 GMT-8 +1979-04-23 17:33:35 GMT-9 +2010-01-16 23:22:58 GMT+2 +1982-07-23 21:47:12 GMT-8 +2013-08-21 20:50:30 GMT+3 +1982-12-23 01:04:16 GMT-7 +1997-04-03 19:25:47 GMT-2 +1986-02-20 22:57:00 GMT-6 +2034-09-08 09:15:33 GMT+11 +1975-04-02 17:16:48 GMT-10 +1999-05-14 02:29:36 GMT-2 +1989-11-18 21:27:15 GMT-5 +\N +2030-05-15 08:09:46 GMT+9 +1978-05-03 02:10:34 GMT-9 +2005-05-22 06:14:24 GMT+0 +2001-02-11 14:04:25 GMT-1 +1980-07-28 03:37:23 GMT-8 +1984-04-11 21:36:37 GMT-7 +2010-06-02 15:41:07 GMT+2 +2023-09-05 03:40:25 GMT+7 +2020-12-29 15:51:36 GMT+6 +1997-06-06 05:10:09 GMT-2 +2005-10-01 18:54:02 GMT+1 +2023-01-17 16:49:50 GMT+7 +2013-04-27 03:18:24 GMT+3 +2018-05-23 23:13:58 GMT+5 +2022-11-23 05:55:06 GMT+7 +1971-05-09 22:21:51 GMT-12 +\N +2008-08-22 07:12:18 GMT+2 +2025-05-23 02:33:13 GMT+8 +1989-02-18 10:23:41 GMT-5 +2006-05-03 15:10:12 GMT+1 +2035-08-21 05:44:10 GMT+11 +1995-02-09 21:59:24 GMT-3 +1994-11-28 00:22:35 GMT-3 +1980-07-20 23:07:01 GMT-8 +1998-09-02 22:25:59 GMT-2 +1993-01-20 13:25:35 GMT-4 +1979-12-21 08:44:13 GMT-8 +\N +2022-10-19 07:06:12 GMT+7 +\N +\N +2006-08-23 13:42:39 GMT+1 +2024-05-31 02:55:37 GMT+7 +2003-04-27 05:24:12 GMT +2029-03-21 01:01:47 GMT+9 +2030-09-08 07:49:06 GMT+9 +2022-05-05 19:07:56 GMT+6 +1993-10-22 03:35:35 GMT-4 +2031-04-01 07:28:30 GMT+10 +2015-03-10 05:00:13 GMT+4 +1988-12-08 16:27:26 GMT-5 +\N +\N +2013-09-28 23:39:57 GMT+3 +2030-02-25 07:31:16 GMT+9 +2036-09-15 19:08:03 GMT+12 +\N +2034-09-05 11:39:43 GMT+11 +2003-11-07 21:07:04 GMT +\N +2002-09-19 07:59:10 GMT +2016-03-16 14:50:17 GMT+4 +1986-02-06 02:57:29 GMT-6 +2018-07-07 23:37:36 GMT+5 +\N +2001-05-06 17:04:36 GMT-1 +1996-10-05 13:24:45 GMT-3 +1995-12-26 23:42:26 GMT-3 +\N +1983-03-10 21:09:05 GMT-7 +2011-08-03 07:47:28 GMT+3 +2029-01-26 13:18:23 GMT+9 +2022-04-24 00:45:21 GMT+6 +1973-09-03 03:30:56 GMT-11 +\N +1982-04-30 14:02:41 GMT-8 +2023-11-30 23:11:51 GMT+7 +1992-05-13 03:34:54 GMT-4 +2001-11-13 00:47:25 GMT-1 +1986-05-11 02:20:03 GMT-6 +\N +\N +2022-02-08 01:26:47 GMT+6 +1999-02-24 01:33:06 GMT-2 +2007-09-30 10:55:32 GMT+1 +1991-11-03 16:14:38 GMT-4 +1985-06-14 21:54:21 GMT-7 +1973-06-15 20:59:06 GMT-11 +1972-07-18 12:26:22 GMT-11 +\N +1989-01-17 11:48:58 GMT-5 +1982-02-13 14:32:43 GMT-8 +1994-11-23 11:36:17 GMT-3 +2008-08-01 06:57:47 GMT+2 +\N +1976-01-04 02:56:30 GMT-10 +2037-04-11 12:30:59 GMT+12 +2011-02-24 11:33:55 GMT+3 +1992-11-16 04:53:09 GMT-4 +2025-11-25 00:01:24 GMT+8 +2013-02-08 02:14:43 GMT+3 +2003-05-05 05:25:07 GMT +2002-05-13 14:26:05 GMT-1 +1983-10-15 18:48:04 GMT-7 +1995-01-07 19:03:41 GMT-3 +1986-11-19 05:35:15 GMT-6 +1991-04-09 21:07:05 GMT-4 +2011-01-03 05:09:25 GMT+2 +1974-12-06 19:39:43 GMT-10 +2000-11-12 19:05:27 GMT-1 +2006-01-31 04:14:52 GMT+1 +2004-07-01 01:25:07 GMT+0 +1978-06-04 03:51:35 GMT-9 +\N +2002-07-29 17:13:57 GMT-1 +1977-01-03 12:24:31 GMT-10 +1991-12-12 18:57:03 GMT-4 +1986-06-13 22:44:23 GMT-6 +2012-12-08 18:42:47 GMT+3 +\N +1977-10-01 22:10:45 GMT-9 +1994-10-26 07:10:14 GMT-3 +1997-01-11 02:26:04 GMT-2 +2013-11-28 22:50:57 GMT+3 +2016-07-14 09:05:32 GMT+4 +\N +1994-10-27 20:12:10 GMT-3 +2018-02-22 01:05:22 GMT+5 +2034-10-05 12:42:38 GMT+11 +2028-03-04 00:08:34 GMT+9 +2037-05-21 23:24:07 GMT+12 +2031-01-14 06:56:54 GMT+10 +2036-04-03 10:15:05 GMT+11 +2036-01-05 10:54:45 GMT+11 +1979-09-22 14:31:26 GMT-9 +\N +2027-08-13 08:28:38 GMT+8 +2005-11-17 13:26:47 GMT+1 +2028-02-23 18:32:03 GMT+9 +1998-06-17 16:08:12 GMT-2 +2024-03-02 13:53:12 GMT+7 +1991-02-24 19:52:40 GMT-5 +1973-11-23 22:00:24 GMT-11 +2004-03-18 17:04:06 GMT+0 +2033-08-13 19:45:25 GMT+10 +2031-04-23 15:11:59 GMT+10 +2009-12-31 01:57:02 GMT+2 +1982-11-10 21:58:20 GMT-7 +2010-10-07 04:13:26 GMT+2 +1982-05-18 11:15:30 GMT-8 +2010-09-28 14:05:51 GMT+2 +1986-01-10 21:00:43 GMT-6 +1997-03-20 05:04:03 GMT-2 +2019-11-15 09:43:14 GMT+6 +2017-09-06 00:45:47 GMT+5 +2026-08-04 01:10:21 GMT+8 +1992-01-20 08:03:40 GMT-4 +2010-11-21 02:58:16 GMT+2 +1974-03-27 16:19:28 GMT-11 +2025-04-12 09:52:38 GMT+7 +2014-09-14 22:11:22 GMT+4 +1978-09-27 23:47:35 GMT-9 +1985-10-29 21:31:25 GMT-6 +2018-07-07 00:50:47 GMT+5 +1989-08-31 01:29:41 GMT-5 +1971-01-10 05:35:01 GMT-12 +1983-05-28 15:52:19 GMT-7 +2036-11-29 15:30:27 GMT+12 +2022-01-24 17:56:31 GMT+6 +2023-06-09 07:37:51 GMT+7 +1989-06-08 04:26:44 GMT-5 +2023-11-15 01:11:44 GMT+7 +1997-04-16 06:19:03 GMT-2 +2021-12-18 16:37:32 GMT+6 +2009-10-07 21:30:12 GMT+2 +1994-01-29 03:07:14 GMT-4 +1991-05-26 12:45:10 GMT-4 +1984-10-01 17:18:39 GMT-7 +2034-01-04 01:22:45 GMT+11 +1977-03-15 11:46:55 GMT-9 +1990-11-22 14:57:16 GMT-5 +2003-03-15 06:11:50 GMT +1977-08-27 08:26:42 GMT-9 +1972-10-17 01:34:22 GMT-11 +2032-09-15 22:54:13 GMT+10 +1994-07-31 10:50:47 GMT-3 +2024-08-25 05:13:02 GMT+7 +2013-09-18 18:19:31 GMT+3 +\N +\N +1981-12-25 22:21:59 GMT-8 +\N +2008-10-31 04:45:33 GMT+2 +2013-02-02 02:15:09 GMT+3 +2018-10-18 12:56:11 GMT+5 +1978-11-07 15:17:16 GMT-9 +2037-01-19 01:13:16 GMT+12 +\N +2021-04-21 08:45:28 GMT+6 +2028-01-21 13:52:14 GMT+8 +2015-03-11 16:07:57 GMT+4 +2005-03-13 17:22:20 GMT+0 +2004-01-28 20:45:12 GMT+0 +1991-03-21 13:23:37 GMT-5 +2029-07-26 06:01:08 GMT+9 +infinity +infinity +infinity +infinity +infinity +infinity +2002-11-15 19:24:22 GMT +1987-05-02 16:25:01 GMT-6 +1999-04-01 05:54:41 GMT-2 +2034-03-18 02:35:05 GMT+11 +1998-07-08 21:50:27 GMT-2 +2015-06-08 05:27:05 GMT+4 +1999-05-16 03:11:42 GMT-2 +2017-10-29 15:46:32 GMT+5 +2037-06-14 02:45:44 GMT+12 +1997-11-29 16:30:09 GMT-2 +1993-02-26 22:47:39 GMT-4 +1998-12-04 16:43:34 GMT-2 +2025-08-14 16:29:42 GMT+8 +1982-07-31 16:54:13 GMT-8 +\N +2029-04-26 12:37:56 GMT+9 +2025-09-21 23:33:16 GMT+8 +\N +2017-06-24 03:39:14 GMT+5 +1990-11-17 19:15:50 GMT-5 +2014-03-30 15:02:02 GMT+4 +\N +2020-08-26 07:34:24 GMT+6 +1992-11-14 14:14:38 GMT-4 +2010-04-22 17:35:09 GMT+2 +2034-03-23 18:10:28 GMT+11 +1984-10-17 02:42:50 GMT-7 +2001-04-29 03:59:54 GMT-1 +1996-08-15 07:16:34 GMT-3 +infinity +infinity +infinity +infinity +infinity +infinity +\N +1974-02-10 02:40:01 GMT-11 +2033-03-28 21:51:56 GMT+10 +2004-07-20 07:26:40 GMT+0 +2017-07-25 17:26:58 GMT+5 +2025-12-12 13:27:04 GMT+8 +2021-10-01 01:30:40 GMT+6 +1986-11-23 18:40:24 GMT-6 +2025-10-28 20:22:31 GMT+8 +2021-04-23 14:09:07 GMT+6 +2002-03-22 12:22:14 GMT-1 +2013-01-18 08:22:38 GMT+3 +1988-06-29 03:31:44 GMT-5 +1976-04-19 18:45:58 GMT-10 +2008-01-09 11:25:14 GMT+1 +1998-11-13 14:35:23 GMT-2 +2033-12-18 16:43:16 GMT+11 +1983-07-06 14:11:03 GMT-7 +2017-07-23 00:18:04 GMT+5 +2037-01-27 05:35:21 GMT+12 +2004-09-14 14:29:32 GMT+0 +2035-11-11 14:27:23 GMT+11 +2021-05-09 15:44:42 GMT+6 +1987-11-02 15:56:40 GMT-6 +\N +2026-12-02 12:28:27 GMT+8 +1990-12-27 22:26:41 GMT-5 +\N +1987-07-11 01:54:54 GMT-6 +2029-11-27 10:59:35 GMT+9 +1990-04-16 21:11:47 GMT-5 +2023-05-11 10:45:45 GMT+7 +\N +2011-08-05 12:47:00 GMT+3 +1993-06-09 06:44:41 GMT-4 +1982-06-02 21:37:08 GMT-8 +2025-08-13 13:03:45 GMT+8 +1974-03-11 03:30:14 GMT-11 +2011-09-27 19:47:44 GMT+3 +1992-12-02 08:40:51 GMT-4 +2033-10-11 14:20:32 GMT+10 +1983-01-20 16:32:49 GMT-7 +2002-09-15 02:56:52 GMT +2006-09-27 11:45:29 GMT+1 +1997-06-29 05:54:17 GMT-2 +\N +1984-02-22 06:27:16 GMT-7 +1992-07-19 22:54:28 GMT-4 +1991-12-22 22:41:11 GMT-4 +1990-07-06 01:25:10 GMT-5 +1973-06-15 06:34:08 GMT-11 +1976-03-21 22:21:06 GMT-10 +1976-06-15 07:14:46 GMT-10 +-infinity +-infinity +-infinity +-infinity +-infinity +-infinity +1972-01-15 06:30:22 GMT-11 +1971-08-06 10:41:43 GMT-11 +1995-08-21 21:12:06 GMT-3 +2006-07-30 07:14:27 GMT+1 +2023-09-20 23:59:35 GMT+7 +2003-03-18 13:53:03 GMT +2008-06-18 05:31:10 GMT+2 +1993-06-07 06:55:33 GMT-4 +2036-08-28 00:46:01 GMT+12 +1970-05-11 08:36:31 GMT-12 +\N +2019-01-10 06:01:34 GMT+5 +1986-04-16 07:43:34 GMT-6 +2007-07-14 07:46:45 GMT+1 +2015-05-11 05:13:03 GMT+4 +2025-12-09 22:39:17 GMT+8 +\N +2012-11-18 03:02:25 GMT+3 +2022-08-08 02:56:37 GMT+7 +1988-10-20 06:54:55 GMT-5 +1985-02-21 08:46:49 GMT-7 +1996-05-22 09:18:01 GMT-3 +1976-10-07 09:21:50 GMT-10 +1981-08-22 18:22:24 GMT-8 +2035-08-31 17:58:57 GMT+11 +\N +\N +\N +2011-11-13 20:09:47 GMT+3 +2018-04-02 08:00:33 GMT+5 +1984-08-07 03:17:37 GMT-7 +1988-07-13 10:31:02 GMT-5 +\N +2029-08-28 23:46:32 GMT+9 +2008-12-10 12:40:01 GMT+2 +1974-01-10 13:11:31 GMT-11 +1996-06-17 05:23:30 GMT-3 +1974-06-05 10:42:56 GMT-10 +1975-02-19 03:03:35 GMT-10 +1994-06-29 10:30:45 GMT-3 +2018-07-08 13:28:04 GMT+5 +\N +1984-10-09 13:52:14 GMT-7 +2033-12-23 18:53:07 GMT+11 +2021-06-11 02:18:52 GMT+6 +2027-01-20 17:50:31 GMT+8 +2012-04-17 09:46:09 GMT+3 +1982-03-08 23:06:30 GMT-8 +2004-11-03 06:34:08 GMT+0 +2028-05-30 05:51:58 GMT+9 +1981-09-06 21:46:45 GMT-8 +2004-08-12 16:29:39 GMT+0 +1998-02-22 05:03:52 GMT-2 +2023-03-08 13:32:55 GMT+7 +1991-09-16 22:16:21 GMT-4 +\N +1999-07-18 22:54:50 GMT-2 +\N +1991-11-12 03:31:16 GMT-4 +1993-08-28 02:05:54 GMT-4 +2026-11-10 04:53:55 GMT+8 +2015-06-21 03:00:10 GMT+4 +2032-02-05 04:32:19 GMT+10 +1974-11-25 19:29:07 GMT-10 +1985-11-04 00:11:55 GMT-6 +1992-11-23 19:38:16 GMT-4 +1986-07-18 15:15:06 GMT-6 +1987-10-25 00:29:12 GMT-6 +1987-12-08 10:12:04 GMT-6 +1996-11-06 14:53:07 GMT-3 +2028-05-15 05:23:09 GMT+9 +1998-12-12 15:18:10 GMT-2 +1974-11-23 21:02:21 GMT-10 +2021-08-17 04:11:28 GMT+6 +2020-04-02 06:42:25 GMT+6 +\N +1994-07-01 07:43:54 GMT-3 +1989-03-09 09:10:29 GMT-5 +2008-04-06 08:50:48 GMT+1 +2027-02-23 20:30:36 GMT+8 +1983-10-09 05:19:36 GMT-7 +1995-10-16 11:47:58 GMT-3 +2007-09-09 12:43:09 GMT+1 +1977-12-16 00:24:08 GMT-9 +2008-04-05 21:05:40 GMT+1 +\N +1983-10-17 15:56:25 GMT-7 +2029-08-12 12:45:17 GMT+9 +1994-10-20 18:41:45 GMT-3 +2017-12-08 16:48:31 GMT+5 +2018-02-09 08:41:07 GMT+5 +1973-12-13 16:00:22 GMT-11 +2022-06-20 01:45:40 GMT+7 +2033-05-25 10:33:11 GMT+10 +2017-02-01 05:21:30 GMT+5 +2019-02-01 20:00:39 GMT+5 +1970-10-01 20:58:08 GMT-12 +2025-01-17 02:23:47 GMT+7 +1974-03-08 16:19:17 GMT-11 +2020-05-27 21:01:29 GMT+6 +1999-01-10 08:39:45 GMT-2 +2009-05-26 10:18:22 GMT+2 +2014-07-22 07:54:09 GMT+4 +2036-10-26 20:20:46 GMT+12 +2025-03-03 08:47:09 GMT+7 +1972-09-22 21:58:10 GMT-11 +1998-05-16 11:05:46 GMT-2 +1994-09-14 08:08:42 GMT-3 +1976-08-14 12:10:43 GMT-10 +1975-03-14 19:29:02 GMT-10 +1980-10-31 16:30:29 GMT-8 +1975-10-03 19:29:30 GMT-10 +2020-02-09 20:43:47 GMT+6 +1975-04-02 10:00:13 GMT-10 +2027-07-27 11:17:48 GMT+8 +2022-05-23 09:28:31 GMT+6 +1999-11-08 15:38:46 GMT-1 +2007-01-14 02:48:02 GMT+1 +2026-05-04 08:12:13 GMT+8 +2029-06-19 09:20:46 GMT+9 +2005-05-07 19:38:36 GMT+0 +2023-04-23 14:15:53 GMT+7 +2022-02-12 01:05:51 GMT+6 diff --git a/contrib/btree_gist/data/timetz.data b/contrib/btree_gist/data/timetz.data new file mode 100644 index 0000000..a179a1e --- /dev/null +++ b/contrib/btree_gist/data/timetz.data @@ -0,0 +1,599 @@ +15:27:57 GMT +06:01:36 GMT-2 +\N +01:21:20 GMT+2 +05:54:01 GMT+6 +09:31:20 GMT-9 +19:54:37 GMT-4 +19:06:34 GMT-1 +18:35:22 GMT+3 +22:44:32 GMT+3 +12:43:45 GMT-2 +04:24:22 GMT-11 +\N +22:06:36 GMT+9 +15:03:19 GMT-10 +06:20:33 GMT-3 +05:37:36 GMT+4 +18:59:04 GMT+10 +10:59:54 GMT+3 +10:47:42 GMT-6 +17:04:29 GMT-10 +05:11:44 GMT+6 +08:51:37 GMT+7 +02:47:04 GMT-3 +11:17:51 GMT-11 +01:36:35 GMT+6 +07:23:02 GMT+11 +\N +21:07:23 GMT+5 +\N +03:37:48 GMT+9 +17:43:22 GMT +08:06:10 GMT+11 +03:21:13 GMT +14:23:04 GMT-1 +00:15:56 GMT-8 +12:30:28 GMT-1 +04:51:05 GMT +\N +04:29:31 GMT+5 +00:38:02 GMT+8 +\N +07:38:35 GMT+10 +03:55:56 GMT +12:06:21 GMT-2 +07:46:45 GMT+3 +10:31:07 GMT-12 +12:13:46 GMT+4 +06:55:31 GMT+0 +00:58:12 GMT+4 +14:46:31 GMT+7 +07:28:15 GMT-2 +\N +02:10:16 GMT-4 +\N +19:05:11 GMT-3 +13:54:34 GMT+2 +04:03:22 GMT+3 +02:13:28 GMT+11 +07:49:42 GMT-2 +02:53:33 GMT-11 +01:08:33 GMT-4 +09:42:32 GMT+10 +17:56:28 GMT+10 +17:37:54 GMT+0 +02:02:52 GMT-4 +07:43:47 GMT+0 +23:07:36 GMT+2 +18:43:22 GMT-9 +21:43:23 GMT-4 +\N +06:17:36 GMT-6 +23:16:48 GMT+11 +19:55:50 GMT+8 +05:55:45 GMT+6 +16:49:59 GMT-9 +17:50:54 GMT-9 +11:52:36 GMT +\N +08:48:56 GMT+7 +\N +06:28:01 GMT+11 +03:02:25 GMT-11 +15:28:12 GMT+5 +12:26:01 GMT+2 +20:02:07 GMT-8 +14:30:34 GMT+3 +09:43:31 GMT-7 +11:44:00 GMT-9 +20:06:06 GMT-11 +17:44:33 GMT+1 +11:53:21 GMT +20:26:36 GMT-5 +07:04:27 GMT+2 +\N +\N +\N +05:37:29 GMT+10 +01:18:02 GMT-1 +23:17:09 GMT-8 +04:50:14 GMT+10 +00:56:52 GMT-10 +\N +21:09:49 GMT+8 +\N +13:58:05 GMT-4 +\N +\N +20:00:56 GMT+8 +11:43:43 GMT+2 +08:43:58 GMT-7 +\N +07:25:38 GMT-7 +15:44:40 GMT-2 +19:38:12 GMT+3 +23:26:57 GMT-8 +05:53:18 GMT+5 +12:51:41 GMT-8 +18:03:27 GMT-4 +22:57:21 GMT+8 +10:57:10 GMT-8 +19:03:21 GMT+1 +15:56:10 GMT-10 +14:41:42 GMT-6 +\N +21:55:54 GMT+10 +07:49:13 GMT-2 +20:08:21 GMT-8 +17:37:16 GMT-4 +11:31:02 GMT+4 +18:16:43 GMT-9 +08:47:52 GMT-6 +20:19:41 GMT-12 +08:10:03 GMT-1 +02:45:28 GMT+10 +15:43:13 GMT-7 +09:00:43 GMT+11 +18:02:24 GMT-2 +19:21:57 GMT+4 +07:24:41 GMT+6 +20:08:46 GMT-2 +22:19:29 GMT-1 +21:22:15 GMT-1 +07:40:41 GMT+1 +20:04:02 GMT-10 +01:44:27 GMT+10 +13:22:27 GMT-7 +23:03:59 GMT+11 +17:46:43 GMT-3 +23:06:32 GMT+7 +19:55:27 GMT+2 +04:24:35 GMT+4 +03:50:26 GMT+5 +01:43:48 GMT+12 +17:05:49 GMT-3 +00:51:15 GMT-9 +18:17:56 GMT-11 +10:01:57 GMT-2 +13:16:32 GMT+3 +\N +05:36:40 GMT-8 +09:35:09 GMT-1 +01:29:26 GMT+8 +03:31:43 GMT+0 +\N +07:03:58 GMT-6 +\N +10:21:54 GMT+8 +07:49:09 GMT+11 +\N +\N +07:54:40 GMT+10 +20:26:20 GMT+2 +09:50:27 GMT+12 +19:05:44 GMT+8 +19:58:12 GMT-3 +11:43:30 GMT-5 +00:25:16 GMT-1 +05:07:26 GMT+9 +13:38:21 GMT-11 +19:48:53 GMT+3 +01:11:45 GMT-6 +07:06:48 GMT-2 +08:56:45 GMT+12 +21:17:06 GMT+2 +10:06:56 GMT-8 +17:33:35 GMT-9 +23:22:58 GMT+2 +21:47:12 GMT-8 +20:50:30 GMT+3 +01:04:16 GMT-7 +19:25:47 GMT-2 +22:57:00 GMT-6 +09:15:33 GMT+11 +17:16:48 GMT-10 +02:29:36 GMT-2 +21:27:15 GMT-5 +\N +08:09:46 GMT+9 +02:10:34 GMT-9 +06:14:24 GMT+0 +14:04:25 GMT-1 +03:37:23 GMT-8 +21:36:37 GMT-7 +15:41:07 GMT+2 +03:40:25 GMT+7 +15:51:36 GMT+6 +05:10:09 GMT-2 +18:54:02 GMT+1 +16:49:50 GMT+7 +03:18:24 GMT+3 +23:13:58 GMT+5 +05:55:06 GMT+7 +22:21:51 GMT-12 +\N +07:12:18 GMT+2 +02:33:13 GMT+8 +10:23:41 GMT-5 +15:10:12 GMT+1 +05:44:10 GMT+11 +21:59:24 GMT-3 +00:22:35 GMT-3 +23:07:01 GMT-8 +22:25:59 GMT-2 +13:25:35 GMT-4 +08:44:13 GMT-8 +\N +07:06:12 GMT+7 +\N +\N +13:42:39 GMT+1 +02:55:37 GMT+7 +05:24:12 GMT +01:01:47 GMT+9 +07:49:06 GMT+9 +19:07:56 GMT+6 +03:35:35 GMT-4 +07:28:30 GMT+10 +05:00:13 GMT+4 +16:27:26 GMT-5 +\N +\N +23:39:57 GMT+3 +07:31:16 GMT+9 +19:08:03 GMT+12 +\N +11:39:43 GMT+11 +21:07:04 GMT +\N +07:59:10 GMT +14:50:17 GMT+4 +02:57:29 GMT-6 +23:37:36 GMT+5 +\N +17:04:36 GMT-1 +13:24:45 GMT-3 +23:42:26 GMT-3 +\N +21:09:05 GMT-7 +07:47:28 GMT+3 +13:18:23 GMT+9 +00:45:21 GMT+6 +03:30:56 GMT-11 +\N +14:02:41 GMT-8 +23:11:51 GMT+7 +03:34:54 GMT-4 +00:47:25 GMT-1 +02:20:03 GMT-6 +\N +\N +01:26:47 GMT+6 +01:33:06 GMT-2 +10:55:32 GMT+1 +16:14:38 GMT-4 +21:54:21 GMT-7 +20:59:06 GMT-11 +12:26:22 GMT-11 +\N +11:48:58 GMT-5 +14:32:43 GMT-8 +11:36:17 GMT-3 +06:57:47 GMT+2 +\N +02:56:30 GMT-10 +12:30:59 GMT+12 +11:33:55 GMT+3 +04:53:09 GMT-4 +00:01:24 GMT+8 +02:14:43 GMT+3 +05:25:07 GMT +14:26:05 GMT-1 +18:48:04 GMT-7 +19:03:41 GMT-3 +05:35:15 GMT-6 +21:07:05 GMT-4 +05:09:25 GMT+2 +19:39:43 GMT-10 +19:05:27 GMT-1 +04:14:52 GMT+1 +01:25:07 GMT+0 +03:51:35 GMT-9 +\N +17:13:57 GMT-1 +12:24:31 GMT-10 +18:57:03 GMT-4 +22:44:23 GMT-6 +18:42:47 GMT+3 +\N +22:10:45 GMT-9 +07:10:14 GMT-3 +02:26:04 GMT-2 +22:50:57 GMT+3 +09:05:32 GMT+4 +\N +20:12:10 GMT-3 +01:05:22 GMT+5 +12:42:38 GMT+11 +00:08:34 GMT+9 +23:24:07 GMT+12 +06:56:54 GMT+10 +10:15:05 GMT+11 +10:54:45 GMT+11 +14:31:26 GMT-9 +\N +08:28:38 GMT+8 +13:26:47 GMT+1 +18:32:03 GMT+9 +16:08:12 GMT-2 +13:53:12 GMT+7 +19:52:40 GMT-5 +22:00:24 GMT-11 +17:04:06 GMT+0 +19:45:25 GMT+10 +15:11:59 GMT+10 +01:57:02 GMT+2 +21:58:20 GMT-7 +04:13:26 GMT+2 +11:15:30 GMT-8 +14:05:51 GMT+2 +21:00:43 GMT-6 +05:04:03 GMT-2 +09:43:14 GMT+6 +00:45:47 GMT+5 +01:10:21 GMT+8 +08:03:40 GMT-4 +02:58:16 GMT+2 +16:19:28 GMT-11 +09:52:38 GMT+7 +22:11:22 GMT+4 +23:47:35 GMT-9 +21:31:25 GMT-6 +00:50:47 GMT+5 +01:29:41 GMT-5 +05:35:01 GMT-12 +15:52:19 GMT-7 +15:30:27 GMT+12 +17:56:31 GMT+6 +07:37:51 GMT+7 +04:26:44 GMT-5 +01:11:44 GMT+7 +06:19:03 GMT-2 +16:37:32 GMT+6 +21:30:12 GMT+2 +03:07:14 GMT-4 +12:45:10 GMT-4 +17:18:39 GMT-7 +01:22:45 GMT+11 +11:46:55 GMT-9 +14:57:16 GMT-5 +06:11:50 GMT +08:26:42 GMT-9 +01:34:22 GMT-11 +22:54:13 GMT+10 +10:50:47 GMT-3 +05:13:02 GMT+7 +18:19:31 GMT+3 +\N +\N +22:21:59 GMT-8 +\N +04:45:33 GMT+2 +02:15:09 GMT+3 +12:56:11 GMT+5 +15:17:16 GMT-9 +01:13:16 GMT+12 +\N +08:45:28 GMT+6 +13:52:14 GMT+8 +16:07:57 GMT+4 +17:22:20 GMT+0 +20:45:12 GMT+0 +13:23:37 GMT-5 +06:01:08 GMT+9 +19:24:22 GMT +16:25:01 GMT-6 +05:54:41 GMT-2 +02:35:05 GMT+11 +21:50:27 GMT-2 +05:27:05 GMT+4 +03:11:42 GMT-2 +15:46:32 GMT+5 +02:45:44 GMT+12 +16:30:09 GMT-2 +22:47:39 GMT-4 +16:43:34 GMT-2 +16:29:42 GMT+8 +16:54:13 GMT-8 +\N +12:37:56 GMT+9 +23:33:16 GMT+8 +\N +03:39:14 GMT+5 +19:15:50 GMT-5 +15:02:02 GMT+4 +\N +07:34:24 GMT+6 +14:14:38 GMT-4 +17:35:09 GMT+2 +18:10:28 GMT+11 +02:42:50 GMT-7 +03:59:54 GMT-1 +07:16:34 GMT-3 +\N +02:40:01 GMT-11 +21:51:56 GMT+10 +07:26:40 GMT+0 +17:26:58 GMT+5 +13:27:04 GMT+8 +01:30:40 GMT+6 +18:40:24 GMT-6 +20:22:31 GMT+8 +14:09:07 GMT+6 +12:22:14 GMT-1 +08:22:38 GMT+3 +03:31:44 GMT-5 +18:45:58 GMT-10 +11:25:14 GMT+1 +14:35:23 GMT-2 +16:43:16 GMT+11 +14:11:03 GMT-7 +00:18:04 GMT+5 +05:35:21 GMT+12 +14:29:32 GMT+0 +14:27:23 GMT+11 +15:44:42 GMT+6 +15:56:40 GMT-6 +\N +12:28:27 GMT+8 +22:26:41 GMT-5 +\N +01:54:54 GMT-6 +10:59:35 GMT+9 +21:11:47 GMT-5 +10:45:45 GMT+7 +\N +12:47:00 GMT+3 +06:44:41 GMT-4 +21:37:08 GMT-8 +13:03:45 GMT+8 +03:30:14 GMT-11 +19:47:44 GMT+3 +08:40:51 GMT-4 +14:20:32 GMT+10 +16:32:49 GMT-7 +02:56:52 GMT +11:45:29 GMT+1 +05:54:17 GMT-2 +\N +06:27:16 GMT-7 +22:54:28 GMT-4 +22:41:11 GMT-4 +01:25:10 GMT-5 +06:34:08 GMT-11 +22:21:06 GMT-10 +07:14:46 GMT-10 +06:30:22 GMT-11 +10:41:43 GMT-11 +21:12:06 GMT-3 +07:14:27 GMT+1 +23:59:35 GMT+7 +13:53:03 GMT +05:31:10 GMT+2 +06:55:33 GMT-4 +00:46:01 GMT+12 +08:36:31 GMT-12 +\N +06:01:34 GMT+5 +07:43:34 GMT-6 +07:46:45 GMT+1 +05:13:03 GMT+4 +22:39:17 GMT+8 +\N +03:02:25 GMT+3 +02:56:37 GMT+7 +06:54:55 GMT-5 +08:46:49 GMT-7 +09:18:01 GMT-3 +09:21:50 GMT-10 +18:22:24 GMT-8 +17:58:57 GMT+11 +\N +\N +\N +20:09:47 GMT+3 +08:00:33 GMT+5 +03:17:37 GMT-7 +10:31:02 GMT-5 +\N +23:46:32 GMT+9 +12:40:01 GMT+2 +13:11:31 GMT-11 +05:23:30 GMT-3 +10:42:56 GMT-10 +03:03:35 GMT-10 +10:30:45 GMT-3 +13:28:04 GMT+5 +\N +13:52:14 GMT-7 +18:53:07 GMT+11 +02:18:52 GMT+6 +17:50:31 GMT+8 +09:46:09 GMT+3 +23:06:30 GMT-8 +06:34:08 GMT+0 +05:51:58 GMT+9 +21:46:45 GMT-8 +16:29:39 GMT+0 +05:03:52 GMT-2 +13:32:55 GMT+7 +22:16:21 GMT-4 +\N +22:54:50 GMT-2 +\N +03:31:16 GMT-4 +02:05:54 GMT-4 +04:53:55 GMT+8 +03:00:10 GMT+4 +04:32:19 GMT+10 +19:29:07 GMT-10 +00:11:55 GMT-6 +19:38:16 GMT-4 +15:15:06 GMT-6 +00:29:12 GMT-6 +10:12:04 GMT-6 +14:53:07 GMT-3 +05:23:09 GMT+9 +15:18:10 GMT-2 +21:02:21 GMT-10 +04:11:28 GMT+6 +06:42:25 GMT+6 +\N +07:43:54 GMT-3 +09:10:29 GMT-5 +08:50:48 GMT+1 +20:30:36 GMT+8 +05:19:36 GMT-7 +11:47:58 GMT-3 +12:43:09 GMT+1 +00:24:08 GMT-9 +21:05:40 GMT+1 +\N +15:56:25 GMT-7 +12:45:17 GMT+9 +18:41:45 GMT-3 +16:48:31 GMT+5 +08:41:07 GMT+5 +16:00:22 GMT-11 +01:45:40 GMT+7 +10:33:11 GMT+10 +05:21:30 GMT+5 +20:00:39 GMT+5 +20:58:08 GMT-12 +02:23:47 GMT+7 +16:19:17 GMT-11 +21:01:29 GMT+6 +08:39:45 GMT-2 +10:18:22 GMT+2 +07:54:09 GMT+4 +20:20:46 GMT+12 +08:47:09 GMT+7 +21:58:10 GMT-11 +11:05:46 GMT-2 +08:08:42 GMT-3 +12:10:43 GMT-10 +19:29:02 GMT-10 +16:30:29 GMT-8 +19:29:30 GMT-10 +20:43:47 GMT+6 +10:00:13 GMT-10 +11:17:48 GMT+8 +09:28:31 GMT+6 +15:38:46 GMT-1 +02:48:02 GMT+1 +08:12:13 GMT+8 +09:20:46 GMT+9 +19:38:36 GMT+0 +14:15:53 GMT+7 +01:05:51 GMT+6 diff --git a/contrib/btree_gist/data/uuid.data b/contrib/btree_gist/data/uuid.data new file mode 100644 index 0000000..df118d3 --- /dev/null +++ b/contrib/btree_gist/data/uuid.data @@ -0,0 +1,703 @@ +5ad32d7e-b463-4363-a65f-52475d9fab27 +7787b66e-cf0e-48c2-b989-1e44e8a00891 +75ec8a55-afcf-442d-9e1d-f9d67a15caf9 +6f36f2e9-5e58-4961-9e8d-9c3ca1cfcd44 +78f6d184-f74e-4a38-81ce-a821e301e9ac +1aa4bf49-dd76-40df-a86c-393fd202b710 +98559f1d-00b3-417e-bc57-9053545a260d +ac24b4d7-1a8f-4abc-a02e-b3294497d18e +a425d99f-ee91-419a-8cd1-6908b8c89679 +815f3632-4d1b-42e3-8dbf-68a20e7fbea5 +ae212041-e64a-4ff1-b8c9-b922fc3f2087 +8e580963-0584-4391-9f1e-3e607d435bd8 +9348cf2f-fe8b-4f05-829e-e2f732482bcc +807b3ff5-0dac-46e1-ba10-07753ec4c7f5 +93325505-83f0-41f4-a060-0d6e1a166815 +cabeaad4-0096-4bc8-8d29-29623d3658c9 +8edda4dd-07fc-4457-b9c0-a28a97cdf9ee +4b7b09ae-d11d-4d54-aad2-c7dcbce92e04 +3ba8ace3-be63-4a11-b057-8ede07d49089 +70dfc341-127f-4d54-a8d4-8159eb119a8f +\N +f4910786-02d1-4874-9be7-c2cd5774fa1e +61a8407e-91f4-41f4-8050-32c8659839ac +d23c6778-d021-426b-8435-e7ee7172a456 +\N +77b3bb98-f8a6-4dc1-b2db-174da47ef88d +4ed9962e-9f7b-4bd2-b791-62c87e7e0f32 +\N +bf30fce0-2497-4594-94e2-5dabb58fa3d6 +289dcd8c-8046-4748-b1e7-3afc51e3791b +622aa432-d5e9-4d02-8a3f-09813dcc00cf +f95c69c4-7d22-41c2-89c1-6bcc6835d478 +2b4ed1c1-38e2-495c-8fba-f2060b983f8a +05905429-70b8-408b-a9b1-b8f00522ec7a +8b1dcbd2-10ca-4a37-a080-a1c846519370 +a6225a20-25c9-4afe-bc33-e6f600eb57a3 +b0121e3f-157c-4c8f-acf3-78dfc752bcb5 +\N +\N +11f510fb-ae06-4542-b936-cb5713908e10 +\N +26293032-dbd0-4914-be29-c060c5adf98b +d3d0b0ce-b60c-4864-9557-ba4a9cca8b1f +16c70611-bbfd-4a46-95fd-3c9e12ff4641 +27dc15a1-5518-4310-bb31-52da9f148afc +a86c2830-282a-4a74-8b5a-e5b562325d82 +8afadc39-3a85-490e-a31e-18d5e787a639 +1159af7d-4d72-4651-a013-9de9f3f03002 +5835b0b1-6a50-44c2-ab41-81fa34a29411 +\N +e031ab87-ccb2-496c-9eba-9966415f82d7 +\N +67b1194b-b700-440e-aa35-93f118707632 +a9a88b6a-c2f0-4de6-ab99-b34853aae425 +c9e3aeda-2001-4ab9-aaf1-57b33d841960 +6bbc3fae-1495-4178-8445-04960dd97c56 +bd7e52f5-4362-4447-9f7c-da14ed54266b +145fd346-4057-4e91-aa82-594d7a16ebce +\N +029d9c72-77e1-4951-b185-30574d00862d +d9233d92-3491-4985-a6bb-763f177535a5 +abe1ffca-8325-4041-8acd-b08904f76cb0 +0c8afca8-f720-4c4b-aab4-e061011db5ca +4f636f73-71e8-4b14-b4c5-25233d115421 +f05a62f1-f495-4ea1-9e20-433c67d2c94e +a21ee4b2-e9bd-4510-9b74-0bac543fc378 +a41bf7a6-92c2-48f1-80ef-0c3d4e002b5f +ed6b5777-b1b4-4e85-ab12-ce1f8054ced1 +04b9cc30-6d01-4f5d-9254-81eb78fbb4af +a89561d0-88e2-43d0-9548-35be00b1f028 +c77ad187-1ed9-4b25-a474-35bf8481cf25 +cc4cf4a1-aa64-4916-ae50-5fa7bb5c3b3b +\N +70337711-dd30-4454-bd6c-0e8f77ba34a0 +1ed02b49-aace-4c5b-a1cf-bc20e2edf03c +84b0f797-baa6-448f-ba29-0234e64ac30b +40556603-648c-4359-9c4c-5e76f21efd1f +af21ec76-27d6-47d8-9ede-1b08049410d3 +0f889e8a-e3b5-46bb-b222-e91cf32623ff +3f5403c1-16b1-4a36-bf4b-77333226e8e4 +5c5ebc3f-aced-4036-9b4a-59e19bd9c74c +8acec555-d518-422b-b416-19a2c5cbf825 +ab1effba-c614-4af2-9c9a-df8f6566f427 +4ae0722c-1c55-4ef1-a9aa-ddd9c43f3dd0 +77e2f0bf-cff2-4128-92da-c6d4bc656267 +2e643fa5-23db-4073-8dac-6f714f823af0 +d6624eac-e776-4e18-9d3c-d6c265a8c378 +\N +623353ca-0675-4a05-94cf-7bb62e49aece +2d29a192-0272-49fa-99ea-9b30e81f0b47 +54cf75a7-6ece-4e20-8f24-036f3daa2fa1 +8964987c-bacd-4474-83a7-2ea51ac7ff3a +87f3e366-6f70-4304-8789-e03f94adcadb +603c2f61-c91c-4f7b-9de4-ad43ea68b00a +fd036a77-ee2a-4c33-a985-709d8667c1a2 +be90eb3d-c147-447b-8ff1-2a62aba6ef3c +448ec399-4249-4daf-85f8-23ad8cc7fbdf +9788716f-1852-4b37-a2c4-f4f65d4eeef6 +bed0413b-64f7-4cf3-b2bd-765b10a31ba8 +6add9145-07d8-4d1d-bad0-57c2a914b90b +\N +caf27337-384f-4f51-92ce-76bd5dbf7317 +0f73ab51-4dc6-4cbf-b74f-faced034c866 +0b8c972d-f1e4-4150-bd9e-baee71af1ca9 +0567dd6d-0386-4002-849b-591d1512cc1e +4c783517-29df-4efa-8f40-12150943e6ab +1be634a9-77cc-4e17-8b24-817067dd2235 +ab233cca-286f-4977-ab03-0c9870b17934 +88d77def-4019-4b0d-8de5-47f3b84b4c1f +036f53a5-5270-4844-bedb-251512538a33 +\N +\N +afca4ef6-56ac-4bbe-8c44-f54858b55ab7 +8613d2fe-d9b6-4491-a70d-8de3ad41b4df +f084c252-43db-4735-bb47-eb45741cedf8 +d35f523d-4c8b-43a7-8839-2c5567ed4934 +40677618-afed-45cd-9299-ff715494d56f +7a5aa5c4-884f-460f-a9f4-a0033eb82de2 +26007d37-61cc-4386-a85e-08e56de23a2c +3753e811-da3f-4a23-92ce-1ae502c99195 +2dfb1eb2-56bf-4b0f-b2f8-3ec2dc2b0191 +551d2a52-f84a-42e4-aa2b-84f7000a3926 +\N +\N +b9cd6a67-e12b-4a0b-b935-92e267ea776f +fd4ec635-a1e5-4fcb-89a3-57bf8aa408b8 +6599e40d-a602-4a54-a24b-a694432992a2 +\N +55e65ca2-4136-4a4b-ba78-02ecb8053cf3 +c5b2bc4d-acf9-4b11-ae9a-02ecb8053cf3 +34bb5f60-3e47-445a-acbd-a5889334f4d5 +743008b5-0779-4d50-8662-1c5988335c33 +71317e40-193f-4a7f-a94a-a188847b0249 +23f14966-adfc-4460-84fa-14190d33d55f +d1bd4bba-8a17-4e8f-93d9-ed09f4719ea4 +b5964792-f607-4cbe-9759-3e459a90fe5e +c49c96e9-77ec-4b75-a3d3-00f9fa8c9ac2 +842f694a-426c-45c7-a7a9-5d750adbdacb +c48446ee-c9f6-4038-9ca2-e32f8706c36f +fd0ab627-022b-4fa5-bb9d-0ada8088c9e6 +b27dd1a0-7938-4e42-a9e7-93672a7d85a1 +\N +5ae9add1-28d5-4fd4-bf1d-f5a3ebabc31e +55e65ca2-4136-4a4b-ba78-cd3fe4678203 +25706dc9-61d1-4c67-be11-22f953132d48 +723f5921-c592-40a8-955c-7f46995b8173 +69c29563-77e3-43e6-93e4-d6ed321a294e +8d6b7ea4-d062-4fb9-a20a-d718abd9e331 +b1e945bc-2a8e-4d1c-b9e7-322b95d31b86 +4ef84213-0b39-4d86-80bb-14d2376f77cf +fd3daf86-96e0-42b7-b83a-c2781aa28e4d +ad6c141d-e257-410b-8596-77153d37b428 +40dd6844-7b56-4f39-a7cb-c00ae3870108 +883559f7-e3af-4f44-aff3-a4bd906e5f86 +98c04f9a-d3b7-4d21-9f89-43ef124d48fa +\N +55e65ca2-4136-4a4b-ba78-698a09529f02 +ee145a4b-6016-4914-b947-698a09529f02 +931473e1-2cb2-4951-9ab0-039396dc2ccb +0df573b5-3ef9-40ef-9b05-208e649965d1 +b13a6d18-33c9-4aa0-b167-88542cd5d8ba +73cbd742-37db-4497-9a30-e10279e9cb10 +63a68ba8-e223-4db7-b56f-459d25d3f873 +aa2e9b1e-e09c-4848-983e-aa4adf34938b +2d58bb19-ac14-42fe-b427-59798b8d9fcb +5d3b3f34-ce9a-4ee6-9400-c7bf7576ffa8 +41945dd7-9fb0-47af-8ee9-10e5a530da3e +\N +c3a9b409-4e0c-4426-8a3d-a87236d189bd +84da10fe-818d-4a3b-85fc-d4c169ca92bc +12a7071d-902d-4438-9c40-c2cd875b51f7 +2e7e60fb-ba46-4005-b6b5-e3630b699254 +9782a6b8-d676-45a8-8991-1c44e60764cc +6760f7e2-6b5a-4ce7-a66a-9014068fc8eb +\N +\N +2dab7131-2ba8-4def-9b28-00c0b1ed929a +9d3df737-16fd-481a-bde6-12a08a01e2f5 +d8390c84-1e90-4759-bd72-2629ca4c51f4 +0c1d5e42-5070-4e51-8076-8e8c15148f99 +9374026b-0ec3-4c44-b656-36520d322eec +\N +\N +ad0aa9a0-122c-4025-b889-70778af8e6e4 +948d0dea-5d30-45da-8867-09d2e1fbedb9 +d295f644-5f4a-4808-b966-b14e73d9fa2a +2de79a17-f91b-4310-ad74-dfa0729ff94d +\N +23eda39b-4f4f-4355-aeba-72771f922d20 +bccc111d-6f15-42c4-95c2-2c3d3329e83c +c4f864e2-f81a-4b65-bd7f-cac745bd7f32 +10447976-1ef1-4599-85fe-d837074c6ea0 +8eecc1f2-77b2-4058-811c-11efb47eb662 +98ad70bc-39b0-4b33-a104-241c6970c4c3 +15c04e8c-69d9-44b7-a177-d08c5b181045 +c6de918d-6536-47b3-b80f-80d049cfe550 +548321e5-cde7-4d74-9bb3-929244806e13 +7e046db3-d1dc-46b3-81e2-fc867ee83298 +5dd5cc44-4ad1-4658-9b4f-509c35a4976e +1aecf136-7583-4d31-980f-7fa5c4610e50 +ddb79c27-a80d-483a-9a52-c61ae2f4f93f +e7aa3080-4d09-43ae-8a2b-5b539ffef909 +aeba0aac-6b8d-4d8e-a5b8-a37359c457c6 +64b5c8e8-118b-414f-9da3-73286249243c +8b72b6d2-d3bb-4e74-a79f-577ff8767777 +dccd9254-24b4-4269-bf07-cfbbd349df54 +51cb6ab8-06c7-4280-9e0f-b2b9dcf026a3 +1f3d16eb-895f-48dd-a9e2-18eda41dacea +3ecc08b6-2b67-442c-92ef-b74af4cb4f42 +b55c7f4a-3d05-43b9-846a-23b86a73d202 +4f54dfd8-39fd-4178-bafa-97b34f131552 +d6874b48-96c0-459f-8dd1-7f7dcbc44b22 +93a213c8-6e28-4eb7-8a94-e1b18e8bebd6 +0743014a-e4a0-4eb9-a9ab-23b0d0d96782 +a7ffb6c6-4e3d-4d08-9cbc-297d8b1bc29e +45e788c4-4054-4d80-886a-e4f006936b7d +c51c64be-f130-40c1-95b9-adb39ef25385 +3af1d3cb-df6b-4d26-910d-a314a57f8550 +\N +477c1cd3-e9c9-4252-bc7c-4db57368ffc6 +b49ecde1-7413-4d6e-80c1-5df680d25617 +1f127009-eb85-4e7d-bfb1-1d804d86609b +\N +25e6a224-70a3-44dc-8faf-50feecee3480 +79094fc2-36d9-4521-bf36-0f6e0850fb6d +cb9b1ab9-96e3-4ac5-aa51-8c3c47437893 +c09d4ab8-5f03-496f-ad31-4b9e7348b9da +d5ab17e1-7017-4fbf-a30e-73a97a85a852 +\N +8c937920-7219-43fe-8436-97aca339a5e5 +b8d001a3-036c-4f15-846d-32e229d66985 +bf2cae32-9096-4ae4-b0c7-efdafdd16e0d +d916d001-fc9b-4e05-985c-b172b873c217 +\N +\N +e2c40a42-ef79-4754-a089-e254345896c3 +953fb2d0-d53a-4cc9-ad48-547263b85399 +8e2b1b14-1c56-48b9-a2b6-7868b42e6efc +\N +\N +76b7f2cb-00e9-4936-bcd0-ebf1db9c6fec +e24d88fb-6219-44c7-9f70-d34e0b274cdc +fda48ed2-113d-4403-b2dc-57f7afe25842 +9aa44ba6-6c48-418d-8689-eab1112b3fbb +42936d25-09a3-4af5-ab98-8c1216c5eca9 +\N +a9ddecb1-b762-4cda-a785-cce77c51c67a +f733a811-b83c-4a29-a59c-786825ea2070 +95e94fe5-eea0-4656-bc34-15724a4785a2 +521c55e4-3a5c-4064-b42f-0e8023af9c94 +a3379a9d-4b32-4956-a814-57b46db4bdc2 +0e46cef6-49ed-4a54-a6be-debcb7546583 +\N +1b1f4424-5c05-4ee6-8eb6-1deb677c743f +\N +caaf024d-ef32-42e5-8875-6fe563e7e18a +\N +98e9ce77-8677-44aa-8703-4633dd089225 +66037e2f-634f-4096-8823-9ded5cb533bb +\N +f6726b9f-8a0b-498e-8479-3ffa7af0cd62 +\N +4fd27a3c-2dd1-4f3d-9f48-a50407e9d1a3 +75737e5a-a509-4239-a627-99e430a4c3d1 +6a8bd5d4-7545-4dc8-86c3-003321cfd437 +73e26e64-baa7-4a9b-8833-8bd822c47146 +\N +d6de86fd-3fae-4de2-9082-e86e53b6ad7c +1143e251-091a-408f-b7b2-c12d05b42c2c +4cc745fe-4667-4041-b941-4a3a23f9c727 +ae7b16f4-9ba8-4dc0-aae7-f78258d4993b +38c5d6b2-ae34-4054-aa07-9a689afb23e1 +3421dcd4-553b-4ba6-b661-5cbf687b982a +\N +75ab9d18-5ba5-479c-b3cc-bcf3c41618b8 +c5a768bc-7765-4973-bd8b-a9528279a0a7 +e7b98405-47e6-404d-ab16-bb76fcca2b1d +\N +6b764c11-a1f1-4ae2-ab87-88bdf7597cbe +\N +e439cb96-6502-4207-a0c8-2fa6bf583bd2 +\N +0d3089c0-3e26-4425-b48a-3e8f18c8b5a2 +1a625bc4-f83b-4735-bf01-a9f748080fa5 +1d1f382c-c702-4c59-84f1-4b4e2f01d1ca +8d3b094e-112e-4535-87b3-04272426a956 +616f4879-2a33-4a5f-a776-08462d27ee1b +e2c83f19-3163-487c-8fc1-7fc00d40ca75 +dbf94f48-5f01-46dd-a13d-def8f9dbb6e0 +8c37b9f1-9766-4a5f-a222-eded294dad76 +\N +4cb9a4a8-e4bf-4757-9d68-36a3e24d7b6f +071ba528-c84a-45fc-8ac5-2368e6c640ae +ad34c9ca-0a4b-4c23-b64c-ccffa1f4dcd8 +4915fbe9-84b6-4bec-b6b4-50ff0cc49e9f +622ef77b-e22d-4ca2-850b-298abb39d870 +3e1803c7-8109-43f2-a742-b4ae6e56be55 +\N +0de0b43f-3e59-4060-a43d-baee17dd2c57 +\N +1a8e57f0-e65b-4e55-b46a-207825e00287 +\N +\N +1877c37c-3567-46e6-9afe-222ac3ccf36f +8f4bab7d-9454-4081-b1af-8e4956ab940f +c1d23aab-ce09-4afc-a95c-b9b0f7b9c16a +04761b0d-40ce-4cb2-a7e0-b1f6d582be26 +a29059f5-d4bc-446f-b903-6f09ec57a410 +4f9fcf12-54ff-442e-a7e3-6aabf45fa474 +facf76d4-e82b-44a3-9d7c-50a84395a2dc +\N +84484041-8e47-4349-91d6-418e9b584750 +44b949a1-502e-4309-af86-90bdfb04a085 +c5da9f0a-cca7-4b5b-82c2-1108e10989df +98e487b8-9527-4fcd-a20a-812c909f1342 +e950bf7c-280d-4774-8891-6cf6da8c8ab4 +d5a28af5-f357-4de9-a8e3-3518a8ca5556 +ae450238-e659-4253-90ee-afd66a676515 +294509e2-b4e1-42a3-8982-b7dca385e4c9 +9546a087-6cac-47d6-b55f-3828df9aec99 +4a715abf-18d1-4ffc-9ea3-fd3f069502d9 +e0e07aef-ce8b-4499-befa-430724707d66 +4df8c6d6-278e-4ba4-af45-7240805ce0a0 +1637abb1-1d05-4055-98fa-296fe8f8bbf2 +e3850663-b73e-4f0a-8107-0775825783ef +7b2dbbbf-2c82-49dd-9383-0edd1aee6d89 +2b99818e-2c53-4ab2-8f78-29bdbaa208b2 +22787985-d2c1-4af4-8b90-7bc441123952 +153166fb-923e-41d4-8413-edad8959a8d5 +732fab66-3055-49f8-807e-463fae914988 +1c39ccc7-2b24-4760-93d3-3e687ed04e1c +33f3d119-ff2a-4341-8145-b69f67e11192 +0370cd89-a0c1-4d8c-85fe-3df1eddc734b +d5a5e95c-e99c-48f6-9ebb-765651b893f9 +3e73a023-7b53-4323-a1f3-511809d322dc +44d2a25c-933c-4345-8d19-cc52daaa9f11 +fc25f498-455d-4a13-9ca4-9ed2b843da17 +f9737152-aa7c-47a3-b96f-d0560ccea84f +\N +c1140446-9885-4a23-a709-593c0ba3818d +050474c6-e6f5-417f-bcb8-2cc288cc412c +9f07cb2f-aec4-4108-a07a-ea51366043f2 +4d9cf071-119b-497d-88de-0d05d21bac34 +7f5de1dd-3848-4ad8-a0e6-fb4039eec4f8 +f97c4e03-e8ce-443f-846b-82485c8bf04d +2354e08c-8415-4b24-8cc2-b7b7616b6588 +b0a5e7da-9382-4a9f-b64c-0c4e5c3755ca +c4812583-5689-4384-8044-e200f796afee +890b609b-ac04-4bf3-9070-492a5db11e1c +b0789be3-59ad-44e5-b1c6-394df496ae1f +512ee0f6-380b-4758-ae20-b10d578473a9 +ad84303a-90e0-44dd-a7a2-b6015e748067 +986947bf-e924-4fb8-bdec-bd91f1b9d634 +fa576306-7eb6-411d-aa92-9e67678530ee +\N +3d66ae17-f27e-45f0-8b95-d47821b1d09c +e8f40639-2c3e-4802-96e1-b8ccf15762f1 +482c9541-9fa9-444f-8155-ceb86056f2f7 +f8dbf3c1-c67f-4a71-8bb1-b754d2fc4d9d +4764906b-3b71-4ccf-80d2-fb985af7728a +628312b0-5f3d-4856-a3b0-6e7f60dd19ee +37b5c576-d2c0-40b5-91cf-712b41dea919 +e7eefe4c-e647-40ba-bde1-bf4aa780b0ab +\N +4089198b-e1f1-4cf5-aa74-1e32aef1f034 +\N +5133a3c7-b9cd-4659-ad99-0453b433f237 +7d6c0ec0-0c7c-4a98-9d75-74a937b7ecba +74faeb7e-e0ac-4505-b2c5-bad6a39c6abb +ad895aff-3527-423b-bc82-607d9586f5fc +19a14c87-ab30-4747-8dc4-7599b4015960 +44955907-c727-4cab-aec3-464332e444fa +\N +b5d4dc6b-b65b-4a36-8f16-cf9760631718 +\N +49b4a368-b530-49ec-b8ed-7664c2dda4cc +e70020b6-eeac-425b-a3ce-9f5ad642f371 +448473ab-2a0f-4200-b7a7-b7583002779e +\N +\N +8a8b74f9-f49a-4f77-b6ab-2011df5645ca +\N +67ae0fa2-08c6-4566-92c4-adae5be3c3cc +1453d200-133e-4df3-9723-eb43bd21d896 +a7f3072e-e567-4e23-9bd0-70aba0554281 +f9caa2e9-6d43-4559-a9e7-6e5d7e7b2769 +b6a0b42b-70a3-42e0-b623-9dee8e2d3b85 +\N +05f2c97f-4c81-43a2-9c7b-8a1cf8de2474 +9287055b-ef1d-4b7f-bc28-fd637adaf530 +e0ed08d4-2521-46b1-983a-03c3cf915e42 +285a1259-f929-4e37-b25b-62af93fb1ea1 +d76631da-ace1-472c-a23d-7d4f2702f771 +80f89372-02be-4ad0-a1e5-9a2490769427 +\N +3c1043f7-f77f-4788-abc7-5615804ccd69 +a3942f4f-27f4-44bf-bf28-6ae854d4d346 +3b741249-a9bd-452b-bd08-9ae337134f13 +5aea4b8f-7dbb-4b7a-ad1b-cee1d93a5393 +cae01e8c-e75d-4c3d-8d90-ee3ebdb011d0 +13aeefb0-dbba-4cee-b108-931f23e286b1 +\N +40aee193-6c24-4a13-a004-9f4dec1ab2cf +2b731fff-597d-4a6e-888c-2ec72fe0dbef +4581b196-149e-492e-9053-5040207dcc19 +68d07598-261e-40bc-a2f5-e8f72cc86104 +94c1ecf0-2bdc-4d0f-962f-226a9617b8cb +2fcfe646-edc5-4397-8032-c4b4cd88afce +2ed39277-375a-4e9a-846d-660fe531bed2 +1244efdd-5d49-403a-9649-2550abae81f2 +281b757c-a039-4668-adf1-ff020ecf17ae +10f75609-865a-4b80-b5b2-39c67aa70c33 +8fb26a73-0535-4603-961e-217353617786 +98a14b8f-9a24-4c1d-b823-26d07b3d0e30 +200890d3-e23d-42dd-85f2-e9e4961495e6 +0faebfcf-6202-4799-b302-40c258d546c3 +714a3c57-cfdf-4db4-81f7-8bc0b9119f51 +c4f0d33b-3b8d-46b7-af89-2f5cce9d495a +\N +0a00b315-9668-493d-ab38-48d20cb5756f +a0a20648-1759-4330-94a3-e39746fdb30a +02890263-b147-4323-b59b-d533ea9d436e +ed12380e-14ff-4ec2-a47d-1b57dcd9cd68 +6d23b5ae-ca06-4aa7-a282-d96315c3ba83 +dac7a9bc-97e3-46e5-a543-ee4071ae9f0a +65995243-a887-407f-b5de-f6b25b07e3a5 +4831f146-46fe-4d57-9569-80852b0f655b +46037b99-bc54-4266-94cb-384c062346e4 +10661769-8d53-476e-9be4-440258481fdd +5ddd5380-3f03-4a76-b682-b65e1ff1a431 +5e1fe5a5-14fd-46e9-afd2-54908de1582b +62c070e4-7647-4c21-a4dd-cd8203ee6b20 +\N +efd4c5c8-629d-41be-8982-e3b8352f96e2 +16a2c85a-f455-45bc-8a29-38f7f664fc7c +0360c257-6ea8-4d6e-834f-43f37a7d8f4e +e2562225-53fe-457e-b538-c089d3946aec +ddbaebbe-8294-4f8d-8452-4a46d1b43c53 +dca63b45-648f-4b4c-a36a-c53f3e0abe28 +04e91983-21f7-408b-b4eb-aa6d9359f37a +905c498a-4b99-49af-89cd-fd1d022193e7 +413d7f85-6bdd-4d50-8859-afd317c841c4 +412ca3ee-90e5-46ae-adf3-80aa6ceee633 +1a8020e7-8671-46e4-b2d3-705e206723bf +9414f47f-088c-4fb1-98f2-bc020b0d550b +8f48e9ef-ae2c-4d0a-acc6-8d4b18622df5 +614cbf04-fb3b-4678-834b-da05f70bd529 +0c96ce68-9135-4199-a351-05f9dfc641e2 +f3dfa1a0-c156-435e-9f2c-662c345b92db +\N +d7c6b8cc-2d67-41c6-bae0-3ab23f8ad65c +b8ae6503-dbed-4455-85ce-6b985b4338ff +3ba1530c-911b-463d-aa61-5d81850b5fcb +\N +0f71da52-80da-4bc5-88f7-013603f8ef06 +f59318ec-1851-4beb-b02e-6e9ca7f2391a +8b712321-af06-4af2-8654-1e174851ae59 +\N +\N +c30e01ed-17eb-495d-8381-dc87cd280002 +10537620-20b9-4706-a1fb-ec470349e4d8 +bc669e4a-bef3-4635-a3c6-47e70a307e70 +5d965491-8d0d-4f7b-bab4-b615dd97dbcd +fa76b0d2-1c46-4855-9381-3ec02b21b475 +311c71c5-e5e2-4224-aa57-fc79adb0d037 +82f18b3d-dd3c-402c-a54b-afe92a8b4582 +8a39cdbf-39c5-41a9-a4cb-c334cccc0414 +396ed0f3-8c28-40a8-a5d0-b41d2448c618 +4dc37b03-f161-4436-bb41-3e114f78bb96 +1de1ff4c-5b47-40d2-a002-ff331900c4ec +7b248f50-920b-45f3-b20b-19d75590ef3d +\N +eba46805-9b82-4ebd-84d2-5aa6cb3d8a48 +2fef1c4c-d97c-423f-923d-cbac15961fc5 +7ae4af7a-3759-4ecb-9d3f-ed5e124ab08c +f812e63b-20a4-4f58-90be-e6c7357d89fd +cd91b9fe-5daa-4087-94a7-459c54d24d39 +54f4f7a3-c581-4bff-9bc1-82d8aaec2d3c +\N +d900a862-a0b2-4776-b418-af075881c53d +593143fa-99b6-464b-b563-33e201668db0 +1d8ffc5d-1011-41d5-b3bc-18e0fe5b7375 +93773b54-be94-4b99-9bcc-e181f1b09978 +\N +7fe6a809-a67e-46ef-b686-4a982a6f6fc1 +00c65908-2e09-4974-8c61-37ec926e74fc +d6f2cc12-6d93-4159-b247-70db9120217b +29806fd9-24bb-4e50-a228-8ad6c17559ab +bd093e48-01be-4a09-a8f0-33a2bfcf23a7 +13d3db20-68e0-4cb2-8530-90648e6a756c +b5bb9551-bb70-4589-a12c-15350d85232b +6736895a-671b-435c-85b7-133c65b09cc8 +a609184a-9035-4b75-b10e-838465bace14 +98c084a0-9c30-4dc0-b8a2-2b818f650034 +122afe72-60e5-497a-92f7-c8139339f999 +6b2b6d77-f49c-4b37-a57f-c8ab6f8deff3 +6e133f18-5a70-4717-a750-1c2ee9ab459a +\N +\N +96e2dc30-cc7e-4c9e-bbbc-e4af1ce9b5f6 +0a430277-f67d-44df-88e6-3ae2e78b8a1e +15445ff0-2087-4fa6-857e-baba197a3ec9 +5a8aba7a-1feb-4acc-b57d-2520233ec15f +a17ef384-5204-4240-a493-7db5dc28a6b2 +bfa0fd7d-7d18-4c57-a066-c94be05d0730 +4de53a8d-d76e-446e-9b68-48618314f2c4 +21cb20a2-36ab-4756-8925-cf8bade61148 +a2376936-9836-4397-a3eb-e779e498ab2d +f4b3d211-79bb-4256-a460-26dac56d9755 +4c4be10f-5141-429a-9ea6-24eb1fbd5330 +a45bb987-b935-44ae-a410-c82b1f895eb5 +b341e29a-e069-4ba2-b2ba-279b53c1fcda +68a4e4f8-9d7b-48f0-8614-e2a2072c859e +dc20e7f1-b79d-4135-b90c-cd87a265169a +1c5bfa73-8814-4f5d-9718-a12417814c41 +3000c028-3656-4455-b095-0b9f5ab1dc9e +\N +3d13811e-7b7a-4779-ab87-5257a1c702d1 +a9ea4067-e53c-484c-87ae-bdb218fbec40 +9c3925cc-4dba-4dd4-8000-c646f45db751 +f2e7334b-9840-4e9c-aff4-d7ac58ecbd91 +889d6968-a515-4458-a353-4a3d8d7528bd +dc02f6d6-fec3-4c2f-ace2-6a124a61f079 +9f48ed94-313c-4607-9c23-d3a1b20eaca4 +\N +\N +2428f056-4dc2-4db1-a110-20bb54a3037a +3cc8dd23-fd3a-4855-8ffa-23d1efa4fdbc +bd3e2f1d-5869-42ff-b1c5-3f65ae2d1974 +\N +\N +8330c73d-0dc5-4caa-be02-10e136137804 +f202e559-6ab2-4b72-a6e3-1bf16cfe8bf5 +54e42957-25fa-46c7-a939-eaaa4b54a5b4 +\N +7c3b2d81-44ca-49d1-8b08-c33f691c4f3c +\N +5083bb0b-7fa7-4ee1-8e51-ce20ee53a16c +ffdd64de-2c27-4858-8baf-b179f0fa690c +6e4632ce-f908-4c13-a15a-ac5cdca38c76 +e428a015-0cca-4b09-bb5c-ee4bffbd2de7 +\N +\N +d7a270ce-7ac4-49bf-a531-e56960f56850 +80b82b49-3984-4b6f-8690-7578f992d987 +824d6c6a-273d-4bfe-8ca6-197c8477d6c1 +\N +\N +5dddc93c-c4c9-44fc-8916-a826089245a4 +21357386-e17f-457e-93b4-77295904e67f +f9598cd5-3c4c-457c-b6d8-11049bbc94b1 +05f7fbbb-1660-42da-a154-de4aa6cce4df +a3b6aeef-e8b5-4692-944f-eb5edfd6a0b5 +cb8f1dea-fefa-470f-9a9c-1e169df844ec +\N +4b12759f-10e0-478c-b2d4-7c71be9f837e +3688c161-bc5a-40a4-a9a9-6854b623a139 +\N +a6a6ac8a-b805-4f15-bc8e-71c3679221e2 +ddcdad12-0919-4b8b-acc8-e775aaf6b6a1 +0dcce500-a4d6-4d34-916b-686cc04ceaa9 +2190bba9-e7e6-413a-85e3-40735e791c1b +3f06e070-1530-47a7-8898-a94d82ea59b8 +bfb7ef50-9a5c-4341-a65d-c7b9ccb76d39 +70972a38-8f23-478c-abfd-9dbdca17dd01 +3c6ba50f-9197-4f0b-bf46-ca51aef246d1 +fb4598bf-ffa0-470e-b8eb-2d704fc08bd5 +fb2bd46e-6f43-4b2a-a122-dd6539ccc03c +49f8d0a6-b7b0-444b-90dd-1cb5d08e95e5 +\N +e8b02af9-8671-49d3-a1bf-86e222fe4ecc +\N +26c6af97-9ffc-43bd-a926-da45469c3c52 +773ec08a-9d02-4a8f-87e9-f4460d703952 +286f3446-6e5c-4b91-bb47-d6106346369f +\N +2e1c1f21-8cdb-4a33-96b2-85b3eec11e41 +d7b3ebc0-62a0-419a-a710-f9950d012f92 +3edde810-79fd-4f1f-aa10-4aa472d9384a +a13013f8-e1cf-4902-9c95-8177d8a220a9 +b7c226d3-d115-4bf8-b03c-e7f14eee2169 +91a75836-a7a6-488b-806a-e8f948c8eb46 +b30da379-97b3-4a94-b6a2-2064767d2e52 +befec357-cf20-4712-9805-34910608e2fd +ca95130e-1c44-4733-a872-c0ce24d1b3c1 +\N +f5001512-9140-43c9-a4c8-54e26f71f1cf +a3f2283e-50dd-40b9-83f3-b50dca485209 +3f7ac41e-bc09-4b74-8665-bdde3ebe47a7 +761e1883-d06e-4360-9df5-5c5caeef905c +98cffbaf-dc52-4674-ade0-f930a70b64b2 +370e189c-b821-41ae-b748-a60b6d7660eb +12563667-28cd-478d-b53c-442f7bf12c67 +\N +29fe6754-ba91-49b4-bfb7-12b3dc03081c +87aa5cc1-3332-4bf3-a669-5bb61e56a7ee +\N +8bcd3587-03fd-44a0-aab8-b8aca2bc9eb9 +9ae7c0f8-6038-48af-b01f-0c5bccac6c8f +\N +3e98dbdb-b10f-4f6d-a87a-c8f1f2b1e22a +cd53b5ef-38fa-4d68-a8ff-4eb07f4162b5 +514644a8-fe52-4bab-8bb8-4cb8c7ef7acc +\N +b01b6978-46f9-46ec-844d-1322be2cfcb2 +04675ff9-2d55-413d-a3d0-4ac0ff1d3a54 +a7ee0137-c56a-472c-b4cb-dc41f1177ef0 +bc41bfcd-e5f0-423d-82f7-ffb7da97b5dd +55824064-db88-4077-ad90-945d878e88ba +a30050dd-1a17-4659-b3a6-c4c182ff0184 +82ffa955-b664-4503-9b1c-095404dbff48 +91d67d53-ec53-4dae-95ca-da25c1cfae7d +da144505-c151-42a4-b8d5-19ff810ae6ea +cfa1ddf5-6149-4896-940e-5dc57e4ec766 +c3a56789-f97f-4d5a-b70a-7d24da43bc5f +4547a150-68f8-4984-b7b4-67ee92315b6a +6b6d5e01-b18d-4afd-8b6e-8c3af536efe4 +bf723c93-f506-4990-9e97-b65476044b30 +c3ec8969-1f70-4b19-977b-237ede99a6a5 +78f11bd7-7a10-42ae-9475-eb16ca80f1e0 +cb3bf2fe-2d6c-47e7-b1e7-ce3254d2f800 +3842e996-3d91-4cab-8cce-da007a08328b +4c55e078-603b-4d4e-9c77-c747960f6aff +16d9f806-448d-48a1-9473-4d30df05aab8 +4611148b-cf71-40e6-829f-95ae0f2c8094 +9f3bcfbc-24ea-4105-8cb8-37a0924ff5e2 +4fac2eaa-2bd1-4d9c-bd80-a7ae083efcf9 +dc5546ca-99fc-4c1d-9559-b2ed9cd3d2aa +29721775-9930-4f6c-af20-bb5b5f8d0d73 +f39a6eac-e7d5-4124-9a65-9508bfc53920 +\N +7bc9960c-cf4f-4cca-a752-b28c5805ae01 +0e1c03e3-2cca-4bc8-b160-d6c2e888c182 +b12bb0d6-45d3-4608-9992-be9804a09448 +31bc67c6-1293-47c0-9732-5094e0b996bc +a262ce01-cdf9-47f4-8f48-e94d4b9d73ce +bc7150a9-0593-444c-b7b7-cc142348f1b4 +2f1e9e36-7e1b-49a5-a83e-b330267b5051 +f919c11a-b74f-4543-9798-da31133f90b5 +8672777e-a462-4042-9604-4392bbbd3308 +37b53421-3c74-40f5-9884-b83033e9f596 +1f843010-c79a-4bd4-a0a5-0251e0389722 +c51ecb09-45b4-40ff-9934-877c168c5038 +131335ce-a059-49c4-81ae-c9d98211ff9c +f467b40d-0c6e-40b9-9959-2f7f466f18e2 +44076ea8-1103-4086-9e7d-8bfb9a65893d +79ca8799-36a4-4982-9cd7-bf93fca45d74 +82569d43-65a3-44d7-8836-2db6de03e6f7 +cb5380e0-b075-48e6-8a9a-eee854444d34 +db88f31c-ce62-43f3-9781-8a8404e6ba39 +78f33ab0-a744-4bf1-82a7-c0b319492607 +2e4b580e-7d69-42c8-9f1d-7f232a3ae74c +427b3d53-2792-47d3-9d45-087b30568413 +40518971-9590-45fb-9219-242ab3053547 +fe49087f-d8c1-4769-b814-fd8bc1611b5c +27f8a8ab-671e-4eb2-88b4-2ad41814df1c +39ebe842-6c44-4fb1-a629-3f86323ca5ea +4a341b56-3523-4163-8563-83b9db172673 +513dd3a8-7354-47e7-9eef-d2a9e59a0e18 +b8e38294-7be9-4c39-b80f-bf2c9acfe69f +e1fa23c2-b0b6-47b3-82c9-eef6e930af08 +1b86903e-c395-42c7-b9ad-1a71a1fb52d1 +632161a2-474e-450c-9b70-0f09f512bcba +73f00c2b-ea38-46bb-aae3-4cf205572baf +013839fd-03de-4fe5-a08c-466670de6cbd +5d951cfe-d988-4b69-bce8-37d66598cbc3 +4da7e8f1-edaf-404a-bd1b-e8dd3a838fe2 +1f2c1809-8b85-48e8-ada4-1fdb418fea0e +\N +1bde5bbb-5d63-4d00-b227-1a706315eaa1 +f7ebf8f8-609f-4ce9-b93c-54759305926e +\N +7c2dd991-9377-4001-8486-7f3c3a6bae9c +9fe1e97c-718b-4cf2-b270-4e0b664aaf27 +2141a8f5-da01-47cc-8104-6dd28874d8ac +304096e8-b118-41e0-8174-32dc8e1fc45d +9d5fac3d-e6f2-4341-9e59-9a155bef7b17 +b42cebe1-f01f-4409-bfc2-150aa9f13159 +91adc8a2-266c-4196-99ff-1de1c361c3ef +54d26aee-0309-4af7-9b12-bbb24eb3e4e1 +bd449351-c50a-43b2-9742-1bcb838d4d04 +9fe70798-e3bd-448a-b461-e462702a9aca +\N +c8ef8969-1332-481b-909a-340ff3fd4473 +64c68c64-f815-4bd7-b0aa-ba68bb15f611 +9f271158-ff4e-41a6-a883-913f2b36ae68 +\N +b1082d66-0065-41ac-9bc5-dcea0bbec070 +\N +3ac2d674-2e12-4db1-b998-2470cba43b11 +\N +3061f573-96e9-4307-a683-df8ab30531a5 +01ce8c0e-7672-4023-be71-5dfae5ffa7d2 +06a9e327-29ea-4913-b6b9-90781484eff4 +9735f9eb-89b3-4f42-bfb5-e2bb208b640a +21ef890c-1c8c-4890-8c6d-851eebe68f40 +c35686c4-cfcc-48ff-b6d9-7c8da68dceb1 +3f08e734-1f52-42b5-ba89-738582a7f5b4 +12975217-8a58-4a95-9ede-4ceb0a487a67 +97e186f8-28a7-4340-b781-cd13168daf99 +2336ce4b-3d57-46f4-b460-cdeb89c81fcd +e824b114-66e0-441f-aa94-27feb7a3f672 +b8bf5230-0174-4f16-9470-dd476b9675d6 diff --git a/contrib/btree_gist/data/varbit.data b/contrib/btree_gist/data/varbit.data new file mode 100644 index 0000000..b25a264 --- /dev/null +++ b/contrib/btree_gist/data/varbit.data @@ -0,0 +1,621 @@ +10101001001110001110101100110110000000110011010000101000100010001000011110000111100110111111111 +001110101100110 +010000011101100110001001111111101110011110110100011001101011110010101100111011011101101101 +1111000010000011110011101001101101011110101111011011001000000101111110000000111011010100011001 +1111110111111000000100000101010010011101111100010100010101101101110010111110110010110000000000 +000000110100001010001011101110111110100111010011000101111111000100001001000011001011000111101 +0100001010001011101110111110100111010011000101111111000100001001000011001011000 +011000001000011110010000111001110101000110000011000000011100010111000111001010111110 +1010100011001111110111111000000100000101010010 +01001110100110001011111110001000010010000110010110001111 +1011000111101100000110100000010101010111101100000100001 +01011011001100110100000011000101000100010100100000110100011110010001010000011 +0111101100000100001111001000011100111010100011000 +1101101110010111110110010110000000 +11010111101011110110110 +01000001111001110100 +01101101011110101111 +1111100110010111101111110101010110000010001111000000110010101010011100100011011000001101011 +1010000111011010111001111111110000001011010011110101101100100000010111010111 +101011011100000010111000010011011100000 +011111000000001101111010000111011001100001110101001110 +11 +\N +0010101010011100100011011000001101011010110001101010110010110101001000000 +0101111101011011001100110100000011000 +110111 +1001111001010101110101000100110110110011000111000011011110101010001110011000100011101 +110000001011010011110101101100100000010111010111101100 +0001010100101110001001000010001101010010001101001001010111100011101101100001100000011 +101000000101010101111 +00011101001010111110100000101011000110100101100001111010111110110001000111101100011011111000000 +1001001111001011100001010100101110001001000010001101010010001101001001010111 +1100011101101100001100000011110111110100011000110100010101111111011011011010 +0010101110100110001001001011011111111111101011 +0100111010011000 +1111101000110111011011111101001010011111001000110011101010000100101110110101010010101001001 +11110 +00011011100110110001000000010101111111111011101111010 +011011110100001110110011000011 +01111010111110110001000111101100011011111000000101100001100000001101000001 +111101111111100111111011000110000100111100101010111010 +001 +1110100111010 +01001101001111101001000000000000100100101010110001111101111100 +\N +\N +0001100101101111000100000000101000110100000100101001001010000100000001000001011010 +10110110111110100001010011000001010110001101000011101010001101101100111100010011000111101111001 +11010101101110101001110010111111010101101000100000011110 +101001000000000000100100101010110001111101111100111000111 +000011001101101011110101000110001101011000 +01100001111100011100000110001100101111100100111101100010101101101111101000010100110000 +01011111110110010010010000000000110111000010011011111000001100100001010001100100010101010111110110 +111110101010000111111111110000010001110001010000011110001110011001111010101111000 +00011000001000101111000000001000010000011100011010100100101100011110101 +010101111011 +11111110111000010111101110101011100 +1110011001101011010000100010101110110111000110100010000011010101011101001011000011010111101010101 +0000001111011010011010110010100001000010011010011110111011101100000101110011111 +00101001000011010110100100010111000101011100110000110111100001010010011 +100101000010000000100000101101010100000110000010001011110000000 +010110001000110010110111001101011111110000101100110010000010011001011111101100010110101011001 +01011110001110101110000011100011111111011111010000 +110101100001100110110101111010100 +010000010100111010111010000100000110011100011110110011 +1001100101111110110001011010101100110110101100100100000100101101110001001101111001101010 +010101110011001010110100000111 +\N +0100011010101110110000010011 +011101111101000110111011011111101001010011111001 +10010100110110011011001111110001001000110111101011101011010110001001001111110 +1110111110011100011111101010110001101110 +01010101110101000100110110110011 +10101110100 +001110100000100101000001010011110101110111011000100000 +100000111010111111110011000100100000111100000010100100011011101001110100001000100011 +101101000001001010011100001111000110000100100011100001000111111011110010110010111101 +11111011111001001110111111101 +1111111011100001001100100001110100011010110000000001001010101001001111101000011110011000000101111000 +001111000110000100100011100001000111111011110010110010111101001100010101110001110 +011000001000101111000000001000010000011100011010 +110010100001000010011010011110111011101100000101110011111 +000011101101110001 +01111001100111010000100100110101 +000001010001100010010010001001000101111000011111 +1001001011011111111 +01100111000110001010010000100101011101010 +11001011111111011111111111111011100001001100100001110100011010110000000001001010101001001 +1101111010111100011 +\N +00101011000110100001110101000110110110011110001001 +001001101110000010100011110011110101111101110100000101111111101011101100111010101011111010 +010101111001101011011111011111000101110101000110010010010010111010001100011001011011100110011000001 +0000011100 +00110010110111010100111111011111111111101100110001000001101111010000100011101110101100010101 +110100110010001011000010010101100010001100101101110011010111 +000111000111111110111110100000010 +0000001011010011 +00100101000001010011110101110111011000100 +\N +001000110110000011 +1101100110110010 +001101011101001100010101 +0001001000101111000011111110011100110011 +10101010101000100101101001111011101000010001101 +000001011001100010011110010011101010001011000110110001000101110110001100001001110101 +10011000011101010001010111101001100011100100001100011100001011110011 +110111110100001010011000001010110001101000 +000110000010001011110000000010000100 +0100000100101100101000101100010000010011110111001100000000000111 +000000111001010001001111101110011011011110011111100011101001001010010011100011111100100010 +001000011111011010000110110111101010000011111100010000110111111110 +001 +000011110001101001110111110101101101111110111110001001001100011011010101010100101101101011 +00011010101100111110001110011001011111111010111010110011101111000100101010 +1011011001001000110010100000111001000111010101 +00110100000010111010100110001101100011001110101011101110111111010000000100010100010110111011 +1101000001010111110110111001011111010000110000110010101111101010100001001000110 +101111000010100010100010000000010110011000001100011010101001011011110110110000110 +00100100001101100011101111110011101110000001001111 +010011010001000001110011101100001111011011110101111 +\N +\N +1001010010011100011111100100010100100110011110111111100101110110101111100011111111 +001111110000001100111011001010010100000000110110001110100100010110000010011011110100011011 +11110110011100011000101001000 +00101001110110111111101011100110110011110110000101100010100110 +011111010101001011000011111000000000101101100101101111100011110011 +10101000110001000001110101111111100110001001000001 +0010100100011010011100100011111110001101010000011101110100101101011 +111111001111110110 +1111110101010011101000111 +001011001111101010000011001101110001011011000011100 +0000001111010111000100111000111010010001100010010101001101101110010101001101000001 +011 +00000010110010 +000000111111110101010011 +101 +0100 +001101101100111100010011000111101 +111000010100111101010111010111010011000010001001100010001110001000000111000010101111010011101110 +000101100110010110101100001000100000000011010101100111110001 +10001000111011001101000110000101100011000000 +11101010000011001101110001011011000011100001101 +\N +0000001101011110001010101010101000 +11010011110100101011110110101110001101011101110010110001000010010011011010011 +1010010100000000110110001110100100010110000010011011110100011011010011110100 +10110011110110000101100010100110110111011110110010110 +1001111001010101 +100011000010110001100000001100001110111100 +\N +001000001110110111111100011000011011010101 +001100101 +000101001100010101100011000101111011110000101000101000100000000 +0000110010101111101010100001001000110101000110110101101000010 +1011000011010111101010101011011100000 +1110100101010010100101100101101111110111000110101100 +001001011000000000000101011100110111000 +0101111111000010110011001000001001100 +01001110110000111111011100011001101001010100000100011101110010101010011001010100 +0001100011111110100101100111110010000111110110 +111000100000000101000110 +01011010101101110011001100100110100010010100011010000111101000000 +001111010100000010101101100010001111100100 +11111110011111111010101011000101111111100110010110111 +01101010001000111111010 +00001110111100010000011101101111111000 +0011101000101011100011100000 +11000111111111011010100110011100110011100 +10100010 +101010001101101100111100010 +1001001011 +\N +11000110 +01100001110000011110001101001110111110101101101111110111110 +0101000110010011001110110111100010101011001101000000001111110111111111100101100001100110101 +1111000 +100011001011011100110101111111000 +0011 +0001000001011010101000 +10001110100100010110000010011011110100011011010011110100101011 +111000001110001111 +11001100011101001110010110100111000101011000000001111010010001 +1111100011110011000111101111110011000000010011 +010010001101111010111010110101 +10100000110111100000010 +10010111110001010001110001001001111000001100010011001101110010111000101000001001000 +1011110100101001100011111011100100011011010010011110000001010110 +\N +000111 +0011000010100110001011000000001110110111110001101100010110100101001101100100001100001101110 +1010110001 +10110 +110011000001111110101011101100111110011111111111101001010110101000101100011001010111111000 +100000100001101011011111011000001011111000000001000011100110100010101 +111001011001011000010100010010101010110110101101110000000101110 +100111001010100011011101110100110101011111000000111011011010010011111110011001000110000010011110 +1011111000010111111010100100101010000101101110011110001000000110000001111010001100000111101100000110 +0011001010111111000100010101001001100110010100101001011011100010000011110110101000000001 +01110 +11111011011110001000111100101111001 +0010011110010010111001100101111011110001101011011011011010010000 +101101101111110111110001001001100011011010101010100 +001100111000111101 +1001010001001111101110011011011110011111100011101 +11000111111100110000110101100101100 +0001101101010100011100100100001 +000111100000110110101111001101110101100001000101111001101000011010 +1110001010010000001001110011101101110101011110000100000110110111111010000101110000001001100111100 +0010000010110001011001001011110101110100101011001111011100110000101111011110100 +00111001001111101011101111001011110100001101100011101001100010110001100 +110101101100100001011000111001011011011010100100011101110000001011010110011011011010111110 +110000111100101111010010100110001100001010011000101100000000111011011111000110110 +000001011111000111100101001001010111000101010101100001101101100010101000101011110111111 +00100010100100110011110111111100101110110101111 +1011110011001 +10000101110100100101011010100001010010010011100000001100011000000100100101101101100 +1011111110011011011001100001110010011111100111101000100011010010000111100000001101001010 +110011011011100000100110000011010000111101100110001100101011 +100000110101101111000011100000001110010001100111110101101011011000010000111001101011110011 +10100001010110100100111000001111001110011011101110 +110100100101 +0010100110100010000011100111 +10101010010100000111010101000001111101000101101 +001000001010001111110011000011010000 +0100111110100 +11111111011111111111111011100001001 +00101010000011010111111011000011000101011100001101011111111111001001110100000000 +110111101011111000111010110100000 +011001 +11101111111011111011110010011100100101111101101110011111001101001001110 +01100010010101010110111000110111100000000110011101010011100001001001000010001100111110100100010010010 +\N +101011101111100101010000000000010100101000111110110001111000011001000001111000100011111111011 +0010010110 +11111110101111010100000000000110000001000010011001 +010110000000011110100100010000110000001101100111 +001000000111000010101111010011101110100100110011101010101 +0111110011110011100001100010010011010111000110111100011101000 +110001111010011000111111100110 +111101010101011101 +1001 +10011110111110011001100011101001000101101 +0100001001100111011110101111100 +011010011100110100110011011110111100110111011011110101110110011000011111000010101011111001100111000 +000011001110001 +0001000010000111000111111000111111001000110010111011001001 +111100101111111000101101001001001000001111101111010 +00010000111001010011001111100111101010011010100 +001111111010100111100010000 +0011000111011111101001001100111010001101100101010101011111110101001 +010110111001100110010011010001001010001101 +1010001110 +001011110010110110010100100101000000001010000010101110010010111111110001101011110000000001101 +010010001100111110010000010011011110101011110101001111000000011100111 +110001 +10011110111111001100110100010110100100101000100100100111101000100101011101101111 +0100111000010010010000100011001111101001000100100101100101111010000110011110100011100101000 +01101100011100 +011001111000100110 +0001010011000101011000110001011110111 +10011010100010011100110011101110000010001100011011100101101010010011001000 +100111001000111111100011010100000 +01011110000111101011011000110110111100111100001001100 +101001011110010110100100100010110100101100011100110010101000111110110010001001111001011101010001 +00000101011011000100011111 +1110010000100001111101101 +111100011001010001110 +011011100011011110000000011001110101001110000100100100001000110011111010010001001001011 +0001101111000000 +011010000101011111010010101001 +010000101001001001110000000110001100000010010010110110110001111101 +0001111110110010110111000000111101010000110011011010100101101101110000001100111001101110000101000 +0000011101110010010101 +1001100001111100001010101111100110011100000001011100100000111111001110011011100001111101 +1101001100010100111010100011011000110111101011011110 +\N +0110100101010000010001110111001010101001100101 +10010101011010011101011010011100010100001011010010111011110111001100011010110101010110110 +00100010110011010101011001 +111000100001001000010010000101011101010000011100110110111011011101100111010101010001110110100 +1100000111101100000110000011010110111100001110000000111001000110011111 +011110101 +11000110001011100010100100000010011100111011011101010111100001000001101 +0110110100101001001000001000011001100000101010101010000111100011010001101101000 +11101001100000010100101111101110000101001111010 +01100101100100001001100111101111000010100000001010001001001111011100010111010010001100 +1010001000011000110011011000111111110001101010100110101011100100001001010011000001000101 +10101011101011110010000110110101100110011111101 +11000001111011000001100000110101101111000011100000001110010001100111 +1111000100000111011011 +001101100111111110000011011101111011011 +00101 +100100110000000110010010000001010111100011100001110000010000110000011111111011010101010010111101 +0111010111101110010101101011011000001000000100001000000010110011000010100010111111110000001110 +010011011111000001 +01011001100100000100 +11111011110101001101000110011101110111101010110111010101100001101011110101011011100101001000100101101 +101000010100000000011000010000101001111001110100100001001100010010101010110111 +1100011110010101010100111111011000100100100110101001100100110011110100001110011 +1001100110011101110000011101110100000010001 +000001110100010011101100000001001111110101110000101100100010000101110011101011101 +0010000010011011110101011110101001111000000011100111001101 +00 +1110011100 +11010110001010101111101001010 +111000000010010101001111111111001011000110011010010111010000 +10010110001000101110111110111001001101001001011010010110101001001111011 +100010100010000110001100000110110011011101001101000101000110010001111010 +01010001100001111011000001001111100100000100001101110001101010101011010110000001100111100111 +00010010110111110100101001010100011011010100111010011100001101011100101000100000010110101100100010100 +10010010000000000 +011110101001101000110011101110111101010110111010101100001101011110101011011100101001000100101101 +111100011001010001 +11101111010111101101000001101100111111101110 +01101110101000001111001010111001011110001000011111011000101011001011010000101001100001111101 +10011 +01100011011011110011110000100110001011100001 +10111010000100011011111011100010011101111001000 +00100111001011111001110110110000000110111010111001010010111011011111000 +0001011111011010000001010011000001111011011010110010 +10000101110110001001010001110110000010000000010000101110110111011010001010 +010110110010000100001110011010111101101011001100010101 +111010011111011111111100001110010100110101111010 +011101010100111010001110000110100010011101010011000111000010000001011101100101001000001000 +1100110101000101010011000010101011111010011111011000100000001 +0100100011101110000001011010110011011011010111110001110100 +1000110101100111110111101 +001111001010011010101101110011101111010111 +011101010100000100101010101111010000100010010110001000101110111110 +011011001001001010011001100100100011001100001011100111101000101010001101000110001010111 +100000001100011000000100100101101101100011111010110111 +10010111011 +101111000000001 +001000100010111110011011011100000100110 +1110111111101000111011011100001 +110010011110111011011110101100111100011 +110110110001100110011110001111111011000001110100010011101100000001001111 +110100011010001001 +11010101101000011011001110100010101010000110010010110 +010110001001000110100100000000100011110011011111101000010111001110000101010000 +0100000111001101101110110111011001110101010100011101101001010111001000100001 +\N +000001111000100011111111011111010001101110100111110001011010111 +000110000110111000100110101101011111001111 +01001101011001 +0011111110101010001000000101110010001000000110100000110010100100010100111001100111000 +00111000111111001000101001001 +10000110 +111111100000000100100100001100 +10000110011010000000000010100011110101101010011110001110100100110000000110010010 +1001001011000111111011101000 +111100011111010111101101101100011001100111100011111110110000011101000 +1010101111000 +011111 +011010000011011001111111011101111010011 +101011001111110101010011000001100111011000001110000011000101101101000001110000011110001010 +01011101110010010100111000101000100001100011000001101100110111 +0110000110011011110000010111110100111001010100011011 +000101000111101011010100111100011101001001100000001100100100000010101111000111 +1101011100001101001010110111000011100011101 +0001010110001 +110010001111111000110101 +100000 +\N +10110111001100110010011010001 +001111111001011001100101000010011011001000111111011001011011100000011110 +00000011100110000110000001010110111011101 +1011100111101000000111000 +01100010011101010111010111011111001011010001101110000011111000101100001000101011110000100100110 +001000111011101011000101 +10100001011111111001101000000100100100001000100011001100001100110001100000100100011100111110100 +100111 +00110010101000111110110010001001111001011101010001001011001111101011 +1000111010110111111101000001010110110000010 +11111010111010110011101 +0100100001000001010000001000010 +101101011000100 +0001111010111111100000010111100100100101111000010000111 +1001011100100110010000010101101010100101000100100011100 +1101010000011101 +110000010000011110011110001011010111100001001001 +111101001101011111010110110000 +010111010010110101101000101011110100000011100000011101101110 +01111110111111111111011 +0001011010101100001101101111000 +0101010 +001110010000001010111001101101110101111101111001010101111101110010111 +101111010 +000001010100110010110000011110000011010110100001011101010110111011101101010111010011100011010000 +011100111110011010010011101100000110110110 +0011011010111010110111001101000000110111100000010111000010101100101110001001001101001100100110000100 +1001100000110111110100100100011000000001111100011001111110001111110010100 +10010001100101 +0011101110111101010110111010101100001101011110101011011100101001000100101101 +011100011000001101111010001000000111011100111111000010010100100101 +11 +111000010010101101101001000010001000110001010111010000000111010101001110101 +1000000100100110001101111001011100000111010010000001010100101110000010011101100111 +01111000 +0011011110100011011010011110 +10001011101010001000010001011001011101100101011001100100111011101000000011101100100111100 +001001100100100000001011100111001001000001110001110011110110110010010011101 +1001101000111011110101110101001001000101000000010101011110111110 +010 +1100110101000101010011000010101011111010011111011 +00011101011110110100100010110011110010000010111001111010000110010110000000 +100001111010111111100000010111100100100101111000010 +\N +10000111011101100011111010011011110010000100010100000111100001011010010001001100010110001001111 +10110101011100101111011101001111011111011011101001110000010111100001110110111001011010111111111 +1101110110110000011100010000000001101000101001111010 +1010010101100110010000001010101000111010111101101001000101100111100100000 +011011101101110110011101010101000111011010010101110010001000011 +1111001011100000111010010000001010100101110000010011101100111000010001101100010 +10 +0100010100111011010110110000100010110111000101101011101111101010101011010 +0111110001010001110001001001111000001 +1101000101101001001010001001001001111010001001010 +0111111101000100000111001001101111000011010100101010010101110110111100011 +110110100010010110010010011110001010100111110000101100011110110011011111101110011111111 +1010011101110010101000111100100011101100001110000 +0111110000000000000100110110011010011011110110001 +001011110101110100101011001111011100110 +0011110010110111001010011111001111110001 +1000110010111001101000111100111111001000000000111111010001001011010011011 +111010100111101111110011001101000101101001001010 +1001100100101011001001110001011010001111011011111111100100110011010101011000 +1001111111111001011000110011010010111010000 +0010100011110100011110101111001110001101101100011000000100100110001101111001 +\N +00100111100101110101000100101100111110101111010011011111100 +0101101011001011010001100111101110100011101011110101000001011010100100010011001111001111 +10001110100111110101001001010111101011111110 +1100110100000010111010100 +01 +1100010010 +1010001110011001101000111001001101001111101000101110110011100001000100111110110111001000001111101001 +000110000111001111110000010010001100001010111110100111011 +1100100111100111010010110001111111101001001011100111010000101010000011110011100100111101100 +10111000000010111000111010000100111110000110100010111001011010111001011010100001001101101000 +11010100110100011001110111011110101011011101010110000110101111010101 +0001110 +0001010001111010010110111111101011110011110010010111110011001011000111001101101011101000100110 +11000001110001111100001110100110101111010000101011100000001000000000100011001001100011001011 +01101111010100000 +00000110011100001100001000000000001110111001001001001110000110101001011010110100 +00111001000011111001111111001011000 +101010000111001010001010000000011001111101111110000011111000101101101 +011111110011011011001001001111111001101100111000001101011110000100110000001 +001011000 +001000010011100101100101011 +\N +010101111010011101110100100110 +0100100100100000111110111101 +000000010000110111100011100001101110111011110000001 +11111111001011011110010 +101000101000000110010010111001000001111001010100101001111000001100110111000000 +011010010100000101001011010001110010000010101111001101100011110110100010100111000000011 +11111100110010001100000100111100010000010 +01011010101000000100000111011011100100111101001001 +010111101111000001101001001110001000100001111101011010011011100101110000101110 +100000101 +10011111001000001001101111010101111010 +0010111011001001010100100000100 +0001010100010101111011111110101101010000 +011110001010001011101000111010011110000011000010001110010101010001111101 +11000100101010101101110001101111000000001100111010 +0001100100011101010101001100110110011111110010100100110 +10011000011000101111010101001000001000001010111001100101110101011101111001101000111111100101100100 +1000101111100110110111000001 +11000101110110001001110000010010000001111010101101011001100000101011101100000101 +100000111010100011010010010101 +110011110100110101011000101011110101010110001101001000110001110110110100010010001111111100100011 +0111111011111001010010101100110100011111110110001111001000001001001101100100111101100011010100 +1110010101001111101010011000000100100100011101011101110001110001101110110010100111110010001000011 +00110010100100010100111001100111000101100010000101110111101001 +1011001011010000101001100001111101010001101110010111100001001 +1101110100101110000001010110100100111111010100001011000110110101101000110101011110010 +111110000001000100101001011011101111010000111100011011111001101000011101110110001 +10000100101001010010101011100111101011000100011001110100000100010000110100100000001101 +0010110000100010101111000010010011011011100011110110110100101001100010000 +010011000110111110100000001110001001110011 +0101010100111111011000100100100110101001100100110 +1001100111000101100010000101110111101001101000001001011001100 +1101011110011000000100011011000110010111001101000111100111111001 +11000000000001001111011010010111001100010110100010001000 +000111001011000110001111111100110 +11111101111010101110110101101111110001110011100101101100000110011001000101010011001011 +110100111111100110100111010000101111111100110100000010010010000100010001 +00101000101000100000 +101100111100011100000110010 +011011101011101011110100111110010011100000101010100010 +011000010000111010110111110010010010011101000101101001001001100001101011 +1000101010001111001000010110110100000110111001010000101001000010101100101011100001011111101000000 +000111100100000100100110110010011110110001101010001110000010111101011011000100000111111101 +\N +101000011110101111111000000101111001001001 +0011010000001011111010100101010000001110101001010111010111011000101011101101000111101 +001111111110000101110100100101011010 +0101 +110011011101000001011010 +0110001111010110011111101010100110000011001110110000011100000110 +100101010110111011001001110000011010111111101110110001010111010001111000001010010 +00001110010011111100111101000100011010 +11110101011010000100111011100101010110011010010011110100101100100001100111100010100001 +1001011100000110100101111010010011001001111000010100101101010001000001010111011110010001111100 +110111110010011111001010101101011001001000111100111011001010101110000100110011001110101011000101 +1101001011111100111000111001 +0110100000110010100100010100111001100111000101100010000101 +10010110110110011110011110100110101011000101011110101010110001101001000110001110110110100 +0001000001011001101101101100101101111000110110000010011011101011101101100 +00101011110110100011011111001001111100101010110101100100100011110011101100101010111000010011001 +011011100110000 +0001001011011111010010100101010001101101010011101001110000110 +0101110010100010000001011010110010001010010010001010000100110 +00 +111100111111000110011001101101010 +\N +111011101 +0011110001100 +110100000000000101000111101011010100111100011110010011 +100000001100100100000010101111000111000011100000100001 +010101100011010010001100011101101101000100100011111111001000110101011100000111000000010 +011000000001110110111110001101100 +0100001110010010110 +10 +000011011100101100000010011010111010101010001111010010101111100 +1000 +001101000101110010000011 +10110010000100010010011100001100110100000000000101000 +0010110101011100111111000000011000001111010011100110000010001000101110001 +1010011100110111010000101111010110110110100001000010100101 +1000110110010010010011111110111000100001111111001010100001 +011011001110000010100001001011001010110001000011110100010010101101101011011000100110111 +11010001011010101101 +111110101100110010111010101110010100011001010011001111101110110010100 +000111101111101110110000110000000100101101001000010010001110110110001111001000 +00111101000000010111 +1101001011000101000101011111111000000011 +110111011111000110011011100011101100000100111100001010101011111111000100111011011001110110010010011 +000010110011011011011001011011110001101100000100110111010111011011001 +010011110001100001101110100011100111010111101110010 +11110110110011110001101100101110000001 +01001001101000101110011011010111110001100100110001001000111000101010111001010000111100 +1010111010101110010111 +1111110000000001010010000011 +110011111001001001000110111000110001100101100110100011010100111101 +101110111101001101000001001011001100001101001000001101 +01 +11001110110100000000000010101001100101100000111100000110101101000010 +1100000110110101111001101 +100 +00011110101110001001 +111110111010100111100011010011001001100111100110010 +0100111110000110100010111001011010111001011010100001001101101000000011000 +101111100110010110001110011011010111010001001100101001110110010111011111101 +00010011010111001111101 +10100011100001000111010110111 +0111010010111111101111110111101010101101010 +10111111011 +10001011010 +1000011111010110100110111001011100001011101100010001101000111110 +010100100111101110110111001110110011110 +000110101001111100110000101001111011101011001100000011010011 +000111 +10010001110110110001111001000101000101111010100110011101111101111100100011 +01111101010001110011001101000111001001101001111101000101110110011100001000100 +10100001110001 +0101100000100001100001010011001010010001110000110010000110010111101110011010110100011 +11101000100101010010111110 +11010010110111011011101110100110000000110100001101100101101010000111110011110100100110100011 +1100000000100100010110110101001101011010010100000111111110011100111011111111010111 +101110111001100111010001111001101110101001101100010000001000001100010101001000001101100110110000001 +01001011100100010001011 +0001010111010000000111010101001110101110000011010100 +110000111000011001001110111100001111111111001111101 +00110001000011100 +0110110011 +0011110111110000001011011101101100000 +0100011101011000011011100100110110011111000011001010010001011101111011001101100101001 +10 +1 +00111101111000100110101010001100001001111010101110000001001001100101010111111001010111001001 +011010011000110110100111010110000000111111111100100010111111101110101010111000000 +111000000100010100001111001011011001101011000101111001000100001010011101 +10100011110100101101111111010111100111100100101111100110010110001110011 +100100000011110111000001011100100000101001000101000001001100010 +0000100100010100101010010010110111011010110011110001101001010110100101 +0101100011110010101111110100001110010011101100000000011 +1001100111100100010110011111101011111101011011010011001101 +1100001000111101010001100111111101111010001100000 +010001111001101100100000111010110 +01000011001110010011001010000 +0111011100101010110011010010011110100101100100001100111100010100001000111 +011110111110000001011011101101100000 +101011010101101100010111111011011101110001011110010001001000001101001111001100011100011001000010 +0001100011101101101000100100011111111001000110101011100000111000000010000011 +11000111111000111110000000000110110101001110110100101011010101010101000010000000110 +1011 +1110110101110110010001011011110110000110000111111001011110010011010111001000111010010001 +110101100010110101000 +10110011011101101001111011110001001101010100011000010011110101011100000010010011001010101 +00011111100100010 +00 +01010010000001011010101000000100000111 +11010010110110101001110011011101010110101100010111011000100111 +10101000110011010000011000101110011000111110011111101111100110100011000011111001100100 +11111100001000011001101110110101101010111 +10110011000111011 +01000111100111 +0000110101010000010111111101101001011100111111101011010111001111110111000100010011001101011011100 +10011100010110000111101101111101100100101010000010011010111110101010110101011100011010011101000010 +110111110101000001111011110010010010010111110000100001001111001010010010010100110100 +001100000000 +10000101101110011110001000000110 +00101010101001101111000 +011000111001010000111101 +0110100000111000001111000101010000110110100101101100 +1011100101010110011010010011110100101100100001100111100010100001000111 diff --git a/contrib/btree_gist/expected/bit.out b/contrib/btree_gist/expected/bit.out new file mode 100644 index 0000000..e57871f --- /dev/null +++ b/contrib/btree_gist/expected/bit.out @@ -0,0 +1,76 @@ +-- bit check +CREATE TABLE bittmp (a bit(33)); +\copy bittmp from 'data/bit.data' +SET enable_seqscan=on; +SELECT count(*) FROM bittmp WHERE a < '011011000100010111011000110000100'; + count +------- + 249 +(1 row) + +SELECT count(*) FROM bittmp WHERE a <= '011011000100010111011000110000100'; + count +------- + 250 +(1 row) + +SELECT count(*) FROM bittmp WHERE a = '011011000100010111011000110000100'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM bittmp WHERE a >= '011011000100010111011000110000100'; + count +------- + 351 +(1 row) + +SELECT count(*) FROM bittmp WHERE a > '011011000100010111011000110000100'; + count +------- + 350 +(1 row) + +CREATE INDEX bitidx ON bittmp USING GIST ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM bittmp WHERE a < '011011000100010111011000110000100'; + count +------- + 249 +(1 row) + +SELECT count(*) FROM bittmp WHERE a <= '011011000100010111011000110000100'; + count +------- + 250 +(1 row) + +SELECT count(*) FROM bittmp WHERE a = '011011000100010111011000110000100'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM bittmp WHERE a >= '011011000100010111011000110000100'; + count +------- + 351 +(1 row) + +SELECT count(*) FROM bittmp WHERE a > '011011000100010111011000110000100'; + count +------- + 350 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT a FROM bittmp WHERE a BETWEEN '1000000' and '1000001'; + QUERY PLAN +--------------------------------------------------------------------- + Index Only Scan using bitidx on bittmp + Index Cond: ((a >= '1000000'::"bit") AND (a <= '1000001'::"bit")) +(2 rows) + diff --git a/contrib/btree_gist/expected/bool.out b/contrib/btree_gist/expected/bool.out new file mode 100644 index 0000000..29390f0 --- /dev/null +++ b/contrib/btree_gist/expected/bool.out @@ -0,0 +1,96 @@ +-- bool check +CREATE TABLE booltmp (a bool); +INSERT INTO booltmp VALUES (false), (true); +SET enable_seqscan=on; +SELECT count(*) FROM booltmp WHERE a < true; + count +------- + 1 +(1 row) + +SELECT count(*) FROM booltmp WHERE a <= true; + count +------- + 2 +(1 row) + +SELECT count(*) FROM booltmp WHERE a = true; + count +------- + 1 +(1 row) + +SELECT count(*) FROM booltmp WHERE a >= true; + count +------- + 1 +(1 row) + +SELECT count(*) FROM booltmp WHERE a > true; + count +------- + 0 +(1 row) + +CREATE INDEX boolidx ON booltmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM booltmp WHERE a < true; + count +------- + 1 +(1 row) + +SELECT count(*) FROM booltmp WHERE a <= true; + count +------- + 2 +(1 row) + +SELECT count(*) FROM booltmp WHERE a = true; + count +------- + 1 +(1 row) + +SELECT count(*) FROM booltmp WHERE a >= true; + count +------- + 1 +(1 row) + +SELECT count(*) FROM booltmp WHERE a > true; + count +------- + 0 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM booltmp WHERE a; + QUERY PLAN +------------------------------------------ + Index Only Scan using boolidx on booltmp + Index Cond: (a = true) +(2 rows) + +SELECT * FROM booltmp WHERE a; + a +--- + t +(1 row) + +EXPLAIN (COSTS OFF) +SELECT * FROM booltmp WHERE NOT a; + QUERY PLAN +------------------------------------------ + Index Only Scan using boolidx on booltmp + Index Cond: (a = false) +(2 rows) + +SELECT * FROM booltmp WHERE NOT a; + a +--- + f +(1 row) + diff --git a/contrib/btree_gist/expected/bytea.out b/contrib/btree_gist/expected/bytea.out new file mode 100644 index 0000000..b9efa73 --- /dev/null +++ b/contrib/btree_gist/expected/bytea.out @@ -0,0 +1,90 @@ +-- bytea check +CREATE TABLE byteatmp (a bytea); +\copy byteatmp from 'data/text.data' +\copy byteatmp from 'data/char.data' +SET enable_seqscan=on; +SELECT count(*) FROM byteatmp WHERE a < '31b0'; + count +------- + 588 +(1 row) + +SELECT count(*) FROM byteatmp WHERE a <= '31b0'; + count +------- + 589 +(1 row) + +SELECT count(*) FROM byteatmp WHERE a = '31b0'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM byteatmp WHERE a >= '31b0'; + count +------- + 401 +(1 row) + +SELECT count(*) FROM byteatmp WHERE a > '31b0'; + count +------- + 400 +(1 row) + +CREATE INDEX byteaidx ON byteatmp USING GIST ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM byteatmp WHERE a < '31b0'::bytea; + count +------- + 588 +(1 row) + +SELECT count(*) FROM byteatmp WHERE a <= '31b0'::bytea; + count +------- + 589 +(1 row) + +SELECT count(*) FROM byteatmp WHERE a = '31b0'::bytea; + count +------- + 1 +(1 row) + +SELECT count(*) FROM byteatmp WHERE a >= '31b0'::bytea; + count +------- + 401 +(1 row) + +SELECT count(*) FROM byteatmp WHERE a > '31b0'::bytea; + count +------- + 400 +(1 row) + +SELECT count(*) FROM byteatmp WHERE a = '2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809'::bytea; + count +------- + 1 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT a FROM byteatmp where a > 'ffa'::bytea; + QUERY PLAN +-------------------------------------------- + Index Only Scan using byteaidx on byteatmp + Index Cond: (a > '\x666661'::bytea) +(2 rows) + +SELECT a FROM byteatmp where a > 'ffa'::bytea; + a +-------------------------------- + \x666662656532373363376262 + \x6666626663313331336339633835 +(2 rows) + diff --git a/contrib/btree_gist/expected/cash.out b/contrib/btree_gist/expected/cash.out new file mode 100644 index 0000000..7fbc735 --- /dev/null +++ b/contrib/btree_gist/expected/cash.out @@ -0,0 +1,91 @@ +-- money check +CREATE TABLE moneytmp (a money); +\copy moneytmp from 'data/cash.data' +SET enable_seqscan=on; +SELECT count(*) FROM moneytmp WHERE a < '22649.64'; + count +------- + 289 +(1 row) + +SELECT count(*) FROM moneytmp WHERE a <= '22649.64'; + count +------- + 290 +(1 row) + +SELECT count(*) FROM moneytmp WHERE a = '22649.64'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM moneytmp WHERE a >= '22649.64'; + count +------- + 254 +(1 row) + +SELECT count(*) FROM moneytmp WHERE a > '22649.64'; + count +------- + 253 +(1 row) + +SELECT a, a <-> '21472.79' FROM moneytmp ORDER BY a <-> '21472.79' LIMIT 3; + a | ?column? +------------+---------- + $21,472.79 | $0.00 + $21,469.25 | $3.54 + $21,915.01 | $442.22 +(3 rows) + +CREATE INDEX moneyidx ON moneytmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM moneytmp WHERE a < '22649.64'::money; + count +------- + 289 +(1 row) + +SELECT count(*) FROM moneytmp WHERE a <= '22649.64'::money; + count +------- + 290 +(1 row) + +SELECT count(*) FROM moneytmp WHERE a = '22649.64'::money; + count +------- + 1 +(1 row) + +SELECT count(*) FROM moneytmp WHERE a >= '22649.64'::money; + count +------- + 254 +(1 row) + +SELECT count(*) FROM moneytmp WHERE a > '22649.64'::money; + count +------- + 253 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '21472.79' FROM moneytmp ORDER BY a <-> '21472.79' LIMIT 3; + QUERY PLAN +-------------------------------------------------- + Limit + -> Index Only Scan using moneyidx on moneytmp + Order By: (a <-> '$21,472.79'::money) +(3 rows) + +SELECT a, a <-> '21472.79' FROM moneytmp ORDER BY a <-> '21472.79' LIMIT 3; + a | ?column? +------------+---------- + $21,472.79 | $0.00 + $21,469.25 | $3.54 + $21,915.01 | $442.22 +(3 rows) + diff --git a/contrib/btree_gist/expected/char.out b/contrib/btree_gist/expected/char.out new file mode 100644 index 0000000..45cf9f3 --- /dev/null +++ b/contrib/btree_gist/expected/char.out @@ -0,0 +1,82 @@ +-- char check +CREATE TABLE chartmp (a char(32)); +\copy chartmp from 'data/char.data' +SET enable_seqscan=on; +SELECT count(*) FROM chartmp WHERE a < '31b0'::char(32); + count +------- + 587 +(1 row) + +SELECT count(*) FROM chartmp WHERE a <= '31b0'::char(32); + count +------- + 588 +(1 row) + +SELECT count(*) FROM chartmp WHERE a = '31b0'::char(32); + count +------- + 1 +(1 row) + +SELECT count(*) FROM chartmp WHERE a >= '31b0'::char(32); + count +------- + 401 +(1 row) + +SELECT count(*) FROM chartmp WHERE a > '31b0'::char(32); + count +------- + 400 +(1 row) + +CREATE INDEX charidx ON chartmp USING GIST ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM chartmp WHERE a < '31b0'::char(32); + count +------- + 587 +(1 row) + +SELECT count(*) FROM chartmp WHERE a <= '31b0'::char(32); + count +------- + 588 +(1 row) + +SELECT count(*) FROM chartmp WHERE a = '31b0'::char(32); + count +------- + 1 +(1 row) + +SELECT count(*) FROM chartmp WHERE a >= '31b0'::char(32); + count +------- + 401 +(1 row) + +SELECT count(*) FROM chartmp WHERE a > '31b0'::char(32); + count +------- + 400 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM chartmp WHERE a BETWEEN '31a' AND '31c'; + QUERY PLAN +--------------------------------------------------------------- + Index Only Scan using charidx on chartmp + Index Cond: ((a >= '31a'::bpchar) AND (a <= '31c'::bpchar)) +(2 rows) + +SELECT * FROM chartmp WHERE a BETWEEN '31a' AND '31c'; + a +---------------------------------- + 31b0 +(1 row) + diff --git a/contrib/btree_gist/expected/char_1.out b/contrib/btree_gist/expected/char_1.out new file mode 100644 index 0000000..7b7824b --- /dev/null +++ b/contrib/btree_gist/expected/char_1.out @@ -0,0 +1,82 @@ +-- char check +CREATE TABLE chartmp (a char(32)); +\copy chartmp from 'data/char.data' +SET enable_seqscan=on; +SELECT count(*) FROM chartmp WHERE a < '31b0'::char(32); + count +------- + 773 +(1 row) + +SELECT count(*) FROM chartmp WHERE a <= '31b0'::char(32); + count +------- + 774 +(1 row) + +SELECT count(*) FROM chartmp WHERE a = '31b0'::char(32); + count +------- + 1 +(1 row) + +SELECT count(*) FROM chartmp WHERE a >= '31b0'::char(32); + count +------- + 215 +(1 row) + +SELECT count(*) FROM chartmp WHERE a > '31b0'::char(32); + count +------- + 214 +(1 row) + +CREATE INDEX charidx ON chartmp USING GIST ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM chartmp WHERE a < '31b0'::char(32); + count +------- + 773 +(1 row) + +SELECT count(*) FROM chartmp WHERE a <= '31b0'::char(32); + count +------- + 774 +(1 row) + +SELECT count(*) FROM chartmp WHERE a = '31b0'::char(32); + count +------- + 1 +(1 row) + +SELECT count(*) FROM chartmp WHERE a >= '31b0'::char(32); + count +------- + 215 +(1 row) + +SELECT count(*) FROM chartmp WHERE a > '31b0'::char(32); + count +------- + 214 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM chartmp WHERE a BETWEEN '31a' AND '31c'; + QUERY PLAN +--------------------------------------------------------------- + Index Only Scan using charidx on chartmp + Index Cond: ((a >= '31a'::bpchar) AND (a <= '31c'::bpchar)) +(2 rows) + +SELECT * FROM chartmp WHERE a BETWEEN '31a' AND '31c'; + a +---------------------------------- + 31b0 +(1 row) + diff --git a/contrib/btree_gist/expected/cidr.out b/contrib/btree_gist/expected/cidr.out new file mode 100644 index 0000000..6d0995a --- /dev/null +++ b/contrib/btree_gist/expected/cidr.out @@ -0,0 +1,66 @@ +-- cidr check +CREATE TABLE cidrtmp AS + SELECT cidr(a) AS a FROM inettmp ; +SET enable_seqscan=on; +SELECT count(*) FROM cidrtmp WHERE a < '121.111.63.82'; + count +------- + 290 +(1 row) + +SELECT count(*) FROM cidrtmp WHERE a <= '121.111.63.82'; + count +------- + 291 +(1 row) + +SELECT count(*) FROM cidrtmp WHERE a = '121.111.63.82'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM cidrtmp WHERE a >= '121.111.63.82'; + count +------- + 310 +(1 row) + +SELECT count(*) FROM cidrtmp WHERE a > '121.111.63.82'; + count +------- + 309 +(1 row) + +CREATE INDEX cidridx ON cidrtmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM cidrtmp WHERE a < '121.111.63.82'::cidr; + count +------- + 290 +(1 row) + +SELECT count(*) FROM cidrtmp WHERE a <= '121.111.63.82'::cidr; + count +------- + 291 +(1 row) + +SELECT count(*) FROM cidrtmp WHERE a = '121.111.63.82'::cidr; + count +------- + 1 +(1 row) + +SELECT count(*) FROM cidrtmp WHERE a >= '121.111.63.82'::cidr; + count +------- + 310 +(1 row) + +SELECT count(*) FROM cidrtmp WHERE a > '121.111.63.82'::cidr; + count +------- + 309 +(1 row) + diff --git a/contrib/btree_gist/expected/date.out b/contrib/btree_gist/expected/date.out new file mode 100644 index 0000000..5db864b --- /dev/null +++ b/contrib/btree_gist/expected/date.out @@ -0,0 +1,91 @@ +-- date check +CREATE TABLE datetmp (a date); +\copy datetmp from 'data/date.data' +SET enable_seqscan=on; +SELECT count(*) FROM datetmp WHERE a < '2001-02-13'; + count +------- + 230 +(1 row) + +SELECT count(*) FROM datetmp WHERE a <= '2001-02-13'; + count +------- + 231 +(1 row) + +SELECT count(*) FROM datetmp WHERE a = '2001-02-13'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM datetmp WHERE a >= '2001-02-13'; + count +------- + 314 +(1 row) + +SELECT count(*) FROM datetmp WHERE a > '2001-02-13'; + count +------- + 313 +(1 row) + +SELECT a, a <-> '2001-02-13' FROM datetmp ORDER BY a <-> '2001-02-13' LIMIT 3; + a | ?column? +------------+---------- + 02-13-2001 | 0 + 02-11-2001 | 2 + 03-24-2001 | 39 +(3 rows) + +CREATE INDEX dateidx ON datetmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM datetmp WHERE a < '2001-02-13'::date; + count +------- + 230 +(1 row) + +SELECT count(*) FROM datetmp WHERE a <= '2001-02-13'::date; + count +------- + 231 +(1 row) + +SELECT count(*) FROM datetmp WHERE a = '2001-02-13'::date; + count +------- + 1 +(1 row) + +SELECT count(*) FROM datetmp WHERE a >= '2001-02-13'::date; + count +------- + 314 +(1 row) + +SELECT count(*) FROM datetmp WHERE a > '2001-02-13'::date; + count +------- + 313 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '2001-02-13' FROM datetmp ORDER BY a <-> '2001-02-13' LIMIT 3; + QUERY PLAN +------------------------------------------------ + Limit + -> Index Only Scan using dateidx on datetmp + Order By: (a <-> '02-13-2001'::date) +(3 rows) + +SELECT a, a <-> '2001-02-13' FROM datetmp ORDER BY a <-> '2001-02-13' LIMIT 3; + a | ?column? +------------+---------- + 02-13-2001 | 0 + 02-11-2001 | 2 + 03-24-2001 | 39 +(3 rows) + diff --git a/contrib/btree_gist/expected/enum.out b/contrib/btree_gist/expected/enum.out new file mode 100644 index 0000000..c4b769d --- /dev/null +++ b/contrib/btree_gist/expected/enum.out @@ -0,0 +1,91 @@ +-- enum check +create type rainbow as enum ('r','o','y','g','b','i','v'); +CREATE TABLE enumtmp (a rainbow); +\copy enumtmp from 'data/enum.data' +SET enable_seqscan=on; +select a, count(*) from enumtmp group by a order by 1; + a | count +---+------- + r | 76 + o | 78 + y | 73 + g | 75 + b | 77 + i | 78 + v | 75 + | 63 +(8 rows) + +SELECT count(*) FROM enumtmp WHERE a < 'g'::rainbow; + count +------- + 227 +(1 row) + +SELECT count(*) FROM enumtmp WHERE a <= 'g'::rainbow; + count +------- + 302 +(1 row) + +SELECT count(*) FROM enumtmp WHERE a = 'g'::rainbow; + count +------- + 75 +(1 row) + +SELECT count(*) FROM enumtmp WHERE a >= 'g'::rainbow; + count +------- + 305 +(1 row) + +SELECT count(*) FROM enumtmp WHERE a > 'g'::rainbow; + count +------- + 230 +(1 row) + +CREATE INDEX enumidx ON enumtmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM enumtmp WHERE a < 'g'::rainbow; + count +------- + 227 +(1 row) + +SELECT count(*) FROM enumtmp WHERE a <= 'g'::rainbow; + count +------- + 302 +(1 row) + +SELECT count(*) FROM enumtmp WHERE a = 'g'::rainbow; + count +------- + 75 +(1 row) + +SELECT count(*) FROM enumtmp WHERE a >= 'g'::rainbow; + count +------- + 305 +(1 row) + +SELECT count(*) FROM enumtmp WHERE a > 'g'::rainbow; + count +------- + 230 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM enumtmp WHERE a >= 'g'::rainbow; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on enumtmp + Recheck Cond: (a >= 'g'::rainbow) + -> Bitmap Index Scan on enumidx + Index Cond: (a >= 'g'::rainbow) +(5 rows) + diff --git a/contrib/btree_gist/expected/float4.out b/contrib/btree_gist/expected/float4.out new file mode 100644 index 0000000..dfe7320 --- /dev/null +++ b/contrib/btree_gist/expected/float4.out @@ -0,0 +1,91 @@ +-- float4 check +CREATE TABLE float4tmp (a float4); +\copy float4tmp from 'data/float4.data' +SET enable_seqscan=on; +SELECT count(*) FROM float4tmp WHERE a < -179.0; + count +------- + 244 +(1 row) + +SELECT count(*) FROM float4tmp WHERE a <= -179.0; + count +------- + 245 +(1 row) + +SELECT count(*) FROM float4tmp WHERE a = -179.0; + count +------- + 1 +(1 row) + +SELECT count(*) FROM float4tmp WHERE a >= -179.0; + count +------- + 303 +(1 row) + +SELECT count(*) FROM float4tmp WHERE a > -179.0; + count +------- + 302 +(1 row) + +SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3; + a | ?column? +------------+----------- + -179 | 0 + -189.02386 | 10.023865 + -158.17741 | 20.822586 +(3 rows) + +CREATE INDEX float4idx ON float4tmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM float4tmp WHERE a < -179.0::float4; + count +------- + 244 +(1 row) + +SELECT count(*) FROM float4tmp WHERE a <= -179.0::float4; + count +------- + 245 +(1 row) + +SELECT count(*) FROM float4tmp WHERE a = -179.0::float4; + count +------- + 1 +(1 row) + +SELECT count(*) FROM float4tmp WHERE a >= -179.0::float4; + count +------- + 303 +(1 row) + +SELECT count(*) FROM float4tmp WHERE a > -179.0::float4; + count +------- + 302 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3; + QUERY PLAN +---------------------------------------------------- + Limit + -> Index Only Scan using float4idx on float4tmp + Order By: (a <-> '-179'::real) +(3 rows) + +SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3; + a | ?column? +------------+----------- + -179 | 0 + -189.02386 | 10.023865 + -158.17741 | 20.822586 +(3 rows) + diff --git a/contrib/btree_gist/expected/float8.out b/contrib/btree_gist/expected/float8.out new file mode 100644 index 0000000..ebd0ef3 --- /dev/null +++ b/contrib/btree_gist/expected/float8.out @@ -0,0 +1,91 @@ +-- float8 check +CREATE TABLE float8tmp (a float8); +\copy float8tmp from 'data/float8.data' +SET enable_seqscan=on; +SELECT count(*) FROM float8tmp WHERE a < -1890.0; + count +------- + 237 +(1 row) + +SELECT count(*) FROM float8tmp WHERE a <= -1890.0; + count +------- + 238 +(1 row) + +SELECT count(*) FROM float8tmp WHERE a = -1890.0; + count +------- + 1 +(1 row) + +SELECT count(*) FROM float8tmp WHERE a >= -1890.0; + count +------- + 307 +(1 row) + +SELECT count(*) FROM float8tmp WHERE a > -1890.0; + count +------- + 306 +(1 row) + +SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3; + a | ?column? +--------------+-------------------- + -1890 | 0 + -2003.634512 | 113.63451200000009 + -1769.73634 | 120.26366000000007 +(3 rows) + +CREATE INDEX float8idx ON float8tmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM float8tmp WHERE a < -1890.0::float8; + count +------- + 237 +(1 row) + +SELECT count(*) FROM float8tmp WHERE a <= -1890.0::float8; + count +------- + 238 +(1 row) + +SELECT count(*) FROM float8tmp WHERE a = -1890.0::float8; + count +------- + 1 +(1 row) + +SELECT count(*) FROM float8tmp WHERE a >= -1890.0::float8; + count +------- + 307 +(1 row) + +SELECT count(*) FROM float8tmp WHERE a > -1890.0::float8; + count +------- + 306 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3; + QUERY PLAN +----------------------------------------------------- + Limit + -> Index Only Scan using float8idx on float8tmp + Order By: (a <-> '-1890'::double precision) +(3 rows) + +SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3; + a | ?column? +--------------+-------------------- + -1890 | 0 + -2003.634512 | 113.63451200000009 + -1769.73634 | 120.26366000000007 +(3 rows) + diff --git a/contrib/btree_gist/expected/inet.out b/contrib/btree_gist/expected/inet.out new file mode 100644 index 0000000..f15f143 --- /dev/null +++ b/contrib/btree_gist/expected/inet.out @@ -0,0 +1,101 @@ +-- inet check +CREATE TABLE inettmp (a inet); +\copy inettmp from 'data/inet.data' +SET enable_seqscan=on; +SELECT count(*) FROM inettmp WHERE a < '89.225.196.191'; + count +------- + 213 +(1 row) + +SELECT count(*) FROM inettmp WHERE a <= '89.225.196.191'; + count +------- + 214 +(1 row) + +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM inettmp WHERE a >= '89.225.196.191'; + count +------- + 387 +(1 row) + +SELECT count(*) FROM inettmp WHERE a > '89.225.196.191'; + count +------- + 386 +(1 row) + +CREATE INDEX inetidx ON inettmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM inettmp WHERE a < '89.225.196.191'::inet; + count +------- + 213 +(1 row) + +SELECT count(*) FROM inettmp WHERE a <= '89.225.196.191'::inet; + count +------- + 214 +(1 row) + +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'::inet; + count +------- + 1 +(1 row) + +SELECT count(*) FROM inettmp WHERE a >= '89.225.196.191'::inet; + count +------- + 387 +(1 row) + +SELECT count(*) FROM inettmp WHERE a > '89.225.196.191'::inet; + count +------- + 386 +(1 row) + +VACUUM ANALYZE inettmp; +-- gist_inet_ops lacks a fetch function, so this should not be index-only scan +EXPLAIN (COSTS OFF) +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'::inet; + QUERY PLAN +-------------------------------------------------- + Aggregate + -> Index Scan using inetidx on inettmp + Index Cond: (a = '89.225.196.191'::inet) +(3 rows) + +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'::inet; + count +------- + 1 +(1 row) + +DROP INDEX inetidx; +CREATE INDEX ON inettmp USING gist (a gist_inet_ops, a inet_ops); +-- this can be an index-only scan, as long as the planner uses the right column +EXPLAIN (COSTS OFF) +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'::inet; + QUERY PLAN +--------------------------------------------------------- + Aggregate + -> Index Only Scan using inettmp_a_a1_idx on inettmp + Index Cond: (a = '89.225.196.191'::inet) +(3 rows) + +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'::inet; + count +------- + 1 +(1 row) + diff --git a/contrib/btree_gist/expected/init.out b/contrib/btree_gist/expected/init.out new file mode 100644 index 0000000..ce4559d --- /dev/null +++ b/contrib/btree_gist/expected/init.out @@ -0,0 +1,9 @@ +CREATE EXTENSION btree_gist; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + amname | opcname +--------+--------- +(0 rows) + diff --git a/contrib/btree_gist/expected/int2.out b/contrib/btree_gist/expected/int2.out new file mode 100644 index 0000000..50a3329 --- /dev/null +++ b/contrib/btree_gist/expected/int2.out @@ -0,0 +1,91 @@ +-- int2 check +CREATE TABLE int2tmp (a int2); +\copy int2tmp from 'data/int2.data' +SET enable_seqscan=on; +SELECT count(*) FROM int2tmp WHERE a < 237; + count +------- + 297 +(1 row) + +SELECT count(*) FROM int2tmp WHERE a <= 237; + count +------- + 298 +(1 row) + +SELECT count(*) FROM int2tmp WHERE a = 237; + count +------- + 1 +(1 row) + +SELECT count(*) FROM int2tmp WHERE a >= 237; + count +------- + 249 +(1 row) + +SELECT count(*) FROM int2tmp WHERE a > 237; + count +------- + 248 +(1 row) + +SELECT a, a <-> '237' FROM int2tmp ORDER BY a <-> '237' LIMIT 3; + a | ?column? +-----+---------- + 237 | 0 + 232 | 5 + 228 | 9 +(3 rows) + +CREATE INDEX int2idx ON int2tmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM int2tmp WHERE a < 237::int2; + count +------- + 297 +(1 row) + +SELECT count(*) FROM int2tmp WHERE a <= 237::int2; + count +------- + 298 +(1 row) + +SELECT count(*) FROM int2tmp WHERE a = 237::int2; + count +------- + 1 +(1 row) + +SELECT count(*) FROM int2tmp WHERE a >= 237::int2; + count +------- + 249 +(1 row) + +SELECT count(*) FROM int2tmp WHERE a > 237::int2; + count +------- + 248 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '237' FROM int2tmp ORDER BY a <-> '237' LIMIT 3; + QUERY PLAN +------------------------------------------------ + Limit + -> Index Only Scan using int2idx on int2tmp + Order By: (a <-> '237'::smallint) +(3 rows) + +SELECT a, a <-> '237' FROM int2tmp ORDER BY a <-> '237' LIMIT 3; + a | ?column? +-----+---------- + 237 | 0 + 232 | 5 + 228 | 9 +(3 rows) + diff --git a/contrib/btree_gist/expected/int4.out b/contrib/btree_gist/expected/int4.out new file mode 100644 index 0000000..6bbdc7c --- /dev/null +++ b/contrib/btree_gist/expected/int4.out @@ -0,0 +1,91 @@ +-- int4 check +CREATE TABLE int4tmp (a int4); +\copy int4tmp from 'data/int2.data' +SET enable_seqscan=on; +SELECT count(*) FROM int4tmp WHERE a < 237; + count +------- + 297 +(1 row) + +SELECT count(*) FROM int4tmp WHERE a <= 237; + count +------- + 298 +(1 row) + +SELECT count(*) FROM int4tmp WHERE a = 237; + count +------- + 1 +(1 row) + +SELECT count(*) FROM int4tmp WHERE a >= 237; + count +------- + 249 +(1 row) + +SELECT count(*) FROM int4tmp WHERE a > 237; + count +------- + 248 +(1 row) + +SELECT a, a <-> '237' FROM int4tmp ORDER BY a <-> '237' LIMIT 3; + a | ?column? +-----+---------- + 237 | 0 + 232 | 5 + 228 | 9 +(3 rows) + +CREATE INDEX int4idx ON int4tmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM int4tmp WHERE a < 237::int4; + count +------- + 297 +(1 row) + +SELECT count(*) FROM int4tmp WHERE a <= 237::int4; + count +------- + 298 +(1 row) + +SELECT count(*) FROM int4tmp WHERE a = 237::int4; + count +------- + 1 +(1 row) + +SELECT count(*) FROM int4tmp WHERE a >= 237::int4; + count +------- + 249 +(1 row) + +SELECT count(*) FROM int4tmp WHERE a > 237::int4; + count +------- + 248 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '237' FROM int4tmp ORDER BY a <-> '237' LIMIT 3; + QUERY PLAN +------------------------------------------------ + Limit + -> Index Only Scan using int4idx on int4tmp + Order By: (a <-> 237) +(3 rows) + +SELECT a, a <-> '237' FROM int4tmp ORDER BY a <-> '237' LIMIT 3; + a | ?column? +-----+---------- + 237 | 0 + 232 | 5 + 228 | 9 +(3 rows) + diff --git a/contrib/btree_gist/expected/int8.out b/contrib/btree_gist/expected/int8.out new file mode 100644 index 0000000..eff77c2 --- /dev/null +++ b/contrib/btree_gist/expected/int8.out @@ -0,0 +1,91 @@ +-- int8 check +CREATE TABLE int8tmp (a int8); +\copy int8tmp from 'data/int8.data' +SET enable_seqscan=on; +SELECT count(*) FROM int8tmp WHERE a < 464571291354841; + count +------- + 276 +(1 row) + +SELECT count(*) FROM int8tmp WHERE a <= 464571291354841; + count +------- + 277 +(1 row) + +SELECT count(*) FROM int8tmp WHERE a = 464571291354841; + count +------- + 1 +(1 row) + +SELECT count(*) FROM int8tmp WHERE a >= 464571291354841; + count +------- + 271 +(1 row) + +SELECT count(*) FROM int8tmp WHERE a > 464571291354841; + count +------- + 270 +(1 row) + +SELECT a, a <-> '464571291354841' FROM int8tmp ORDER BY a <-> '464571291354841' LIMIT 3; + a | ?column? +-----------------+---------------- + 464571291354841 | 0 + 457257666629329 | 7313624725512 + 478227196042750 | 13655904687909 +(3 rows) + +CREATE INDEX int8idx ON int8tmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM int8tmp WHERE a < 464571291354841::int8; + count +------- + 276 +(1 row) + +SELECT count(*) FROM int8tmp WHERE a <= 464571291354841::int8; + count +------- + 277 +(1 row) + +SELECT count(*) FROM int8tmp WHERE a = 464571291354841::int8; + count +------- + 1 +(1 row) + +SELECT count(*) FROM int8tmp WHERE a >= 464571291354841::int8; + count +------- + 271 +(1 row) + +SELECT count(*) FROM int8tmp WHERE a > 464571291354841::int8; + count +------- + 270 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '464571291354841' FROM int8tmp ORDER BY a <-> '464571291354841' LIMIT 3; + QUERY PLAN +----------------------------------------------------- + Limit + -> Index Only Scan using int8idx on int8tmp + Order By: (a <-> '464571291354841'::bigint) +(3 rows) + +SELECT a, a <-> '464571291354841' FROM int8tmp ORDER BY a <-> '464571291354841' LIMIT 3; + a | ?column? +-----------------+---------------- + 464571291354841 | 0 + 457257666629329 | 7313624725512 + 478227196042750 | 13655904687909 +(3 rows) + diff --git a/contrib/btree_gist/expected/interval.out b/contrib/btree_gist/expected/interval.out new file mode 100644 index 0000000..4c3d494 --- /dev/null +++ b/contrib/btree_gist/expected/interval.out @@ -0,0 +1,109 @@ +-- interval check +CREATE TABLE intervaltmp (a interval); +\copy intervaltmp from 'data/interval.data' +SET enable_seqscan=on; +SELECT count(*) FROM intervaltmp WHERE a < '199 days 21:21:23'; + count +------- + 329 +(1 row) + +SELECT count(*) FROM intervaltmp WHERE a <= '199 days 21:21:23'; + count +------- + 330 +(1 row) + +SELECT count(*) FROM intervaltmp WHERE a = '199 days 21:21:23'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM intervaltmp WHERE a >= '199 days 21:21:23'; + count +------- + 271 +(1 row) + +SELECT count(*) FROM intervaltmp WHERE a > '199 days 21:21:23'; + count +------- + 270 +(1 row) + +SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; + a | ?column? +-------------------------------------+-------------------------------------- + @ 199 days 21 hours 21 mins 23 secs | @ 0 + @ 183 days 6 hours 52 mins 48 secs | @ 16 days 14 hours 28 mins 35 secs + @ 220 days 19 hours 5 mins 42 secs | @ 21 days -2 hours -15 mins -41 secs +(3 rows) + +CREATE INDEX intervalidx ON intervaltmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM intervaltmp WHERE a < '199 days 21:21:23'::interval; + count +------- + 329 +(1 row) + +SELECT count(*) FROM intervaltmp WHERE a <= '199 days 21:21:23'::interval; + count +------- + 330 +(1 row) + +SELECT count(*) FROM intervaltmp WHERE a = '199 days 21:21:23'::interval; + count +------- + 1 +(1 row) + +SELECT count(*) FROM intervaltmp WHERE a >= '199 days 21:21:23'::interval; + count +------- + 271 +(1 row) + +SELECT count(*) FROM intervaltmp WHERE a > '199 days 21:21:23'::interval; + count +------- + 270 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; + QUERY PLAN +--------------------------------------------------------------------------- + Limit + -> Index Only Scan using intervalidx on intervaltmp + Order By: (a <-> '@ 199 days 21 hours 21 mins 23 secs'::interval) +(3 rows) + +SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; + a | ?column? +-------------------------------------+-------------------------------------- + @ 199 days 21 hours 21 mins 23 secs | @ 0 + @ 183 days 6 hours 52 mins 48 secs | @ 16 days 14 hours 28 mins 35 secs + @ 220 days 19 hours 5 mins 42 secs | @ 21 days -2 hours -15 mins -41 secs +(3 rows) + +SET enable_indexonlyscan=off; +EXPLAIN (COSTS OFF) +SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; + QUERY PLAN +--------------------------------------------------------------------------- + Limit + -> Index Scan using intervalidx on intervaltmp + Order By: (a <-> '@ 199 days 21 hours 21 mins 23 secs'::interval) +(3 rows) + +SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; + a | ?column? +-------------------------------------+-------------------------------------- + @ 199 days 21 hours 21 mins 23 secs | @ 0 + @ 183 days 6 hours 52 mins 48 secs | @ 16 days 14 hours 28 mins 35 secs + @ 220 days 19 hours 5 mins 42 secs | @ 21 days -2 hours -15 mins -41 secs +(3 rows) + diff --git a/contrib/btree_gist/expected/macaddr.out b/contrib/btree_gist/expected/macaddr.out new file mode 100644 index 0000000..c0a4c62 --- /dev/null +++ b/contrib/btree_gist/expected/macaddr.out @@ -0,0 +1,89 @@ +-- macaddr check +CREATE TABLE macaddrtmp (a macaddr); +\copy macaddrtmp from 'data/macaddr.data' +SET enable_seqscan=on; +SELECT count(*) FROM macaddrtmp WHERE a < '22:00:5c:e5:9b:0d'; + count +------- + 56 +(1 row) + +SELECT count(*) FROM macaddrtmp WHERE a <= '22:00:5c:e5:9b:0d'; + count +------- + 60 +(1 row) + +SELECT count(*) FROM macaddrtmp WHERE a = '22:00:5c:e5:9b:0d'; + count +------- + 4 +(1 row) + +SELECT count(*) FROM macaddrtmp WHERE a >= '22:00:5c:e5:9b:0d'; + count +------- + 544 +(1 row) + +SELECT count(*) FROM macaddrtmp WHERE a > '22:00:5c:e5:9b:0d'; + count +------- + 540 +(1 row) + +CREATE INDEX macaddridx ON macaddrtmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM macaddrtmp WHERE a < '22:00:5c:e5:9b:0d'::macaddr; + count +------- + 56 +(1 row) + +SELECT count(*) FROM macaddrtmp WHERE a <= '22:00:5c:e5:9b:0d'::macaddr; + count +------- + 60 +(1 row) + +SELECT count(*) FROM macaddrtmp WHERE a = '22:00:5c:e5:9b:0d'::macaddr; + count +------- + 4 +(1 row) + +SELECT count(*) FROM macaddrtmp WHERE a >= '22:00:5c:e5:9b:0d'::macaddr; + count +------- + 544 +(1 row) + +SELECT count(*) FROM macaddrtmp WHERE a > '22:00:5c:e5:9b:0d'::macaddr; + count +------- + 540 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM macaddrtmp WHERE a < '02:03:04:05:06:07'::macaddr; + QUERY PLAN +-------------------------------------------------- + Index Only Scan using macaddridx on macaddrtmp + Index Cond: (a < '02:03:04:05:06:07'::macaddr) +(2 rows) + +SELECT * FROM macaddrtmp WHERE a < '02:03:04:05:06:07'::macaddr; + a +------------------- + 01:02:37:05:4f:36 + 01:02:37:05:4f:36 + 01:02:37:05:4f:36 + 01:02:37:05:4f:36 + 01:43:b5:79:eb:0f + 01:43:b5:79:eb:0f + 01:43:b5:79:eb:0f + 01:43:b5:79:eb:0f +(8 rows) + diff --git a/contrib/btree_gist/expected/macaddr8.out b/contrib/btree_gist/expected/macaddr8.out new file mode 100644 index 0000000..e5ec6a5 --- /dev/null +++ b/contrib/btree_gist/expected/macaddr8.out @@ -0,0 +1,89 @@ +-- macaddr check +CREATE TABLE macaddr8tmp (a macaddr8); +\copy macaddr8tmp from 'data/macaddr.data' +SET enable_seqscan=on; +SELECT count(*) FROM macaddr8tmp WHERE a < '22:00:5c:e5:9b:0d'; + count +------- + 56 +(1 row) + +SELECT count(*) FROM macaddr8tmp WHERE a <= '22:00:5c:e5:9b:0d'; + count +------- + 60 +(1 row) + +SELECT count(*) FROM macaddr8tmp WHERE a = '22:00:5c:e5:9b:0d'; + count +------- + 4 +(1 row) + +SELECT count(*) FROM macaddr8tmp WHERE a >= '22:00:5c:e5:9b:0d'; + count +------- + 544 +(1 row) + +SELECT count(*) FROM macaddr8tmp WHERE a > '22:00:5c:e5:9b:0d'; + count +------- + 540 +(1 row) + +CREATE INDEX macaddr8idx ON macaddr8tmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM macaddr8tmp WHERE a < '22:00:5c:e5:9b:0d'::macaddr8; + count +------- + 56 +(1 row) + +SELECT count(*) FROM macaddr8tmp WHERE a <= '22:00:5c:e5:9b:0d'::macaddr8; + count +------- + 60 +(1 row) + +SELECT count(*) FROM macaddr8tmp WHERE a = '22:00:5c:e5:9b:0d'::macaddr8; + count +------- + 4 +(1 row) + +SELECT count(*) FROM macaddr8tmp WHERE a >= '22:00:5c:e5:9b:0d'::macaddr8; + count +------- + 544 +(1 row) + +SELECT count(*) FROM macaddr8tmp WHERE a > '22:00:5c:e5:9b:0d'::macaddr8; + count +------- + 540 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM macaddr8tmp WHERE a < '02:03:04:05:06:07'::macaddr8; + QUERY PLAN +--------------------------------------------------------- + Index Only Scan using macaddr8idx on macaddr8tmp + Index Cond: (a < '02:03:04:ff:fe:05:06:07'::macaddr8) +(2 rows) + +SELECT * FROM macaddr8tmp WHERE a < '02:03:04:05:06:07'::macaddr8; + a +------------------------- + 01:02:37:ff:fe:05:4f:36 + 01:02:37:ff:fe:05:4f:36 + 01:02:37:ff:fe:05:4f:36 + 01:02:37:ff:fe:05:4f:36 + 01:43:b5:ff:fe:79:eb:0f + 01:43:b5:ff:fe:79:eb:0f + 01:43:b5:ff:fe:79:eb:0f + 01:43:b5:ff:fe:79:eb:0f +(8 rows) + diff --git a/contrib/btree_gist/expected/not_equal.out b/contrib/btree_gist/expected/not_equal.out new file mode 100644 index 0000000..85b1e86 --- /dev/null +++ b/contrib/btree_gist/expected/not_equal.out @@ -0,0 +1,41 @@ +SET enable_seqscan to false; +-- test search for "not equals" +CREATE TABLE test_ne ( + a TIMESTAMP, + b NUMERIC +); +CREATE INDEX test_ne_idx ON test_ne USING gist (a, b); +INSERT INTO test_ne SELECT '2009-01-01', 10.7 FROM generate_series(1,1000); +INSERT INTO test_ne VALUES('2007-02-03', -91.3); +INSERT INTO test_ne VALUES('2011-09-01', 43.7); +INSERT INTO test_ne SELECT '2009-01-01', 10.7 FROM generate_series(1,1000); +SET enable_indexscan to false; +EXPLAIN (COSTS OFF) SELECT * FROM test_ne WHERE a <> '2009-01-01' AND b <> 10.7; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Bitmap Heap Scan on test_ne + Recheck Cond: ((a <> 'Thu Jan 01 00:00:00 2009'::timestamp without time zone) AND (b <> 10.7)) + -> Bitmap Index Scan on test_ne_idx + Index Cond: ((a <> 'Thu Jan 01 00:00:00 2009'::timestamp without time zone) AND (b <> 10.7)) +(4 rows) + +SELECT * FROM test_ne WHERE a <> '2009-01-01' AND b <> 10.7; + a | b +--------------------------+------- + Sat Feb 03 00:00:00 2007 | -91.3 + Thu Sep 01 00:00:00 2011 | 43.7 +(2 rows) + +RESET enable_indexscan; +-- test search for "not equals" using an exclusion constraint +CREATE TABLE zoo ( + cage INTEGER, + animal TEXT, + EXCLUDE USING gist (cage WITH =, animal WITH <>) +); +INSERT INTO zoo VALUES(123, 'zebra'); +INSERT INTO zoo VALUES(123, 'zebra'); +INSERT INTO zoo VALUES(123, 'lion'); +ERROR: conflicting key value violates exclusion constraint "zoo_cage_animal_excl" +DETAIL: Key (cage, animal)=(123, lion) conflicts with existing key (cage, animal)=(123, zebra). +INSERT INTO zoo VALUES(124, 'lion'); diff --git a/contrib/btree_gist/expected/numeric.out b/contrib/btree_gist/expected/numeric.out new file mode 100644 index 0000000..ae839b8 --- /dev/null +++ b/contrib/btree_gist/expected/numeric.out @@ -0,0 +1,207 @@ +-- numeric check +CREATE TABLE numerictmp (a numeric); +\copy numerictmp from 'data/int8.data' +\copy numerictmp from 'data/numeric.data' +\copy numerictmp from 'data/float8.data' +SET enable_seqscan=on; +SELECT count(*) FROM numerictmp WHERE a < -1890.0; + count +------- + 505 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a <= -1890.0; + count +------- + 506 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a = -1890.0; + count +------- + 1 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a >= -1890.0; + count +------- + 597 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a > -1890.0; + count +------- + 596 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a < 'NaN' ; + count +------- + 1100 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a <= 'NaN' ; + count +------- + 1102 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a = 'NaN' ; + count +------- + 2 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a >= 'NaN' ; + count +------- + 2 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a > 'NaN' ; + count +------- + 0 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a < 0 ; + count +------- + 523 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a <= 0 ; + count +------- + 526 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a = 0 ; + count +------- + 3 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a >= 0 ; + count +------- + 579 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a > 0 ; + count +------- + 576 +(1 row) + +CREATE INDEX numericidx ON numerictmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM numerictmp WHERE a < -1890.0; + count +------- + 505 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a <= -1890.0; + count +------- + 506 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a = -1890.0; + count +------- + 1 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a >= -1890.0; + count +------- + 597 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a > -1890.0; + count +------- + 596 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a < 'NaN' ; + count +------- + 1100 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a <= 'NaN' ; + count +------- + 1102 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a = 'NaN' ; + count +------- + 2 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a >= 'NaN' ; + count +------- + 2 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a > 'NaN' ; + count +------- + 0 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a < 0 ; + count +------- + 523 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a <= 0 ; + count +------- + 526 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a = 0 ; + count +------- + 3 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a >= 0 ; + count +------- + 579 +(1 row) + +SELECT count(*) FROM numerictmp WHERE a > 0 ; + count +------- + 576 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM numerictmp WHERE a BETWEEN 1 AND 300 ORDER BY a; + QUERY PLAN +--------------------------------------------------------------------- + Sort + Sort Key: a + -> Index Only Scan using numericidx on numerictmp + Index Cond: ((a >= '1'::numeric) AND (a <= '300'::numeric)) +(4 rows) + +SELECT * FROM numerictmp WHERE a BETWEEN 1 AND 300 ORDER BY a; + a +------------ + 204.035430 + 207.400532 +(2 rows) + diff --git a/contrib/btree_gist/expected/oid.out b/contrib/btree_gist/expected/oid.out new file mode 100644 index 0000000..776bbb1 --- /dev/null +++ b/contrib/btree_gist/expected/oid.out @@ -0,0 +1,66 @@ +-- oid check +SET enable_seqscan=on; +CREATE TEMPORARY TABLE oidtmp (oid oid); +INSERT INTO oidtmp SELECT g.i::oid FROM generate_series(1, 1000) g(i); +SELECT count(*) FROM oidtmp WHERE oid < 17; + count +------- + 16 +(1 row) + +SELECT count(*) FROM oidtmp WHERE oid <= 17; + count +------- + 17 +(1 row) + +SELECT count(*) FROM oidtmp WHERE oid = 17; + count +------- + 1 +(1 row) + +SELECT count(*) FROM oidtmp WHERE oid >= 17; + count +------- + 984 +(1 row) + +SELECT count(*) FROM oidtmp WHERE oid > 17; + count +------- + 983 +(1 row) + +CREATE INDEX oididx ON oidtmp USING gist ( oid ); +SET enable_seqscan=off; +SELECT count(*) FROM oidtmp WHERE oid < 17; + count +------- + 16 +(1 row) + +SELECT count(*) FROM oidtmp WHERE oid <= 17; + count +------- + 17 +(1 row) + +SELECT count(*) FROM oidtmp WHERE oid = 17; + count +------- + 1 +(1 row) + +SELECT count(*) FROM oidtmp WHERE oid >= 17; + count +------- + 984 +(1 row) + +SELECT count(*) FROM oidtmp WHERE oid > 17; + count +------- + 983 +(1 row) + diff --git a/contrib/btree_gist/expected/text.out b/contrib/btree_gist/expected/text.out new file mode 100644 index 0000000..bb4e2e6 --- /dev/null +++ b/contrib/btree_gist/expected/text.out @@ -0,0 +1,89 @@ +-- text check +CREATE TABLE texttmp (a text); +\copy texttmp from 'data/text.data' +\copy texttmp from 'data/char.data' +SET enable_seqscan=on; +SELECT count(*) FROM texttmp WHERE a < '31b0'; + count +------- + 588 +(1 row) + +SELECT count(*) FROM texttmp WHERE a <= '31b0'; + count +------- + 589 +(1 row) + +SELECT count(*) FROM texttmp WHERE a = '31b0'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM texttmp WHERE a >= '31b0'; + count +------- + 401 +(1 row) + +SELECT count(*) FROM texttmp WHERE a > '31b0'; + count +------- + 400 +(1 row) + +CREATE INDEX textidx ON texttmp USING GIST ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM texttmp WHERE a < '31b0'::text; + count +------- + 588 +(1 row) + +SELECT count(*) FROM texttmp WHERE a <= '31b0'::text; + count +------- + 589 +(1 row) + +SELECT count(*) FROM texttmp WHERE a = '31b0'::text; + count +------- + 1 +(1 row) + +SELECT count(*) FROM texttmp WHERE a >= '31b0'::text; + count +------- + 401 +(1 row) + +SELECT count(*) FROM texttmp WHERE a > '31b0'::text; + count +------- + 400 +(1 row) + +SELECT count(*) FROM texttmp WHERE a = '2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809'::text; + count +------- + 1 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM texttmp WHERE a BETWEEN '31a' AND '31c'; + QUERY PLAN +----------------------------------------------------------- + Index Only Scan using textidx on texttmp + Index Cond: ((a >= '31a'::text) AND (a <= '31c'::text)) +(2 rows) + +SELECT * FROM texttmp WHERE a BETWEEN '31a' AND '31c'; + a +------ + 31b0 +(1 row) + diff --git a/contrib/btree_gist/expected/text_1.out b/contrib/btree_gist/expected/text_1.out new file mode 100644 index 0000000..8ef1ffb --- /dev/null +++ b/contrib/btree_gist/expected/text_1.out @@ -0,0 +1,89 @@ +-- text check +CREATE TABLE texttmp (a text); +\copy texttmp from 'data/text.data' +\copy texttmp from 'data/char.data' +SET enable_seqscan=on; +SELECT count(*) FROM texttmp WHERE a < '31b0'; + count +------- + 774 +(1 row) + +SELECT count(*) FROM texttmp WHERE a <= '31b0'; + count +------- + 775 +(1 row) + +SELECT count(*) FROM texttmp WHERE a = '31b0'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM texttmp WHERE a >= '31b0'; + count +------- + 215 +(1 row) + +SELECT count(*) FROM texttmp WHERE a > '31b0'; + count +------- + 214 +(1 row) + +CREATE INDEX textidx ON texttmp USING GIST ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM texttmp WHERE a < '31b0'::text; + count +------- + 774 +(1 row) + +SELECT count(*) FROM texttmp WHERE a <= '31b0'::text; + count +------- + 775 +(1 row) + +SELECT count(*) FROM texttmp WHERE a = '31b0'::text; + count +------- + 1 +(1 row) + +SELECT count(*) FROM texttmp WHERE a >= '31b0'::text; + count +------- + 215 +(1 row) + +SELECT count(*) FROM texttmp WHERE a > '31b0'::text; + count +------- + 214 +(1 row) + +SELECT count(*) FROM texttmp WHERE a = '2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809'::text; + count +------- + 1 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM texttmp WHERE a BETWEEN '31a' AND '31c'; + QUERY PLAN +----------------------------------------------------------- + Index Only Scan using textidx on texttmp + Index Cond: ((a >= '31a'::text) AND (a <= '31c'::text)) +(2 rows) + +SELECT * FROM texttmp WHERE a BETWEEN '31a' AND '31c'; + a +------ + 31b0 +(1 row) + diff --git a/contrib/btree_gist/expected/time.out b/contrib/btree_gist/expected/time.out new file mode 100644 index 0000000..ec95ef7 --- /dev/null +++ b/contrib/btree_gist/expected/time.out @@ -0,0 +1,91 @@ +-- time check +CREATE TABLE timetmp (a time); +\copy timetmp from 'data/time.data' +SET enable_seqscan=on; +SELECT count(*) FROM timetmp WHERE a < '10:57:11'; + count +------- + 251 +(1 row) + +SELECT count(*) FROM timetmp WHERE a <= '10:57:11'; + count +------- + 252 +(1 row) + +SELECT count(*) FROM timetmp WHERE a = '10:57:11'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM timetmp WHERE a >= '10:57:11'; + count +------- + 293 +(1 row) + +SELECT count(*) FROM timetmp WHERE a > '10:57:11'; + count +------- + 292 +(1 row) + +SELECT a, a <-> '10:57:11' FROM timetmp ORDER BY a <-> '10:57:11' LIMIT 3; + a | ?column? +----------+----------------- + 10:57:11 | @ 0 + 10:57:10 | @ 1 sec + 10:55:32 | @ 1 min 39 secs +(3 rows) + +CREATE INDEX timeidx ON timetmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM timetmp WHERE a < '10:57:11'::time; + count +------- + 251 +(1 row) + +SELECT count(*) FROM timetmp WHERE a <= '10:57:11'::time; + count +------- + 252 +(1 row) + +SELECT count(*) FROM timetmp WHERE a = '10:57:11'::time; + count +------- + 1 +(1 row) + +SELECT count(*) FROM timetmp WHERE a >= '10:57:11'::time; + count +------- + 293 +(1 row) + +SELECT count(*) FROM timetmp WHERE a > '10:57:11'::time; + count +------- + 292 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '10:57:11' FROM timetmp ORDER BY a <-> '10:57:11' LIMIT 3; + QUERY PLAN +-------------------------------------------------------------- + Limit + -> Index Only Scan using timeidx on timetmp + Order By: (a <-> '10:57:11'::time without time zone) +(3 rows) + +SELECT a, a <-> '10:57:11' FROM timetmp ORDER BY a <-> '10:57:11' LIMIT 3; + a | ?column? +----------+----------------- + 10:57:11 | @ 0 + 10:57:10 | @ 1 sec + 10:55:32 | @ 1 min 39 secs +(3 rows) + diff --git a/contrib/btree_gist/expected/timestamp.out b/contrib/btree_gist/expected/timestamp.out new file mode 100644 index 0000000..0d94f2f --- /dev/null +++ b/contrib/btree_gist/expected/timestamp.out @@ -0,0 +1,91 @@ +-- timestamp check +CREATE TABLE timestamptmp (a timestamp); +\copy timestamptmp from 'data/timestamp.data' +SET enable_seqscan=on; +SELECT count(*) FROM timestamptmp WHERE a < '2004-10-26 08:55:08'; + count +------- + 278 +(1 row) + +SELECT count(*) FROM timestamptmp WHERE a <= '2004-10-26 08:55:08'; + count +------- + 279 +(1 row) + +SELECT count(*) FROM timestamptmp WHERE a = '2004-10-26 08:55:08'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM timestamptmp WHERE a >= '2004-10-26 08:55:08'; + count +------- + 290 +(1 row) + +SELECT count(*) FROM timestamptmp WHERE a > '2004-10-26 08:55:08'; + count +------- + 289 +(1 row) + +SELECT a, a <-> '2004-10-26 08:55:08' FROM timestamptmp ORDER BY a <-> '2004-10-26 08:55:08' LIMIT 3; + a | ?column? +--------------------------+------------------------------------ + Tue Oct 26 08:55:08 2004 | @ 0 + Sun Oct 31 06:35:03 2004 | @ 4 days 21 hours 39 mins 55 secs + Mon Nov 29 20:12:43 2004 | @ 34 days 11 hours 17 mins 35 secs +(3 rows) + +CREATE INDEX timestampidx ON timestamptmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM timestamptmp WHERE a < '2004-10-26 08:55:08'::timestamp; + count +------- + 278 +(1 row) + +SELECT count(*) FROM timestamptmp WHERE a <= '2004-10-26 08:55:08'::timestamp; + count +------- + 279 +(1 row) + +SELECT count(*) FROM timestamptmp WHERE a = '2004-10-26 08:55:08'::timestamp; + count +------- + 1 +(1 row) + +SELECT count(*) FROM timestamptmp WHERE a >= '2004-10-26 08:55:08'::timestamp; + count +------- + 290 +(1 row) + +SELECT count(*) FROM timestamptmp WHERE a > '2004-10-26 08:55:08'::timestamp; + count +------- + 289 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '2004-10-26 08:55:08' FROM timestamptmp ORDER BY a <-> '2004-10-26 08:55:08' LIMIT 3; + QUERY PLAN +----------------------------------------------------------------------------------- + Limit + -> Index Only Scan using timestampidx on timestamptmp + Order By: (a <-> 'Tue Oct 26 08:55:08 2004'::timestamp without time zone) +(3 rows) + +SELECT a, a <-> '2004-10-26 08:55:08' FROM timestamptmp ORDER BY a <-> '2004-10-26 08:55:08' LIMIT 3; + a | ?column? +--------------------------+------------------------------------ + Tue Oct 26 08:55:08 2004 | @ 0 + Sun Oct 31 06:35:03 2004 | @ 4 days 21 hours 39 mins 55 secs + Mon Nov 29 20:12:43 2004 | @ 34 days 11 hours 17 mins 35 secs +(3 rows) + diff --git a/contrib/btree_gist/expected/timestamptz.out b/contrib/btree_gist/expected/timestamptz.out new file mode 100644 index 0000000..75a15a4 --- /dev/null +++ b/contrib/btree_gist/expected/timestamptz.out @@ -0,0 +1,211 @@ +-- timestamptz check +CREATE TABLE timestamptztmp (a timestamptz); +\copy timestamptztmp from 'data/timestamptz.data' +SET enable_seqscan=on; +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+3'; + count +------- + 391 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+3'; + count +------- + 392 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+3'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+3'; + count +------- + 158 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+3'; + count +------- + 157 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+2'; + count +------- + 391 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+2'; + count +------- + 391 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+2'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+2'; + count +------- + 158 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+2'; + count +------- + 158 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+4'; + count +------- + 392 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+4'; + count +------- + 392 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+4'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+4'; + count +------- + 157 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+4'; + count +------- + 157 +(1 row) + +SELECT a, a <-> '2018-12-18 10:59:54 GMT+2' FROM timestamptztmp ORDER BY a <-> '2018-12-18 10:59:54 GMT+2' LIMIT 3; + a | ?column? +------------------------------+----------------------------------- + Tue Dec 18 05:59:54 2018 PST | @ 1 hour + Thu Jan 10 03:01:34 2019 PST | @ 22 days 22 hours 1 min 40 secs + Thu Jan 24 12:28:12 2019 PST | @ 37 days 7 hours 28 mins 18 secs +(3 rows) + +CREATE INDEX timestamptzidx ON timestamptztmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+3'::timestamptz; + count +------- + 391 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+3'::timestamptz; + count +------- + 392 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+3'::timestamptz; + count +------- + 1 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+3'::timestamptz; + count +------- + 158 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+3'::timestamptz; + count +------- + 157 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+2'::timestamptz; + count +------- + 391 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+2'::timestamptz; + count +------- + 391 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+2'::timestamptz; + count +------- + 0 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+2'::timestamptz; + count +------- + 158 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+2'::timestamptz; + count +------- + 158 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+4'::timestamptz; + count +------- + 392 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+4'::timestamptz; + count +------- + 392 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+4'::timestamptz; + count +------- + 0 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+4'::timestamptz; + count +------- + 157 +(1 row) + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+4'::timestamptz; + count +------- + 157 +(1 row) + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '2018-12-18 10:59:54 GMT+2' FROM timestamptztmp ORDER BY a <-> '2018-12-18 10:59:54 GMT+2' LIMIT 3; + QUERY PLAN +------------------------------------------------------------------------------------ + Limit + -> Index Only Scan using timestamptzidx on timestamptztmp + Order By: (a <-> 'Tue Dec 18 04:59:54 2018 PST'::timestamp with time zone) +(3 rows) + +SELECT a, a <-> '2018-12-18 10:59:54 GMT+2' FROM timestamptztmp ORDER BY a <-> '2018-12-18 10:59:54 GMT+2' LIMIT 3; + a | ?column? +------------------------------+----------------------------------- + Tue Dec 18 05:59:54 2018 PST | @ 1 hour + Thu Jan 10 03:01:34 2019 PST | @ 22 days 22 hours 1 min 40 secs + Thu Jan 24 12:28:12 2019 PST | @ 37 days 7 hours 28 mins 18 secs +(3 rows) + diff --git a/contrib/btree_gist/expected/timetz.out b/contrib/btree_gist/expected/timetz.out new file mode 100644 index 0000000..7f73e44 --- /dev/null +++ b/contrib/btree_gist/expected/timetz.out @@ -0,0 +1,43 @@ +-- timetz check +CREATE TABLE timetztmp (a timetz); +\copy timetztmp from 'data/timetz.data' +CREATE TABLE timetzcmp ( r_id int2, a int4, b int4 ); +SET enable_seqscan=on; +INSERT INTO timetzcmp (r_id,a) SELECT 1,count(*) FROM timetztmp WHERE a < '07:46:45 GMT+3'; +INSERT INTO timetzcmp (r_id,a) SELECT 2,count(*) FROM timetztmp WHERE a <= '07:46:45 GMT+3'; +INSERT INTO timetzcmp (r_id,a) SELECT 3,count(*) FROM timetztmp WHERE a = '07:46:45 GMT+3'; +INSERT INTO timetzcmp (r_id,a) SELECT 4,count(*) FROM timetztmp WHERE a >= '07:46:45 GMT+3'; +INSERT INTO timetzcmp (r_id,a) SELECT 5,count(*) FROM timetztmp WHERE a > '07:46:45 GMT+3'; +INSERT INTO timetzcmp (r_id,a) SELECT 11,count(*) FROM timetztmp WHERE a < '07:46:45 GMT+2'; +INSERT INTO timetzcmp (r_id,a) SELECT 12,count(*) FROM timetztmp WHERE a <= '07:46:45 GMT+2'; +INSERT INTO timetzcmp (r_id,a) SELECT 13,count(*) FROM timetztmp WHERE a = '07:46:45 GMT+2'; +INSERT INTO timetzcmp (r_id,a) SELECT 14,count(*) FROM timetztmp WHERE a >= '07:46:45 GMT+2'; +INSERT INTO timetzcmp (r_id,a) SELECT 15,count(*) FROM timetztmp WHERE a > '07:46:45 GMT+2'; +INSERT INTO timetzcmp (r_id,a) SELECT 21,count(*) FROM timetztmp WHERE a < '07:46:45 GMT+4'; +INSERT INTO timetzcmp (r_id,a) SELECT 22,count(*) FROM timetztmp WHERE a <= '07:46:45 GMT+4'; +INSERT INTO timetzcmp (r_id,a) SELECT 23,count(*) FROM timetztmp WHERE a = '07:46:45 GMT+4'; +INSERT INTO timetzcmp (r_id,a) SELECT 24,count(*) FROM timetztmp WHERE a >= '07:46:45 GMT+4'; +INSERT INTO timetzcmp (r_id,a) SELECT 25,count(*) FROM timetztmp WHERE a > '07:46:45 GMT+4'; +CREATE INDEX timetzidx ON timetztmp USING gist ( a ); +SET enable_seqscan=off; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a < '07:46:45 GMT+3'::timetz ) q WHERE r_id=1 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a <= '07:46:45 GMT+3'::timetz ) q WHERE r_id=2 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a = '07:46:45 GMT+3'::timetz ) q WHERE r_id=3 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a >= '07:46:45 GMT+3'::timetz ) q WHERE r_id=4 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a > '07:46:45 GMT+3'::timetz ) q WHERE r_id=5 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a < '07:46:45 GMT+2'::timetz ) q WHERE r_id=11 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a <= '07:46:45 GMT+2'::timetz ) q WHERE r_id=12 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a = '07:46:45 GMT+2'::timetz ) q WHERE r_id=13 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a >= '07:46:45 GMT+2'::timetz ) q WHERE r_id=14 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a > '07:46:45 GMT+2'::timetz ) q WHERE r_id=15 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a < '07:46:45 GMT+4'::timetz ) q WHERE r_id=21 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a <= '07:46:45 GMT+4'::timetz ) q WHERE r_id=22 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a = '07:46:45 GMT+4'::timetz ) q WHERE r_id=23 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a >= '07:46:45 GMT+4'::timetz ) q WHERE r_id=24 ; +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a > '07:46:45 GMT+4'::timetz ) q WHERE r_id=25 ; +SELECT count(*) FROM timetzcmp WHERE a=b; + count +------- + 15 +(1 row) + diff --git a/contrib/btree_gist/expected/uuid.out b/contrib/btree_gist/expected/uuid.out new file mode 100644 index 0000000..a34b024 --- /dev/null +++ b/contrib/btree_gist/expected/uuid.out @@ -0,0 +1,66 @@ +-- uuid check +CREATE TABLE uuidtmp (a uuid); +\copy uuidtmp from 'data/uuid.data' +SET enable_seqscan=on; +SELECT count(*) FROM uuidtmp WHERE a < '55e65ca2-4136-4a4b-ba78-cd3fe4678203'; + count +------- + 227 +(1 row) + +SELECT count(*) FROM uuidtmp WHERE a <= '55e65ca2-4136-4a4b-ba78-cd3fe4678203'; + count +------- + 228 +(1 row) + +SELECT count(*) FROM uuidtmp WHERE a = '55e65ca2-4136-4a4b-ba78-cd3fe4678203'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM uuidtmp WHERE a >= '55e65ca2-4136-4a4b-ba78-cd3fe4678203'; + count +------- + 376 +(1 row) + +SELECT count(*) FROM uuidtmp WHERE a > '55e65ca2-4136-4a4b-ba78-cd3fe4678203'; + count +------- + 375 +(1 row) + +CREATE INDEX uuididx ON uuidtmp USING gist ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM uuidtmp WHERE a < '55e65ca2-4136-4a4b-ba78-cd3fe4678203'::uuid; + count +------- + 227 +(1 row) + +SELECT count(*) FROM uuidtmp WHERE a <= '55e65ca2-4136-4a4b-ba78-cd3fe4678203'::uuid; + count +------- + 228 +(1 row) + +SELECT count(*) FROM uuidtmp WHERE a = '55e65ca2-4136-4a4b-ba78-cd3fe4678203'::uuid; + count +------- + 1 +(1 row) + +SELECT count(*) FROM uuidtmp WHERE a >= '55e65ca2-4136-4a4b-ba78-cd3fe4678203'::uuid; + count +------- + 376 +(1 row) + +SELECT count(*) FROM uuidtmp WHERE a > '55e65ca2-4136-4a4b-ba78-cd3fe4678203'::uuid; + count +------- + 375 +(1 row) + diff --git a/contrib/btree_gist/expected/varbit.out b/contrib/btree_gist/expected/varbit.out new file mode 100644 index 0000000..ede36bc --- /dev/null +++ b/contrib/btree_gist/expected/varbit.out @@ -0,0 +1,76 @@ +-- varbit check +CREATE TABLE varbittmp (a varbit); +\copy varbittmp from 'data/varbit.data' +SET enable_seqscan=on; +SELECT count(*) FROM varbittmp WHERE a < '1110100111010'; + count +------- + 549 +(1 row) + +SELECT count(*) FROM varbittmp WHERE a <= '1110100111010'; + count +------- + 550 +(1 row) + +SELECT count(*) FROM varbittmp WHERE a = '1110100111010'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM varbittmp WHERE a >= '1110100111010'; + count +------- + 51 +(1 row) + +SELECT count(*) FROM varbittmp WHERE a > '1110100111010'; + count +------- + 50 +(1 row) + +CREATE INDEX varbitidx ON varbittmp USING GIST ( a ); +SET enable_seqscan=off; +SELECT count(*) FROM varbittmp WHERE a < '1110100111010'::varbit; + count +------- + 549 +(1 row) + +SELECT count(*) FROM varbittmp WHERE a <= '1110100111010'::varbit; + count +------- + 550 +(1 row) + +SELECT count(*) FROM varbittmp WHERE a = '1110100111010'::varbit; + count +------- + 1 +(1 row) + +SELECT count(*) FROM varbittmp WHERE a >= '1110100111010'::varbit; + count +------- + 51 +(1 row) + +SELECT count(*) FROM varbittmp WHERE a > '1110100111010'::varbit; + count +------- + 50 +(1 row) + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT a FROM bittmp WHERE a BETWEEN '1000000' and '1000001'; + QUERY PLAN +--------------------------------------------------------------------- + Index Only Scan using bitidx on bittmp + Index Cond: ((a >= '1000000'::"bit") AND (a <= '1000001'::"bit")) +(2 rows) + diff --git a/contrib/btree_gist/expected/varchar.out b/contrib/btree_gist/expected/varchar.out new file mode 100644 index 0000000..d071d71 --- /dev/null +++ b/contrib/btree_gist/expected/varchar.out @@ -0,0 +1,66 @@ +-- char check +CREATE TABLE vchartmp (a varchar(32)); +\copy vchartmp from 'data/char.data' +SET enable_seqscan=on; +SELECT count(*) FROM vchartmp WHERE a < '31b0'::varchar(32); + count +------- + 587 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a <= '31b0'::varchar(32); + count +------- + 588 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a = '31b0'::varchar(32); + count +------- + 1 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a >= '31b0'::varchar(32); + count +------- + 401 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a > '31b0'::varchar(32); + count +------- + 400 +(1 row) + +CREATE INDEX vcharidx ON vchartmp USING GIST ( text(a) ); +SET enable_seqscan=off; +SELECT count(*) FROM vchartmp WHERE a < '31b0'::varchar(32); + count +------- + 587 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a <= '31b0'::varchar(32); + count +------- + 588 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a = '31b0'::varchar(32); + count +------- + 1 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a >= '31b0'::varchar(32); + count +------- + 401 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a > '31b0'::varchar(32); + count +------- + 400 +(1 row) + diff --git a/contrib/btree_gist/expected/varchar_1.out b/contrib/btree_gist/expected/varchar_1.out new file mode 100644 index 0000000..e4099d2 --- /dev/null +++ b/contrib/btree_gist/expected/varchar_1.out @@ -0,0 +1,66 @@ +-- char check +CREATE TABLE vchartmp (a varchar(32)); +\copy vchartmp from 'data/char.data' +SET enable_seqscan=on; +SELECT count(*) FROM vchartmp WHERE a < '31b0'::varchar(32); + count +------- + 773 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a <= '31b0'::varchar(32); + count +------- + 774 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a = '31b0'::varchar(32); + count +------- + 1 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a >= '31b0'::varchar(32); + count +------- + 215 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a > '31b0'::varchar(32); + count +------- + 214 +(1 row) + +CREATE INDEX vcharidx ON vchartmp USING GIST ( text(a) ); +SET enable_seqscan=off; +SELECT count(*) FROM vchartmp WHERE a < '31b0'::varchar(32); + count +------- + 773 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a <= '31b0'::varchar(32); + count +------- + 774 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a = '31b0'::varchar(32); + count +------- + 1 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a >= '31b0'::varchar(32); + count +------- + 215 +(1 row) + +SELECT count(*) FROM vchartmp WHERE a > '31b0'::varchar(32); + count +------- + 214 +(1 row) + diff --git a/contrib/btree_gist/meson.build b/contrib/btree_gist/meson.build new file mode 100644 index 0000000..5811026 --- /dev/null +++ b/contrib/btree_gist/meson.build @@ -0,0 +1,93 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +btree_gist_sources = files( + 'btree_bit.c', + 'btree_bool.c', + 'btree_bytea.c', + 'btree_cash.c', + 'btree_date.c', + 'btree_enum.c', + 'btree_float4.c', + 'btree_float8.c', + 'btree_gist.c', + 'btree_inet.c', + 'btree_int2.c', + 'btree_int4.c', + 'btree_int8.c', + 'btree_interval.c', + 'btree_macaddr.c', + 'btree_macaddr8.c', + 'btree_numeric.c', + 'btree_oid.c', + 'btree_text.c', + 'btree_time.c', + 'btree_ts.c', + 'btree_utils_num.c', + 'btree_utils_var.c', + 'btree_uuid.c', +) + +if host_system == 'windows' + btree_gist_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'btree_gist', + '--FILEDESC', 'btree_gist - B-tree equivalent GiST operator classes',]) +endif + +btree_gist = shared_module('btree_gist', + btree_gist_sources, + c_pch: pch_postgres_h, + kwargs: contrib_mod_args, +) +contrib_targets += btree_gist + +install_data( + 'btree_gist.control', + 'btree_gist--1.0--1.1.sql', + 'btree_gist--1.1--1.2.sql', + 'btree_gist--1.2.sql', + 'btree_gist--1.2--1.3.sql', + 'btree_gist--1.3--1.4.sql', + 'btree_gist--1.4--1.5.sql', + 'btree_gist--1.5--1.6.sql', + 'btree_gist--1.6--1.7.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'btree_gist', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'init', + 'int2', + 'int4', + 'int8', + 'float4', + 'float8', + 'cash', + 'oid', + 'timestamp', + 'timestamptz', + 'time', + 'timetz', + 'date', + 'interval', + 'macaddr', + 'macaddr8', + 'inet', + 'cidr', + 'text', + 'varchar', + 'char', + 'bytea', + 'bit', + 'varbit', + 'numeric', + 'uuid', + 'not_equal', + 'enum', + 'bool', + ], + }, +} diff --git a/contrib/btree_gist/sql/bit.sql b/contrib/btree_gist/sql/bit.sql new file mode 100644 index 0000000..a733042 --- /dev/null +++ b/contrib/btree_gist/sql/bit.sql @@ -0,0 +1,36 @@ +-- bit check + +CREATE TABLE bittmp (a bit(33)); + +\copy bittmp from 'data/bit.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM bittmp WHERE a < '011011000100010111011000110000100'; + +SELECT count(*) FROM bittmp WHERE a <= '011011000100010111011000110000100'; + +SELECT count(*) FROM bittmp WHERE a = '011011000100010111011000110000100'; + +SELECT count(*) FROM bittmp WHERE a >= '011011000100010111011000110000100'; + +SELECT count(*) FROM bittmp WHERE a > '011011000100010111011000110000100'; + +CREATE INDEX bitidx ON bittmp USING GIST ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM bittmp WHERE a < '011011000100010111011000110000100'; + +SELECT count(*) FROM bittmp WHERE a <= '011011000100010111011000110000100'; + +SELECT count(*) FROM bittmp WHERE a = '011011000100010111011000110000100'; + +SELECT count(*) FROM bittmp WHERE a >= '011011000100010111011000110000100'; + +SELECT count(*) FROM bittmp WHERE a > '011011000100010111011000110000100'; + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT a FROM bittmp WHERE a BETWEEN '1000000' and '1000001'; diff --git a/contrib/btree_gist/sql/bool.sql b/contrib/btree_gist/sql/bool.sql new file mode 100644 index 0000000..4c2a539 --- /dev/null +++ b/contrib/btree_gist/sql/bool.sql @@ -0,0 +1,42 @@ +-- bool check + +CREATE TABLE booltmp (a bool); + +INSERT INTO booltmp VALUES (false), (true); + +SET enable_seqscan=on; + +SELECT count(*) FROM booltmp WHERE a < true; + +SELECT count(*) FROM booltmp WHERE a <= true; + +SELECT count(*) FROM booltmp WHERE a = true; + +SELECT count(*) FROM booltmp WHERE a >= true; + +SELECT count(*) FROM booltmp WHERE a > true; + +CREATE INDEX boolidx ON booltmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM booltmp WHERE a < true; + +SELECT count(*) FROM booltmp WHERE a <= true; + +SELECT count(*) FROM booltmp WHERE a = true; + +SELECT count(*) FROM booltmp WHERE a >= true; + +SELECT count(*) FROM booltmp WHERE a > true; + +-- Test index-only scans +SET enable_bitmapscan=off; + +EXPLAIN (COSTS OFF) +SELECT * FROM booltmp WHERE a; +SELECT * FROM booltmp WHERE a; + +EXPLAIN (COSTS OFF) +SELECT * FROM booltmp WHERE NOT a; +SELECT * FROM booltmp WHERE NOT a; diff --git a/contrib/btree_gist/sql/bytea.sql b/contrib/btree_gist/sql/bytea.sql new file mode 100644 index 0000000..6885f5e --- /dev/null +++ b/contrib/btree_gist/sql/bytea.sql @@ -0,0 +1,40 @@ +-- bytea check + +CREATE TABLE byteatmp (a bytea); + +\copy byteatmp from 'data/text.data' +\copy byteatmp from 'data/char.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM byteatmp WHERE a < '31b0'; + +SELECT count(*) FROM byteatmp WHERE a <= '31b0'; + +SELECT count(*) FROM byteatmp WHERE a = '31b0'; + +SELECT count(*) FROM byteatmp WHERE a >= '31b0'; + +SELECT count(*) FROM byteatmp WHERE a > '31b0'; + +CREATE INDEX byteaidx ON byteatmp USING GIST ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM byteatmp WHERE a < '31b0'::bytea; + +SELECT count(*) FROM byteatmp WHERE a <= '31b0'::bytea; + +SELECT count(*) FROM byteatmp WHERE a = '31b0'::bytea; + +SELECT count(*) FROM byteatmp WHERE a >= '31b0'::bytea; + +SELECT count(*) FROM byteatmp WHERE a > '31b0'::bytea; + +SELECT count(*) FROM byteatmp WHERE a = '2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809'::bytea; + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT a FROM byteatmp where a > 'ffa'::bytea; +SELECT a FROM byteatmp where a > 'ffa'::bytea; diff --git a/contrib/btree_gist/sql/cash.sql b/contrib/btree_gist/sql/cash.sql new file mode 100644 index 0000000..4526cc4 --- /dev/null +++ b/contrib/btree_gist/sql/cash.sql @@ -0,0 +1,37 @@ +-- money check + +CREATE TABLE moneytmp (a money); + +\copy moneytmp from 'data/cash.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM moneytmp WHERE a < '22649.64'; + +SELECT count(*) FROM moneytmp WHERE a <= '22649.64'; + +SELECT count(*) FROM moneytmp WHERE a = '22649.64'; + +SELECT count(*) FROM moneytmp WHERE a >= '22649.64'; + +SELECT count(*) FROM moneytmp WHERE a > '22649.64'; + +SELECT a, a <-> '21472.79' FROM moneytmp ORDER BY a <-> '21472.79' LIMIT 3; + +CREATE INDEX moneyidx ON moneytmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM moneytmp WHERE a < '22649.64'::money; + +SELECT count(*) FROM moneytmp WHERE a <= '22649.64'::money; + +SELECT count(*) FROM moneytmp WHERE a = '22649.64'::money; + +SELECT count(*) FROM moneytmp WHERE a >= '22649.64'::money; + +SELECT count(*) FROM moneytmp WHERE a > '22649.64'::money; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '21472.79' FROM moneytmp ORDER BY a <-> '21472.79' LIMIT 3; +SELECT a, a <-> '21472.79' FROM moneytmp ORDER BY a <-> '21472.79' LIMIT 3; diff --git a/contrib/btree_gist/sql/char.sql b/contrib/btree_gist/sql/char.sql new file mode 100644 index 0000000..f6eb52e --- /dev/null +++ b/contrib/btree_gist/sql/char.sql @@ -0,0 +1,37 @@ +-- char check + +CREATE TABLE chartmp (a char(32)); + +\copy chartmp from 'data/char.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM chartmp WHERE a < '31b0'::char(32); + +SELECT count(*) FROM chartmp WHERE a <= '31b0'::char(32); + +SELECT count(*) FROM chartmp WHERE a = '31b0'::char(32); + +SELECT count(*) FROM chartmp WHERE a >= '31b0'::char(32); + +SELECT count(*) FROM chartmp WHERE a > '31b0'::char(32); + +CREATE INDEX charidx ON chartmp USING GIST ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM chartmp WHERE a < '31b0'::char(32); + +SELECT count(*) FROM chartmp WHERE a <= '31b0'::char(32); + +SELECT count(*) FROM chartmp WHERE a = '31b0'::char(32); + +SELECT count(*) FROM chartmp WHERE a >= '31b0'::char(32); + +SELECT count(*) FROM chartmp WHERE a > '31b0'::char(32); + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM chartmp WHERE a BETWEEN '31a' AND '31c'; +SELECT * FROM chartmp WHERE a BETWEEN '31a' AND '31c'; diff --git a/contrib/btree_gist/sql/cidr.sql b/contrib/btree_gist/sql/cidr.sql new file mode 100644 index 0000000..9bd7718 --- /dev/null +++ b/contrib/btree_gist/sql/cidr.sql @@ -0,0 +1,30 @@ +-- cidr check + +CREATE TABLE cidrtmp AS + SELECT cidr(a) AS a FROM inettmp ; + +SET enable_seqscan=on; + +SELECT count(*) FROM cidrtmp WHERE a < '121.111.63.82'; + +SELECT count(*) FROM cidrtmp WHERE a <= '121.111.63.82'; + +SELECT count(*) FROM cidrtmp WHERE a = '121.111.63.82'; + +SELECT count(*) FROM cidrtmp WHERE a >= '121.111.63.82'; + +SELECT count(*) FROM cidrtmp WHERE a > '121.111.63.82'; + +CREATE INDEX cidridx ON cidrtmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM cidrtmp WHERE a < '121.111.63.82'::cidr; + +SELECT count(*) FROM cidrtmp WHERE a <= '121.111.63.82'::cidr; + +SELECT count(*) FROM cidrtmp WHERE a = '121.111.63.82'::cidr; + +SELECT count(*) FROM cidrtmp WHERE a >= '121.111.63.82'::cidr; + +SELECT count(*) FROM cidrtmp WHERE a > '121.111.63.82'::cidr; diff --git a/contrib/btree_gist/sql/date.sql b/contrib/btree_gist/sql/date.sql new file mode 100644 index 0000000..f969ef0 --- /dev/null +++ b/contrib/btree_gist/sql/date.sql @@ -0,0 +1,37 @@ +-- date check + +CREATE TABLE datetmp (a date); + +\copy datetmp from 'data/date.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM datetmp WHERE a < '2001-02-13'; + +SELECT count(*) FROM datetmp WHERE a <= '2001-02-13'; + +SELECT count(*) FROM datetmp WHERE a = '2001-02-13'; + +SELECT count(*) FROM datetmp WHERE a >= '2001-02-13'; + +SELECT count(*) FROM datetmp WHERE a > '2001-02-13'; + +SELECT a, a <-> '2001-02-13' FROM datetmp ORDER BY a <-> '2001-02-13' LIMIT 3; + +CREATE INDEX dateidx ON datetmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM datetmp WHERE a < '2001-02-13'::date; + +SELECT count(*) FROM datetmp WHERE a <= '2001-02-13'::date; + +SELECT count(*) FROM datetmp WHERE a = '2001-02-13'::date; + +SELECT count(*) FROM datetmp WHERE a >= '2001-02-13'::date; + +SELECT count(*) FROM datetmp WHERE a > '2001-02-13'::date; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '2001-02-13' FROM datetmp ORDER BY a <-> '2001-02-13' LIMIT 3; +SELECT a, a <-> '2001-02-13' FROM datetmp ORDER BY a <-> '2001-02-13' LIMIT 3; diff --git a/contrib/btree_gist/sql/enum.sql b/contrib/btree_gist/sql/enum.sql new file mode 100644 index 0000000..476211e --- /dev/null +++ b/contrib/btree_gist/sql/enum.sql @@ -0,0 +1,38 @@ +-- enum check + +create type rainbow as enum ('r','o','y','g','b','i','v'); + +CREATE TABLE enumtmp (a rainbow); + +\copy enumtmp from 'data/enum.data' + +SET enable_seqscan=on; + +select a, count(*) from enumtmp group by a order by 1; + +SELECT count(*) FROM enumtmp WHERE a < 'g'::rainbow; + +SELECT count(*) FROM enumtmp WHERE a <= 'g'::rainbow; + +SELECT count(*) FROM enumtmp WHERE a = 'g'::rainbow; + +SELECT count(*) FROM enumtmp WHERE a >= 'g'::rainbow; + +SELECT count(*) FROM enumtmp WHERE a > 'g'::rainbow; + +CREATE INDEX enumidx ON enumtmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM enumtmp WHERE a < 'g'::rainbow; + +SELECT count(*) FROM enumtmp WHERE a <= 'g'::rainbow; + +SELECT count(*) FROM enumtmp WHERE a = 'g'::rainbow; + +SELECT count(*) FROM enumtmp WHERE a >= 'g'::rainbow; + +SELECT count(*) FROM enumtmp WHERE a > 'g'::rainbow; + +EXPLAIN (COSTS OFF) +SELECT count(*) FROM enumtmp WHERE a >= 'g'::rainbow; diff --git a/contrib/btree_gist/sql/float4.sql b/contrib/btree_gist/sql/float4.sql new file mode 100644 index 0000000..3da1ce9 --- /dev/null +++ b/contrib/btree_gist/sql/float4.sql @@ -0,0 +1,37 @@ +-- float4 check + +CREATE TABLE float4tmp (a float4); + +\copy float4tmp from 'data/float4.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM float4tmp WHERE a < -179.0; + +SELECT count(*) FROM float4tmp WHERE a <= -179.0; + +SELECT count(*) FROM float4tmp WHERE a = -179.0; + +SELECT count(*) FROM float4tmp WHERE a >= -179.0; + +SELECT count(*) FROM float4tmp WHERE a > -179.0; + +SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3; + +CREATE INDEX float4idx ON float4tmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM float4tmp WHERE a < -179.0::float4; + +SELECT count(*) FROM float4tmp WHERE a <= -179.0::float4; + +SELECT count(*) FROM float4tmp WHERE a = -179.0::float4; + +SELECT count(*) FROM float4tmp WHERE a >= -179.0::float4; + +SELECT count(*) FROM float4tmp WHERE a > -179.0::float4; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3; +SELECT a, a <-> '-179.0' FROM float4tmp ORDER BY a <-> '-179.0' LIMIT 3; diff --git a/contrib/btree_gist/sql/float8.sql b/contrib/btree_gist/sql/float8.sql new file mode 100644 index 0000000..e1e819b --- /dev/null +++ b/contrib/btree_gist/sql/float8.sql @@ -0,0 +1,37 @@ +-- float8 check + +CREATE TABLE float8tmp (a float8); + +\copy float8tmp from 'data/float8.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM float8tmp WHERE a < -1890.0; + +SELECT count(*) FROM float8tmp WHERE a <= -1890.0; + +SELECT count(*) FROM float8tmp WHERE a = -1890.0; + +SELECT count(*) FROM float8tmp WHERE a >= -1890.0; + +SELECT count(*) FROM float8tmp WHERE a > -1890.0; + +SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3; + +CREATE INDEX float8idx ON float8tmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM float8tmp WHERE a < -1890.0::float8; + +SELECT count(*) FROM float8tmp WHERE a <= -1890.0::float8; + +SELECT count(*) FROM float8tmp WHERE a = -1890.0::float8; + +SELECT count(*) FROM float8tmp WHERE a >= -1890.0::float8; + +SELECT count(*) FROM float8tmp WHERE a > -1890.0::float8; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3; +SELECT a, a <-> '-1890.0' FROM float8tmp ORDER BY a <-> '-1890.0' LIMIT 3; diff --git a/contrib/btree_gist/sql/inet.sql b/contrib/btree_gist/sql/inet.sql new file mode 100644 index 0000000..249e808 --- /dev/null +++ b/contrib/btree_gist/sql/inet.sql @@ -0,0 +1,49 @@ +-- inet check + +CREATE TABLE inettmp (a inet); + +\copy inettmp from 'data/inet.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM inettmp WHERE a < '89.225.196.191'; + +SELECT count(*) FROM inettmp WHERE a <= '89.225.196.191'; + +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'; + +SELECT count(*) FROM inettmp WHERE a >= '89.225.196.191'; + +SELECT count(*) FROM inettmp WHERE a > '89.225.196.191'; + +CREATE INDEX inetidx ON inettmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM inettmp WHERE a < '89.225.196.191'::inet; + +SELECT count(*) FROM inettmp WHERE a <= '89.225.196.191'::inet; + +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'::inet; + +SELECT count(*) FROM inettmp WHERE a >= '89.225.196.191'::inet; + +SELECT count(*) FROM inettmp WHERE a > '89.225.196.191'::inet; + +VACUUM ANALYZE inettmp; + +-- gist_inet_ops lacks a fetch function, so this should not be index-only scan +EXPLAIN (COSTS OFF) +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'::inet; + +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'::inet; + +DROP INDEX inetidx; + +CREATE INDEX ON inettmp USING gist (a gist_inet_ops, a inet_ops); + +-- this can be an index-only scan, as long as the planner uses the right column +EXPLAIN (COSTS OFF) +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'::inet; + +SELECT count(*) FROM inettmp WHERE a = '89.225.196.191'::inet; diff --git a/contrib/btree_gist/sql/init.sql b/contrib/btree_gist/sql/init.sql new file mode 100644 index 0000000..a6d2cff --- /dev/null +++ b/contrib/btree_gist/sql/init.sql @@ -0,0 +1,6 @@ +CREATE EXTENSION btree_gist; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); diff --git a/contrib/btree_gist/sql/int2.sql b/contrib/btree_gist/sql/int2.sql new file mode 100644 index 0000000..9885187 --- /dev/null +++ b/contrib/btree_gist/sql/int2.sql @@ -0,0 +1,37 @@ +-- int2 check + +CREATE TABLE int2tmp (a int2); + +\copy int2tmp from 'data/int2.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM int2tmp WHERE a < 237; + +SELECT count(*) FROM int2tmp WHERE a <= 237; + +SELECT count(*) FROM int2tmp WHERE a = 237; + +SELECT count(*) FROM int2tmp WHERE a >= 237; + +SELECT count(*) FROM int2tmp WHERE a > 237; + +SELECT a, a <-> '237' FROM int2tmp ORDER BY a <-> '237' LIMIT 3; + +CREATE INDEX int2idx ON int2tmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM int2tmp WHERE a < 237::int2; + +SELECT count(*) FROM int2tmp WHERE a <= 237::int2; + +SELECT count(*) FROM int2tmp WHERE a = 237::int2; + +SELECT count(*) FROM int2tmp WHERE a >= 237::int2; + +SELECT count(*) FROM int2tmp WHERE a > 237::int2; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '237' FROM int2tmp ORDER BY a <-> '237' LIMIT 3; +SELECT a, a <-> '237' FROM int2tmp ORDER BY a <-> '237' LIMIT 3; diff --git a/contrib/btree_gist/sql/int4.sql b/contrib/btree_gist/sql/int4.sql new file mode 100644 index 0000000..659ab5e --- /dev/null +++ b/contrib/btree_gist/sql/int4.sql @@ -0,0 +1,37 @@ +-- int4 check + +CREATE TABLE int4tmp (a int4); + +\copy int4tmp from 'data/int2.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM int4tmp WHERE a < 237; + +SELECT count(*) FROM int4tmp WHERE a <= 237; + +SELECT count(*) FROM int4tmp WHERE a = 237; + +SELECT count(*) FROM int4tmp WHERE a >= 237; + +SELECT count(*) FROM int4tmp WHERE a > 237; + +SELECT a, a <-> '237' FROM int4tmp ORDER BY a <-> '237' LIMIT 3; + +CREATE INDEX int4idx ON int4tmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM int4tmp WHERE a < 237::int4; + +SELECT count(*) FROM int4tmp WHERE a <= 237::int4; + +SELECT count(*) FROM int4tmp WHERE a = 237::int4; + +SELECT count(*) FROM int4tmp WHERE a >= 237::int4; + +SELECT count(*) FROM int4tmp WHERE a > 237::int4; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '237' FROM int4tmp ORDER BY a <-> '237' LIMIT 3; +SELECT a, a <-> '237' FROM int4tmp ORDER BY a <-> '237' LIMIT 3; diff --git a/contrib/btree_gist/sql/int8.sql b/contrib/btree_gist/sql/int8.sql new file mode 100644 index 0000000..51e55e9 --- /dev/null +++ b/contrib/btree_gist/sql/int8.sql @@ -0,0 +1,37 @@ +-- int8 check + +CREATE TABLE int8tmp (a int8); + +\copy int8tmp from 'data/int8.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM int8tmp WHERE a < 464571291354841; + +SELECT count(*) FROM int8tmp WHERE a <= 464571291354841; + +SELECT count(*) FROM int8tmp WHERE a = 464571291354841; + +SELECT count(*) FROM int8tmp WHERE a >= 464571291354841; + +SELECT count(*) FROM int8tmp WHERE a > 464571291354841; + +SELECT a, a <-> '464571291354841' FROM int8tmp ORDER BY a <-> '464571291354841' LIMIT 3; + +CREATE INDEX int8idx ON int8tmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM int8tmp WHERE a < 464571291354841::int8; + +SELECT count(*) FROM int8tmp WHERE a <= 464571291354841::int8; + +SELECT count(*) FROM int8tmp WHERE a = 464571291354841::int8; + +SELECT count(*) FROM int8tmp WHERE a >= 464571291354841::int8; + +SELECT count(*) FROM int8tmp WHERE a > 464571291354841::int8; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '464571291354841' FROM int8tmp ORDER BY a <-> '464571291354841' LIMIT 3; +SELECT a, a <-> '464571291354841' FROM int8tmp ORDER BY a <-> '464571291354841' LIMIT 3; diff --git a/contrib/btree_gist/sql/interval.sql b/contrib/btree_gist/sql/interval.sql new file mode 100644 index 0000000..346d6ad --- /dev/null +++ b/contrib/btree_gist/sql/interval.sql @@ -0,0 +1,43 @@ +-- interval check + +CREATE TABLE intervaltmp (a interval); + +\copy intervaltmp from 'data/interval.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM intervaltmp WHERE a < '199 days 21:21:23'; + +SELECT count(*) FROM intervaltmp WHERE a <= '199 days 21:21:23'; + +SELECT count(*) FROM intervaltmp WHERE a = '199 days 21:21:23'; + +SELECT count(*) FROM intervaltmp WHERE a >= '199 days 21:21:23'; + +SELECT count(*) FROM intervaltmp WHERE a > '199 days 21:21:23'; + +SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; + +CREATE INDEX intervalidx ON intervaltmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM intervaltmp WHERE a < '199 days 21:21:23'::interval; + +SELECT count(*) FROM intervaltmp WHERE a <= '199 days 21:21:23'::interval; + +SELECT count(*) FROM intervaltmp WHERE a = '199 days 21:21:23'::interval; + +SELECT count(*) FROM intervaltmp WHERE a >= '199 days 21:21:23'::interval; + +SELECT count(*) FROM intervaltmp WHERE a > '199 days 21:21:23'::interval; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; +SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; + +SET enable_indexonlyscan=off; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; +SELECT a, a <-> '199 days 21:21:23' FROM intervaltmp ORDER BY a <-> '199 days 21:21:23' LIMIT 3; diff --git a/contrib/btree_gist/sql/macaddr.sql b/contrib/btree_gist/sql/macaddr.sql new file mode 100644 index 0000000..85c271f --- /dev/null +++ b/contrib/btree_gist/sql/macaddr.sql @@ -0,0 +1,37 @@ +-- macaddr check + +CREATE TABLE macaddrtmp (a macaddr); + +\copy macaddrtmp from 'data/macaddr.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM macaddrtmp WHERE a < '22:00:5c:e5:9b:0d'; + +SELECT count(*) FROM macaddrtmp WHERE a <= '22:00:5c:e5:9b:0d'; + +SELECT count(*) FROM macaddrtmp WHERE a = '22:00:5c:e5:9b:0d'; + +SELECT count(*) FROM macaddrtmp WHERE a >= '22:00:5c:e5:9b:0d'; + +SELECT count(*) FROM macaddrtmp WHERE a > '22:00:5c:e5:9b:0d'; + +CREATE INDEX macaddridx ON macaddrtmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM macaddrtmp WHERE a < '22:00:5c:e5:9b:0d'::macaddr; + +SELECT count(*) FROM macaddrtmp WHERE a <= '22:00:5c:e5:9b:0d'::macaddr; + +SELECT count(*) FROM macaddrtmp WHERE a = '22:00:5c:e5:9b:0d'::macaddr; + +SELECT count(*) FROM macaddrtmp WHERE a >= '22:00:5c:e5:9b:0d'::macaddr; + +SELECT count(*) FROM macaddrtmp WHERE a > '22:00:5c:e5:9b:0d'::macaddr; + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM macaddrtmp WHERE a < '02:03:04:05:06:07'::macaddr; +SELECT * FROM macaddrtmp WHERE a < '02:03:04:05:06:07'::macaddr; diff --git a/contrib/btree_gist/sql/macaddr8.sql b/contrib/btree_gist/sql/macaddr8.sql new file mode 100644 index 0000000..61e7d7a --- /dev/null +++ b/contrib/btree_gist/sql/macaddr8.sql @@ -0,0 +1,37 @@ +-- macaddr check + +CREATE TABLE macaddr8tmp (a macaddr8); + +\copy macaddr8tmp from 'data/macaddr.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM macaddr8tmp WHERE a < '22:00:5c:e5:9b:0d'; + +SELECT count(*) FROM macaddr8tmp WHERE a <= '22:00:5c:e5:9b:0d'; + +SELECT count(*) FROM macaddr8tmp WHERE a = '22:00:5c:e5:9b:0d'; + +SELECT count(*) FROM macaddr8tmp WHERE a >= '22:00:5c:e5:9b:0d'; + +SELECT count(*) FROM macaddr8tmp WHERE a > '22:00:5c:e5:9b:0d'; + +CREATE INDEX macaddr8idx ON macaddr8tmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM macaddr8tmp WHERE a < '22:00:5c:e5:9b:0d'::macaddr8; + +SELECT count(*) FROM macaddr8tmp WHERE a <= '22:00:5c:e5:9b:0d'::macaddr8; + +SELECT count(*) FROM macaddr8tmp WHERE a = '22:00:5c:e5:9b:0d'::macaddr8; + +SELECT count(*) FROM macaddr8tmp WHERE a >= '22:00:5c:e5:9b:0d'::macaddr8; + +SELECT count(*) FROM macaddr8tmp WHERE a > '22:00:5c:e5:9b:0d'::macaddr8; + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM macaddr8tmp WHERE a < '02:03:04:05:06:07'::macaddr8; +SELECT * FROM macaddr8tmp WHERE a < '02:03:04:05:06:07'::macaddr8; diff --git a/contrib/btree_gist/sql/not_equal.sql b/contrib/btree_gist/sql/not_equal.sql new file mode 100644 index 0000000..6dfac5d --- /dev/null +++ b/contrib/btree_gist/sql/not_equal.sql @@ -0,0 +1,36 @@ + +SET enable_seqscan to false; + +-- test search for "not equals" + +CREATE TABLE test_ne ( + a TIMESTAMP, + b NUMERIC +); +CREATE INDEX test_ne_idx ON test_ne USING gist (a, b); + +INSERT INTO test_ne SELECT '2009-01-01', 10.7 FROM generate_series(1,1000); +INSERT INTO test_ne VALUES('2007-02-03', -91.3); +INSERT INTO test_ne VALUES('2011-09-01', 43.7); +INSERT INTO test_ne SELECT '2009-01-01', 10.7 FROM generate_series(1,1000); + +SET enable_indexscan to false; + +EXPLAIN (COSTS OFF) SELECT * FROM test_ne WHERE a <> '2009-01-01' AND b <> 10.7; + +SELECT * FROM test_ne WHERE a <> '2009-01-01' AND b <> 10.7; + +RESET enable_indexscan; + +-- test search for "not equals" using an exclusion constraint + +CREATE TABLE zoo ( + cage INTEGER, + animal TEXT, + EXCLUDE USING gist (cage WITH =, animal WITH <>) +); + +INSERT INTO zoo VALUES(123, 'zebra'); +INSERT INTO zoo VALUES(123, 'zebra'); +INSERT INTO zoo VALUES(123, 'lion'); +INSERT INTO zoo VALUES(124, 'lion'); diff --git a/contrib/btree_gist/sql/numeric.sql b/contrib/btree_gist/sql/numeric.sql new file mode 100644 index 0000000..dbb2f2f --- /dev/null +++ b/contrib/btree_gist/sql/numeric.sql @@ -0,0 +1,83 @@ +-- numeric check + +CREATE TABLE numerictmp (a numeric); + +\copy numerictmp from 'data/int8.data' +\copy numerictmp from 'data/numeric.data' +\copy numerictmp from 'data/float8.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM numerictmp WHERE a < -1890.0; + +SELECT count(*) FROM numerictmp WHERE a <= -1890.0; + +SELECT count(*) FROM numerictmp WHERE a = -1890.0; + +SELECT count(*) FROM numerictmp WHERE a >= -1890.0; + +SELECT count(*) FROM numerictmp WHERE a > -1890.0; + + +SELECT count(*) FROM numerictmp WHERE a < 'NaN' ; + +SELECT count(*) FROM numerictmp WHERE a <= 'NaN' ; + +SELECT count(*) FROM numerictmp WHERE a = 'NaN' ; + +SELECT count(*) FROM numerictmp WHERE a >= 'NaN' ; + +SELECT count(*) FROM numerictmp WHERE a > 'NaN' ; + +SELECT count(*) FROM numerictmp WHERE a < 0 ; + +SELECT count(*) FROM numerictmp WHERE a <= 0 ; + +SELECT count(*) FROM numerictmp WHERE a = 0 ; + +SELECT count(*) FROM numerictmp WHERE a >= 0 ; + +SELECT count(*) FROM numerictmp WHERE a > 0 ; + + +CREATE INDEX numericidx ON numerictmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM numerictmp WHERE a < -1890.0; + +SELECT count(*) FROM numerictmp WHERE a <= -1890.0; + +SELECT count(*) FROM numerictmp WHERE a = -1890.0; + +SELECT count(*) FROM numerictmp WHERE a >= -1890.0; + +SELECT count(*) FROM numerictmp WHERE a > -1890.0; + + +SELECT count(*) FROM numerictmp WHERE a < 'NaN' ; + +SELECT count(*) FROM numerictmp WHERE a <= 'NaN' ; + +SELECT count(*) FROM numerictmp WHERE a = 'NaN' ; + +SELECT count(*) FROM numerictmp WHERE a >= 'NaN' ; + +SELECT count(*) FROM numerictmp WHERE a > 'NaN' ; + + +SELECT count(*) FROM numerictmp WHERE a < 0 ; + +SELECT count(*) FROM numerictmp WHERE a <= 0 ; + +SELECT count(*) FROM numerictmp WHERE a = 0 ; + +SELECT count(*) FROM numerictmp WHERE a >= 0 ; + +SELECT count(*) FROM numerictmp WHERE a > 0 ; + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM numerictmp WHERE a BETWEEN 1 AND 300 ORDER BY a; +SELECT * FROM numerictmp WHERE a BETWEEN 1 AND 300 ORDER BY a; diff --git a/contrib/btree_gist/sql/oid.sql b/contrib/btree_gist/sql/oid.sql new file mode 100644 index 0000000..c935823 --- /dev/null +++ b/contrib/btree_gist/sql/oid.sql @@ -0,0 +1,30 @@ +-- oid check + +SET enable_seqscan=on; + +CREATE TEMPORARY TABLE oidtmp (oid oid); +INSERT INTO oidtmp SELECT g.i::oid FROM generate_series(1, 1000) g(i); + +SELECT count(*) FROM oidtmp WHERE oid < 17; + +SELECT count(*) FROM oidtmp WHERE oid <= 17; + +SELECT count(*) FROM oidtmp WHERE oid = 17; + +SELECT count(*) FROM oidtmp WHERE oid >= 17; + +SELECT count(*) FROM oidtmp WHERE oid > 17; + +CREATE INDEX oididx ON oidtmp USING gist ( oid ); + +SET enable_seqscan=off; + +SELECT count(*) FROM oidtmp WHERE oid < 17; + +SELECT count(*) FROM oidtmp WHERE oid <= 17; + +SELECT count(*) FROM oidtmp WHERE oid = 17; + +SELECT count(*) FROM oidtmp WHERE oid >= 17; + +SELECT count(*) FROM oidtmp WHERE oid > 17; diff --git a/contrib/btree_gist/sql/text.sql b/contrib/btree_gist/sql/text.sql new file mode 100644 index 0000000..46597e7 --- /dev/null +++ b/contrib/btree_gist/sql/text.sql @@ -0,0 +1,40 @@ +-- text check + +CREATE TABLE texttmp (a text); + +\copy texttmp from 'data/text.data' +\copy texttmp from 'data/char.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM texttmp WHERE a < '31b0'; + +SELECT count(*) FROM texttmp WHERE a <= '31b0'; + +SELECT count(*) FROM texttmp WHERE a = '31b0'; + +SELECT count(*) FROM texttmp WHERE a >= '31b0'; + +SELECT count(*) FROM texttmp WHERE a > '31b0'; + +CREATE INDEX textidx ON texttmp USING GIST ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM texttmp WHERE a < '31b0'::text; + +SELECT count(*) FROM texttmp WHERE a <= '31b0'::text; + +SELECT count(*) FROM texttmp WHERE a = '31b0'::text; + +SELECT count(*) FROM texttmp WHERE a >= '31b0'::text; + +SELECT count(*) FROM texttmp WHERE a > '31b0'::text; + +SELECT count(*) FROM texttmp WHERE a = '2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809 2eb2c961c1cbf6 cf8d7b68cb9a2f36 7bbedb4ae7 06ec46c55611a466eb7e3edcc009ca6 e 5ed9cd0ea5a4e55d601027c56a 64cacf3a42afc 90e63000c34506993345355640 79bce 173bb7 c5 574ea7c921cb0f25 089d56d16dff24f336e4740 6870470f1f9afcb4f7c56c9f b97e117fc965 7013029 e48f6dd481 7d00e1e227beef84a9 904d4c34241f cb5c0f14 3a8a70 f51a73164e92052fbb53b4cc2f1fed 3c3fecaa0270175 2521ef03594 fa05756812648f450fb 13c2f b39a0729d6182e9 15b5ea204fe73 d8991afd72d21acd188df1 a29fff57ab897338 de549b3ed5a024534c007125c 2fcf3e5c3e3 7427b6daec5c3f 473 8 a5d9 840410976ac2eeab58e1ca8bf46c2b7 1db9cc85a336f1291ea19922 db808f3548cda91 2e379ce80af12bd7ed56d0338c a ea67a7c847f6620fc894f0ba10044 0e 52e97d975af7201d8 d95e6f08184d8ff 19762476fa 42f278f3534f3f2be0abaed71 f0aba11835e4e1d94 e8534cf677046eafb8f5f761865 ffbee273c7bb 2bb77f6e780 c77e81851c491 e a9f45d765b01a030d5d317 ff7345a22bc360 c87363ba121297b063e83 13ea32e9618d 40304f6c2a7e92c1c66ff4208e a781b4a21419abfdf5eb467e4d48908 8a65656e514b2b3ef8f86310aaf85 4 90b7b2862e3dbc8f0eef3dfc6075bfa eb94a1c a58abb5def4fa43840e6e2716 260e6eaebb 42415d712bf83944dcd1204e 305254fc3b849150b5 5bbd7f8471dcd3621 2ae0548115a250 0c1988e9 76f98bef45639b7 0d5a28f01dc b71 c046576faa4d49eff8 c1e8d01c 10c86c457ea050455a742da4f8 ea7676af85c71c7eeca635 6a07137227404d a4 7186172 8150f31c9a15401c f1bb9057a9938bfa 22b482be08f424ec4 21daea994293589 15bff393f6b17fef24786dd6f9 d5a2d 4b3b5dd9370543e b4a93b2ac4341945d06 d384447812e0 4e3c97e9b8f7 f7d4d644b2a1d373 5102c b9531f725674b28 1aa16e7e34285797c1439 51aa762ea14b40fb8876c887eea6 45a62d3d5d3e946250904697486591 b3f1a8 243524767bf846d 8 95 45a922872 dd2497eb1e3da8d513d2 7821db9e14d4f 24c4f085de60d7c0c6ea3fc6bc e4c9f8c68596d7d afd6c8cb0f2516b87f24bbd8 61d2e457c70949 d2d362cdc657 3605f9d27fd6d72 32de91d66fe5bf537530 859e1a08b65 9b5a55f 4116cda9fddeb843964002 e81f3b2c0ca566ad3dbbc6e234 0d3b1d54 10c440be5c0bca95 7dad841f a61f041967972e805ccfee55c deee9cc16e92ab197 7627554073c1f56b9e 21bebcbfd2e2282f84 7b121a83eeb91db8bda81ba88c634b46394 59885ebc737617addaaf0cb809'::text; + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT * FROM texttmp WHERE a BETWEEN '31a' AND '31c'; +SELECT * FROM texttmp WHERE a BETWEEN '31a' AND '31c'; diff --git a/contrib/btree_gist/sql/time.sql b/contrib/btree_gist/sql/time.sql new file mode 100644 index 0000000..6104e7f --- /dev/null +++ b/contrib/btree_gist/sql/time.sql @@ -0,0 +1,37 @@ +-- time check + +CREATE TABLE timetmp (a time); + +\copy timetmp from 'data/time.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM timetmp WHERE a < '10:57:11'; + +SELECT count(*) FROM timetmp WHERE a <= '10:57:11'; + +SELECT count(*) FROM timetmp WHERE a = '10:57:11'; + +SELECT count(*) FROM timetmp WHERE a >= '10:57:11'; + +SELECT count(*) FROM timetmp WHERE a > '10:57:11'; + +SELECT a, a <-> '10:57:11' FROM timetmp ORDER BY a <-> '10:57:11' LIMIT 3; + +CREATE INDEX timeidx ON timetmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM timetmp WHERE a < '10:57:11'::time; + +SELECT count(*) FROM timetmp WHERE a <= '10:57:11'::time; + +SELECT count(*) FROM timetmp WHERE a = '10:57:11'::time; + +SELECT count(*) FROM timetmp WHERE a >= '10:57:11'::time; + +SELECT count(*) FROM timetmp WHERE a > '10:57:11'::time; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '10:57:11' FROM timetmp ORDER BY a <-> '10:57:11' LIMIT 3; +SELECT a, a <-> '10:57:11' FROM timetmp ORDER BY a <-> '10:57:11' LIMIT 3; diff --git a/contrib/btree_gist/sql/timestamp.sql b/contrib/btree_gist/sql/timestamp.sql new file mode 100644 index 0000000..95effeb --- /dev/null +++ b/contrib/btree_gist/sql/timestamp.sql @@ -0,0 +1,37 @@ +-- timestamp check + +CREATE TABLE timestamptmp (a timestamp); + +\copy timestamptmp from 'data/timestamp.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM timestamptmp WHERE a < '2004-10-26 08:55:08'; + +SELECT count(*) FROM timestamptmp WHERE a <= '2004-10-26 08:55:08'; + +SELECT count(*) FROM timestamptmp WHERE a = '2004-10-26 08:55:08'; + +SELECT count(*) FROM timestamptmp WHERE a >= '2004-10-26 08:55:08'; + +SELECT count(*) FROM timestamptmp WHERE a > '2004-10-26 08:55:08'; + +SELECT a, a <-> '2004-10-26 08:55:08' FROM timestamptmp ORDER BY a <-> '2004-10-26 08:55:08' LIMIT 3; + +CREATE INDEX timestampidx ON timestamptmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM timestamptmp WHERE a < '2004-10-26 08:55:08'::timestamp; + +SELECT count(*) FROM timestamptmp WHERE a <= '2004-10-26 08:55:08'::timestamp; + +SELECT count(*) FROM timestamptmp WHERE a = '2004-10-26 08:55:08'::timestamp; + +SELECT count(*) FROM timestamptmp WHERE a >= '2004-10-26 08:55:08'::timestamp; + +SELECT count(*) FROM timestamptmp WHERE a > '2004-10-26 08:55:08'::timestamp; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '2004-10-26 08:55:08' FROM timestamptmp ORDER BY a <-> '2004-10-26 08:55:08' LIMIT 3; +SELECT a, a <-> '2004-10-26 08:55:08' FROM timestamptmp ORDER BY a <-> '2004-10-26 08:55:08' LIMIT 3; diff --git a/contrib/btree_gist/sql/timestamptz.sql b/contrib/btree_gist/sql/timestamptz.sql new file mode 100644 index 0000000..f70caa4 --- /dev/null +++ b/contrib/btree_gist/sql/timestamptz.sql @@ -0,0 +1,80 @@ +-- timestamptz check + +CREATE TABLE timestamptztmp (a timestamptz); + +\copy timestamptztmp from 'data/timestamptz.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+3'; + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+3'; + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+3'; + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+3'; + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+3'; + + +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+2'; + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+2'; + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+2'; + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+2'; + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+2'; + +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+4'; + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+4'; + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+4'; + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+4'; + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+4'; + +SELECT a, a <-> '2018-12-18 10:59:54 GMT+2' FROM timestamptztmp ORDER BY a <-> '2018-12-18 10:59:54 GMT+2' LIMIT 3; + +CREATE INDEX timestamptzidx ON timestamptztmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+3'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+3'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+3'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+3'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+3'::timestamptz; + + +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+2'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+2'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+2'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+2'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+2'::timestamptz; + + +SELECT count(*) FROM timestamptztmp WHERE a < '2018-12-18 10:59:54 GMT+4'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a <= '2018-12-18 10:59:54 GMT+4'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a = '2018-12-18 10:59:54 GMT+4'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a >= '2018-12-18 10:59:54 GMT+4'::timestamptz; + +SELECT count(*) FROM timestamptztmp WHERE a > '2018-12-18 10:59:54 GMT+4'::timestamptz; + +EXPLAIN (COSTS OFF) +SELECT a, a <-> '2018-12-18 10:59:54 GMT+2' FROM timestamptztmp ORDER BY a <-> '2018-12-18 10:59:54 GMT+2' LIMIT 3; +SELECT a, a <-> '2018-12-18 10:59:54 GMT+2' FROM timestamptztmp ORDER BY a <-> '2018-12-18 10:59:54 GMT+2' LIMIT 3; diff --git a/contrib/btree_gist/sql/timetz.sql b/contrib/btree_gist/sql/timetz.sql new file mode 100644 index 0000000..2fb725d --- /dev/null +++ b/contrib/btree_gist/sql/timetz.sql @@ -0,0 +1,82 @@ +-- timetz check + +CREATE TABLE timetztmp (a timetz); +\copy timetztmp from 'data/timetz.data' + +CREATE TABLE timetzcmp ( r_id int2, a int4, b int4 ); + + +SET enable_seqscan=on; + +INSERT INTO timetzcmp (r_id,a) SELECT 1,count(*) FROM timetztmp WHERE a < '07:46:45 GMT+3'; + +INSERT INTO timetzcmp (r_id,a) SELECT 2,count(*) FROM timetztmp WHERE a <= '07:46:45 GMT+3'; + +INSERT INTO timetzcmp (r_id,a) SELECT 3,count(*) FROM timetztmp WHERE a = '07:46:45 GMT+3'; + +INSERT INTO timetzcmp (r_id,a) SELECT 4,count(*) FROM timetztmp WHERE a >= '07:46:45 GMT+3'; + +INSERT INTO timetzcmp (r_id,a) SELECT 5,count(*) FROM timetztmp WHERE a > '07:46:45 GMT+3'; + + +INSERT INTO timetzcmp (r_id,a) SELECT 11,count(*) FROM timetztmp WHERE a < '07:46:45 GMT+2'; + +INSERT INTO timetzcmp (r_id,a) SELECT 12,count(*) FROM timetztmp WHERE a <= '07:46:45 GMT+2'; + +INSERT INTO timetzcmp (r_id,a) SELECT 13,count(*) FROM timetztmp WHERE a = '07:46:45 GMT+2'; + +INSERT INTO timetzcmp (r_id,a) SELECT 14,count(*) FROM timetztmp WHERE a >= '07:46:45 GMT+2'; + +INSERT INTO timetzcmp (r_id,a) SELECT 15,count(*) FROM timetztmp WHERE a > '07:46:45 GMT+2'; + + +INSERT INTO timetzcmp (r_id,a) SELECT 21,count(*) FROM timetztmp WHERE a < '07:46:45 GMT+4'; + +INSERT INTO timetzcmp (r_id,a) SELECT 22,count(*) FROM timetztmp WHERE a <= '07:46:45 GMT+4'; + +INSERT INTO timetzcmp (r_id,a) SELECT 23,count(*) FROM timetztmp WHERE a = '07:46:45 GMT+4'; + +INSERT INTO timetzcmp (r_id,a) SELECT 24,count(*) FROM timetztmp WHERE a >= '07:46:45 GMT+4'; + +INSERT INTO timetzcmp (r_id,a) SELECT 25,count(*) FROM timetztmp WHERE a > '07:46:45 GMT+4'; + + + +CREATE INDEX timetzidx ON timetztmp USING gist ( a ); + +SET enable_seqscan=off; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a < '07:46:45 GMT+3'::timetz ) q WHERE r_id=1 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a <= '07:46:45 GMT+3'::timetz ) q WHERE r_id=2 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a = '07:46:45 GMT+3'::timetz ) q WHERE r_id=3 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a >= '07:46:45 GMT+3'::timetz ) q WHERE r_id=4 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a > '07:46:45 GMT+3'::timetz ) q WHERE r_id=5 ; + + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a < '07:46:45 GMT+2'::timetz ) q WHERE r_id=11 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a <= '07:46:45 GMT+2'::timetz ) q WHERE r_id=12 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a = '07:46:45 GMT+2'::timetz ) q WHERE r_id=13 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a >= '07:46:45 GMT+2'::timetz ) q WHERE r_id=14 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a > '07:46:45 GMT+2'::timetz ) q WHERE r_id=15 ; + + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a < '07:46:45 GMT+4'::timetz ) q WHERE r_id=21 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a <= '07:46:45 GMT+4'::timetz ) q WHERE r_id=22 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a = '07:46:45 GMT+4'::timetz ) q WHERE r_id=23 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a >= '07:46:45 GMT+4'::timetz ) q WHERE r_id=24 ; + +UPDATE timetzcmp SET b=c FROM ( SELECT count(*) AS c FROM timetztmp WHERE a > '07:46:45 GMT+4'::timetz ) q WHERE r_id=25 ; + + +SELECT count(*) FROM timetzcmp WHERE a=b; diff --git a/contrib/btree_gist/sql/uuid.sql b/contrib/btree_gist/sql/uuid.sql new file mode 100644 index 0000000..3f7ad76 --- /dev/null +++ b/contrib/btree_gist/sql/uuid.sql @@ -0,0 +1,31 @@ +-- uuid check + +CREATE TABLE uuidtmp (a uuid); + +\copy uuidtmp from 'data/uuid.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM uuidtmp WHERE a < '55e65ca2-4136-4a4b-ba78-cd3fe4678203'; + +SELECT count(*) FROM uuidtmp WHERE a <= '55e65ca2-4136-4a4b-ba78-cd3fe4678203'; + +SELECT count(*) FROM uuidtmp WHERE a = '55e65ca2-4136-4a4b-ba78-cd3fe4678203'; + +SELECT count(*) FROM uuidtmp WHERE a >= '55e65ca2-4136-4a4b-ba78-cd3fe4678203'; + +SELECT count(*) FROM uuidtmp WHERE a > '55e65ca2-4136-4a4b-ba78-cd3fe4678203'; + +CREATE INDEX uuididx ON uuidtmp USING gist ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM uuidtmp WHERE a < '55e65ca2-4136-4a4b-ba78-cd3fe4678203'::uuid; + +SELECT count(*) FROM uuidtmp WHERE a <= '55e65ca2-4136-4a4b-ba78-cd3fe4678203'::uuid; + +SELECT count(*) FROM uuidtmp WHERE a = '55e65ca2-4136-4a4b-ba78-cd3fe4678203'::uuid; + +SELECT count(*) FROM uuidtmp WHERE a >= '55e65ca2-4136-4a4b-ba78-cd3fe4678203'::uuid; + +SELECT count(*) FROM uuidtmp WHERE a > '55e65ca2-4136-4a4b-ba78-cd3fe4678203'::uuid; diff --git a/contrib/btree_gist/sql/varbit.sql b/contrib/btree_gist/sql/varbit.sql new file mode 100644 index 0000000..e2a33b5 --- /dev/null +++ b/contrib/btree_gist/sql/varbit.sql @@ -0,0 +1,36 @@ +-- varbit check + +CREATE TABLE varbittmp (a varbit); + +\copy varbittmp from 'data/varbit.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM varbittmp WHERE a < '1110100111010'; + +SELECT count(*) FROM varbittmp WHERE a <= '1110100111010'; + +SELECT count(*) FROM varbittmp WHERE a = '1110100111010'; + +SELECT count(*) FROM varbittmp WHERE a >= '1110100111010'; + +SELECT count(*) FROM varbittmp WHERE a > '1110100111010'; + +CREATE INDEX varbitidx ON varbittmp USING GIST ( a ); + +SET enable_seqscan=off; + +SELECT count(*) FROM varbittmp WHERE a < '1110100111010'::varbit; + +SELECT count(*) FROM varbittmp WHERE a <= '1110100111010'::varbit; + +SELECT count(*) FROM varbittmp WHERE a = '1110100111010'::varbit; + +SELECT count(*) FROM varbittmp WHERE a >= '1110100111010'::varbit; + +SELECT count(*) FROM varbittmp WHERE a > '1110100111010'::varbit; + +-- Test index-only scans +SET enable_bitmapscan=off; +EXPLAIN (COSTS OFF) +SELECT a FROM bittmp WHERE a BETWEEN '1000000' and '1000001'; diff --git a/contrib/btree_gist/sql/varchar.sql b/contrib/btree_gist/sql/varchar.sql new file mode 100644 index 0000000..8087a17 --- /dev/null +++ b/contrib/btree_gist/sql/varchar.sql @@ -0,0 +1,31 @@ +-- char check + +CREATE TABLE vchartmp (a varchar(32)); + +\copy vchartmp from 'data/char.data' + +SET enable_seqscan=on; + +SELECT count(*) FROM vchartmp WHERE a < '31b0'::varchar(32); + +SELECT count(*) FROM vchartmp WHERE a <= '31b0'::varchar(32); + +SELECT count(*) FROM vchartmp WHERE a = '31b0'::varchar(32); + +SELECT count(*) FROM vchartmp WHERE a >= '31b0'::varchar(32); + +SELECT count(*) FROM vchartmp WHERE a > '31b0'::varchar(32); + +CREATE INDEX vcharidx ON vchartmp USING GIST ( text(a) ); + +SET enable_seqscan=off; + +SELECT count(*) FROM vchartmp WHERE a < '31b0'::varchar(32); + +SELECT count(*) FROM vchartmp WHERE a <= '31b0'::varchar(32); + +SELECT count(*) FROM vchartmp WHERE a = '31b0'::varchar(32); + +SELECT count(*) FROM vchartmp WHERE a >= '31b0'::varchar(32); + +SELECT count(*) FROM vchartmp WHERE a > '31b0'::varchar(32); diff --git a/contrib/citext/.gitignore b/contrib/citext/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/citext/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/citext/Makefile b/contrib/citext/Makefile new file mode 100644 index 0000000..35db6ea --- /dev/null +++ b/contrib/citext/Makefile @@ -0,0 +1,25 @@ +# contrib/citext/Makefile + +MODULES = citext + +EXTENSION = citext +DATA = citext--1.4.sql \ + citext--1.5--1.6.sql \ + citext--1.4--1.5.sql \ + citext--1.3--1.4.sql \ + citext--1.2--1.3.sql citext--1.1--1.2.sql \ + citext--1.0--1.1.sql +PGFILEDESC = "citext - case-insensitive character string data type" + +REGRESS = create_index_acl citext citext_utf8 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/citext +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/citext/citext--1.0--1.1.sql b/contrib/citext/citext--1.0--1.1.sql new file mode 100644 index 0000000..e06627e --- /dev/null +++ b/contrib/citext/citext--1.0--1.1.sql @@ -0,0 +1,21 @@ +/* contrib/citext/citext--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION citext UPDATE TO '1.1'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION citext DROP FUNCTION regexp_matches( citext, citext ); +ALTER EXTENSION citext DROP FUNCTION regexp_matches( citext, citext, text ); + +/* Then we can drop them */ +DROP FUNCTION regexp_matches( citext, citext ); +DROP FUNCTION regexp_matches( citext, citext, text ); + +/* Now redefine */ +CREATE FUNCTION regexp_matches( citext, citext ) RETURNS SETOF TEXT[] AS $$ + SELECT pg_catalog.regexp_matches( $1::pg_catalog.text, $2::pg_catalog.text, 'i' ); +$$ LANGUAGE SQL IMMUTABLE STRICT ROWS 1; + +CREATE FUNCTION regexp_matches( citext, citext, text ) RETURNS SETOF TEXT[] AS $$ + SELECT pg_catalog.regexp_matches( $1::pg_catalog.text, $2::pg_catalog.text, CASE WHEN pg_catalog.strpos($3, 'c') = 0 THEN $3 || 'i' ELSE $3 END ); +$$ LANGUAGE SQL IMMUTABLE STRICT ROWS 10; diff --git a/contrib/citext/citext--1.1--1.2.sql b/contrib/citext/citext--1.1--1.2.sql new file mode 100644 index 0000000..a8bba86 --- /dev/null +++ b/contrib/citext/citext--1.1--1.2.sql @@ -0,0 +1,68 @@ +/* contrib/citext/citext--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION citext UPDATE TO '1.2'" to load this file. \quit + +ALTER FUNCTION citextin(cstring) PARALLEL SAFE; +ALTER FUNCTION citextout(citext) PARALLEL SAFE; +ALTER FUNCTION citextrecv(internal) PARALLEL SAFE; +ALTER FUNCTION citextsend(citext) PARALLEL SAFE; +ALTER FUNCTION citext(bpchar) PARALLEL SAFE; +ALTER FUNCTION citext(boolean) PARALLEL SAFE; +ALTER FUNCTION citext(inet) PARALLEL SAFE; +ALTER FUNCTION citext_eq(citext, citext) PARALLEL SAFE; +ALTER FUNCTION citext_ne(citext, citext) PARALLEL SAFE; +ALTER FUNCTION citext_lt(citext, citext) PARALLEL SAFE; +ALTER FUNCTION citext_le(citext, citext) PARALLEL SAFE; +ALTER FUNCTION citext_gt(citext, citext) PARALLEL SAFE; +ALTER FUNCTION citext_ge(citext, citext) PARALLEL SAFE; +ALTER FUNCTION citext_cmp(citext, citext) PARALLEL SAFE; +ALTER FUNCTION citext_hash(citext) PARALLEL SAFE; +ALTER FUNCTION citext_smaller(citext, citext) PARALLEL SAFE; +ALTER FUNCTION citext_larger(citext, citext) PARALLEL SAFE; +ALTER FUNCTION texticlike(citext, citext) PARALLEL SAFE; +ALTER FUNCTION texticnlike(citext, citext) PARALLEL SAFE; +ALTER FUNCTION texticregexeq(citext, citext) PARALLEL SAFE; +ALTER FUNCTION texticregexne(citext, citext) PARALLEL SAFE; +ALTER FUNCTION texticlike(citext, text) PARALLEL SAFE; +ALTER FUNCTION texticnlike(citext, text) PARALLEL SAFE; +ALTER FUNCTION texticregexeq(citext, text) PARALLEL SAFE; +ALTER FUNCTION texticregexne(citext, text) PARALLEL SAFE; +ALTER FUNCTION regexp_matches(citext, citext) PARALLEL SAFE; +ALTER FUNCTION regexp_matches(citext, citext, text) PARALLEL SAFE; +ALTER FUNCTION regexp_replace(citext, citext, text) PARALLEL SAFE; +ALTER FUNCTION regexp_replace(citext, citext, text, text) PARALLEL SAFE; +ALTER FUNCTION regexp_split_to_array(citext, citext) PARALLEL SAFE; +ALTER FUNCTION regexp_split_to_array(citext, citext, text) PARALLEL SAFE; +ALTER FUNCTION regexp_split_to_table(citext, citext) PARALLEL SAFE; +ALTER FUNCTION regexp_split_to_table(citext, citext, text) PARALLEL SAFE; +ALTER FUNCTION strpos(citext, citext) PARALLEL SAFE; +ALTER FUNCTION replace(citext, citext, citext) PARALLEL SAFE; +ALTER FUNCTION split_part(citext, citext, int) PARALLEL SAFE; +ALTER FUNCTION translate(citext, citext, text) PARALLEL SAFE; + +-- We have to update aggregates the hard way for lack of ALTER support +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_proc SET proparallel = 's' +WHERE oid = (my_schema || '.min(' || my_schema || '.citext)')::pg_catalog.regprocedure; + +UPDATE pg_proc SET proparallel = 's' +WHERE oid = (my_schema || '.max(' || my_schema || '.citext)')::pg_catalog.regprocedure; + +UPDATE pg_aggregate SET aggcombinefn = (my_schema || '.citext_smaller')::regproc +WHERE aggfnoid = (my_schema || '.max(' || my_schema || '.citext)')::pg_catalog.regprocedure; + +UPDATE pg_aggregate SET aggcombinefn = (my_schema || '.citext_larger')::regproc +WHERE aggfnoid = (my_schema || '.max(' || my_schema || '.citext)')::pg_catalog.regprocedure; + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; diff --git a/contrib/citext/citext--1.2--1.3.sql b/contrib/citext/citext--1.2--1.3.sql new file mode 100644 index 0000000..24a7145 --- /dev/null +++ b/contrib/citext/citext--1.2--1.3.sql @@ -0,0 +1,21 @@ +/* contrib/citext/citext--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION citext UPDATE TO '1.3'" to load this file. \quit + +-- We have to update aggregates the hard way for lack of ALTER support +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_aggregate SET aggcombinefn = (my_schema || '.citext_smaller')::regproc +WHERE aggfnoid = (my_schema || '.min(' || my_schema || '.citext)')::pg_catalog.regprocedure; + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; diff --git a/contrib/citext/citext--1.3--1.4.sql b/contrib/citext/citext--1.3--1.4.sql new file mode 100644 index 0000000..7b36651 --- /dev/null +++ b/contrib/citext/citext--1.3--1.4.sql @@ -0,0 +1,12 @@ +/* contrib/citext/citext--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION citext UPDATE TO '1.4'" to load this file. \quit + +CREATE FUNCTION regexp_match( citext, citext ) RETURNS TEXT[] AS $$ + SELECT pg_catalog.regexp_match( $1::pg_catalog.text, $2::pg_catalog.text, 'i' ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION regexp_match( citext, citext, text ) RETURNS TEXT[] AS $$ + SELECT pg_catalog.regexp_match( $1::pg_catalog.text, $2::pg_catalog.text, CASE WHEN pg_catalog.strpos($3, 'c') = 0 THEN $3 || 'i' ELSE $3 END ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; diff --git a/contrib/citext/citext--1.4--1.5.sql b/contrib/citext/citext--1.4--1.5.sql new file mode 100644 index 0000000..5ae522b --- /dev/null +++ b/contrib/citext/citext--1.4--1.5.sql @@ -0,0 +1,88 @@ +/* contrib/citext/citext--1.4--1.5.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION citext UPDATE TO '1.5'" to load this file. \quit + +ALTER OPERATOR <= (citext, citext) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +ALTER OPERATOR >= (citext, citext) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); + +CREATE FUNCTION citext_pattern_lt( citext, citext ) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext_pattern_le( citext, citext ) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext_pattern_gt( citext, citext ) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext_pattern_ge( citext, citext ) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR ~<~ ( + LEFTARG = CITEXT, + RIGHTARG = CITEXT, + NEGATOR = ~>=~, + COMMUTATOR = ~>~, + PROCEDURE = citext_pattern_lt, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR ~<=~ ( + LEFTARG = CITEXT, + RIGHTARG = CITEXT, + NEGATOR = ~>~, + COMMUTATOR = ~>=~, + PROCEDURE = citext_pattern_le, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR ~>=~ ( + LEFTARG = CITEXT, + RIGHTARG = CITEXT, + NEGATOR = ~<~, + COMMUTATOR = ~<=~, + PROCEDURE = citext_pattern_ge, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR ~>~ ( + LEFTARG = CITEXT, + RIGHTARG = CITEXT, + NEGATOR = ~<=~, + COMMUTATOR = ~<~, + PROCEDURE = citext_pattern_gt, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE FUNCTION citext_pattern_cmp(citext, citext) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR CLASS citext_pattern_ops +FOR TYPE CITEXT USING btree AS + OPERATOR 1 ~<~ (citext, citext), + OPERATOR 2 ~<=~ (citext, citext), + OPERATOR 3 = (citext, citext), + OPERATOR 4 ~>=~ (citext, citext), + OPERATOR 5 ~>~ (citext, citext), + FUNCTION 1 citext_pattern_cmp(citext, citext); diff --git a/contrib/citext/citext--1.4.sql b/contrib/citext/citext--1.4.sql new file mode 100644 index 0000000..7b06198 --- /dev/null +++ b/contrib/citext/citext--1.4.sql @@ -0,0 +1,501 @@ +/* contrib/citext/citext--1.4.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION citext" to load this file. \quit + +-- +-- PostgreSQL code for CITEXT. +-- +-- Most I/O functions, and a few others, piggyback on the "text" type +-- functions via the implicit cast to text. +-- + +-- +-- Shell type to keep things a bit quieter. +-- + +CREATE TYPE citext; + +-- +-- Input and output functions. +-- +CREATE FUNCTION citextin(cstring) +RETURNS citext +AS 'textin' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citextout(citext) +RETURNS cstring +AS 'textout' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citextrecv(internal) +RETURNS citext +AS 'textrecv' +LANGUAGE internal STABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citextsend(citext) +RETURNS bytea +AS 'textsend' +LANGUAGE internal STABLE STRICT PARALLEL SAFE; + +-- +-- The type itself. +-- + +CREATE TYPE citext ( + INPUT = citextin, + OUTPUT = citextout, + RECEIVE = citextrecv, + SEND = citextsend, + INTERNALLENGTH = VARIABLE, + STORAGE = extended, + -- make it a non-preferred member of string type category + CATEGORY = 'S', + PREFERRED = false, + COLLATABLE = true +); + +-- +-- Type casting functions for those situations where the I/O casts don't +-- automatically kick in. +-- + +CREATE FUNCTION citext(bpchar) +RETURNS citext +AS 'rtrim1' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext(boolean) +RETURNS citext +AS 'booltext' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext(inet) +RETURNS citext +AS 'network_show' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +-- +-- Implicit and assignment type casts. +-- + +CREATE CAST (citext AS text) WITHOUT FUNCTION AS IMPLICIT; +CREATE CAST (citext AS varchar) WITHOUT FUNCTION AS IMPLICIT; +CREATE CAST (citext AS bpchar) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (text AS citext) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (varchar AS citext) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (bpchar AS citext) WITH FUNCTION citext(bpchar) AS ASSIGNMENT; +CREATE CAST (boolean AS citext) WITH FUNCTION citext(boolean) AS ASSIGNMENT; +CREATE CAST (inet AS citext) WITH FUNCTION citext(inet) AS ASSIGNMENT; + +-- +-- Operator Functions. +-- + +CREATE FUNCTION citext_eq( citext, citext ) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext_ne( citext, citext ) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext_lt( citext, citext ) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext_le( citext, citext ) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext_gt( citext, citext ) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext_ge( citext, citext ) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- +-- Operators. +-- + +CREATE OPERATOR = ( + LEFTARG = CITEXT, + RIGHTARG = CITEXT, + COMMUTATOR = =, + NEGATOR = <>, + PROCEDURE = citext_eq, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES +); + +CREATE OPERATOR <> ( + LEFTARG = CITEXT, + RIGHTARG = CITEXT, + NEGATOR = =, + COMMUTATOR = <>, + PROCEDURE = citext_ne, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE OPERATOR < ( + LEFTARG = CITEXT, + RIGHTARG = CITEXT, + NEGATOR = >=, + COMMUTATOR = >, + PROCEDURE = citext_lt, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = CITEXT, + RIGHTARG = CITEXT, + NEGATOR = >, + COMMUTATOR = >=, + PROCEDURE = citext_le, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = CITEXT, + RIGHTARG = CITEXT, + NEGATOR = <, + COMMUTATOR = <=, + PROCEDURE = citext_ge, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = CITEXT, + RIGHTARG = CITEXT, + NEGATOR = <=, + COMMUTATOR = <, + PROCEDURE = citext_gt, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +-- +-- Support functions for indexing. +-- + +CREATE FUNCTION citext_cmp(citext, citext) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION citext_hash(citext) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +-- +-- The btree indexing operator class. +-- + +CREATE OPERATOR CLASS citext_ops +DEFAULT FOR TYPE CITEXT USING btree AS + OPERATOR 1 < (citext, citext), + OPERATOR 2 <= (citext, citext), + OPERATOR 3 = (citext, citext), + OPERATOR 4 >= (citext, citext), + OPERATOR 5 > (citext, citext), + FUNCTION 1 citext_cmp(citext, citext); + +-- +-- The hash indexing operator class. +-- + +CREATE OPERATOR CLASS citext_ops +DEFAULT FOR TYPE citext USING hash AS + OPERATOR 1 = (citext, citext), + FUNCTION 1 citext_hash(citext); + +-- +-- Aggregates. +-- + +CREATE FUNCTION citext_smaller(citext, citext) +RETURNS citext +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION citext_larger(citext, citext) +RETURNS citext +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE AGGREGATE min(citext) ( + SFUNC = citext_smaller, + STYPE = citext, + SORTOP = <, + PARALLEL = SAFE, + COMBINEFUNC = citext_smaller +); + +CREATE AGGREGATE max(citext) ( + SFUNC = citext_larger, + STYPE = citext, + SORTOP = >, + PARALLEL = SAFE, + COMBINEFUNC = citext_larger +); + +-- +-- CITEXT pattern matching. +-- + +CREATE FUNCTION texticlike(citext, citext) +RETURNS bool AS 'texticlike' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION texticnlike(citext, citext) +RETURNS bool AS 'texticnlike' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION texticregexeq(citext, citext) +RETURNS bool AS 'texticregexeq' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION texticregexne(citext, citext) +RETURNS bool AS 'texticregexne' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR ~ ( + PROCEDURE = texticregexeq, + LEFTARG = citext, + RIGHTARG = citext, + NEGATOR = !~, + RESTRICT = icregexeqsel, + JOIN = icregexeqjoinsel +); + +CREATE OPERATOR ~* ( + PROCEDURE = texticregexeq, + LEFTARG = citext, + RIGHTARG = citext, + NEGATOR = !~*, + RESTRICT = icregexeqsel, + JOIN = icregexeqjoinsel +); + +CREATE OPERATOR !~ ( + PROCEDURE = texticregexne, + LEFTARG = citext, + RIGHTARG = citext, + NEGATOR = ~, + RESTRICT = icregexnesel, + JOIN = icregexnejoinsel +); + +CREATE OPERATOR !~* ( + PROCEDURE = texticregexne, + LEFTARG = citext, + RIGHTARG = citext, + NEGATOR = ~*, + RESTRICT = icregexnesel, + JOIN = icregexnejoinsel +); + +CREATE OPERATOR ~~ ( + PROCEDURE = texticlike, + LEFTARG = citext, + RIGHTARG = citext, + NEGATOR = !~~, + RESTRICT = iclikesel, + JOIN = iclikejoinsel +); + +CREATE OPERATOR ~~* ( + PROCEDURE = texticlike, + LEFTARG = citext, + RIGHTARG = citext, + NEGATOR = !~~*, + RESTRICT = iclikesel, + JOIN = iclikejoinsel +); + +CREATE OPERATOR !~~ ( + PROCEDURE = texticnlike, + LEFTARG = citext, + RIGHTARG = citext, + NEGATOR = ~~, + RESTRICT = icnlikesel, + JOIN = icnlikejoinsel +); + +CREATE OPERATOR !~~* ( + PROCEDURE = texticnlike, + LEFTARG = citext, + RIGHTARG = citext, + NEGATOR = ~~*, + RESTRICT = icnlikesel, + JOIN = icnlikejoinsel +); + +-- +-- Matching citext to text. +-- + +CREATE FUNCTION texticlike(citext, text) +RETURNS bool AS 'texticlike' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION texticnlike(citext, text) +RETURNS bool AS 'texticnlike' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION texticregexeq(citext, text) +RETURNS bool AS 'texticregexeq' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION texticregexne(citext, text) +RETURNS bool AS 'texticregexne' +LANGUAGE internal IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR ~ ( + PROCEDURE = texticregexeq, + LEFTARG = citext, + RIGHTARG = text, + NEGATOR = !~, + RESTRICT = icregexeqsel, + JOIN = icregexeqjoinsel +); + +CREATE OPERATOR ~* ( + PROCEDURE = texticregexeq, + LEFTARG = citext, + RIGHTARG = text, + NEGATOR = !~*, + RESTRICT = icregexeqsel, + JOIN = icregexeqjoinsel +); + +CREATE OPERATOR !~ ( + PROCEDURE = texticregexne, + LEFTARG = citext, + RIGHTARG = text, + NEGATOR = ~, + RESTRICT = icregexnesel, + JOIN = icregexnejoinsel +); + +CREATE OPERATOR !~* ( + PROCEDURE = texticregexne, + LEFTARG = citext, + RIGHTARG = text, + NEGATOR = ~*, + RESTRICT = icregexnesel, + JOIN = icregexnejoinsel +); + +CREATE OPERATOR ~~ ( + PROCEDURE = texticlike, + LEFTARG = citext, + RIGHTARG = text, + NEGATOR = !~~, + RESTRICT = iclikesel, + JOIN = iclikejoinsel +); + +CREATE OPERATOR ~~* ( + PROCEDURE = texticlike, + LEFTARG = citext, + RIGHTARG = text, + NEGATOR = !~~*, + RESTRICT = iclikesel, + JOIN = iclikejoinsel +); + +CREATE OPERATOR !~~ ( + PROCEDURE = texticnlike, + LEFTARG = citext, + RIGHTARG = text, + NEGATOR = ~~, + RESTRICT = icnlikesel, + JOIN = icnlikejoinsel +); + +CREATE OPERATOR !~~* ( + PROCEDURE = texticnlike, + LEFTARG = citext, + RIGHTARG = text, + NEGATOR = ~~*, + RESTRICT = icnlikesel, + JOIN = icnlikejoinsel +); + +-- +-- Matching citext in string comparison functions. +-- XXX TODO Ideally these would be implemented in C. +-- + +CREATE FUNCTION regexp_match( citext, citext ) RETURNS TEXT[] AS $$ + SELECT pg_catalog.regexp_match( $1::pg_catalog.text, $2::pg_catalog.text, 'i' ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION regexp_match( citext, citext, text ) RETURNS TEXT[] AS $$ + SELECT pg_catalog.regexp_match( $1::pg_catalog.text, $2::pg_catalog.text, CASE WHEN pg_catalog.strpos($3, 'c') = 0 THEN $3 || 'i' ELSE $3 END ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION regexp_matches( citext, citext ) RETURNS SETOF TEXT[] AS $$ + SELECT pg_catalog.regexp_matches( $1::pg_catalog.text, $2::pg_catalog.text, 'i' ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE ROWS 1; + +CREATE FUNCTION regexp_matches( citext, citext, text ) RETURNS SETOF TEXT[] AS $$ + SELECT pg_catalog.regexp_matches( $1::pg_catalog.text, $2::pg_catalog.text, CASE WHEN pg_catalog.strpos($3, 'c') = 0 THEN $3 || 'i' ELSE $3 END ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE ROWS 10; + +CREATE FUNCTION regexp_replace( citext, citext, text ) returns TEXT AS $$ + SELECT pg_catalog.regexp_replace( $1::pg_catalog.text, $2::pg_catalog.text, $3, 'i'); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION regexp_replace( citext, citext, text, text ) returns TEXT AS $$ + SELECT pg_catalog.regexp_replace( $1::pg_catalog.text, $2::pg_catalog.text, $3, CASE WHEN pg_catalog.strpos($4, 'c') = 0 THEN $4 || 'i' ELSE $4 END); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION regexp_split_to_array( citext, citext ) RETURNS TEXT[] AS $$ + SELECT pg_catalog.regexp_split_to_array( $1::pg_catalog.text, $2::pg_catalog.text, 'i' ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION regexp_split_to_array( citext, citext, text ) RETURNS TEXT[] AS $$ + SELECT pg_catalog.regexp_split_to_array( $1::pg_catalog.text, $2::pg_catalog.text, CASE WHEN pg_catalog.strpos($3, 'c') = 0 THEN $3 || 'i' ELSE $3 END ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION regexp_split_to_table( citext, citext ) RETURNS SETOF TEXT AS $$ + SELECT pg_catalog.regexp_split_to_table( $1::pg_catalog.text, $2::pg_catalog.text, 'i' ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION regexp_split_to_table( citext, citext, text ) RETURNS SETOF TEXT AS $$ + SELECT pg_catalog.regexp_split_to_table( $1::pg_catalog.text, $2::pg_catalog.text, CASE WHEN pg_catalog.strpos($3, 'c') = 0 THEN $3 || 'i' ELSE $3 END ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION strpos( citext, citext ) RETURNS INT AS $$ + SELECT pg_catalog.strpos( pg_catalog.lower( $1::pg_catalog.text ), pg_catalog.lower( $2::pg_catalog.text ) ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION replace( citext, citext, citext ) RETURNS TEXT AS $$ + SELECT pg_catalog.regexp_replace( $1::pg_catalog.text, pg_catalog.regexp_replace($2::pg_catalog.text, '([^a-zA-Z_0-9])', E'\\\\\\1', 'g'), $3::pg_catalog.text, 'gi' ); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION split_part( citext, citext, int ) RETURNS TEXT AS $$ + SELECT (pg_catalog.regexp_split_to_array( $1::pg_catalog.text, pg_catalog.regexp_replace($2::pg_catalog.text, '([^a-zA-Z_0-9])', E'\\\\\\1', 'g'), 'i'))[$3]; +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION translate( citext, citext, text ) RETURNS TEXT AS $$ + SELECT pg_catalog.translate( pg_catalog.translate( $1::pg_catalog.text, pg_catalog.lower($2::pg_catalog.text), $3), pg_catalog.upper($2::pg_catalog.text), $3); +$$ LANGUAGE SQL IMMUTABLE STRICT PARALLEL SAFE; diff --git a/contrib/citext/citext--1.5--1.6.sql b/contrib/citext/citext--1.5--1.6.sql new file mode 100644 index 0000000..3226898 --- /dev/null +++ b/contrib/citext/citext--1.5--1.6.sql @@ -0,0 +1,12 @@ +/* contrib/citext/citext--1.5--1.6.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION citext UPDATE TO '1.6'" to load this file. \quit + +CREATE FUNCTION citext_hash_extended(citext, int8) +RETURNS int8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +ALTER OPERATOR FAMILY citext_ops USING hash ADD + FUNCTION 2 citext_hash_extended(citext, int8); diff --git a/contrib/citext/citext.c b/contrib/citext/citext.c new file mode 100644 index 0000000..26af935 --- /dev/null +++ b/contrib/citext/citext.c @@ -0,0 +1,409 @@ +/* + * contrib/citext/citext.c + */ +#include "postgres.h" + +#include "catalog/pg_collation.h" +#include "common/hashfn.h" +#include "utils/builtins.h" +#include "utils/formatting.h" +#include "utils/varlena.h" +#include "varatt.h" + +PG_MODULE_MAGIC; + +/* + * ==================== + * FORWARD DECLARATIONS + * ==================== + */ + +static int32 citextcmp(text *left, text *right, Oid collid); +static int32 internal_citext_pattern_cmp(text *left, text *right, Oid collid); + +/* + * ================= + * UTILITY FUNCTIONS + * ================= + */ + +/* + * citextcmp() + * Internal comparison function for citext strings. + * Returns int32 negative, zero, or positive. + */ +static int32 +citextcmp(text *left, text *right, Oid collid) +{ + char *lcstr, + *rcstr; + int32 result; + + /* + * We must do our str_tolower calls with DEFAULT_COLLATION_OID, not the + * input collation as you might expect. This is so that the behavior of + * citext's equality and hashing functions is not collation-dependent. We + * should change this once the core infrastructure is able to cope with + * collation-dependent equality and hashing functions. + */ + + lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID); + rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID); + + result = varstr_cmp(lcstr, strlen(lcstr), + rcstr, strlen(rcstr), + collid); + + pfree(lcstr); + pfree(rcstr); + + return result; +} + +/* + * citext_pattern_cmp() + * Internal character-by-character comparison function for citext strings. + * Returns int32 negative, zero, or positive. + */ +static int32 +internal_citext_pattern_cmp(text *left, text *right, Oid collid) +{ + char *lcstr, + *rcstr; + int llen, + rlen; + int32 result; + + lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID); + rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID); + + llen = strlen(lcstr); + rlen = strlen(rcstr); + + result = memcmp(lcstr, rcstr, Min(llen, rlen)); + if (result == 0) + { + if (llen < rlen) + result = -1; + else if (llen > rlen) + result = 1; + } + + pfree(lcstr); + pfree(rcstr); + + return result; +} + +/* + * ================== + * INDEXING FUNCTIONS + * ================== + */ + +PG_FUNCTION_INFO_V1(citext_cmp); + +Datum +citext_cmp(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + int32 result; + + result = citextcmp(left, right, PG_GET_COLLATION()); + + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_INT32(result); +} + +PG_FUNCTION_INFO_V1(citext_pattern_cmp); + +Datum +citext_pattern_cmp(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + int32 result; + + result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()); + + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_INT32(result); +} + +PG_FUNCTION_INFO_V1(citext_hash); + +Datum +citext_hash(PG_FUNCTION_ARGS) +{ + text *txt = PG_GETARG_TEXT_PP(0); + char *str; + Datum result; + + str = str_tolower(VARDATA_ANY(txt), VARSIZE_ANY_EXHDR(txt), DEFAULT_COLLATION_OID); + result = hash_any((unsigned char *) str, strlen(str)); + pfree(str); + + /* Avoid leaking memory for toasted inputs */ + PG_FREE_IF_COPY(txt, 0); + + PG_RETURN_DATUM(result); +} + +PG_FUNCTION_INFO_V1(citext_hash_extended); + +Datum +citext_hash_extended(PG_FUNCTION_ARGS) +{ + text *txt = PG_GETARG_TEXT_PP(0); + uint64 seed = PG_GETARG_INT64(1); + char *str; + Datum result; + + str = str_tolower(VARDATA_ANY(txt), VARSIZE_ANY_EXHDR(txt), DEFAULT_COLLATION_OID); + result = hash_any_extended((unsigned char *) str, strlen(str), seed); + pfree(str); + + /* Avoid leaking memory for toasted inputs */ + PG_FREE_IF_COPY(txt, 0); + + PG_RETURN_DATUM(result); +} + +/* + * ================== + * OPERATOR FUNCTIONS + * ================== + */ + +PG_FUNCTION_INFO_V1(citext_eq); + +Datum +citext_eq(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + char *lcstr, + *rcstr; + bool result; + + /* We can't compare lengths in advance of downcasing ... */ + + lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID); + rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID); + + /* + * Since we only care about equality or not-equality, we can avoid all the + * expense of strcoll() here, and just do bitwise comparison. + */ + result = (strcmp(lcstr, rcstr) == 0); + + pfree(lcstr); + pfree(rcstr); + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(citext_ne); + +Datum +citext_ne(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + char *lcstr, + *rcstr; + bool result; + + /* We can't compare lengths in advance of downcasing ... */ + + lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID); + rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID); + + /* + * Since we only care about equality or not-equality, we can avoid all the + * expense of strcoll() here, and just do bitwise comparison. + */ + result = (strcmp(lcstr, rcstr) != 0); + + pfree(lcstr); + pfree(rcstr); + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(citext_lt); + +Datum +citext_lt(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + bool result; + + result = citextcmp(left, right, PG_GET_COLLATION()) < 0; + + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(citext_le); + +Datum +citext_le(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + bool result; + + result = citextcmp(left, right, PG_GET_COLLATION()) <= 0; + + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(citext_gt); + +Datum +citext_gt(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + bool result; + + result = citextcmp(left, right, PG_GET_COLLATION()) > 0; + + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(citext_ge); + +Datum +citext_ge(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + bool result; + + result = citextcmp(left, right, PG_GET_COLLATION()) >= 0; + + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(citext_pattern_lt); + +Datum +citext_pattern_lt(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + bool result; + + result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) < 0; + + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(citext_pattern_le); + +Datum +citext_pattern_le(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + bool result; + + result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) <= 0; + + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(citext_pattern_gt); + +Datum +citext_pattern_gt(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + bool result; + + result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) > 0; + + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(citext_pattern_ge); + +Datum +citext_pattern_ge(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + bool result; + + result = internal_citext_pattern_cmp(left, right, PG_GET_COLLATION()) >= 0; + + PG_FREE_IF_COPY(left, 0); + PG_FREE_IF_COPY(right, 1); + + PG_RETURN_BOOL(result); +} + +/* + * =================== + * AGGREGATE FUNCTIONS + * =================== + */ + +PG_FUNCTION_INFO_V1(citext_smaller); + +Datum +citext_smaller(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + text *result; + + result = citextcmp(left, right, PG_GET_COLLATION()) < 0 ? left : right; + PG_RETURN_TEXT_P(result); +} + +PG_FUNCTION_INFO_V1(citext_larger); + +Datum +citext_larger(PG_FUNCTION_ARGS) +{ + text *left = PG_GETARG_TEXT_PP(0); + text *right = PG_GETARG_TEXT_PP(1); + text *result; + + result = citextcmp(left, right, PG_GET_COLLATION()) > 0 ? left : right; + PG_RETURN_TEXT_P(result); +} diff --git a/contrib/citext/citext.control b/contrib/citext/citext.control new file mode 100644 index 0000000..ccf4454 --- /dev/null +++ b/contrib/citext/citext.control @@ -0,0 +1,6 @@ +# citext extension +comment = 'data type for case-insensitive character strings' +default_version = '1.6' +module_pathname = '$libdir/citext' +relocatable = true +trusted = true diff --git a/contrib/citext/expected/citext.out b/contrib/citext/expected/citext.out new file mode 100644 index 0000000..1c55598 --- /dev/null +++ b/contrib/citext/expected/citext.out @@ -0,0 +1,2693 @@ +-- +-- Test citext datatype +-- +CREATE EXTENSION citext; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + amname | opcname +--------+--------- +(0 rows) + +-- Test the operators and indexing functions +-- Test = and <>. +SELECT 'a'::citext = 'a'::citext AS t; + t +--- + t +(1 row) + +SELECT 'a'::citext = 'A'::citext AS t; + t +--- + t +(1 row) + +SELECT 'a'::citext = 'A'::text AS f; -- text wins the discussion + f +--- + f +(1 row) + +SELECT 'a'::citext = 'b'::citext AS f; + f +--- + f +(1 row) + +SELECT 'a'::citext = 'ab'::citext AS f; + f +--- + f +(1 row) + +SELECT 'a'::citext <> 'ab'::citext AS t; + t +--- + t +(1 row) + +-- Test > and >= +SELECT 'B'::citext > 'a'::citext AS t; + t +--- + t +(1 row) + +SELECT 'b'::citext > 'A'::citext AS t; + t +--- + t +(1 row) + +SELECT 'B'::citext > 'b'::citext AS f; + f +--- + f +(1 row) + +SELECT 'B'::citext >= 'b'::citext AS t; + t +--- + t +(1 row) + +-- Test < and <= +SELECT 'a'::citext < 'B'::citext AS t; + t +--- + t +(1 row) + +SELECT 'a'::citext <= 'B'::citext AS t; + t +--- + t +(1 row) + +-- Test implicit casting. citext casts to text, but not vice-versa. +SELECT 'a'::citext = 'a'::text AS t; + t +--- + t +(1 row) + +SELECT 'A'::text <> 'a'::citext AS t; + t +--- + t +(1 row) + +SELECT 'B'::citext < 'a'::text AS t; -- text wins. + t +--- + t +(1 row) + +SELECT 'B'::citext <= 'a'::text AS t; -- text wins. + t +--- + t +(1 row) + +SELECT 'a'::citext > 'B'::text AS t; -- text wins. + t +--- + t +(1 row) + +SELECT 'a'::citext >= 'B'::text AS t; -- text wins. + t +--- + t +(1 row) + +-- Test implicit casting. citext casts to varchar, but not vice-versa. +SELECT 'a'::citext = 'a'::varchar AS t; + t +--- + t +(1 row) + +SELECT 'A'::varchar <> 'a'::citext AS t; + t +--- + t +(1 row) + +SELECT 'B'::citext < 'a'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + +SELECT 'B'::citext <= 'a'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + +SELECT 'a'::citext > 'B'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + +SELECT 'a'::citext >= 'B'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + +-- A couple of longer examples to ensure that we don't get any issues with bad +-- conversions to char[] in the c code. Yes, I did do this. +SELECT 'aardvark'::citext = 'aardvark'::citext AS t; + t +--- + t +(1 row) + +SELECT 'aardvark'::citext = 'aardVark'::citext AS t; + t +--- + t +(1 row) + +-- Check the citext_cmp() function explicitly. +SELECT citext_cmp('aardvark'::citext, 'aardvark'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_cmp('aardvark'::citext, 'aardVark'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_cmp('AARDVARK'::citext, 'AARDVARK'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_cmp('B'::citext, 'a'::citext) > 0 AS true; + true +------ + t +(1 row) + +-- Check the citext_hash() and citext_hash_extended() function explicitly. +SELECT v as value, citext_hash(v)::bit(32) as standard, + citext_hash_extended(v, 0)::bit(32) as extended0, + citext_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::citext), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), + ('muop28x03'), ('yi3nm0d73')) x(v) +WHERE citext_hash(v)::bit(32) != citext_hash_extended(v, 0)::bit(32) + OR citext_hash(v)::bit(32) = citext_hash_extended(v, 1)::bit(32); + value | standard | extended0 | extended1 +-------+----------+-----------+----------- +(0 rows) + +-- Do some tests using a table and index. +CREATE TEMP TABLE try ( + name citext PRIMARY KEY +); +INSERT INTO try (name) +VALUES ('a'), ('ab'), ('â'), ('aba'), ('b'), ('ba'), ('bab'), ('AZ'); +SELECT name, 'a' = name AS eq_a FROM try WHERE name <> 'â'; + name | eq_a +------+------ + a | t + ab | f + aba | f + b | f + ba | f + bab | f + AZ | f +(7 rows) + +SELECT name, 'a' = name AS t FROM try where name = 'a'; + name | t +------+--- + a | t +(1 row) + +SELECT name, 'A' = name AS "eq_A" FROM try WHERE name <> 'â'; + name | eq_A +------+------ + a | t + ab | f + aba | f + b | f + ba | f + bab | f + AZ | f +(7 rows) + +SELECT name, 'A' = name AS t FROM try where name = 'A'; + name | t +------+--- + a | t +(1 row) + +SELECT name, 'A' = name AS t FROM try where name = 'A'; + name | t +------+--- + a | t +(1 row) + +-- expected failures on duplicate key +INSERT INTO try (name) VALUES ('a'); +ERROR: duplicate key value violates unique constraint "try_pkey" +DETAIL: Key (name)=(a) already exists. +INSERT INTO try (name) VALUES ('A'); +ERROR: duplicate key value violates unique constraint "try_pkey" +DETAIL: Key (name)=(A) already exists. +INSERT INTO try (name) VALUES ('aB'); +ERROR: duplicate key value violates unique constraint "try_pkey" +DETAIL: Key (name)=(aB) already exists. +-- Make sure that citext_smaller() and citext_larger() work properly. +SELECT citext_smaller( 'ab'::citext, 'ac'::citext ) = 'ab' AS t; + t +--- + t +(1 row) + +SELECT citext_smaller( 'ABC'::citext, 'bbbb'::citext ) = 'ABC' AS t; + t +--- + t +(1 row) + +SELECT citext_smaller( 'aardvark'::citext, 'Aaba'::citext ) = 'Aaba' AS t; + t +--- + t +(1 row) + +SELECT citext_smaller( 'aardvark'::citext, 'AARDVARK'::citext ) = 'AARDVARK' AS t; + t +--- + t +(1 row) + +SELECT citext_larger( 'ab'::citext, 'ac'::citext ) = 'ac' AS t; + t +--- + t +(1 row) + +SELECT citext_larger( 'ABC'::citext, 'bbbb'::citext ) = 'bbbb' AS t; + t +--- + t +(1 row) + +SELECT citext_larger( 'aardvark'::citext, 'Aaba'::citext ) = 'aardvark' AS t; + t +--- + t +(1 row) + +-- Test aggregate functions and sort ordering +CREATE TEMP TABLE srt ( + name CITEXT +); +INSERT INTO srt (name) +VALUES ('abb'), + ('ABA'), + ('ABC'), + ('abd'); +CREATE INDEX srt_name ON srt (name); +-- Check the min() and max() aggregates, with and without index. +set enable_seqscan = off; +SELECT MIN(name) AS "ABA" FROM srt; + ABA +----- + ABA +(1 row) + +SELECT MAX(name) AS abd FROM srt; + abd +----- + abd +(1 row) + +reset enable_seqscan; +set enable_indexscan = off; +SELECT MIN(name) AS "ABA" FROM srt; + ABA +----- + ABA +(1 row) + +SELECT MAX(name) AS abd FROM srt; + abd +----- + abd +(1 row) + +reset enable_indexscan; +-- Check sorting likewise +set enable_seqscan = off; +SELECT name FROM srt ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +reset enable_seqscan; +set enable_indexscan = off; +SELECT name FROM srt ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +reset enable_indexscan; +-- Test assignment casts. +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::text; + aba +----- + aba +(1 row) + +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::varchar; + aba +----- + aba +(1 row) + +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::bpchar; + aba +----- + aba +(1 row) + +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'; + aba +----- + aba +(1 row) + +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::citext; + aba +----- + aba +(1 row) + +-- LIKE should be case-insensitive +SELECT name FROM srt WHERE name LIKE '%a%' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name NOT LIKE '%b%' ORDER BY name; + name +------ +(0 rows) + +SELECT name FROM srt WHERE name LIKE '%A%' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name NOT LIKE '%B%' ORDER BY name; + name +------ +(0 rows) + +-- ~~ should be case-insensitive +SELECT name FROM srt WHERE name ~~ '%a%' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name !~~ '%b%' ORDER BY name; + name +------ +(0 rows) + +SELECT name FROM srt WHERE name ~~ '%A%' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name !~~ '%B%' ORDER BY name; + name +------ +(0 rows) + +-- ~ should be case-insensitive +SELECT name FROM srt WHERE name ~ '^a' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name !~ 'a$' ORDER BY name; + name +------ + abb + ABC + abd +(3 rows) + +SELECT name FROM srt WHERE name ~ '^A' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name !~ 'A$' ORDER BY name; + name +------ + abb + ABC + abd +(3 rows) + +-- SIMILAR TO should be case-insensitive. +SELECT name FROM srt WHERE name SIMILAR TO '%a.*'; + name +------ + ABA +(1 row) + +SELECT name FROM srt WHERE name SIMILAR TO '%A.*'; + name +------ + ABA +(1 row) + +-- Explicit casts. +SELECT true::citext = 'true' AS t; + t +--- + t +(1 row) + +SELECT 'true'::citext::boolean = true AS t; + t +--- + t +(1 row) + +SELECT 4::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT 4::int4::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::int4 = 4 AS t; + t +--- + t +(1 row) + +SELECT 4::integer::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::integer = 4 AS t; + t +--- + t +(1 row) + +SELECT 4::int8::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::int8 = 4 AS t; + t +--- + t +(1 row) + +SELECT 4::bigint::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::bigint = 4 AS t; + t +--- + t +(1 row) + +SELECT 4::int2::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::int2 = 4 AS t; + t +--- + t +(1 row) + +SELECT 4::smallint::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::smallint = 4 AS t; + t +--- + t +(1 row) + +SELECT 4.0::numeric = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::numeric = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 4.0::decimal = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::decimal = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 4.0::real = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::real = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 4.0::float4 = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::float4 = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 4.0::double precision = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::double precision = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 4.0::float8 = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::float8 = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 'foo'::name::citext = 'foo' AS t; + t +--- + t +(1 row) + +SELECT 'foo'::citext::name = 'foo'::name AS t; + t +--- + t +(1 row) + +SELECT 'f'::char::citext = 'f' AS t; + t +--- + t +(1 row) + +SELECT 'f'::citext::char = 'f'::char AS t; + t +--- + t +(1 row) + +SELECT 'f'::"char"::citext = 'f' AS t; + t +--- + t +(1 row) + +SELECT 'f'::citext::"char" = 'f'::"char" AS t; + t +--- + t +(1 row) + +SELECT '100'::money::citext = '$100.00' AS t; + t +--- + t +(1 row) + +SELECT '100'::citext::money = '100'::money AS t; + t +--- + t +(1 row) + +SELECT 'a'::char::citext = 'a' AS t; + t +--- + t +(1 row) + +SELECT 'a'::citext::char = 'a'::char AS t; + t +--- + t +(1 row) + +SELECT 'foo'::varchar::citext = 'foo' AS t; + t +--- + t +(1 row) + +SELECT 'foo'::citext::varchar = 'foo'::varchar AS t; + t +--- + t +(1 row) + +SELECT 'foo'::text::citext = 'foo' AS t; + t +--- + t +(1 row) + +SELECT 'foo'::citext::text = 'foo'::text AS t; + t +--- + t +(1 row) + +SELECT '192.168.100.128/25'::cidr::citext = '192.168.100.128/25' AS t; + t +--- + t +(1 row) + +SELECT '192.168.100.128/25'::citext::cidr = '192.168.100.128/25'::cidr AS t; + t +--- + t +(1 row) + +SELECT '192.168.100.128'::inet::citext = '192.168.100.128/32' AS t; + t +--- + t +(1 row) + +SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t; + t +--- + t +(1 row) + +SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t; + t +--- + t +(1 row) + +SELECT '08:00:2b:01:02:03'::citext::macaddr = '08:00:2b:01:02:03'::macaddr AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08 04:05:06'::timestamp::citext = '1999-01-08 04:05:06'::timestamp::text AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08 04:05:06'::citext::timestamp = '1999-01-08 04:05:06'::timestamp AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08 04:05:06'::timestamptz::citext = '1999-01-08 04:05:06'::timestamptz::text AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08 04:05:06'::citext::timestamptz = '1999-01-08 04:05:06'::timestamptz AS t; + t +--- + t +(1 row) + +SELECT '1 hour'::interval::citext = '1 hour'::interval::text AS t; + t +--- + t +(1 row) + +SELECT '1 hour'::citext::interval = '1 hour'::interval AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08'::date::citext = '1999-01-08'::date::text AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08'::citext::date = '1999-01-08'::date AS t; + t +--- + t +(1 row) + +SELECT '04:05:06'::time::citext = '04:05:06' AS t; + t +--- + t +(1 row) + +SELECT '04:05:06'::citext::time = '04:05:06'::time AS t; + t +--- + t +(1 row) + +SELECT '04:05:06'::timetz::citext = '04:05:06'::timetz::text AS t; + t +--- + t +(1 row) + +SELECT '04:05:06'::citext::timetz = '04:05:06'::timetz AS t; + t +--- + t +(1 row) + +SELECT '( 1 , 1)'::point::citext = '(1,1)' AS t; + t +--- + t +(1 row) + +SELECT '( 1 , 1)'::citext::point ~= '(1,1)'::point AS t; + t +--- + t +(1 row) + +SELECT '( 1 , 1 ) , ( 2 , 2 )'::lseg::citext = '[(1,1),(2,2)]' AS t; + t +--- + t +(1 row) + +SELECT '( 1 , 1 ) , ( 2 , 2 )'::citext::lseg = '[(1,1),(2,2)]'::lseg AS t; + t +--- + t +(1 row) + +SELECT '( 0 , 0 ) , ( 1 , 1 )'::box::citext = '(0,0),(1,1)'::box::text AS t; + t +--- + t +(1 row) + +SELECT '( 0 , 0 ) , ( 1 , 1 )'::citext::box ~= '(0,0),(1,1)'::text::box AS t; + t +--- + t +(1 row) + +SELECT '((0,0),(1,1),(2,0))'::path::citext = '((0,0),(1,1),(2,0))' AS t; + t +--- + t +(1 row) + +SELECT '((0,0),(1,1),(2,0))'::citext::path = '((0,0),(1,1),(2,0))'::path AS t; + t +--- + t +(1 row) + +SELECT '((0,0),(1,1))'::polygon::citext = '((0,0),(1,1))' AS t; + t +--- + t +(1 row) + +SELECT '((0,0),(1,1))'::citext::polygon ~= '((0,0),(1,1))'::polygon AS t; + t +--- + t +(1 row) + +SELECT '((0,0),2)'::circle::citext = '((0,0),2)'::circle::text AS t; + t +--- + t +(1 row) + +SELECT '((0,0),2)'::citext::circle ~= '((0,0),2)'::text::circle AS t; + t +--- + t +(1 row) + +SELECT '101'::bit::citext = '101'::bit::text AS t; + t +--- + t +(1 row) + +SELECT '101'::citext::bit = '101'::text::bit AS t; + t +--- + t +(1 row) + +SELECT '101'::bit varying::citext = '101'::bit varying::text AS t; + t +--- + t +(1 row) + +SELECT '101'::citext::bit varying = '101'::text::bit varying AS t; + t +--- + t +(1 row) + +SELECT 'a fat cat'::tsvector::citext = '''a'' ''cat'' ''fat''' AS t; + t +--- + t +(1 row) + +SELECT 'a fat cat'::citext::tsvector = 'a fat cat'::tsvector AS t; + t +--- + t +(1 row) + +SELECT 'fat & rat'::tsquery::citext = '''fat'' & ''rat''' AS t; + t +--- + t +(1 row) + +SELECT 'fat & rat'::citext::tsquery = 'fat & rat'::tsquery AS t; + t +--- + t +(1 row) + +SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid::citext = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11' AS t; + t +--- + t +(1 row) + +SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::citext::uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid AS t; + t +--- + t +(1 row) + +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); +SELECT 'sad'::mood::citext = 'sad' AS t; + t +--- + t +(1 row) + +SELECT 'sad'::citext::mood = 'sad'::mood AS t; + t +--- + t +(1 row) + +-- Assignment casts. +CREATE TABLE caster ( + citext citext, + text text, + varchar varchar, + bpchar bpchar, + char char, + chr "char", + name name, + bytea bytea, + boolean boolean, + float4 float4, + float8 float8, + numeric numeric, + int8 int8, + int4 int4, + int2 int2, + cidr cidr, + inet inet, + macaddr macaddr, + money money, + timestamp timestamp, + timestamptz timestamptz, + interval interval, + date date, + time time, + timetz timetz, + point point, + lseg lseg, + box box, + path path, + polygon polygon, + circle circle, + bit bit, + bitv bit varying, + tsvector tsvector, + tsquery tsquery, + uuid uuid +); +INSERT INTO caster (text) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::text); +INSERT INTO caster (varchar) VALUES ('foo'::text); +INSERT INTO caster (text) VALUES ('foo'::varchar); +INSERT INTO caster (varchar) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::varchar); +INSERT INTO caster (bpchar) VALUES ('foo'::text); +INSERT INTO caster (text) VALUES ('foo'::bpchar); +INSERT INTO caster (bpchar) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::bpchar); +INSERT INTO caster (char) VALUES ('f'::text); +INSERT INTO caster (text) VALUES ('f'::char); +INSERT INTO caster (char) VALUES ('f'::citext); +INSERT INTO caster (citext) VALUES ('f'::char); +INSERT INTO caster (chr) VALUES ('f'::text); +INSERT INTO caster (text) VALUES ('f'::"char"); +INSERT INTO caster (chr) VALUES ('f'::citext); -- requires cast +ERROR: column "chr" is of type "char" but expression is of type citext +LINE 1: INSERT INTO caster (chr) VALUES ('f'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (chr) VALUES ('f'::citext::text); +INSERT INTO caster (citext) VALUES ('f'::"char"); +INSERT INTO caster (name) VALUES ('foo'::text); +INSERT INTO caster (text) VALUES ('foo'::name); +INSERT INTO caster (name) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::name); +-- Cannot cast to bytea on assignment. +INSERT INTO caster (bytea) VALUES ('foo'::text); +ERROR: column "bytea" is of type bytea but expression is of type text +LINE 1: INSERT INTO caster (bytea) VALUES ('foo'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('foo'::bytea); +INSERT INTO caster (bytea) VALUES ('foo'::citext); +ERROR: column "bytea" is of type bytea but expression is of type citext +LINE 1: INSERT INTO caster (bytea) VALUES ('foo'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('foo'::bytea); +-- Cannot cast to boolean on assignment. +INSERT INTO caster (boolean) VALUES ('t'::text); +ERROR: column "boolean" is of type boolean but expression is of type text +LINE 1: INSERT INTO caster (boolean) VALUES ('t'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('t'::boolean); +INSERT INTO caster (boolean) VALUES ('t'::citext); +ERROR: column "boolean" is of type boolean but expression is of type citext +LINE 1: INSERT INTO caster (boolean) VALUES ('t'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('t'::boolean); +-- Cannot cast to float8 on assignment. +INSERT INTO caster (float8) VALUES ('12.42'::text); +ERROR: column "float8" is of type double precision but expression is of type text +LINE 1: INSERT INTO caster (float8) VALUES ('12.42'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12.42'::float8); +INSERT INTO caster (float8) VALUES ('12.42'::citext); +ERROR: column "float8" is of type double precision but expression is of type citext +LINE 1: INSERT INTO caster (float8) VALUES ('12.42'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12.42'::float8); +-- Cannot cast to float4 on assignment. +INSERT INTO caster (float4) VALUES ('12.42'::text); +ERROR: column "float4" is of type real but expression is of type text +LINE 1: INSERT INTO caster (float4) VALUES ('12.42'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12.42'::float4); +INSERT INTO caster (float4) VALUES ('12.42'::citext); +ERROR: column "float4" is of type real but expression is of type citext +LINE 1: INSERT INTO caster (float4) VALUES ('12.42'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12.42'::float4); +-- Cannot cast to numeric on assignment. +INSERT INTO caster (numeric) VALUES ('12.42'::text); +ERROR: column "numeric" is of type numeric but expression is of type text +LINE 1: INSERT INTO caster (numeric) VALUES ('12.42'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12.42'::numeric); +INSERT INTO caster (numeric) VALUES ('12.42'::citext); +ERROR: column "numeric" is of type numeric but expression is of type citext +LINE 1: INSERT INTO caster (numeric) VALUES ('12.42'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12.42'::numeric); +-- Cannot cast to int8 on assignment. +INSERT INTO caster (int8) VALUES ('12'::text); +ERROR: column "int8" is of type bigint but expression is of type text +LINE 1: INSERT INTO caster (int8) VALUES ('12'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12'::int8); +INSERT INTO caster (int8) VALUES ('12'::citext); +ERROR: column "int8" is of type bigint but expression is of type citext +LINE 1: INSERT INTO caster (int8) VALUES ('12'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12'::int8); +-- Cannot cast to int4 on assignment. +INSERT INTO caster (int4) VALUES ('12'::text); +ERROR: column "int4" is of type integer but expression is of type text +LINE 1: INSERT INTO caster (int4) VALUES ('12'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12'::int4); +INSERT INTO caster (int4) VALUES ('12'::citext); +ERROR: column "int4" is of type integer but expression is of type citext +LINE 1: INSERT INTO caster (int4) VALUES ('12'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12'::int4); +-- Cannot cast to int2 on assignment. +INSERT INTO caster (int2) VALUES ('12'::text); +ERROR: column "int2" is of type smallint but expression is of type text +LINE 1: INSERT INTO caster (int2) VALUES ('12'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12'::int2); +INSERT INTO caster (int2) VALUES ('12'::citext); +ERROR: column "int2" is of type smallint but expression is of type citext +LINE 1: INSERT INTO caster (int2) VALUES ('12'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12'::int2); +-- Cannot cast to cidr on assignment. +INSERT INTO caster (cidr) VALUES ('192.168.100.128/25'::text); +ERROR: column "cidr" is of type cidr but expression is of type text +LINE 1: INSERT INTO caster (cidr) VALUES ('192.168.100.128/... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('192.168.100.128/25'::cidr); +INSERT INTO caster (cidr) VALUES ('192.168.100.128/25'::citext); +ERROR: column "cidr" is of type cidr but expression is of type citext +LINE 1: INSERT INTO caster (cidr) VALUES ('192.168.100.128/... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('192.168.100.128/25'::cidr); +-- Cannot cast to inet on assignment. +INSERT INTO caster (inet) VALUES ('192.168.100.128'::text); +ERROR: column "inet" is of type inet but expression is of type text +LINE 1: INSERT INTO caster (inet) VALUES ('192.168.100.128'... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('192.168.100.128'::inet); +INSERT INTO caster (inet) VALUES ('192.168.100.128'::citext); +ERROR: column "inet" is of type inet but expression is of type citext +LINE 1: INSERT INTO caster (inet) VALUES ('192.168.100.128'... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('192.168.100.128'::inet); +-- Cannot cast to macaddr on assignment. +INSERT INTO caster (macaddr) VALUES ('08:00:2b:01:02:03'::text); +ERROR: column "macaddr" is of type macaddr but expression is of type text +LINE 1: INSERT INTO caster (macaddr) VALUES ('08:00:2b:01:02:0... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('08:00:2b:01:02:03'::macaddr); +INSERT INTO caster (macaddr) VALUES ('08:00:2b:01:02:03'::citext); +ERROR: column "macaddr" is of type macaddr but expression is of type citext +LINE 1: INSERT INTO caster (macaddr) VALUES ('08:00:2b:01:02:0... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('08:00:2b:01:02:03'::macaddr); +-- Cannot cast to money on assignment. +INSERT INTO caster (money) VALUES ('12'::text); +ERROR: column "money" is of type money but expression is of type text +LINE 1: INSERT INTO caster (money) VALUES ('12'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12'::money); +INSERT INTO caster (money) VALUES ('12'::citext); +ERROR: column "money" is of type money but expression is of type citext +LINE 1: INSERT INTO caster (money) VALUES ('12'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12'::money); +-- Cannot cast to timestamp on assignment. +INSERT INTO caster (timestamp) VALUES ('1999-01-08 04:05:06'::text); +ERROR: column "timestamp" is of type timestamp without time zone but expression is of type text +LINE 1: INSERT INTO caster (timestamp) VALUES ('1999-01-08 04:05... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('1999-01-08 04:05:06'::timestamp); +INSERT INTO caster (timestamp) VALUES ('1999-01-08 04:05:06'::citext); +ERROR: column "timestamp" is of type timestamp without time zone but expression is of type citext +LINE 1: INSERT INTO caster (timestamp) VALUES ('1999-01-08 04:05... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('1999-01-08 04:05:06'::timestamp); +-- Cannot cast to timestamptz on assignment. +INSERT INTO caster (timestamptz) VALUES ('1999-01-08 04:05:06'::text); +ERROR: column "timestamptz" is of type timestamp with time zone but expression is of type text +LINE 1: INSERT INTO caster (timestamptz) VALUES ('1999-01-08 04:05... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('1999-01-08 04:05:06'::timestamptz); +INSERT INTO caster (timestamptz) VALUES ('1999-01-08 04:05:06'::citext); +ERROR: column "timestamptz" is of type timestamp with time zone but expression is of type citext +LINE 1: INSERT INTO caster (timestamptz) VALUES ('1999-01-08 04:05... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('1999-01-08 04:05:06'::timestamptz); +-- Cannot cast to interval on assignment. +INSERT INTO caster (interval) VALUES ('1 hour'::text); +ERROR: column "interval" is of type interval but expression is of type text +LINE 1: INSERT INTO caster (interval) VALUES ('1 hour'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('1 hour'::interval); +INSERT INTO caster (interval) VALUES ('1 hour'::citext); +ERROR: column "interval" is of type interval but expression is of type citext +LINE 1: INSERT INTO caster (interval) VALUES ('1 hour'::citext)... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('1 hour'::interval); +-- Cannot cast to date on assignment. +INSERT INTO caster (date) VALUES ('1999-01-08'::text); +ERROR: column "date" is of type date but expression is of type text +LINE 1: INSERT INTO caster (date) VALUES ('1999-01-08'::tex... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('1999-01-08'::date); +INSERT INTO caster (date) VALUES ('1999-01-08'::citext); +ERROR: column "date" is of type date but expression is of type citext +LINE 1: INSERT INTO caster (date) VALUES ('1999-01-08'::cit... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('1999-01-08'::date); +-- Cannot cast to time on assignment. +INSERT INTO caster (time) VALUES ('04:05:06'::text); +ERROR: column "time" is of type time without time zone but expression is of type text +LINE 1: INSERT INTO caster (time) VALUES ('04:05:06'::text)... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('04:05:06'::time); +INSERT INTO caster (time) VALUES ('04:05:06'::citext); +ERROR: column "time" is of type time without time zone but expression is of type citext +LINE 1: INSERT INTO caster (time) VALUES ('04:05:06'::citex... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('04:05:06'::time); +-- Cannot cast to timetz on assignment. +INSERT INTO caster (timetz) VALUES ('04:05:06'::text); +ERROR: column "timetz" is of type time with time zone but expression is of type text +LINE 1: INSERT INTO caster (timetz) VALUES ('04:05:06'::text)... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('04:05:06'::timetz); +INSERT INTO caster (timetz) VALUES ('04:05:06'::citext); +ERROR: column "timetz" is of type time with time zone but expression is of type citext +LINE 1: INSERT INTO caster (timetz) VALUES ('04:05:06'::citex... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('04:05:06'::timetz); +-- Cannot cast to point on assignment. +INSERT INTO caster (point) VALUES ('( 1 , 1)'::text); +ERROR: column "point" is of type point but expression is of type text +LINE 1: INSERT INTO caster (point) VALUES ('( 1 , 1)'::text)... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('( 1 , 1)'::point); +INSERT INTO caster (point) VALUES ('( 1 , 1)'::citext); +ERROR: column "point" is of type point but expression is of type citext +LINE 1: INSERT INTO caster (point) VALUES ('( 1 , 1)'::citex... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('( 1 , 1)'::point); +-- Cannot cast to lseg on assignment. +INSERT INTO caster (lseg) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::text); +ERROR: column "lseg" is of type lseg but expression is of type text +LINE 1: INSERT INTO caster (lseg) VALUES ('( 1 , 1 ) , ( 2 ... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::lseg); +INSERT INTO caster (lseg) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::citext); +ERROR: column "lseg" is of type lseg but expression is of type citext +LINE 1: INSERT INTO caster (lseg) VALUES ('( 1 , 1 ) , ( 2 ... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::lseg); +-- Cannot cast to box on assignment. +INSERT INTO caster (box) VALUES ('(0,0),(1,1)'::text); +ERROR: column "box" is of type box but expression is of type text +LINE 1: INSERT INTO caster (box) VALUES ('(0,0),(1,1)'::te... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('(0,0),(1,1)'::box); +INSERT INTO caster (box) VALUES ('(0,0),(1,1)'::citext); +ERROR: column "box" is of type box but expression is of type citext +LINE 1: INSERT INTO caster (box) VALUES ('(0,0),(1,1)'::ci... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('(0,0),(1,1)'::box); +-- Cannot cast to path on assignment. +INSERT INTO caster (path) VALUES ('((0,0),(1,1),(2,0))'::text); +ERROR: column "path" is of type path but expression is of type text +LINE 1: INSERT INTO caster (path) VALUES ('((0,0),(1,1),(2,... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('((0,0),(1,1),(2,0))'::path); +INSERT INTO caster (path) VALUES ('((0,0),(1,1),(2,0))'::citext); +ERROR: column "path" is of type path but expression is of type citext +LINE 1: INSERT INTO caster (path) VALUES ('((0,0),(1,1),(2,... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('((0,0),(1,1),(2,0))'::path); +-- Cannot cast to polygon on assignment. +INSERT INTO caster (polygon) VALUES ('((0,0),(1,1))'::text); +ERROR: column "polygon" is of type polygon but expression is of type text +LINE 1: INSERT INTO caster (polygon) VALUES ('((0,0),(1,1))'::... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('((0,0),(1,1))'::polygon); +INSERT INTO caster (polygon) VALUES ('((0,0),(1,1))'::citext); +ERROR: column "polygon" is of type polygon but expression is of type citext +LINE 1: INSERT INTO caster (polygon) VALUES ('((0,0),(1,1))'::... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('((0,0),(1,1))'::polygon); +-- Cannot cast to circle on assignment. +INSERT INTO caster (circle) VALUES ('((0,0),2)'::text); +ERROR: column "circle" is of type circle but expression is of type text +LINE 1: INSERT INTO caster (circle) VALUES ('((0,0),2)'::text... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('((0,0),2)'::circle); +INSERT INTO caster (circle) VALUES ('((0,0),2)'::citext); +ERROR: column "circle" is of type circle but expression is of type citext +LINE 1: INSERT INTO caster (circle) VALUES ('((0,0),2)'::cite... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('((0,0),2)'::circle); +-- Cannot cast to bit on assignment. +INSERT INTO caster (bit) VALUES ('101'::text); +ERROR: column "bit" is of type bit but expression is of type text +LINE 1: INSERT INTO caster (bit) VALUES ('101'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('101'::bit); +INSERT INTO caster (bit) VALUES ('101'::citext); +ERROR: column "bit" is of type bit but expression is of type citext +LINE 1: INSERT INTO caster (bit) VALUES ('101'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('101'::bit); +-- Cannot cast to bit varying on assignment. +INSERT INTO caster (bitv) VALUES ('101'::text); +ERROR: column "bitv" is of type bit varying but expression is of type text +LINE 1: INSERT INTO caster (bitv) VALUES ('101'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('101'::bit varying); +INSERT INTO caster (bitv) VALUES ('101'::citext); +ERROR: column "bitv" is of type bit varying but expression is of type citext +LINE 1: INSERT INTO caster (bitv) VALUES ('101'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('101'::bit varying); +-- Cannot cast to tsvector on assignment. +INSERT INTO caster (tsvector) VALUES ('the fat cat'::text); +ERROR: column "tsvector" is of type tsvector but expression is of type text +LINE 1: INSERT INTO caster (tsvector) VALUES ('the fat cat'::te... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('the fat cat'::tsvector); +INSERT INTO caster (tsvector) VALUES ('the fat cat'::citext); +ERROR: column "tsvector" is of type tsvector but expression is of type citext +LINE 1: INSERT INTO caster (tsvector) VALUES ('the fat cat'::ci... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('the fat cat'::tsvector); +-- Cannot cast to tsquery on assignment. +INSERT INTO caster (tsquery) VALUES ('fat & rat'::text); +ERROR: column "tsquery" is of type tsquery but expression is of type text +LINE 1: INSERT INTO caster (tsquery) VALUES ('fat & rat'::text... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('fat & rat'::tsquery); +INSERT INTO caster (tsquery) VALUES ('fat & rat'::citext); +ERROR: column "tsquery" is of type tsquery but expression is of type citext +LINE 1: INSERT INTO caster (tsquery) VALUES ('fat & rat'::cite... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('fat & rat'::tsquery); +-- Cannot cast to uuid on assignment. +INSERT INTO caster (uuid) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::text); +ERROR: column "uuid" is of type uuid but expression is of type text +LINE 1: INSERT INTO caster (uuid) VALUES ('a0eebc99-9c0b-4e... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid); +INSERT INTO caster (uuid) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::citext); +ERROR: column "uuid" is of type uuid but expression is of type citext +LINE 1: INSERT INTO caster (uuid) VALUES ('a0eebc99-9c0b-4e... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid); +-- Table 9-5. SQL String Functions and Operators +SELECT 'D'::citext || 'avid'::citext = 'David'::citext AS citext_concat; + citext_concat +--------------- + t +(1 row) + +SELECT 'Value: '::citext || 42 = 'Value: 42' AS text_concat; + text_concat +------------- + t +(1 row) + +SELECT 42 || ': value'::citext ='42: value' AS int_concat; + int_concat +------------ + t +(1 row) + +SELECT bit_length('jose'::citext) = 32 AS t; + t +--- + t +(1 row) + +SELECT bit_length( name ) = bit_length( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT textlen( name ) = textlen( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT char_length( name ) = char_length( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT lower( name ) = lower( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT octet_length( name ) = octet_length( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT overlay( name placing 'hom' from 2 for 4) = overlay( name::text placing 'hom' from 2 for 4) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT position( 'a' IN name ) = position( 'a' IN name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT substr('alphabet'::citext, 3) = 'phabet' AS t; + t +--- + t +(1 row) + +SELECT substr('alphabet'::citext, 3, 2) = 'ph' AS t; + t +--- + t +(1 row) + +SELECT substring('alphabet'::citext, 3) = 'phabet' AS t; + t +--- + t +(1 row) + +SELECT substring('alphabet'::citext, 3, 2) = 'ph' AS t; + t +--- + t +(1 row) + +SELECT substring('Thomas'::citext from 2 for 3) = 'hom' AS t; + t +--- + t +(1 row) + +SELECT substring('Thomas'::citext from 2) = 'homas' AS t; + t +--- + t +(1 row) + +SELECT substring('Thomas'::citext from '...$') = 'mas' AS t; + t +--- + t +(1 row) + +SELECT substring('Thomas'::citext similar '%#"o_a#"_' escape '#') = 'oma' AS t; + t +--- + t +(1 row) + +SELECT trim(' trim '::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT trim('xxxxxtrimxxxx'::citext, 'x'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT trim('xxxxxxtrimxxxx'::text, 'x'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT trim('xxxxxtrimxxxx'::text, 'x'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT upper( name ) = upper( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +-- Table 9-6. Other String Functions. +SELECT ascii( name ) = ascii( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT btrim(' trim'::citext ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT btrim('xxxxxtrimxxxx'::citext, 'x'::citext ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT btrim('xyxtrimyyx'::citext, 'xy'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT btrim('xyxtrimyyx'::text, 'xy'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT btrim('xyxtrimyyx'::citext, 'xy'::text ) = 'trim' AS t; + t +--- + t +(1 row) + +-- chr() takes an int and returns text. +-- convert() and convert_from take bytea and return text. +SELECT convert_from( name::bytea, 'SQL_ASCII' ) = convert_from( name::text::bytea, 'SQL_ASCII' ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT decode('MTIzAAE='::citext, 'base64') = decode('MTIzAAE='::text, 'base64') AS t; + t +--- + t +(1 row) + +-- encode() takes bytea and returns text. +SELECT initcap('hi THOMAS'::citext) = initcap('hi THOMAS'::text) AS t; + t +--- + t +(1 row) + +SELECT length( name ) = length( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT lpad('hi'::citext, 5 ) = ' hi' AS t; + t +--- + t +(1 row) + +SELECT lpad('hi'::citext, 5, 'xy'::citext) = 'xyxhi' AS t; + t +--- + t +(1 row) + +SELECT lpad('hi'::text, 5, 'xy'::citext) = 'xyxhi' AS t; + t +--- + t +(1 row) + +SELECT lpad('hi'::citext, 5, 'xy'::text ) = 'xyxhi' AS t; + t +--- + t +(1 row) + +SELECT ltrim(' trim'::citext ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT ltrim('zzzytrim'::citext, 'xyz'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT ltrim('zzzytrim'::text, 'xyz'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT ltrim('zzzytrim'::citext, 'xyz'::text ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT md5( name ) = md5( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +-- pg_client_encoding() takes no args and returns name. +SELECT quote_ident( name ) = quote_ident( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT quote_literal( name ) = quote_literal( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT regexp_match('foobarbequebaz'::citext, '(bar)(beque)') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext) = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)', '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz', '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, ''::citext) = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +-- c forces case-sensitive +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, 'c'::citext) = ARRAY[ 'bar', 'beque' ] AS "no result"; + no result +----------- + +(1 row) + +-- g is not allowed +SELECT regexp_match('foobarbequebazmorebarbequetoo'::citext, '(BAR)(BEQUE)'::citext, 'g') AS "error"; +ERROR: regexp_match() does not support the "global" option +HINT: Use the regexp_matches function instead. +CONTEXT: SQL function "regexp_match" statement 1 +SELECT regexp_matches('foobarbequebaz'::citext, '(bar)(beque)') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext) = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)', '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz', '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, ''::citext) = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +-- c forces case-sensitive +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, 'c'::citext) = ARRAY[ 'bar', 'beque' ] AS "no rows"; + no rows +--------- +(0 rows) + +-- g allows multiple output rows +SELECT regexp_matches('foobarbequebazmorebarbequetoo'::citext, '(BAR)(BEQUE)'::citext, 'g'::citext) AS "two rows"; + two rows +------------- + {bar,beque} + {bar,beque} +(2 rows) + +SELECT regexp_replace('Thomas'::citext, '.[mN]a.', 'M') = 'ThM' AS t; + t +--- + t +(1 row) + +SELECT regexp_replace('Thomas'::citext, '.[MN]A.', 'M') = 'ThM' AS t; + t +--- + t +(1 row) + +SELECT regexp_replace('Thomas', '.[MN]A.'::citext, 'M') = 'ThM' AS t; + t +--- + t +(1 row) + +SELECT regexp_replace('Thomas'::citext, '.[MN]A.'::citext, 'M') = 'ThM' AS t; + t +--- + t +(1 row) + +-- c forces case-sensitive +SELECT regexp_replace('Thomas'::citext, '.[MN]A.'::citext, 'M', 'c') = 'Thomas' AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('hello world'::citext, E'\\s+') = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld'::citext, 't') = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld', 't'::citext) = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld'::citext, 't'::citext) = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld'::citext, 't', 's') = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld', 't'::citext, 's') = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld'::citext, 't'::citext, 's') = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +-- c forces case-sensitive +SELECT regexp_split_to_array('helloTworld'::citext, 't'::citext, 'c') = ARRAY[ 'helloTworld' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_table('hello world'::citext, E'\\s+') AS words; + words +------- + hello + world +(2 rows) + +SELECT regexp_split_to_table('helloTworld'::citext, 't') AS words; + words +------- + hello + world +(2 rows) + +SELECT regexp_split_to_table('helloTworld', 't'::citext) AS words; + words +------- + hello + world +(2 rows) + +SELECT regexp_split_to_table('helloTworld'::citext, 't'::citext) AS words; + words +------- + hello + world +(2 rows) + +-- c forces case-sensitive +SELECT regexp_split_to_table('helloTworld'::citext, 't'::citext, 'c') AS word; + word +------------- + helloTworld +(1 row) + +SELECT repeat('Pg'::citext, 4) = 'PgPgPgPg' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef'::citext, 'cd', 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef'::citext, 'CD', 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('ab^is$abcdef'::citext, '^is$', 'XX') = 'abXXabcdef' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef', 'cd'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef', 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('ab^is$abcdef', '^is$'::citext, 'XX') = 'abXXabcdef' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef'::citext, 'cd'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef'::citext, 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('ab^is$abcdef'::citext, '^is$'::citext, 'XX') = 'abXXabcdef' AS t; + t +--- + t +(1 row) + +SELECT rpad('hi'::citext, 5 ) = 'hi ' AS t; + t +--- + t +(1 row) + +SELECT rpad('hi'::citext, 5, 'xy'::citext) = 'hixyx' AS t; + t +--- + t +(1 row) + +SELECT rpad('hi'::text, 5, 'xy'::citext) = 'hixyx' AS t; + t +--- + t +(1 row) + +SELECT rpad('hi'::citext, 5, 'xy'::text ) = 'hixyx' AS t; + t +--- + t +(1 row) + +SELECT rtrim('trim '::citext ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT rtrim('trimxxxx'::citext, 'x'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT rtrim('trimxxxx'::text, 'x'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT rtrim('trimxxxx'::text, 'x'::text ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT split_part('abc~@~def~@~ghi'::citext, '~@~', 2) = 'def' AS t; + t +--- + t +(1 row) + +SELECT split_part('abcTdefTghi'::citext, 't', 2) = 'def' AS t; + t +--- + t +(1 row) + +SELECT split_part('abcTdefTghi'::citext, 't'::citext, 2) = 'def' AS t; + t +--- + t +(1 row) + +SELECT split_part('abcTdefTghi', 't'::citext, 2) = 'def' AS t; + t +--- + t +(1 row) + +SELECT strpos('high'::citext, 'gh' ) = 3 AS t; + t +--- + t +(1 row) + +SELECT strpos('high', 'gh'::citext) = 3 AS t; + t +--- + t +(1 row) + +SELECT strpos('high'::citext, 'gh'::citext) = 3 AS t; + t +--- + t +(1 row) + +SELECT strpos('high'::citext, 'GH' ) = 3 AS t; + t +--- + t +(1 row) + +SELECT strpos('high', 'GH'::citext) = 3 AS t; + t +--- + t +(1 row) + +SELECT strpos('high'::citext, 'GH'::citext) = 3 AS t; + t +--- + t +(1 row) + +-- to_ascii() does not support UTF-8. +-- to_hex() takes a numeric argument. +SELECT substr('alphabet', 3, 2) = 'ph' AS t; + t +--- + t +(1 row) + +SELECT translate('abcdefabcdef'::citext, 'cd', 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT translate('abcdefabcdef'::citext, 'CD', 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT translate('abcdefabcdef'::citext, 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT translate('abcdefabcdef', 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +-- Table 9-20. Formatting Functions +SELECT to_date('05 Dec 2000'::citext, 'DD Mon YYYY'::citext) + = to_date('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +SELECT to_date('05 Dec 2000'::citext, 'DD Mon YYYY') + = to_date('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +SELECT to_date('05 Dec 2000', 'DD Mon YYYY'::citext) + = to_date('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +SELECT to_number('12,454.8-'::citext, '99G999D9S'::citext) + = to_number('12,454.8-', '99G999D9S') AS t; + t +--- + t +(1 row) + +SELECT to_number('12,454.8-'::citext, '99G999D9S') + = to_number('12,454.8-', '99G999D9S') AS t; + t +--- + t +(1 row) + +SELECT to_number('12,454.8-', '99G999D9S'::citext) + = to_number('12,454.8-', '99G999D9S') AS t; + t +--- + t +(1 row) + +SELECT to_timestamp('05 Dec 2000'::citext, 'DD Mon YYYY'::citext) + = to_timestamp('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +SELECT to_timestamp('05 Dec 2000'::citext, 'DD Mon YYYY') + = to_timestamp('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +SELECT to_timestamp('05 Dec 2000', 'DD Mon YYYY'::citext) + = to_timestamp('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +-- Try assigning function results to a column. +SELECT COUNT(*) = 8::bigint AS t FROM try; + t +--- + t +(1 row) + +INSERT INTO try +VALUES ( to_char( now()::timestamp, 'HH12:MI:SS') ), + ( to_char( now() + '1 sec'::interval, 'HH12:MI:SS') ), -- timestamptz + ( to_char( '15h 2m 12s'::interval, 'HH24:MI:SS') ), + ( to_char( current_date, '999') ), + ( to_char( 125::int, '999') ), + ( to_char( 127::int4, '999') ), + ( to_char( 126::int8, '999') ), + ( to_char( 128.8::real, '999D9') ), + ( to_char( 125.7::float4, '999D9') ), + ( to_char( 125.9::float8, '999D9') ), + ( to_char( -125.8::numeric, '999D99S') ); +SELECT COUNT(*) = 19::bigint AS t FROM try; + t +--- + t +(1 row) + +SELECT like_escape( name, '' ) = like_escape( name::text, '' ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT like_escape( name::text, ''::citext ) = like_escape( name::text, '' ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +-- Ensure correct behavior for citext with materialized views. +CREATE TABLE citext_table ( + id serial primary key, + name citext +); +INSERT INTO citext_table (name) + VALUES ('one'), ('two'), ('three'), (NULL), (NULL); +CREATE MATERIALIZED VIEW citext_matview AS + SELECT * FROM citext_table; +CREATE UNIQUE INDEX citext_matview_id + ON citext_matview (id); +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; + id | name | id | name +----+------+----+------ +(0 rows) + +UPDATE citext_table SET name = 'Two' WHERE name = 'TWO'; +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; + id | name | id | name +----+------+----+------ + | | 2 | Two + 2 | two | | +(2 rows) + +REFRESH MATERIALIZED VIEW CONCURRENTLY citext_matview; +SELECT * FROM citext_matview ORDER BY id; + id | name +----+------- + 1 | one + 2 | Two + 3 | three + 4 | + 5 | +(5 rows) + +-- test citext_pattern_cmp() function explicitly. +SELECT citext_pattern_cmp('aardvark'::citext, 'aardvark'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_pattern_cmp('aardvark'::citext, 'aardVark'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_pattern_cmp('AARDVARK'::citext, 'AARDVARK'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_pattern_cmp('B'::citext, 'a'::citext) > 0 AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_cmp('a'::citext, 'B'::citext) < 0 AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_cmp('A'::citext, 'b'::citext) < 0 AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_cmp('ABCD'::citext, 'abc'::citext) > 0 AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_cmp('ABC'::citext, 'abcd'::citext) < 0 AS true; + true +------ + t +(1 row) + +-- test operator functions +-- lt +SELECT citext_pattern_lt('a'::citext, 'b'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_lt('A'::citext, 'b'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_lt('a'::citext, 'B'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_lt('b'::citext, 'a'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_lt('B'::citext, 'a'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_lt('b'::citext, 'A'::citext) AS false; + false +------- + f +(1 row) + +-- le +SELECT citext_pattern_le('a'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('a'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('A'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('A'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('a'::citext, 'B'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('A'::citext, 'b'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('a'::citext, 'B'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('b'::citext, 'a'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_le('B'::citext, 'a'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_le('b'::citext, 'A'::citext) AS false; + false +------- + f +(1 row) + +-- gt +SELECT citext_pattern_gt('a'::citext, 'b'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_gt('A'::citext, 'b'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_gt('a'::citext, 'B'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_gt('b'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_gt('B'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_gt('b'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +-- ge +SELECT citext_pattern_ge('a'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('a'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('A'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('A'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('a'::citext, 'B'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_ge('A'::citext, 'b'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_ge('a'::citext, 'B'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_ge('b'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('B'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('b'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +-- Test ~<~ and ~<=~ +SELECT 'a'::citext ~<~ 'B'::citext AS t; + t +--- + t +(1 row) + +SELECT 'b'::citext ~<~ 'A'::citext AS f; + f +--- + f +(1 row) + +SELECT 'a'::citext ~<=~ 'B'::citext AS t; + t +--- + t +(1 row) + +SELECT 'a'::citext ~<=~ 'A'::citext AS t; + t +--- + t +(1 row) + +-- Test ~>~ and ~>=~ +SELECT 'B'::citext ~>~ 'a'::citext AS t; + t +--- + t +(1 row) + +SELECT 'b'::citext ~>~ 'A'::citext AS t; + t +--- + t +(1 row) + +SELECT 'B'::citext ~>~ 'b'::citext AS f; + f +--- + f +(1 row) + +SELECT 'B'::citext ~>=~ 'b'::citext AS t; + t +--- + t +(1 row) + +-- Test implicit casting. citext casts to text, but not vice-versa. +SELECT 'B'::citext ~<~ 'a'::text AS t; -- text wins. + t +--- + t +(1 row) + +SELECT 'B'::citext ~<=~ 'a'::text AS t; -- text wins. + t +--- + t +(1 row) + +SELECT 'a'::citext ~>~ 'B'::text AS t; -- text wins. + t +--- + t +(1 row) + +SELECT 'a'::citext ~>=~ 'B'::text AS t; -- text wins. + t +--- + t +(1 row) + +-- Test implicit casting. citext casts to varchar, but not vice-versa. +SELECT 'B'::citext ~<~ 'a'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + +SELECT 'B'::citext ~<=~ 'a'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + +SELECT 'a'::citext ~>~ 'B'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + +SELECT 'a'::citext ~>=~ 'B'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + diff --git a/contrib/citext/expected/citext_1.out b/contrib/citext/expected/citext_1.out new file mode 100644 index 0000000..4a979d7 --- /dev/null +++ b/contrib/citext/expected/citext_1.out @@ -0,0 +1,2693 @@ +-- +-- Test citext datatype +-- +CREATE EXTENSION citext; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + amname | opcname +--------+--------- +(0 rows) + +-- Test the operators and indexing functions +-- Test = and <>. +SELECT 'a'::citext = 'a'::citext AS t; + t +--- + t +(1 row) + +SELECT 'a'::citext = 'A'::citext AS t; + t +--- + t +(1 row) + +SELECT 'a'::citext = 'A'::text AS f; -- text wins the discussion + f +--- + f +(1 row) + +SELECT 'a'::citext = 'b'::citext AS f; + f +--- + f +(1 row) + +SELECT 'a'::citext = 'ab'::citext AS f; + f +--- + f +(1 row) + +SELECT 'a'::citext <> 'ab'::citext AS t; + t +--- + t +(1 row) + +-- Test > and >= +SELECT 'B'::citext > 'a'::citext AS t; + t +--- + t +(1 row) + +SELECT 'b'::citext > 'A'::citext AS t; + t +--- + t +(1 row) + +SELECT 'B'::citext > 'b'::citext AS f; + f +--- + f +(1 row) + +SELECT 'B'::citext >= 'b'::citext AS t; + t +--- + t +(1 row) + +-- Test < and <= +SELECT 'a'::citext < 'B'::citext AS t; + t +--- + t +(1 row) + +SELECT 'a'::citext <= 'B'::citext AS t; + t +--- + t +(1 row) + +-- Test implicit casting. citext casts to text, but not vice-versa. +SELECT 'a'::citext = 'a'::text AS t; + t +--- + t +(1 row) + +SELECT 'A'::text <> 'a'::citext AS t; + t +--- + t +(1 row) + +SELECT 'B'::citext < 'a'::text AS t; -- text wins. + t +--- + f +(1 row) + +SELECT 'B'::citext <= 'a'::text AS t; -- text wins. + t +--- + f +(1 row) + +SELECT 'a'::citext > 'B'::text AS t; -- text wins. + t +--- + f +(1 row) + +SELECT 'a'::citext >= 'B'::text AS t; -- text wins. + t +--- + f +(1 row) + +-- Test implicit casting. citext casts to varchar, but not vice-versa. +SELECT 'a'::citext = 'a'::varchar AS t; + t +--- + t +(1 row) + +SELECT 'A'::varchar <> 'a'::citext AS t; + t +--- + t +(1 row) + +SELECT 'B'::citext < 'a'::varchar AS t; -- varchar wins. + t +--- + f +(1 row) + +SELECT 'B'::citext <= 'a'::varchar AS t; -- varchar wins. + t +--- + f +(1 row) + +SELECT 'a'::citext > 'B'::varchar AS t; -- varchar wins. + t +--- + f +(1 row) + +SELECT 'a'::citext >= 'B'::varchar AS t; -- varchar wins. + t +--- + f +(1 row) + +-- A couple of longer examples to ensure that we don't get any issues with bad +-- conversions to char[] in the c code. Yes, I did do this. +SELECT 'aardvark'::citext = 'aardvark'::citext AS t; + t +--- + t +(1 row) + +SELECT 'aardvark'::citext = 'aardVark'::citext AS t; + t +--- + t +(1 row) + +-- Check the citext_cmp() function explicitly. +SELECT citext_cmp('aardvark'::citext, 'aardvark'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_cmp('aardvark'::citext, 'aardVark'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_cmp('AARDVARK'::citext, 'AARDVARK'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_cmp('B'::citext, 'a'::citext) > 0 AS true; + true +------ + t +(1 row) + +-- Check the citext_hash() and citext_hash_extended() function explicitly. +SELECT v as value, citext_hash(v)::bit(32) as standard, + citext_hash_extended(v, 0)::bit(32) as extended0, + citext_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::citext), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), + ('muop28x03'), ('yi3nm0d73')) x(v) +WHERE citext_hash(v)::bit(32) != citext_hash_extended(v, 0)::bit(32) + OR citext_hash(v)::bit(32) = citext_hash_extended(v, 1)::bit(32); + value | standard | extended0 | extended1 +-------+----------+-----------+----------- +(0 rows) + +-- Do some tests using a table and index. +CREATE TEMP TABLE try ( + name citext PRIMARY KEY +); +INSERT INTO try (name) +VALUES ('a'), ('ab'), ('â'), ('aba'), ('b'), ('ba'), ('bab'), ('AZ'); +SELECT name, 'a' = name AS eq_a FROM try WHERE name <> 'â'; + name | eq_a +------+------ + a | t + ab | f + aba | f + b | f + ba | f + bab | f + AZ | f +(7 rows) + +SELECT name, 'a' = name AS t FROM try where name = 'a'; + name | t +------+--- + a | t +(1 row) + +SELECT name, 'A' = name AS "eq_A" FROM try WHERE name <> 'â'; + name | eq_A +------+------ + a | t + ab | f + aba | f + b | f + ba | f + bab | f + AZ | f +(7 rows) + +SELECT name, 'A' = name AS t FROM try where name = 'A'; + name | t +------+--- + a | t +(1 row) + +SELECT name, 'A' = name AS t FROM try where name = 'A'; + name | t +------+--- + a | t +(1 row) + +-- expected failures on duplicate key +INSERT INTO try (name) VALUES ('a'); +ERROR: duplicate key value violates unique constraint "try_pkey" +DETAIL: Key (name)=(a) already exists. +INSERT INTO try (name) VALUES ('A'); +ERROR: duplicate key value violates unique constraint "try_pkey" +DETAIL: Key (name)=(A) already exists. +INSERT INTO try (name) VALUES ('aB'); +ERROR: duplicate key value violates unique constraint "try_pkey" +DETAIL: Key (name)=(aB) already exists. +-- Make sure that citext_smaller() and citext_larger() work properly. +SELECT citext_smaller( 'ab'::citext, 'ac'::citext ) = 'ab' AS t; + t +--- + t +(1 row) + +SELECT citext_smaller( 'ABC'::citext, 'bbbb'::citext ) = 'ABC' AS t; + t +--- + t +(1 row) + +SELECT citext_smaller( 'aardvark'::citext, 'Aaba'::citext ) = 'Aaba' AS t; + t +--- + t +(1 row) + +SELECT citext_smaller( 'aardvark'::citext, 'AARDVARK'::citext ) = 'AARDVARK' AS t; + t +--- + t +(1 row) + +SELECT citext_larger( 'ab'::citext, 'ac'::citext ) = 'ac' AS t; + t +--- + t +(1 row) + +SELECT citext_larger( 'ABC'::citext, 'bbbb'::citext ) = 'bbbb' AS t; + t +--- + t +(1 row) + +SELECT citext_larger( 'aardvark'::citext, 'Aaba'::citext ) = 'aardvark' AS t; + t +--- + t +(1 row) + +-- Test aggregate functions and sort ordering +CREATE TEMP TABLE srt ( + name CITEXT +); +INSERT INTO srt (name) +VALUES ('abb'), + ('ABA'), + ('ABC'), + ('abd'); +CREATE INDEX srt_name ON srt (name); +-- Check the min() and max() aggregates, with and without index. +set enable_seqscan = off; +SELECT MIN(name) AS "ABA" FROM srt; + ABA +----- + ABA +(1 row) + +SELECT MAX(name) AS abd FROM srt; + abd +----- + abd +(1 row) + +reset enable_seqscan; +set enable_indexscan = off; +SELECT MIN(name) AS "ABA" FROM srt; + ABA +----- + ABA +(1 row) + +SELECT MAX(name) AS abd FROM srt; + abd +----- + abd +(1 row) + +reset enable_indexscan; +-- Check sorting likewise +set enable_seqscan = off; +SELECT name FROM srt ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +reset enable_seqscan; +set enable_indexscan = off; +SELECT name FROM srt ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +reset enable_indexscan; +-- Test assignment casts. +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::text; + aba +----- + aba +(1 row) + +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::varchar; + aba +----- + aba +(1 row) + +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::bpchar; + aba +----- + aba +(1 row) + +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'; + aba +----- + aba +(1 row) + +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::citext; + aba +----- + aba +(1 row) + +-- LIKE should be case-insensitive +SELECT name FROM srt WHERE name LIKE '%a%' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name NOT LIKE '%b%' ORDER BY name; + name +------ +(0 rows) + +SELECT name FROM srt WHERE name LIKE '%A%' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name NOT LIKE '%B%' ORDER BY name; + name +------ +(0 rows) + +-- ~~ should be case-insensitive +SELECT name FROM srt WHERE name ~~ '%a%' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name !~~ '%b%' ORDER BY name; + name +------ +(0 rows) + +SELECT name FROM srt WHERE name ~~ '%A%' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name !~~ '%B%' ORDER BY name; + name +------ +(0 rows) + +-- ~ should be case-insensitive +SELECT name FROM srt WHERE name ~ '^a' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name !~ 'a$' ORDER BY name; + name +------ + abb + ABC + abd +(3 rows) + +SELECT name FROM srt WHERE name ~ '^A' ORDER BY name; + name +------ + ABA + abb + ABC + abd +(4 rows) + +SELECT name FROM srt WHERE name !~ 'A$' ORDER BY name; + name +------ + abb + ABC + abd +(3 rows) + +-- SIMILAR TO should be case-insensitive. +SELECT name FROM srt WHERE name SIMILAR TO '%a.*'; + name +------ + ABA +(1 row) + +SELECT name FROM srt WHERE name SIMILAR TO '%A.*'; + name +------ + ABA +(1 row) + +-- Explicit casts. +SELECT true::citext = 'true' AS t; + t +--- + t +(1 row) + +SELECT 'true'::citext::boolean = true AS t; + t +--- + t +(1 row) + +SELECT 4::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT 4::int4::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::int4 = 4 AS t; + t +--- + t +(1 row) + +SELECT 4::integer::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::integer = 4 AS t; + t +--- + t +(1 row) + +SELECT 4::int8::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::int8 = 4 AS t; + t +--- + t +(1 row) + +SELECT 4::bigint::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::bigint = 4 AS t; + t +--- + t +(1 row) + +SELECT 4::int2::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::int2 = 4 AS t; + t +--- + t +(1 row) + +SELECT 4::smallint::citext = '4' AS t; + t +--- + t +(1 row) + +SELECT '4'::citext::smallint = 4 AS t; + t +--- + t +(1 row) + +SELECT 4.0::numeric = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::numeric = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 4.0::decimal = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::decimal = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 4.0::real = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::real = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 4.0::float4 = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::float4 = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 4.0::double precision = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::double precision = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 4.0::float8 = '4.0' AS t; + t +--- + t +(1 row) + +SELECT '4.0'::citext::float8 = 4.0 AS t; + t +--- + t +(1 row) + +SELECT 'foo'::name::citext = 'foo' AS t; + t +--- + t +(1 row) + +SELECT 'foo'::citext::name = 'foo'::name AS t; + t +--- + t +(1 row) + +SELECT 'f'::char::citext = 'f' AS t; + t +--- + t +(1 row) + +SELECT 'f'::citext::char = 'f'::char AS t; + t +--- + t +(1 row) + +SELECT 'f'::"char"::citext = 'f' AS t; + t +--- + t +(1 row) + +SELECT 'f'::citext::"char" = 'f'::"char" AS t; + t +--- + t +(1 row) + +SELECT '100'::money::citext = '$100.00' AS t; + t +--- + t +(1 row) + +SELECT '100'::citext::money = '100'::money AS t; + t +--- + t +(1 row) + +SELECT 'a'::char::citext = 'a' AS t; + t +--- + t +(1 row) + +SELECT 'a'::citext::char = 'a'::char AS t; + t +--- + t +(1 row) + +SELECT 'foo'::varchar::citext = 'foo' AS t; + t +--- + t +(1 row) + +SELECT 'foo'::citext::varchar = 'foo'::varchar AS t; + t +--- + t +(1 row) + +SELECT 'foo'::text::citext = 'foo' AS t; + t +--- + t +(1 row) + +SELECT 'foo'::citext::text = 'foo'::text AS t; + t +--- + t +(1 row) + +SELECT '192.168.100.128/25'::cidr::citext = '192.168.100.128/25' AS t; + t +--- + t +(1 row) + +SELECT '192.168.100.128/25'::citext::cidr = '192.168.100.128/25'::cidr AS t; + t +--- + t +(1 row) + +SELECT '192.168.100.128'::inet::citext = '192.168.100.128/32' AS t; + t +--- + t +(1 row) + +SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t; + t +--- + t +(1 row) + +SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t; + t +--- + t +(1 row) + +SELECT '08:00:2b:01:02:03'::citext::macaddr = '08:00:2b:01:02:03'::macaddr AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08 04:05:06'::timestamp::citext = '1999-01-08 04:05:06'::timestamp::text AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08 04:05:06'::citext::timestamp = '1999-01-08 04:05:06'::timestamp AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08 04:05:06'::timestamptz::citext = '1999-01-08 04:05:06'::timestamptz::text AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08 04:05:06'::citext::timestamptz = '1999-01-08 04:05:06'::timestamptz AS t; + t +--- + t +(1 row) + +SELECT '1 hour'::interval::citext = '1 hour'::interval::text AS t; + t +--- + t +(1 row) + +SELECT '1 hour'::citext::interval = '1 hour'::interval AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08'::date::citext = '1999-01-08'::date::text AS t; + t +--- + t +(1 row) + +SELECT '1999-01-08'::citext::date = '1999-01-08'::date AS t; + t +--- + t +(1 row) + +SELECT '04:05:06'::time::citext = '04:05:06' AS t; + t +--- + t +(1 row) + +SELECT '04:05:06'::citext::time = '04:05:06'::time AS t; + t +--- + t +(1 row) + +SELECT '04:05:06'::timetz::citext = '04:05:06'::timetz::text AS t; + t +--- + t +(1 row) + +SELECT '04:05:06'::citext::timetz = '04:05:06'::timetz AS t; + t +--- + t +(1 row) + +SELECT '( 1 , 1)'::point::citext = '(1,1)' AS t; + t +--- + t +(1 row) + +SELECT '( 1 , 1)'::citext::point ~= '(1,1)'::point AS t; + t +--- + t +(1 row) + +SELECT '( 1 , 1 ) , ( 2 , 2 )'::lseg::citext = '[(1,1),(2,2)]' AS t; + t +--- + t +(1 row) + +SELECT '( 1 , 1 ) , ( 2 , 2 )'::citext::lseg = '[(1,1),(2,2)]'::lseg AS t; + t +--- + t +(1 row) + +SELECT '( 0 , 0 ) , ( 1 , 1 )'::box::citext = '(0,0),(1,1)'::box::text AS t; + t +--- + t +(1 row) + +SELECT '( 0 , 0 ) , ( 1 , 1 )'::citext::box ~= '(0,0),(1,1)'::text::box AS t; + t +--- + t +(1 row) + +SELECT '((0,0),(1,1),(2,0))'::path::citext = '((0,0),(1,1),(2,0))' AS t; + t +--- + t +(1 row) + +SELECT '((0,0),(1,1),(2,0))'::citext::path = '((0,0),(1,1),(2,0))'::path AS t; + t +--- + t +(1 row) + +SELECT '((0,0),(1,1))'::polygon::citext = '((0,0),(1,1))' AS t; + t +--- + t +(1 row) + +SELECT '((0,0),(1,1))'::citext::polygon ~= '((0,0),(1,1))'::polygon AS t; + t +--- + t +(1 row) + +SELECT '((0,0),2)'::circle::citext = '((0,0),2)'::circle::text AS t; + t +--- + t +(1 row) + +SELECT '((0,0),2)'::citext::circle ~= '((0,0),2)'::text::circle AS t; + t +--- + t +(1 row) + +SELECT '101'::bit::citext = '101'::bit::text AS t; + t +--- + t +(1 row) + +SELECT '101'::citext::bit = '101'::text::bit AS t; + t +--- + t +(1 row) + +SELECT '101'::bit varying::citext = '101'::bit varying::text AS t; + t +--- + t +(1 row) + +SELECT '101'::citext::bit varying = '101'::text::bit varying AS t; + t +--- + t +(1 row) + +SELECT 'a fat cat'::tsvector::citext = '''a'' ''cat'' ''fat''' AS t; + t +--- + t +(1 row) + +SELECT 'a fat cat'::citext::tsvector = 'a fat cat'::tsvector AS t; + t +--- + t +(1 row) + +SELECT 'fat & rat'::tsquery::citext = '''fat'' & ''rat''' AS t; + t +--- + t +(1 row) + +SELECT 'fat & rat'::citext::tsquery = 'fat & rat'::tsquery AS t; + t +--- + t +(1 row) + +SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid::citext = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11' AS t; + t +--- + t +(1 row) + +SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::citext::uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid AS t; + t +--- + t +(1 row) + +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); +SELECT 'sad'::mood::citext = 'sad' AS t; + t +--- + t +(1 row) + +SELECT 'sad'::citext::mood = 'sad'::mood AS t; + t +--- + t +(1 row) + +-- Assignment casts. +CREATE TABLE caster ( + citext citext, + text text, + varchar varchar, + bpchar bpchar, + char char, + chr "char", + name name, + bytea bytea, + boolean boolean, + float4 float4, + float8 float8, + numeric numeric, + int8 int8, + int4 int4, + int2 int2, + cidr cidr, + inet inet, + macaddr macaddr, + money money, + timestamp timestamp, + timestamptz timestamptz, + interval interval, + date date, + time time, + timetz timetz, + point point, + lseg lseg, + box box, + path path, + polygon polygon, + circle circle, + bit bit, + bitv bit varying, + tsvector tsvector, + tsquery tsquery, + uuid uuid +); +INSERT INTO caster (text) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::text); +INSERT INTO caster (varchar) VALUES ('foo'::text); +INSERT INTO caster (text) VALUES ('foo'::varchar); +INSERT INTO caster (varchar) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::varchar); +INSERT INTO caster (bpchar) VALUES ('foo'::text); +INSERT INTO caster (text) VALUES ('foo'::bpchar); +INSERT INTO caster (bpchar) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::bpchar); +INSERT INTO caster (char) VALUES ('f'::text); +INSERT INTO caster (text) VALUES ('f'::char); +INSERT INTO caster (char) VALUES ('f'::citext); +INSERT INTO caster (citext) VALUES ('f'::char); +INSERT INTO caster (chr) VALUES ('f'::text); +INSERT INTO caster (text) VALUES ('f'::"char"); +INSERT INTO caster (chr) VALUES ('f'::citext); -- requires cast +ERROR: column "chr" is of type "char" but expression is of type citext +LINE 1: INSERT INTO caster (chr) VALUES ('f'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (chr) VALUES ('f'::citext::text); +INSERT INTO caster (citext) VALUES ('f'::"char"); +INSERT INTO caster (name) VALUES ('foo'::text); +INSERT INTO caster (text) VALUES ('foo'::name); +INSERT INTO caster (name) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::name); +-- Cannot cast to bytea on assignment. +INSERT INTO caster (bytea) VALUES ('foo'::text); +ERROR: column "bytea" is of type bytea but expression is of type text +LINE 1: INSERT INTO caster (bytea) VALUES ('foo'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('foo'::bytea); +INSERT INTO caster (bytea) VALUES ('foo'::citext); +ERROR: column "bytea" is of type bytea but expression is of type citext +LINE 1: INSERT INTO caster (bytea) VALUES ('foo'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('foo'::bytea); +-- Cannot cast to boolean on assignment. +INSERT INTO caster (boolean) VALUES ('t'::text); +ERROR: column "boolean" is of type boolean but expression is of type text +LINE 1: INSERT INTO caster (boolean) VALUES ('t'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('t'::boolean); +INSERT INTO caster (boolean) VALUES ('t'::citext); +ERROR: column "boolean" is of type boolean but expression is of type citext +LINE 1: INSERT INTO caster (boolean) VALUES ('t'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('t'::boolean); +-- Cannot cast to float8 on assignment. +INSERT INTO caster (float8) VALUES ('12.42'::text); +ERROR: column "float8" is of type double precision but expression is of type text +LINE 1: INSERT INTO caster (float8) VALUES ('12.42'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12.42'::float8); +INSERT INTO caster (float8) VALUES ('12.42'::citext); +ERROR: column "float8" is of type double precision but expression is of type citext +LINE 1: INSERT INTO caster (float8) VALUES ('12.42'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12.42'::float8); +-- Cannot cast to float4 on assignment. +INSERT INTO caster (float4) VALUES ('12.42'::text); +ERROR: column "float4" is of type real but expression is of type text +LINE 1: INSERT INTO caster (float4) VALUES ('12.42'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12.42'::float4); +INSERT INTO caster (float4) VALUES ('12.42'::citext); +ERROR: column "float4" is of type real but expression is of type citext +LINE 1: INSERT INTO caster (float4) VALUES ('12.42'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12.42'::float4); +-- Cannot cast to numeric on assignment. +INSERT INTO caster (numeric) VALUES ('12.42'::text); +ERROR: column "numeric" is of type numeric but expression is of type text +LINE 1: INSERT INTO caster (numeric) VALUES ('12.42'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12.42'::numeric); +INSERT INTO caster (numeric) VALUES ('12.42'::citext); +ERROR: column "numeric" is of type numeric but expression is of type citext +LINE 1: INSERT INTO caster (numeric) VALUES ('12.42'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12.42'::numeric); +-- Cannot cast to int8 on assignment. +INSERT INTO caster (int8) VALUES ('12'::text); +ERROR: column "int8" is of type bigint but expression is of type text +LINE 1: INSERT INTO caster (int8) VALUES ('12'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12'::int8); +INSERT INTO caster (int8) VALUES ('12'::citext); +ERROR: column "int8" is of type bigint but expression is of type citext +LINE 1: INSERT INTO caster (int8) VALUES ('12'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12'::int8); +-- Cannot cast to int4 on assignment. +INSERT INTO caster (int4) VALUES ('12'::text); +ERROR: column "int4" is of type integer but expression is of type text +LINE 1: INSERT INTO caster (int4) VALUES ('12'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12'::int4); +INSERT INTO caster (int4) VALUES ('12'::citext); +ERROR: column "int4" is of type integer but expression is of type citext +LINE 1: INSERT INTO caster (int4) VALUES ('12'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12'::int4); +-- Cannot cast to int2 on assignment. +INSERT INTO caster (int2) VALUES ('12'::text); +ERROR: column "int2" is of type smallint but expression is of type text +LINE 1: INSERT INTO caster (int2) VALUES ('12'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12'::int2); +INSERT INTO caster (int2) VALUES ('12'::citext); +ERROR: column "int2" is of type smallint but expression is of type citext +LINE 1: INSERT INTO caster (int2) VALUES ('12'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12'::int2); +-- Cannot cast to cidr on assignment. +INSERT INTO caster (cidr) VALUES ('192.168.100.128/25'::text); +ERROR: column "cidr" is of type cidr but expression is of type text +LINE 1: INSERT INTO caster (cidr) VALUES ('192.168.100.128/... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('192.168.100.128/25'::cidr); +INSERT INTO caster (cidr) VALUES ('192.168.100.128/25'::citext); +ERROR: column "cidr" is of type cidr but expression is of type citext +LINE 1: INSERT INTO caster (cidr) VALUES ('192.168.100.128/... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('192.168.100.128/25'::cidr); +-- Cannot cast to inet on assignment. +INSERT INTO caster (inet) VALUES ('192.168.100.128'::text); +ERROR: column "inet" is of type inet but expression is of type text +LINE 1: INSERT INTO caster (inet) VALUES ('192.168.100.128'... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('192.168.100.128'::inet); +INSERT INTO caster (inet) VALUES ('192.168.100.128'::citext); +ERROR: column "inet" is of type inet but expression is of type citext +LINE 1: INSERT INTO caster (inet) VALUES ('192.168.100.128'... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('192.168.100.128'::inet); +-- Cannot cast to macaddr on assignment. +INSERT INTO caster (macaddr) VALUES ('08:00:2b:01:02:03'::text); +ERROR: column "macaddr" is of type macaddr but expression is of type text +LINE 1: INSERT INTO caster (macaddr) VALUES ('08:00:2b:01:02:0... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('08:00:2b:01:02:03'::macaddr); +INSERT INTO caster (macaddr) VALUES ('08:00:2b:01:02:03'::citext); +ERROR: column "macaddr" is of type macaddr but expression is of type citext +LINE 1: INSERT INTO caster (macaddr) VALUES ('08:00:2b:01:02:0... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('08:00:2b:01:02:03'::macaddr); +-- Cannot cast to money on assignment. +INSERT INTO caster (money) VALUES ('12'::text); +ERROR: column "money" is of type money but expression is of type text +LINE 1: INSERT INTO caster (money) VALUES ('12'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('12'::money); +INSERT INTO caster (money) VALUES ('12'::citext); +ERROR: column "money" is of type money but expression is of type citext +LINE 1: INSERT INTO caster (money) VALUES ('12'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('12'::money); +-- Cannot cast to timestamp on assignment. +INSERT INTO caster (timestamp) VALUES ('1999-01-08 04:05:06'::text); +ERROR: column "timestamp" is of type timestamp without time zone but expression is of type text +LINE 1: INSERT INTO caster (timestamp) VALUES ('1999-01-08 04:05... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('1999-01-08 04:05:06'::timestamp); +INSERT INTO caster (timestamp) VALUES ('1999-01-08 04:05:06'::citext); +ERROR: column "timestamp" is of type timestamp without time zone but expression is of type citext +LINE 1: INSERT INTO caster (timestamp) VALUES ('1999-01-08 04:05... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('1999-01-08 04:05:06'::timestamp); +-- Cannot cast to timestamptz on assignment. +INSERT INTO caster (timestamptz) VALUES ('1999-01-08 04:05:06'::text); +ERROR: column "timestamptz" is of type timestamp with time zone but expression is of type text +LINE 1: INSERT INTO caster (timestamptz) VALUES ('1999-01-08 04:05... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('1999-01-08 04:05:06'::timestamptz); +INSERT INTO caster (timestamptz) VALUES ('1999-01-08 04:05:06'::citext); +ERROR: column "timestamptz" is of type timestamp with time zone but expression is of type citext +LINE 1: INSERT INTO caster (timestamptz) VALUES ('1999-01-08 04:05... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('1999-01-08 04:05:06'::timestamptz); +-- Cannot cast to interval on assignment. +INSERT INTO caster (interval) VALUES ('1 hour'::text); +ERROR: column "interval" is of type interval but expression is of type text +LINE 1: INSERT INTO caster (interval) VALUES ('1 hour'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('1 hour'::interval); +INSERT INTO caster (interval) VALUES ('1 hour'::citext); +ERROR: column "interval" is of type interval but expression is of type citext +LINE 1: INSERT INTO caster (interval) VALUES ('1 hour'::citext)... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('1 hour'::interval); +-- Cannot cast to date on assignment. +INSERT INTO caster (date) VALUES ('1999-01-08'::text); +ERROR: column "date" is of type date but expression is of type text +LINE 1: INSERT INTO caster (date) VALUES ('1999-01-08'::tex... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('1999-01-08'::date); +INSERT INTO caster (date) VALUES ('1999-01-08'::citext); +ERROR: column "date" is of type date but expression is of type citext +LINE 1: INSERT INTO caster (date) VALUES ('1999-01-08'::cit... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('1999-01-08'::date); +-- Cannot cast to time on assignment. +INSERT INTO caster (time) VALUES ('04:05:06'::text); +ERROR: column "time" is of type time without time zone but expression is of type text +LINE 1: INSERT INTO caster (time) VALUES ('04:05:06'::text)... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('04:05:06'::time); +INSERT INTO caster (time) VALUES ('04:05:06'::citext); +ERROR: column "time" is of type time without time zone but expression is of type citext +LINE 1: INSERT INTO caster (time) VALUES ('04:05:06'::citex... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('04:05:06'::time); +-- Cannot cast to timetz on assignment. +INSERT INTO caster (timetz) VALUES ('04:05:06'::text); +ERROR: column "timetz" is of type time with time zone but expression is of type text +LINE 1: INSERT INTO caster (timetz) VALUES ('04:05:06'::text)... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('04:05:06'::timetz); +INSERT INTO caster (timetz) VALUES ('04:05:06'::citext); +ERROR: column "timetz" is of type time with time zone but expression is of type citext +LINE 1: INSERT INTO caster (timetz) VALUES ('04:05:06'::citex... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('04:05:06'::timetz); +-- Cannot cast to point on assignment. +INSERT INTO caster (point) VALUES ('( 1 , 1)'::text); +ERROR: column "point" is of type point but expression is of type text +LINE 1: INSERT INTO caster (point) VALUES ('( 1 , 1)'::text)... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('( 1 , 1)'::point); +INSERT INTO caster (point) VALUES ('( 1 , 1)'::citext); +ERROR: column "point" is of type point but expression is of type citext +LINE 1: INSERT INTO caster (point) VALUES ('( 1 , 1)'::citex... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('( 1 , 1)'::point); +-- Cannot cast to lseg on assignment. +INSERT INTO caster (lseg) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::text); +ERROR: column "lseg" is of type lseg but expression is of type text +LINE 1: INSERT INTO caster (lseg) VALUES ('( 1 , 1 ) , ( 2 ... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::lseg); +INSERT INTO caster (lseg) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::citext); +ERROR: column "lseg" is of type lseg but expression is of type citext +LINE 1: INSERT INTO caster (lseg) VALUES ('( 1 , 1 ) , ( 2 ... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::lseg); +-- Cannot cast to box on assignment. +INSERT INTO caster (box) VALUES ('(0,0),(1,1)'::text); +ERROR: column "box" is of type box but expression is of type text +LINE 1: INSERT INTO caster (box) VALUES ('(0,0),(1,1)'::te... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('(0,0),(1,1)'::box); +INSERT INTO caster (box) VALUES ('(0,0),(1,1)'::citext); +ERROR: column "box" is of type box but expression is of type citext +LINE 1: INSERT INTO caster (box) VALUES ('(0,0),(1,1)'::ci... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('(0,0),(1,1)'::box); +-- Cannot cast to path on assignment. +INSERT INTO caster (path) VALUES ('((0,0),(1,1),(2,0))'::text); +ERROR: column "path" is of type path but expression is of type text +LINE 1: INSERT INTO caster (path) VALUES ('((0,0),(1,1),(2,... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('((0,0),(1,1),(2,0))'::path); +INSERT INTO caster (path) VALUES ('((0,0),(1,1),(2,0))'::citext); +ERROR: column "path" is of type path but expression is of type citext +LINE 1: INSERT INTO caster (path) VALUES ('((0,0),(1,1),(2,... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('((0,0),(1,1),(2,0))'::path); +-- Cannot cast to polygon on assignment. +INSERT INTO caster (polygon) VALUES ('((0,0),(1,1))'::text); +ERROR: column "polygon" is of type polygon but expression is of type text +LINE 1: INSERT INTO caster (polygon) VALUES ('((0,0),(1,1))'::... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('((0,0),(1,1))'::polygon); +INSERT INTO caster (polygon) VALUES ('((0,0),(1,1))'::citext); +ERROR: column "polygon" is of type polygon but expression is of type citext +LINE 1: INSERT INTO caster (polygon) VALUES ('((0,0),(1,1))'::... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('((0,0),(1,1))'::polygon); +-- Cannot cast to circle on assignment. +INSERT INTO caster (circle) VALUES ('((0,0),2)'::text); +ERROR: column "circle" is of type circle but expression is of type text +LINE 1: INSERT INTO caster (circle) VALUES ('((0,0),2)'::text... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('((0,0),2)'::circle); +INSERT INTO caster (circle) VALUES ('((0,0),2)'::citext); +ERROR: column "circle" is of type circle but expression is of type citext +LINE 1: INSERT INTO caster (circle) VALUES ('((0,0),2)'::cite... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('((0,0),2)'::circle); +-- Cannot cast to bit on assignment. +INSERT INTO caster (bit) VALUES ('101'::text); +ERROR: column "bit" is of type bit but expression is of type text +LINE 1: INSERT INTO caster (bit) VALUES ('101'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('101'::bit); +INSERT INTO caster (bit) VALUES ('101'::citext); +ERROR: column "bit" is of type bit but expression is of type citext +LINE 1: INSERT INTO caster (bit) VALUES ('101'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('101'::bit); +-- Cannot cast to bit varying on assignment. +INSERT INTO caster (bitv) VALUES ('101'::text); +ERROR: column "bitv" is of type bit varying but expression is of type text +LINE 1: INSERT INTO caster (bitv) VALUES ('101'::text); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('101'::bit varying); +INSERT INTO caster (bitv) VALUES ('101'::citext); +ERROR: column "bitv" is of type bit varying but expression is of type citext +LINE 1: INSERT INTO caster (bitv) VALUES ('101'::citext); + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('101'::bit varying); +-- Cannot cast to tsvector on assignment. +INSERT INTO caster (tsvector) VALUES ('the fat cat'::text); +ERROR: column "tsvector" is of type tsvector but expression is of type text +LINE 1: INSERT INTO caster (tsvector) VALUES ('the fat cat'::te... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('the fat cat'::tsvector); +INSERT INTO caster (tsvector) VALUES ('the fat cat'::citext); +ERROR: column "tsvector" is of type tsvector but expression is of type citext +LINE 1: INSERT INTO caster (tsvector) VALUES ('the fat cat'::ci... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('the fat cat'::tsvector); +-- Cannot cast to tsquery on assignment. +INSERT INTO caster (tsquery) VALUES ('fat & rat'::text); +ERROR: column "tsquery" is of type tsquery but expression is of type text +LINE 1: INSERT INTO caster (tsquery) VALUES ('fat & rat'::text... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('fat & rat'::tsquery); +INSERT INTO caster (tsquery) VALUES ('fat & rat'::citext); +ERROR: column "tsquery" is of type tsquery but expression is of type citext +LINE 1: INSERT INTO caster (tsquery) VALUES ('fat & rat'::cite... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('fat & rat'::tsquery); +-- Cannot cast to uuid on assignment. +INSERT INTO caster (uuid) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::text); +ERROR: column "uuid" is of type uuid but expression is of type text +LINE 1: INSERT INTO caster (uuid) VALUES ('a0eebc99-9c0b-4e... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (text) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid); +INSERT INTO caster (uuid) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::citext); +ERROR: column "uuid" is of type uuid but expression is of type citext +LINE 1: INSERT INTO caster (uuid) VALUES ('a0eebc99-9c0b-4e... + ^ +HINT: You will need to rewrite or cast the expression. +INSERT INTO caster (citext) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid); +-- Table 9-5. SQL String Functions and Operators +SELECT 'D'::citext || 'avid'::citext = 'David'::citext AS citext_concat; + citext_concat +--------------- + t +(1 row) + +SELECT 'Value: '::citext || 42 = 'Value: 42' AS text_concat; + text_concat +------------- + t +(1 row) + +SELECT 42 || ': value'::citext ='42: value' AS int_concat; + int_concat +------------ + t +(1 row) + +SELECT bit_length('jose'::citext) = 32 AS t; + t +--- + t +(1 row) + +SELECT bit_length( name ) = bit_length( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT textlen( name ) = textlen( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT char_length( name ) = char_length( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT lower( name ) = lower( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT octet_length( name ) = octet_length( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT overlay( name placing 'hom' from 2 for 4) = overlay( name::text placing 'hom' from 2 for 4) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT position( 'a' IN name ) = position( 'a' IN name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT substr('alphabet'::citext, 3) = 'phabet' AS t; + t +--- + t +(1 row) + +SELECT substr('alphabet'::citext, 3, 2) = 'ph' AS t; + t +--- + t +(1 row) + +SELECT substring('alphabet'::citext, 3) = 'phabet' AS t; + t +--- + t +(1 row) + +SELECT substring('alphabet'::citext, 3, 2) = 'ph' AS t; + t +--- + t +(1 row) + +SELECT substring('Thomas'::citext from 2 for 3) = 'hom' AS t; + t +--- + t +(1 row) + +SELECT substring('Thomas'::citext from 2) = 'homas' AS t; + t +--- + t +(1 row) + +SELECT substring('Thomas'::citext from '...$') = 'mas' AS t; + t +--- + t +(1 row) + +SELECT substring('Thomas'::citext similar '%#"o_a#"_' escape '#') = 'oma' AS t; + t +--- + t +(1 row) + +SELECT trim(' trim '::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT trim('xxxxxtrimxxxx'::citext, 'x'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT trim('xxxxxxtrimxxxx'::text, 'x'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT trim('xxxxxtrimxxxx'::text, 'x'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT upper( name ) = upper( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +-- Table 9-6. Other String Functions. +SELECT ascii( name ) = ascii( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT btrim(' trim'::citext ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT btrim('xxxxxtrimxxxx'::citext, 'x'::citext ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT btrim('xyxtrimyyx'::citext, 'xy'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT btrim('xyxtrimyyx'::text, 'xy'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT btrim('xyxtrimyyx'::citext, 'xy'::text ) = 'trim' AS t; + t +--- + t +(1 row) + +-- chr() takes an int and returns text. +-- convert() and convert_from take bytea and return text. +SELECT convert_from( name::bytea, 'SQL_ASCII' ) = convert_from( name::text::bytea, 'SQL_ASCII' ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT decode('MTIzAAE='::citext, 'base64') = decode('MTIzAAE='::text, 'base64') AS t; + t +--- + t +(1 row) + +-- encode() takes bytea and returns text. +SELECT initcap('hi THOMAS'::citext) = initcap('hi THOMAS'::text) AS t; + t +--- + t +(1 row) + +SELECT length( name ) = length( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT lpad('hi'::citext, 5 ) = ' hi' AS t; + t +--- + t +(1 row) + +SELECT lpad('hi'::citext, 5, 'xy'::citext) = 'xyxhi' AS t; + t +--- + t +(1 row) + +SELECT lpad('hi'::text, 5, 'xy'::citext) = 'xyxhi' AS t; + t +--- + t +(1 row) + +SELECT lpad('hi'::citext, 5, 'xy'::text ) = 'xyxhi' AS t; + t +--- + t +(1 row) + +SELECT ltrim(' trim'::citext ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT ltrim('zzzytrim'::citext, 'xyz'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT ltrim('zzzytrim'::text, 'xyz'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT ltrim('zzzytrim'::citext, 'xyz'::text ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT md5( name ) = md5( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +-- pg_client_encoding() takes no args and returns name. +SELECT quote_ident( name ) = quote_ident( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT quote_literal( name ) = quote_literal( name::text ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT regexp_match('foobarbequebaz'::citext, '(bar)(beque)') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext) = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)', '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz', '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, ''::citext) = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +-- c forces case-sensitive +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, 'c'::citext) = ARRAY[ 'bar', 'beque' ] AS "no result"; + no result +----------- + +(1 row) + +-- g is not allowed +SELECT regexp_match('foobarbequebazmorebarbequetoo'::citext, '(BAR)(BEQUE)'::citext, 'g') AS "error"; +ERROR: regexp_match() does not support the "global" option +HINT: Use the regexp_matches function instead. +CONTEXT: SQL function "regexp_match" statement 1 +SELECT regexp_matches('foobarbequebaz'::citext, '(bar)(beque)') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext) = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)', '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz', '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, ''::citext) = ARRAY[ 'bar', 'beque' ] AS t; + t +--- + t +(1 row) + +-- c forces case-sensitive +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, 'c'::citext) = ARRAY[ 'bar', 'beque' ] AS "no rows"; + no rows +--------- +(0 rows) + +-- g allows multiple output rows +SELECT regexp_matches('foobarbequebazmorebarbequetoo'::citext, '(BAR)(BEQUE)'::citext, 'g'::citext) AS "two rows"; + two rows +------------- + {bar,beque} + {bar,beque} +(2 rows) + +SELECT regexp_replace('Thomas'::citext, '.[mN]a.', 'M') = 'ThM' AS t; + t +--- + t +(1 row) + +SELECT regexp_replace('Thomas'::citext, '.[MN]A.', 'M') = 'ThM' AS t; + t +--- + t +(1 row) + +SELECT regexp_replace('Thomas', '.[MN]A.'::citext, 'M') = 'ThM' AS t; + t +--- + t +(1 row) + +SELECT regexp_replace('Thomas'::citext, '.[MN]A.'::citext, 'M') = 'ThM' AS t; + t +--- + t +(1 row) + +-- c forces case-sensitive +SELECT regexp_replace('Thomas'::citext, '.[MN]A.'::citext, 'M', 'c') = 'Thomas' AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('hello world'::citext, E'\\s+') = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld'::citext, 't') = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld', 't'::citext) = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld'::citext, 't'::citext) = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld'::citext, 't', 's') = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld', 't'::citext, 's') = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_array('helloTworld'::citext, 't'::citext, 's') = ARRAY[ 'hello', 'world' ] AS t; + t +--- + t +(1 row) + +-- c forces case-sensitive +SELECT regexp_split_to_array('helloTworld'::citext, 't'::citext, 'c') = ARRAY[ 'helloTworld' ] AS t; + t +--- + t +(1 row) + +SELECT regexp_split_to_table('hello world'::citext, E'\\s+') AS words; + words +------- + hello + world +(2 rows) + +SELECT regexp_split_to_table('helloTworld'::citext, 't') AS words; + words +------- + hello + world +(2 rows) + +SELECT regexp_split_to_table('helloTworld', 't'::citext) AS words; + words +------- + hello + world +(2 rows) + +SELECT regexp_split_to_table('helloTworld'::citext, 't'::citext) AS words; + words +------- + hello + world +(2 rows) + +-- c forces case-sensitive +SELECT regexp_split_to_table('helloTworld'::citext, 't'::citext, 'c') AS word; + word +------------- + helloTworld +(1 row) + +SELECT repeat('Pg'::citext, 4) = 'PgPgPgPg' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef'::citext, 'cd', 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef'::citext, 'CD', 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('ab^is$abcdef'::citext, '^is$', 'XX') = 'abXXabcdef' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef', 'cd'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef', 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('ab^is$abcdef', '^is$'::citext, 'XX') = 'abXXabcdef' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef'::citext, 'cd'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('abcdefabcdef'::citext, 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT replace('ab^is$abcdef'::citext, '^is$'::citext, 'XX') = 'abXXabcdef' AS t; + t +--- + t +(1 row) + +SELECT rpad('hi'::citext, 5 ) = 'hi ' AS t; + t +--- + t +(1 row) + +SELECT rpad('hi'::citext, 5, 'xy'::citext) = 'hixyx' AS t; + t +--- + t +(1 row) + +SELECT rpad('hi'::text, 5, 'xy'::citext) = 'hixyx' AS t; + t +--- + t +(1 row) + +SELECT rpad('hi'::citext, 5, 'xy'::text ) = 'hixyx' AS t; + t +--- + t +(1 row) + +SELECT rtrim('trim '::citext ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT rtrim('trimxxxx'::citext, 'x'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT rtrim('trimxxxx'::text, 'x'::citext) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT rtrim('trimxxxx'::text, 'x'::text ) = 'trim' AS t; + t +--- + t +(1 row) + +SELECT split_part('abc~@~def~@~ghi'::citext, '~@~', 2) = 'def' AS t; + t +--- + t +(1 row) + +SELECT split_part('abcTdefTghi'::citext, 't', 2) = 'def' AS t; + t +--- + t +(1 row) + +SELECT split_part('abcTdefTghi'::citext, 't'::citext, 2) = 'def' AS t; + t +--- + t +(1 row) + +SELECT split_part('abcTdefTghi', 't'::citext, 2) = 'def' AS t; + t +--- + t +(1 row) + +SELECT strpos('high'::citext, 'gh' ) = 3 AS t; + t +--- + t +(1 row) + +SELECT strpos('high', 'gh'::citext) = 3 AS t; + t +--- + t +(1 row) + +SELECT strpos('high'::citext, 'gh'::citext) = 3 AS t; + t +--- + t +(1 row) + +SELECT strpos('high'::citext, 'GH' ) = 3 AS t; + t +--- + t +(1 row) + +SELECT strpos('high', 'GH'::citext) = 3 AS t; + t +--- + t +(1 row) + +SELECT strpos('high'::citext, 'GH'::citext) = 3 AS t; + t +--- + t +(1 row) + +-- to_ascii() does not support UTF-8. +-- to_hex() takes a numeric argument. +SELECT substr('alphabet', 3, 2) = 'ph' AS t; + t +--- + t +(1 row) + +SELECT translate('abcdefabcdef'::citext, 'cd', 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT translate('abcdefabcdef'::citext, 'CD', 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT translate('abcdefabcdef'::citext, 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +SELECT translate('abcdefabcdef', 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; + t +--- + t +(1 row) + +-- Table 9-20. Formatting Functions +SELECT to_date('05 Dec 2000'::citext, 'DD Mon YYYY'::citext) + = to_date('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +SELECT to_date('05 Dec 2000'::citext, 'DD Mon YYYY') + = to_date('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +SELECT to_date('05 Dec 2000', 'DD Mon YYYY'::citext) + = to_date('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +SELECT to_number('12,454.8-'::citext, '99G999D9S'::citext) + = to_number('12,454.8-', '99G999D9S') AS t; + t +--- + t +(1 row) + +SELECT to_number('12,454.8-'::citext, '99G999D9S') + = to_number('12,454.8-', '99G999D9S') AS t; + t +--- + t +(1 row) + +SELECT to_number('12,454.8-', '99G999D9S'::citext) + = to_number('12,454.8-', '99G999D9S') AS t; + t +--- + t +(1 row) + +SELECT to_timestamp('05 Dec 2000'::citext, 'DD Mon YYYY'::citext) + = to_timestamp('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +SELECT to_timestamp('05 Dec 2000'::citext, 'DD Mon YYYY') + = to_timestamp('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +SELECT to_timestamp('05 Dec 2000', 'DD Mon YYYY'::citext) + = to_timestamp('05 Dec 2000', 'DD Mon YYYY') AS t; + t +--- + t +(1 row) + +-- Try assigning function results to a column. +SELECT COUNT(*) = 8::bigint AS t FROM try; + t +--- + t +(1 row) + +INSERT INTO try +VALUES ( to_char( now()::timestamp, 'HH12:MI:SS') ), + ( to_char( now() + '1 sec'::interval, 'HH12:MI:SS') ), -- timestamptz + ( to_char( '15h 2m 12s'::interval, 'HH24:MI:SS') ), + ( to_char( current_date, '999') ), + ( to_char( 125::int, '999') ), + ( to_char( 127::int4, '999') ), + ( to_char( 126::int8, '999') ), + ( to_char( 128.8::real, '999D9') ), + ( to_char( 125.7::float4, '999D9') ), + ( to_char( 125.9::float8, '999D9') ), + ( to_char( -125.8::numeric, '999D99S') ); +SELECT COUNT(*) = 19::bigint AS t FROM try; + t +--- + t +(1 row) + +SELECT like_escape( name, '' ) = like_escape( name::text, '' ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +SELECT like_escape( name::text, ''::citext ) = like_escape( name::text, '' ) AS t FROM srt; + t +--- + t + t + t + t +(4 rows) + +-- Ensure correct behavior for citext with materialized views. +CREATE TABLE citext_table ( + id serial primary key, + name citext +); +INSERT INTO citext_table (name) + VALUES ('one'), ('two'), ('three'), (NULL), (NULL); +CREATE MATERIALIZED VIEW citext_matview AS + SELECT * FROM citext_table; +CREATE UNIQUE INDEX citext_matview_id + ON citext_matview (id); +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; + id | name | id | name +----+------+----+------ +(0 rows) + +UPDATE citext_table SET name = 'Two' WHERE name = 'TWO'; +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; + id | name | id | name +----+------+----+------ + | | 2 | Two + 2 | two | | +(2 rows) + +REFRESH MATERIALIZED VIEW CONCURRENTLY citext_matview; +SELECT * FROM citext_matview ORDER BY id; + id | name +----+------- + 1 | one + 2 | Two + 3 | three + 4 | + 5 | +(5 rows) + +-- test citext_pattern_cmp() function explicitly. +SELECT citext_pattern_cmp('aardvark'::citext, 'aardvark'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_pattern_cmp('aardvark'::citext, 'aardVark'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_pattern_cmp('AARDVARK'::citext, 'AARDVARK'::citext) AS zero; + zero +------ + 0 +(1 row) + +SELECT citext_pattern_cmp('B'::citext, 'a'::citext) > 0 AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_cmp('a'::citext, 'B'::citext) < 0 AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_cmp('A'::citext, 'b'::citext) < 0 AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_cmp('ABCD'::citext, 'abc'::citext) > 0 AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_cmp('ABC'::citext, 'abcd'::citext) < 0 AS true; + true +------ + t +(1 row) + +-- test operator functions +-- lt +SELECT citext_pattern_lt('a'::citext, 'b'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_lt('A'::citext, 'b'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_lt('a'::citext, 'B'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_lt('b'::citext, 'a'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_lt('B'::citext, 'a'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_lt('b'::citext, 'A'::citext) AS false; + false +------- + f +(1 row) + +-- le +SELECT citext_pattern_le('a'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('a'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('A'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('A'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('a'::citext, 'B'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('A'::citext, 'b'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('a'::citext, 'B'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_le('b'::citext, 'a'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_le('B'::citext, 'a'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_le('b'::citext, 'A'::citext) AS false; + false +------- + f +(1 row) + +-- gt +SELECT citext_pattern_gt('a'::citext, 'b'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_gt('A'::citext, 'b'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_gt('a'::citext, 'B'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_gt('b'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_gt('B'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_gt('b'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +-- ge +SELECT citext_pattern_ge('a'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('a'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('A'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('A'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('a'::citext, 'B'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_ge('A'::citext, 'b'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_ge('a'::citext, 'B'::citext) AS false; + false +------- + f +(1 row) + +SELECT citext_pattern_ge('b'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('B'::citext, 'a'::citext) AS true; + true +------ + t +(1 row) + +SELECT citext_pattern_ge('b'::citext, 'A'::citext) AS true; + true +------ + t +(1 row) + +-- Test ~<~ and ~<=~ +SELECT 'a'::citext ~<~ 'B'::citext AS t; + t +--- + t +(1 row) + +SELECT 'b'::citext ~<~ 'A'::citext AS f; + f +--- + f +(1 row) + +SELECT 'a'::citext ~<=~ 'B'::citext AS t; + t +--- + t +(1 row) + +SELECT 'a'::citext ~<=~ 'A'::citext AS t; + t +--- + t +(1 row) + +-- Test ~>~ and ~>=~ +SELECT 'B'::citext ~>~ 'a'::citext AS t; + t +--- + t +(1 row) + +SELECT 'b'::citext ~>~ 'A'::citext AS t; + t +--- + t +(1 row) + +SELECT 'B'::citext ~>~ 'b'::citext AS f; + f +--- + f +(1 row) + +SELECT 'B'::citext ~>=~ 'b'::citext AS t; + t +--- + t +(1 row) + +-- Test implicit casting. citext casts to text, but not vice-versa. +SELECT 'B'::citext ~<~ 'a'::text AS t; -- text wins. + t +--- + t +(1 row) + +SELECT 'B'::citext ~<=~ 'a'::text AS t; -- text wins. + t +--- + t +(1 row) + +SELECT 'a'::citext ~>~ 'B'::text AS t; -- text wins. + t +--- + t +(1 row) + +SELECT 'a'::citext ~>=~ 'B'::text AS t; -- text wins. + t +--- + t +(1 row) + +-- Test implicit casting. citext casts to varchar, but not vice-versa. +SELECT 'B'::citext ~<~ 'a'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + +SELECT 'B'::citext ~<=~ 'a'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + +SELECT 'a'::citext ~>~ 'B'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + +SELECT 'a'::citext ~>=~ 'B'::varchar AS t; -- varchar wins. + t +--- + t +(1 row) + diff --git a/contrib/citext/expected/citext_utf8.out b/contrib/citext/expected/citext_utf8.out new file mode 100644 index 0000000..6630e09 --- /dev/null +++ b/contrib/citext/expected/citext_utf8.out @@ -0,0 +1,153 @@ +/* + * This test must be run in a database with UTF-8 encoding + * and a Unicode-aware locale. + * + * Also disable this file for ICU, because the test for the the + * Turkish dotted I is not correct for many ICU locales. citext always + * uses the default collation, so it's not easy to restrict the test + * to the "tr-TR-x-icu" collation where it will succeed. + */ +SELECT getdatabaseencoding() <> 'UTF8' OR + (SELECT (datlocprovider = 'c' AND datctype = 'C') OR datlocprovider = 'i' + FROM pg_database + WHERE datname=current_database()) + AS skip_test \gset +\if :skip_test +\quit +\endif +set client_encoding = utf8; +-- CREATE EXTENSION IF NOT EXISTS citext; +-- Multibyte sanity tests. +SELECT 'À'::citext = 'À'::citext AS t; + t +--- + t +(1 row) + +SELECT 'À'::citext = 'à'::citext AS t; + t +--- + t +(1 row) + +SELECT 'À'::text = 'à'::text AS f; -- text wins. + f +--- + f +(1 row) + +SELECT 'À'::citext <> 'B'::citext AS t; + t +--- + t +(1 row) + +-- Test combining characters making up canonically equivalent strings. +SELECT 'Ä'::text <> 'Ä'::text AS t; + t +--- + t +(1 row) + +SELECT 'Ä'::citext <> 'Ä'::citext AS t; + t +--- + t +(1 row) + +-- Test the Turkish dotted I. The lowercase is a single byte while the +-- uppercase is multibyte. This is why the comparison code can't be optimized +-- to compare string lengths. +SELECT 'i'::citext = 'İ'::citext AS t; + t +--- + t +(1 row) + +-- Regression. +SELECT 'láska'::citext <> 'laská'::citext AS t; + t +--- + t +(1 row) + +SELECT 'Ask Bjørn Hansen'::citext = 'Ask Bjørn Hansen'::citext AS t; + t +--- + t +(1 row) + +SELECT 'Ask Bjørn Hansen'::citext = 'ASK BJØRN HANSEN'::citext AS t; + t +--- + t +(1 row) + +SELECT 'Ask Bjørn Hansen'::citext <> 'Ask Bjorn Hansen'::citext AS t; + t +--- + t +(1 row) + +SELECT 'Ask Bjørn Hansen'::citext <> 'ASK BJORN HANSEN'::citext AS t; + t +--- + t +(1 row) + +SELECT citext_cmp('Ask Bjørn Hansen'::citext, 'Ask Bjørn Hansen'::citext) = 0 AS t; + t +--- + t +(1 row) + +SELECT citext_cmp('Ask Bjørn Hansen'::citext, 'ask bjørn hansen'::citext) = 0 AS t; + t +--- + t +(1 row) + +SELECT citext_cmp('Ask Bjørn Hansen'::citext, 'ASK BJØRN HANSEN'::citext) = 0 AS t; + t +--- + t +(1 row) + +SELECT citext_cmp('Ask Bjørn Hansen'::citext, 'Ask Bjorn Hansen'::citext) > 0 AS t; + t +--- + t +(1 row) + +SELECT citext_cmp('Ask Bjorn Hansen'::citext, 'Ask Bjørn Hansen'::citext) < 0 AS t; + t +--- + t +(1 row) + +-- Test ~<~ and ~<=~ +SELECT 'à'::citext ~<~ 'À'::citext AS f; + f +--- + f +(1 row) + +SELECT 'à'::citext ~<=~ 'À'::citext AS t; + t +--- + t +(1 row) + +-- Test ~>~ and ~>=~ +SELECT 'à'::citext ~>~ 'À'::citext AS f; + f +--- + f +(1 row) + +SELECT 'à'::citext ~>=~ 'À'::citext AS t; + t +--- + t +(1 row) + diff --git a/contrib/citext/expected/citext_utf8_1.out b/contrib/citext/expected/citext_utf8_1.out new file mode 100644 index 0000000..3caa7a0 --- /dev/null +++ b/contrib/citext/expected/citext_utf8_1.out @@ -0,0 +1,16 @@ +/* + * This test must be run in a database with UTF-8 encoding + * and a Unicode-aware locale. + * + * Also disable this file for ICU, because the test for the the + * Turkish dotted I is not correct for many ICU locales. citext always + * uses the default collation, so it's not easy to restrict the test + * to the "tr-TR-x-icu" collation where it will succeed. + */ +SELECT getdatabaseencoding() <> 'UTF8' OR + (SELECT (datlocprovider = 'c' AND datctype = 'C') OR datlocprovider = 'i' + FROM pg_database + WHERE datname=current_database()) + AS skip_test \gset +\if :skip_test +\quit diff --git a/contrib/citext/expected/create_index_acl.out b/contrib/citext/expected/create_index_acl.out new file mode 100644 index 0000000..33be13a --- /dev/null +++ b/contrib/citext/expected/create_index_acl.out @@ -0,0 +1,86 @@ +-- Each DefineIndex() ACL check uses either the original userid or the table +-- owner userid; see its header comment. Here, confirm that DefineIndex() +-- uses its original userid where necessary. The test works by creating +-- indexes that refer to as many sorts of objects as possible, with the table +-- owner having as few applicable privileges as possible. (The privileges.sql +-- regress_sro_user tests look for the opposite defect; they confirm that +-- DefineIndex() uses the table owner userid where necessary.) +SET allow_in_place_tablespaces = true; +CREATE TABLESPACE regress_create_idx_tblspace LOCATION ''; +RESET allow_in_place_tablespaces; +BEGIN; +CREATE ROLE regress_minimal; +CREATE SCHEMA s; +CREATE EXTENSION citext SCHEMA s; +-- Revoke all conceivably-relevant ACLs within the extension. The system +-- doesn't check all these ACLs, but this will provide some coverage if that +-- ever changes. +REVOKE ALL ON TYPE s.citext FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_pattern_lt FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_pattern_le FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_eq FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_pattern_ge FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_pattern_gt FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_pattern_cmp FROM PUBLIC; +-- Functions sufficient for making an index column that has the side effect of +-- changing search_path at expression planning time. +CREATE FUNCTION public.setter() RETURNS bool VOLATILE + LANGUAGE SQL AS $$SET search_path = s; SELECT true$$; +CREATE FUNCTION s.const() RETURNS bool IMMUTABLE + LANGUAGE SQL AS $$SELECT public.setter()$$; +CREATE FUNCTION s.index_this_expr(s.citext, bool) RETURNS s.citext IMMUTABLE + LANGUAGE SQL AS $$SELECT $1$$; +REVOKE ALL ON FUNCTION public.setter FROM PUBLIC; +REVOKE ALL ON FUNCTION s.const FROM PUBLIC; +REVOKE ALL ON FUNCTION s.index_this_expr FROM PUBLIC; +-- Even for an empty table, expression planning calls s.const & public.setter. +GRANT EXECUTE ON FUNCTION public.setter TO regress_minimal; +GRANT EXECUTE ON FUNCTION s.const TO regress_minimal; +-- Function for index predicate. +CREATE FUNCTION s.index_row_if(s.citext) RETURNS bool IMMUTABLE + LANGUAGE SQL AS $$SELECT $1 IS NOT NULL$$; +REVOKE ALL ON FUNCTION s.index_row_if FROM PUBLIC; +-- Even for an empty table, CREATE INDEX checks ii_Predicate permissions. +GRANT EXECUTE ON FUNCTION s.index_row_if TO regress_minimal; +-- Non-extension, non-function objects. +CREATE COLLATION s.coll (LOCALE="C"); +CREATE TABLE s.x (y s.citext); +ALTER TABLE s.x OWNER TO regress_minimal; +-- Empty-table DefineIndex() +CREATE UNIQUE INDEX u0rows ON s.x USING btree + ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_pattern_ops) + TABLESPACE regress_create_idx_tblspace + WHERE s.index_row_if(y); +ALTER TABLE s.x ADD CONSTRAINT e0rows EXCLUDE USING btree + ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=) + USING INDEX TABLESPACE regress_create_idx_tblspace + WHERE (s.index_row_if(y)); +-- Make the table nonempty. +INSERT INTO s.x VALUES ('foo'), ('bar'); +-- If the INSERT runs the planner on index expressions, a search_path change +-- survives. As of 2022-06, the INSERT reuses a cached plan. It does so even +-- under debug_discard_caches, since each index is new-in-transaction. If +-- future work changes a cache lifecycle, this RESET may become necessary. +RESET search_path; +-- For a nonempty table, owner needs permissions throughout ii_Expressions. +GRANT EXECUTE ON FUNCTION s.index_this_expr TO regress_minimal; +CREATE UNIQUE INDEX u2rows ON s.x USING btree + ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_pattern_ops) + TABLESPACE regress_create_idx_tblspace + WHERE s.index_row_if(y); +ALTER TABLE s.x ADD CONSTRAINT e2rows EXCLUDE USING btree + ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=) + USING INDEX TABLESPACE regress_create_idx_tblspace + WHERE (s.index_row_if(y)); +-- Shall not find s.coll via search_path, despite the s.const->public.setter +-- call having set search_path=s during expression planning. Suppress the +-- message itself, which depends on the database encoding. +\set VERBOSITY sqlstate +ALTER TABLE s.x ADD CONSTRAINT underqualified EXCLUDE USING btree + ((s.index_this_expr(y, s.const())) COLLATE coll WITH s.=) + USING INDEX TABLESPACE regress_create_idx_tblspace + WHERE (s.index_row_if(y)); +ERROR: 42704 +\set VERBOSITY default +ROLLBACK; +DROP TABLESPACE regress_create_idx_tblspace; diff --git a/contrib/citext/meson.build b/contrib/citext/meson.build new file mode 100644 index 0000000..b9bc243 --- /dev/null +++ b/contrib/citext/meson.build @@ -0,0 +1,42 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +citext_sources = files( + 'citext.c', +) + +if host_system == 'windows' + citext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'citext', + '--FILEDESC', 'citext - case-insensitive character string data type',]) +endif + +citext = shared_module('citext', + citext_sources, + kwargs: contrib_mod_args, +) +contrib_targets += citext + +install_data( + 'citext.control', + 'citext--1.0--1.1.sql', + 'citext--1.1--1.2.sql', + 'citext--1.2--1.3.sql', + 'citext--1.3--1.4.sql', + 'citext--1.4.sql', + 'citext--1.4--1.5.sql', + 'citext--1.5--1.6.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'citext', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'create_index_acl', + 'citext', + 'citext_utf8', + ], + }, +} diff --git a/contrib/citext/sql/citext.sql b/contrib/citext/sql/citext.sql new file mode 100644 index 0000000..b329253 --- /dev/null +++ b/contrib/citext/sql/citext.sql @@ -0,0 +1,810 @@ +-- +-- Test citext datatype +-- + +CREATE EXTENSION citext; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +-- Test the operators and indexing functions + +-- Test = and <>. +SELECT 'a'::citext = 'a'::citext AS t; +SELECT 'a'::citext = 'A'::citext AS t; +SELECT 'a'::citext = 'A'::text AS f; -- text wins the discussion +SELECT 'a'::citext = 'b'::citext AS f; +SELECT 'a'::citext = 'ab'::citext AS f; +SELECT 'a'::citext <> 'ab'::citext AS t; + +-- Test > and >= +SELECT 'B'::citext > 'a'::citext AS t; +SELECT 'b'::citext > 'A'::citext AS t; +SELECT 'B'::citext > 'b'::citext AS f; +SELECT 'B'::citext >= 'b'::citext AS t; + +-- Test < and <= +SELECT 'a'::citext < 'B'::citext AS t; +SELECT 'a'::citext <= 'B'::citext AS t; + +-- Test implicit casting. citext casts to text, but not vice-versa. +SELECT 'a'::citext = 'a'::text AS t; +SELECT 'A'::text <> 'a'::citext AS t; + +SELECT 'B'::citext < 'a'::text AS t; -- text wins. +SELECT 'B'::citext <= 'a'::text AS t; -- text wins. + +SELECT 'a'::citext > 'B'::text AS t; -- text wins. +SELECT 'a'::citext >= 'B'::text AS t; -- text wins. + +-- Test implicit casting. citext casts to varchar, but not vice-versa. +SELECT 'a'::citext = 'a'::varchar AS t; +SELECT 'A'::varchar <> 'a'::citext AS t; + +SELECT 'B'::citext < 'a'::varchar AS t; -- varchar wins. +SELECT 'B'::citext <= 'a'::varchar AS t; -- varchar wins. + +SELECT 'a'::citext > 'B'::varchar AS t; -- varchar wins. +SELECT 'a'::citext >= 'B'::varchar AS t; -- varchar wins. + +-- A couple of longer examples to ensure that we don't get any issues with bad +-- conversions to char[] in the c code. Yes, I did do this. + +SELECT 'aardvark'::citext = 'aardvark'::citext AS t; +SELECT 'aardvark'::citext = 'aardVark'::citext AS t; + +-- Check the citext_cmp() function explicitly. +SELECT citext_cmp('aardvark'::citext, 'aardvark'::citext) AS zero; +SELECT citext_cmp('aardvark'::citext, 'aardVark'::citext) AS zero; +SELECT citext_cmp('AARDVARK'::citext, 'AARDVARK'::citext) AS zero; +SELECT citext_cmp('B'::citext, 'a'::citext) > 0 AS true; + +-- Check the citext_hash() and citext_hash_extended() function explicitly. +SELECT v as value, citext_hash(v)::bit(32) as standard, + citext_hash_extended(v, 0)::bit(32) as extended0, + citext_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::citext), ('PostgreSQL'), ('eIpUEtqmY89'), ('AXKEJBTK'), + ('muop28x03'), ('yi3nm0d73')) x(v) +WHERE citext_hash(v)::bit(32) != citext_hash_extended(v, 0)::bit(32) + OR citext_hash(v)::bit(32) = citext_hash_extended(v, 1)::bit(32); + +-- Do some tests using a table and index. + +CREATE TEMP TABLE try ( + name citext PRIMARY KEY +); + +INSERT INTO try (name) +VALUES ('a'), ('ab'), ('â'), ('aba'), ('b'), ('ba'), ('bab'), ('AZ'); + +SELECT name, 'a' = name AS eq_a FROM try WHERE name <> 'â'; +SELECT name, 'a' = name AS t FROM try where name = 'a'; +SELECT name, 'A' = name AS "eq_A" FROM try WHERE name <> 'â'; +SELECT name, 'A' = name AS t FROM try where name = 'A'; +SELECT name, 'A' = name AS t FROM try where name = 'A'; + +-- expected failures on duplicate key +INSERT INTO try (name) VALUES ('a'); +INSERT INTO try (name) VALUES ('A'); +INSERT INTO try (name) VALUES ('aB'); + +-- Make sure that citext_smaller() and citext_larger() work properly. +SELECT citext_smaller( 'ab'::citext, 'ac'::citext ) = 'ab' AS t; +SELECT citext_smaller( 'ABC'::citext, 'bbbb'::citext ) = 'ABC' AS t; +SELECT citext_smaller( 'aardvark'::citext, 'Aaba'::citext ) = 'Aaba' AS t; +SELECT citext_smaller( 'aardvark'::citext, 'AARDVARK'::citext ) = 'AARDVARK' AS t; + +SELECT citext_larger( 'ab'::citext, 'ac'::citext ) = 'ac' AS t; +SELECT citext_larger( 'ABC'::citext, 'bbbb'::citext ) = 'bbbb' AS t; +SELECT citext_larger( 'aardvark'::citext, 'Aaba'::citext ) = 'aardvark' AS t; + +-- Test aggregate functions and sort ordering + +CREATE TEMP TABLE srt ( + name CITEXT +); + +INSERT INTO srt (name) +VALUES ('abb'), + ('ABA'), + ('ABC'), + ('abd'); + +CREATE INDEX srt_name ON srt (name); + +-- Check the min() and max() aggregates, with and without index. +set enable_seqscan = off; +SELECT MIN(name) AS "ABA" FROM srt; +SELECT MAX(name) AS abd FROM srt; +reset enable_seqscan; +set enable_indexscan = off; +SELECT MIN(name) AS "ABA" FROM srt; +SELECT MAX(name) AS abd FROM srt; +reset enable_indexscan; + +-- Check sorting likewise +set enable_seqscan = off; +SELECT name FROM srt ORDER BY name; +reset enable_seqscan; +set enable_indexscan = off; +SELECT name FROM srt ORDER BY name; +reset enable_indexscan; + +-- Test assignment casts. +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::text; +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::varchar; +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::bpchar; +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'; +SELECT LOWER(name) as aba FROM srt WHERE name = 'ABA'::citext; + +-- LIKE should be case-insensitive +SELECT name FROM srt WHERE name LIKE '%a%' ORDER BY name; +SELECT name FROM srt WHERE name NOT LIKE '%b%' ORDER BY name; +SELECT name FROM srt WHERE name LIKE '%A%' ORDER BY name; +SELECT name FROM srt WHERE name NOT LIKE '%B%' ORDER BY name; + +-- ~~ should be case-insensitive +SELECT name FROM srt WHERE name ~~ '%a%' ORDER BY name; +SELECT name FROM srt WHERE name !~~ '%b%' ORDER BY name; +SELECT name FROM srt WHERE name ~~ '%A%' ORDER BY name; +SELECT name FROM srt WHERE name !~~ '%B%' ORDER BY name; + +-- ~ should be case-insensitive +SELECT name FROM srt WHERE name ~ '^a' ORDER BY name; +SELECT name FROM srt WHERE name !~ 'a$' ORDER BY name; +SELECT name FROM srt WHERE name ~ '^A' ORDER BY name; +SELECT name FROM srt WHERE name !~ 'A$' ORDER BY name; + +-- SIMILAR TO should be case-insensitive. +SELECT name FROM srt WHERE name SIMILAR TO '%a.*'; +SELECT name FROM srt WHERE name SIMILAR TO '%A.*'; + +-- Explicit casts. +SELECT true::citext = 'true' AS t; +SELECT 'true'::citext::boolean = true AS t; + +SELECT 4::citext = '4' AS t; +SELECT 4::int4::citext = '4' AS t; +SELECT '4'::citext::int4 = 4 AS t; +SELECT 4::integer::citext = '4' AS t; +SELECT '4'::citext::integer = 4 AS t; + +SELECT 4::int8::citext = '4' AS t; +SELECT '4'::citext::int8 = 4 AS t; +SELECT 4::bigint::citext = '4' AS t; +SELECT '4'::citext::bigint = 4 AS t; + +SELECT 4::int2::citext = '4' AS t; +SELECT '4'::citext::int2 = 4 AS t; +SELECT 4::smallint::citext = '4' AS t; +SELECT '4'::citext::smallint = 4 AS t; + +SELECT 4.0::numeric = '4.0' AS t; +SELECT '4.0'::citext::numeric = 4.0 AS t; +SELECT 4.0::decimal = '4.0' AS t; +SELECT '4.0'::citext::decimal = 4.0 AS t; + +SELECT 4.0::real = '4.0' AS t; +SELECT '4.0'::citext::real = 4.0 AS t; +SELECT 4.0::float4 = '4.0' AS t; +SELECT '4.0'::citext::float4 = 4.0 AS t; + +SELECT 4.0::double precision = '4.0' AS t; +SELECT '4.0'::citext::double precision = 4.0 AS t; +SELECT 4.0::float8 = '4.0' AS t; +SELECT '4.0'::citext::float8 = 4.0 AS t; + +SELECT 'foo'::name::citext = 'foo' AS t; +SELECT 'foo'::citext::name = 'foo'::name AS t; + +SELECT 'f'::char::citext = 'f' AS t; +SELECT 'f'::citext::char = 'f'::char AS t; + +SELECT 'f'::"char"::citext = 'f' AS t; +SELECT 'f'::citext::"char" = 'f'::"char" AS t; + +SELECT '100'::money::citext = '$100.00' AS t; +SELECT '100'::citext::money = '100'::money AS t; + +SELECT 'a'::char::citext = 'a' AS t; +SELECT 'a'::citext::char = 'a'::char AS t; + +SELECT 'foo'::varchar::citext = 'foo' AS t; +SELECT 'foo'::citext::varchar = 'foo'::varchar AS t; + +SELECT 'foo'::text::citext = 'foo' AS t; +SELECT 'foo'::citext::text = 'foo'::text AS t; + +SELECT '192.168.100.128/25'::cidr::citext = '192.168.100.128/25' AS t; +SELECT '192.168.100.128/25'::citext::cidr = '192.168.100.128/25'::cidr AS t; + +SELECT '192.168.100.128'::inet::citext = '192.168.100.128/32' AS t; +SELECT '192.168.100.128'::citext::inet = '192.168.100.128'::inet AS t; + +SELECT '08:00:2b:01:02:03'::macaddr::citext = '08:00:2b:01:02:03' AS t; +SELECT '08:00:2b:01:02:03'::citext::macaddr = '08:00:2b:01:02:03'::macaddr AS t; + +SELECT '1999-01-08 04:05:06'::timestamp::citext = '1999-01-08 04:05:06'::timestamp::text AS t; +SELECT '1999-01-08 04:05:06'::citext::timestamp = '1999-01-08 04:05:06'::timestamp AS t; +SELECT '1999-01-08 04:05:06'::timestamptz::citext = '1999-01-08 04:05:06'::timestamptz::text AS t; +SELECT '1999-01-08 04:05:06'::citext::timestamptz = '1999-01-08 04:05:06'::timestamptz AS t; + +SELECT '1 hour'::interval::citext = '1 hour'::interval::text AS t; +SELECT '1 hour'::citext::interval = '1 hour'::interval AS t; + +SELECT '1999-01-08'::date::citext = '1999-01-08'::date::text AS t; +SELECT '1999-01-08'::citext::date = '1999-01-08'::date AS t; + +SELECT '04:05:06'::time::citext = '04:05:06' AS t; +SELECT '04:05:06'::citext::time = '04:05:06'::time AS t; +SELECT '04:05:06'::timetz::citext = '04:05:06'::timetz::text AS t; +SELECT '04:05:06'::citext::timetz = '04:05:06'::timetz AS t; + +SELECT '( 1 , 1)'::point::citext = '(1,1)' AS t; +SELECT '( 1 , 1)'::citext::point ~= '(1,1)'::point AS t; +SELECT '( 1 , 1 ) , ( 2 , 2 )'::lseg::citext = '[(1,1),(2,2)]' AS t; +SELECT '( 1 , 1 ) , ( 2 , 2 )'::citext::lseg = '[(1,1),(2,2)]'::lseg AS t; +SELECT '( 0 , 0 ) , ( 1 , 1 )'::box::citext = '(0,0),(1,1)'::box::text AS t; +SELECT '( 0 , 0 ) , ( 1 , 1 )'::citext::box ~= '(0,0),(1,1)'::text::box AS t; + +SELECT '((0,0),(1,1),(2,0))'::path::citext = '((0,0),(1,1),(2,0))' AS t; +SELECT '((0,0),(1,1),(2,0))'::citext::path = '((0,0),(1,1),(2,0))'::path AS t; + +SELECT '((0,0),(1,1))'::polygon::citext = '((0,0),(1,1))' AS t; +SELECT '((0,0),(1,1))'::citext::polygon ~= '((0,0),(1,1))'::polygon AS t; + +SELECT '((0,0),2)'::circle::citext = '((0,0),2)'::circle::text AS t; +SELECT '((0,0),2)'::citext::circle ~= '((0,0),2)'::text::circle AS t; + +SELECT '101'::bit::citext = '101'::bit::text AS t; +SELECT '101'::citext::bit = '101'::text::bit AS t; +SELECT '101'::bit varying::citext = '101'::bit varying::text AS t; +SELECT '101'::citext::bit varying = '101'::text::bit varying AS t; +SELECT 'a fat cat'::tsvector::citext = '''a'' ''cat'' ''fat''' AS t; +SELECT 'a fat cat'::citext::tsvector = 'a fat cat'::tsvector AS t; +SELECT 'fat & rat'::tsquery::citext = '''fat'' & ''rat''' AS t; +SELECT 'fat & rat'::citext::tsquery = 'fat & rat'::tsquery AS t; +SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid::citext = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11' AS t; +SELECT 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::citext::uuid = 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid AS t; + +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); +SELECT 'sad'::mood::citext = 'sad' AS t; +SELECT 'sad'::citext::mood = 'sad'::mood AS t; + +-- Assignment casts. +CREATE TABLE caster ( + citext citext, + text text, + varchar varchar, + bpchar bpchar, + char char, + chr "char", + name name, + bytea bytea, + boolean boolean, + float4 float4, + float8 float8, + numeric numeric, + int8 int8, + int4 int4, + int2 int2, + cidr cidr, + inet inet, + macaddr macaddr, + money money, + timestamp timestamp, + timestamptz timestamptz, + interval interval, + date date, + time time, + timetz timetz, + point point, + lseg lseg, + box box, + path path, + polygon polygon, + circle circle, + bit bit, + bitv bit varying, + tsvector tsvector, + tsquery tsquery, + uuid uuid +); + +INSERT INTO caster (text) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::text); + +INSERT INTO caster (varchar) VALUES ('foo'::text); +INSERT INTO caster (text) VALUES ('foo'::varchar); +INSERT INTO caster (varchar) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::varchar); + +INSERT INTO caster (bpchar) VALUES ('foo'::text); +INSERT INTO caster (text) VALUES ('foo'::bpchar); +INSERT INTO caster (bpchar) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::bpchar); + +INSERT INTO caster (char) VALUES ('f'::text); +INSERT INTO caster (text) VALUES ('f'::char); +INSERT INTO caster (char) VALUES ('f'::citext); +INSERT INTO caster (citext) VALUES ('f'::char); + +INSERT INTO caster (chr) VALUES ('f'::text); +INSERT INTO caster (text) VALUES ('f'::"char"); +INSERT INTO caster (chr) VALUES ('f'::citext); -- requires cast +INSERT INTO caster (chr) VALUES ('f'::citext::text); +INSERT INTO caster (citext) VALUES ('f'::"char"); + +INSERT INTO caster (name) VALUES ('foo'::text); +INSERT INTO caster (text) VALUES ('foo'::name); +INSERT INTO caster (name) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::name); + +-- Cannot cast to bytea on assignment. +INSERT INTO caster (bytea) VALUES ('foo'::text); +INSERT INTO caster (text) VALUES ('foo'::bytea); +INSERT INTO caster (bytea) VALUES ('foo'::citext); +INSERT INTO caster (citext) VALUES ('foo'::bytea); + +-- Cannot cast to boolean on assignment. +INSERT INTO caster (boolean) VALUES ('t'::text); +INSERT INTO caster (text) VALUES ('t'::boolean); +INSERT INTO caster (boolean) VALUES ('t'::citext); +INSERT INTO caster (citext) VALUES ('t'::boolean); + +-- Cannot cast to float8 on assignment. +INSERT INTO caster (float8) VALUES ('12.42'::text); +INSERT INTO caster (text) VALUES ('12.42'::float8); +INSERT INTO caster (float8) VALUES ('12.42'::citext); +INSERT INTO caster (citext) VALUES ('12.42'::float8); + +-- Cannot cast to float4 on assignment. +INSERT INTO caster (float4) VALUES ('12.42'::text); +INSERT INTO caster (text) VALUES ('12.42'::float4); +INSERT INTO caster (float4) VALUES ('12.42'::citext); +INSERT INTO caster (citext) VALUES ('12.42'::float4); + +-- Cannot cast to numeric on assignment. +INSERT INTO caster (numeric) VALUES ('12.42'::text); +INSERT INTO caster (text) VALUES ('12.42'::numeric); +INSERT INTO caster (numeric) VALUES ('12.42'::citext); +INSERT INTO caster (citext) VALUES ('12.42'::numeric); + +-- Cannot cast to int8 on assignment. +INSERT INTO caster (int8) VALUES ('12'::text); +INSERT INTO caster (text) VALUES ('12'::int8); +INSERT INTO caster (int8) VALUES ('12'::citext); +INSERT INTO caster (citext) VALUES ('12'::int8); + +-- Cannot cast to int4 on assignment. +INSERT INTO caster (int4) VALUES ('12'::text); +INSERT INTO caster (text) VALUES ('12'::int4); +INSERT INTO caster (int4) VALUES ('12'::citext); +INSERT INTO caster (citext) VALUES ('12'::int4); + +-- Cannot cast to int2 on assignment. +INSERT INTO caster (int2) VALUES ('12'::text); +INSERT INTO caster (text) VALUES ('12'::int2); +INSERT INTO caster (int2) VALUES ('12'::citext); +INSERT INTO caster (citext) VALUES ('12'::int2); + +-- Cannot cast to cidr on assignment. +INSERT INTO caster (cidr) VALUES ('192.168.100.128/25'::text); +INSERT INTO caster (text) VALUES ('192.168.100.128/25'::cidr); +INSERT INTO caster (cidr) VALUES ('192.168.100.128/25'::citext); +INSERT INTO caster (citext) VALUES ('192.168.100.128/25'::cidr); + +-- Cannot cast to inet on assignment. +INSERT INTO caster (inet) VALUES ('192.168.100.128'::text); +INSERT INTO caster (text) VALUES ('192.168.100.128'::inet); +INSERT INTO caster (inet) VALUES ('192.168.100.128'::citext); +INSERT INTO caster (citext) VALUES ('192.168.100.128'::inet); + +-- Cannot cast to macaddr on assignment. +INSERT INTO caster (macaddr) VALUES ('08:00:2b:01:02:03'::text); +INSERT INTO caster (text) VALUES ('08:00:2b:01:02:03'::macaddr); +INSERT INTO caster (macaddr) VALUES ('08:00:2b:01:02:03'::citext); +INSERT INTO caster (citext) VALUES ('08:00:2b:01:02:03'::macaddr); + +-- Cannot cast to money on assignment. +INSERT INTO caster (money) VALUES ('12'::text); +INSERT INTO caster (text) VALUES ('12'::money); +INSERT INTO caster (money) VALUES ('12'::citext); +INSERT INTO caster (citext) VALUES ('12'::money); + +-- Cannot cast to timestamp on assignment. +INSERT INTO caster (timestamp) VALUES ('1999-01-08 04:05:06'::text); +INSERT INTO caster (text) VALUES ('1999-01-08 04:05:06'::timestamp); +INSERT INTO caster (timestamp) VALUES ('1999-01-08 04:05:06'::citext); +INSERT INTO caster (citext) VALUES ('1999-01-08 04:05:06'::timestamp); + +-- Cannot cast to timestamptz on assignment. +INSERT INTO caster (timestamptz) VALUES ('1999-01-08 04:05:06'::text); +INSERT INTO caster (text) VALUES ('1999-01-08 04:05:06'::timestamptz); +INSERT INTO caster (timestamptz) VALUES ('1999-01-08 04:05:06'::citext); +INSERT INTO caster (citext) VALUES ('1999-01-08 04:05:06'::timestamptz); + +-- Cannot cast to interval on assignment. +INSERT INTO caster (interval) VALUES ('1 hour'::text); +INSERT INTO caster (text) VALUES ('1 hour'::interval); +INSERT INTO caster (interval) VALUES ('1 hour'::citext); +INSERT INTO caster (citext) VALUES ('1 hour'::interval); + +-- Cannot cast to date on assignment. +INSERT INTO caster (date) VALUES ('1999-01-08'::text); +INSERT INTO caster (text) VALUES ('1999-01-08'::date); +INSERT INTO caster (date) VALUES ('1999-01-08'::citext); +INSERT INTO caster (citext) VALUES ('1999-01-08'::date); + +-- Cannot cast to time on assignment. +INSERT INTO caster (time) VALUES ('04:05:06'::text); +INSERT INTO caster (text) VALUES ('04:05:06'::time); +INSERT INTO caster (time) VALUES ('04:05:06'::citext); +INSERT INTO caster (citext) VALUES ('04:05:06'::time); + +-- Cannot cast to timetz on assignment. +INSERT INTO caster (timetz) VALUES ('04:05:06'::text); +INSERT INTO caster (text) VALUES ('04:05:06'::timetz); +INSERT INTO caster (timetz) VALUES ('04:05:06'::citext); +INSERT INTO caster (citext) VALUES ('04:05:06'::timetz); + +-- Cannot cast to point on assignment. +INSERT INTO caster (point) VALUES ('( 1 , 1)'::text); +INSERT INTO caster (text) VALUES ('( 1 , 1)'::point); +INSERT INTO caster (point) VALUES ('( 1 , 1)'::citext); +INSERT INTO caster (citext) VALUES ('( 1 , 1)'::point); + +-- Cannot cast to lseg on assignment. +INSERT INTO caster (lseg) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::text); +INSERT INTO caster (text) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::lseg); +INSERT INTO caster (lseg) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::citext); +INSERT INTO caster (citext) VALUES ('( 1 , 1 ) , ( 2 , 2 )'::lseg); + +-- Cannot cast to box on assignment. +INSERT INTO caster (box) VALUES ('(0,0),(1,1)'::text); +INSERT INTO caster (text) VALUES ('(0,0),(1,1)'::box); +INSERT INTO caster (box) VALUES ('(0,0),(1,1)'::citext); +INSERT INTO caster (citext) VALUES ('(0,0),(1,1)'::box); + +-- Cannot cast to path on assignment. +INSERT INTO caster (path) VALUES ('((0,0),(1,1),(2,0))'::text); +INSERT INTO caster (text) VALUES ('((0,0),(1,1),(2,0))'::path); +INSERT INTO caster (path) VALUES ('((0,0),(1,1),(2,0))'::citext); +INSERT INTO caster (citext) VALUES ('((0,0),(1,1),(2,0))'::path); + +-- Cannot cast to polygon on assignment. +INSERT INTO caster (polygon) VALUES ('((0,0),(1,1))'::text); +INSERT INTO caster (text) VALUES ('((0,0),(1,1))'::polygon); +INSERT INTO caster (polygon) VALUES ('((0,0),(1,1))'::citext); +INSERT INTO caster (citext) VALUES ('((0,0),(1,1))'::polygon); + +-- Cannot cast to circle on assignment. +INSERT INTO caster (circle) VALUES ('((0,0),2)'::text); +INSERT INTO caster (text) VALUES ('((0,0),2)'::circle); +INSERT INTO caster (circle) VALUES ('((0,0),2)'::citext); +INSERT INTO caster (citext) VALUES ('((0,0),2)'::circle); + +-- Cannot cast to bit on assignment. +INSERT INTO caster (bit) VALUES ('101'::text); +INSERT INTO caster (text) VALUES ('101'::bit); +INSERT INTO caster (bit) VALUES ('101'::citext); +INSERT INTO caster (citext) VALUES ('101'::bit); + +-- Cannot cast to bit varying on assignment. +INSERT INTO caster (bitv) VALUES ('101'::text); +INSERT INTO caster (text) VALUES ('101'::bit varying); +INSERT INTO caster (bitv) VALUES ('101'::citext); +INSERT INTO caster (citext) VALUES ('101'::bit varying); + +-- Cannot cast to tsvector on assignment. +INSERT INTO caster (tsvector) VALUES ('the fat cat'::text); +INSERT INTO caster (text) VALUES ('the fat cat'::tsvector); +INSERT INTO caster (tsvector) VALUES ('the fat cat'::citext); +INSERT INTO caster (citext) VALUES ('the fat cat'::tsvector); + +-- Cannot cast to tsquery on assignment. +INSERT INTO caster (tsquery) VALUES ('fat & rat'::text); +INSERT INTO caster (text) VALUES ('fat & rat'::tsquery); +INSERT INTO caster (tsquery) VALUES ('fat & rat'::citext); +INSERT INTO caster (citext) VALUES ('fat & rat'::tsquery); + +-- Cannot cast to uuid on assignment. +INSERT INTO caster (uuid) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::text); +INSERT INTO caster (text) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid); +INSERT INTO caster (uuid) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::citext); +INSERT INTO caster (citext) VALUES ('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid); + +-- Table 9-5. SQL String Functions and Operators +SELECT 'D'::citext || 'avid'::citext = 'David'::citext AS citext_concat; +SELECT 'Value: '::citext || 42 = 'Value: 42' AS text_concat; +SELECT 42 || ': value'::citext ='42: value' AS int_concat; +SELECT bit_length('jose'::citext) = 32 AS t; +SELECT bit_length( name ) = bit_length( name::text ) AS t FROM srt; +SELECT textlen( name ) = textlen( name::text ) AS t FROM srt; +SELECT char_length( name ) = char_length( name::text ) AS t FROM srt; +SELECT lower( name ) = lower( name::text ) AS t FROM srt; +SELECT octet_length( name ) = octet_length( name::text ) AS t FROM srt; +SELECT overlay( name placing 'hom' from 2 for 4) = overlay( name::text placing 'hom' from 2 for 4) AS t FROM srt; +SELECT position( 'a' IN name ) = position( 'a' IN name::text ) AS t FROM srt; + +SELECT substr('alphabet'::citext, 3) = 'phabet' AS t; +SELECT substr('alphabet'::citext, 3, 2) = 'ph' AS t; + +SELECT substring('alphabet'::citext, 3) = 'phabet' AS t; +SELECT substring('alphabet'::citext, 3, 2) = 'ph' AS t; +SELECT substring('Thomas'::citext from 2 for 3) = 'hom' AS t; +SELECT substring('Thomas'::citext from 2) = 'homas' AS t; +SELECT substring('Thomas'::citext from '...$') = 'mas' AS t; +SELECT substring('Thomas'::citext similar '%#"o_a#"_' escape '#') = 'oma' AS t; + +SELECT trim(' trim '::citext) = 'trim' AS t; +SELECT trim('xxxxxtrimxxxx'::citext, 'x'::citext) = 'trim' AS t; +SELECT trim('xxxxxxtrimxxxx'::text, 'x'::citext) = 'trim' AS t; +SELECT trim('xxxxxtrimxxxx'::text, 'x'::citext) = 'trim' AS t; + +SELECT upper( name ) = upper( name::text ) AS t FROM srt; + +-- Table 9-6. Other String Functions. +SELECT ascii( name ) = ascii( name::text ) AS t FROM srt; + +SELECT btrim(' trim'::citext ) = 'trim' AS t; +SELECT btrim('xxxxxtrimxxxx'::citext, 'x'::citext ) = 'trim' AS t; +SELECT btrim('xyxtrimyyx'::citext, 'xy'::citext) = 'trim' AS t; +SELECT btrim('xyxtrimyyx'::text, 'xy'::citext) = 'trim' AS t; +SELECT btrim('xyxtrimyyx'::citext, 'xy'::text ) = 'trim' AS t; + +-- chr() takes an int and returns text. +-- convert() and convert_from take bytea and return text. + +SELECT convert_from( name::bytea, 'SQL_ASCII' ) = convert_from( name::text::bytea, 'SQL_ASCII' ) AS t FROM srt; +SELECT decode('MTIzAAE='::citext, 'base64') = decode('MTIzAAE='::text, 'base64') AS t; +-- encode() takes bytea and returns text. +SELECT initcap('hi THOMAS'::citext) = initcap('hi THOMAS'::text) AS t; +SELECT length( name ) = length( name::text ) AS t FROM srt; + +SELECT lpad('hi'::citext, 5 ) = ' hi' AS t; +SELECT lpad('hi'::citext, 5, 'xy'::citext) = 'xyxhi' AS t; +SELECT lpad('hi'::text, 5, 'xy'::citext) = 'xyxhi' AS t; +SELECT lpad('hi'::citext, 5, 'xy'::text ) = 'xyxhi' AS t; + +SELECT ltrim(' trim'::citext ) = 'trim' AS t; +SELECT ltrim('zzzytrim'::citext, 'xyz'::citext) = 'trim' AS t; +SELECT ltrim('zzzytrim'::text, 'xyz'::citext) = 'trim' AS t; +SELECT ltrim('zzzytrim'::citext, 'xyz'::text ) = 'trim' AS t; + +SELECT md5( name ) = md5( name::text ) AS t FROM srt; +-- pg_client_encoding() takes no args and returns name. +SELECT quote_ident( name ) = quote_ident( name::text ) AS t FROM srt; +SELECT quote_literal( name ) = quote_literal( name::text ) AS t FROM srt; + +SELECT regexp_match('foobarbequebaz'::citext, '(bar)(beque)') = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)') = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext) = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)', '') = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_match('foobarbequebaz', '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, ''::citext) = ARRAY[ 'bar', 'beque' ] AS t; +-- c forces case-sensitive +SELECT regexp_match('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, 'c'::citext) = ARRAY[ 'bar', 'beque' ] AS "no result"; +-- g is not allowed +SELECT regexp_match('foobarbequebazmorebarbequetoo'::citext, '(BAR)(BEQUE)'::citext, 'g') AS "error"; + +SELECT regexp_matches('foobarbequebaz'::citext, '(bar)(beque)') = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)') = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext) = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)', '') = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_matches('foobarbequebaz', '(BAR)(BEQUE)'::citext, '') = ARRAY[ 'bar', 'beque' ] AS t; +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, ''::citext) = ARRAY[ 'bar', 'beque' ] AS t; +-- c forces case-sensitive +SELECT regexp_matches('foobarbequebaz'::citext, '(BAR)(BEQUE)'::citext, 'c'::citext) = ARRAY[ 'bar', 'beque' ] AS "no rows"; +-- g allows multiple output rows +SELECT regexp_matches('foobarbequebazmorebarbequetoo'::citext, '(BAR)(BEQUE)'::citext, 'g'::citext) AS "two rows"; + +SELECT regexp_replace('Thomas'::citext, '.[mN]a.', 'M') = 'ThM' AS t; +SELECT regexp_replace('Thomas'::citext, '.[MN]A.', 'M') = 'ThM' AS t; +SELECT regexp_replace('Thomas', '.[MN]A.'::citext, 'M') = 'ThM' AS t; +SELECT regexp_replace('Thomas'::citext, '.[MN]A.'::citext, 'M') = 'ThM' AS t; +-- c forces case-sensitive +SELECT regexp_replace('Thomas'::citext, '.[MN]A.'::citext, 'M', 'c') = 'Thomas' AS t; + +SELECT regexp_split_to_array('hello world'::citext, E'\\s+') = ARRAY[ 'hello', 'world' ] AS t; +SELECT regexp_split_to_array('helloTworld'::citext, 't') = ARRAY[ 'hello', 'world' ] AS t; +SELECT regexp_split_to_array('helloTworld', 't'::citext) = ARRAY[ 'hello', 'world' ] AS t; +SELECT regexp_split_to_array('helloTworld'::citext, 't'::citext) = ARRAY[ 'hello', 'world' ] AS t; +SELECT regexp_split_to_array('helloTworld'::citext, 't', 's') = ARRAY[ 'hello', 'world' ] AS t; +SELECT regexp_split_to_array('helloTworld', 't'::citext, 's') = ARRAY[ 'hello', 'world' ] AS t; +SELECT regexp_split_to_array('helloTworld'::citext, 't'::citext, 's') = ARRAY[ 'hello', 'world' ] AS t; + +-- c forces case-sensitive +SELECT regexp_split_to_array('helloTworld'::citext, 't'::citext, 'c') = ARRAY[ 'helloTworld' ] AS t; + +SELECT regexp_split_to_table('hello world'::citext, E'\\s+') AS words; +SELECT regexp_split_to_table('helloTworld'::citext, 't') AS words; +SELECT regexp_split_to_table('helloTworld', 't'::citext) AS words; +SELECT regexp_split_to_table('helloTworld'::citext, 't'::citext) AS words; +-- c forces case-sensitive +SELECT regexp_split_to_table('helloTworld'::citext, 't'::citext, 'c') AS word; + +SELECT repeat('Pg'::citext, 4) = 'PgPgPgPg' AS t; + +SELECT replace('abcdefabcdef'::citext, 'cd', 'XX') = 'abXXefabXXef' AS t; +SELECT replace('abcdefabcdef'::citext, 'CD', 'XX') = 'abXXefabXXef' AS t; +SELECT replace('ab^is$abcdef'::citext, '^is$', 'XX') = 'abXXabcdef' AS t; +SELECT replace('abcdefabcdef', 'cd'::citext, 'XX') = 'abXXefabXXef' AS t; +SELECT replace('abcdefabcdef', 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; +SELECT replace('ab^is$abcdef', '^is$'::citext, 'XX') = 'abXXabcdef' AS t; +SELECT replace('abcdefabcdef'::citext, 'cd'::citext, 'XX') = 'abXXefabXXef' AS t; +SELECT replace('abcdefabcdef'::citext, 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; +SELECT replace('ab^is$abcdef'::citext, '^is$'::citext, 'XX') = 'abXXabcdef' AS t; + +SELECT rpad('hi'::citext, 5 ) = 'hi ' AS t; +SELECT rpad('hi'::citext, 5, 'xy'::citext) = 'hixyx' AS t; +SELECT rpad('hi'::text, 5, 'xy'::citext) = 'hixyx' AS t; +SELECT rpad('hi'::citext, 5, 'xy'::text ) = 'hixyx' AS t; + +SELECT rtrim('trim '::citext ) = 'trim' AS t; +SELECT rtrim('trimxxxx'::citext, 'x'::citext) = 'trim' AS t; +SELECT rtrim('trimxxxx'::text, 'x'::citext) = 'trim' AS t; +SELECT rtrim('trimxxxx'::text, 'x'::text ) = 'trim' AS t; + +SELECT split_part('abc~@~def~@~ghi'::citext, '~@~', 2) = 'def' AS t; +SELECT split_part('abcTdefTghi'::citext, 't', 2) = 'def' AS t; +SELECT split_part('abcTdefTghi'::citext, 't'::citext, 2) = 'def' AS t; +SELECT split_part('abcTdefTghi', 't'::citext, 2) = 'def' AS t; + +SELECT strpos('high'::citext, 'gh' ) = 3 AS t; +SELECT strpos('high', 'gh'::citext) = 3 AS t; +SELECT strpos('high'::citext, 'gh'::citext) = 3 AS t; +SELECT strpos('high'::citext, 'GH' ) = 3 AS t; +SELECT strpos('high', 'GH'::citext) = 3 AS t; +SELECT strpos('high'::citext, 'GH'::citext) = 3 AS t; + +-- to_ascii() does not support UTF-8. +-- to_hex() takes a numeric argument. +SELECT substr('alphabet', 3, 2) = 'ph' AS t; +SELECT translate('abcdefabcdef'::citext, 'cd', 'XX') = 'abXXefabXXef' AS t; +SELECT translate('abcdefabcdef'::citext, 'CD', 'XX') = 'abXXefabXXef' AS t; +SELECT translate('abcdefabcdef'::citext, 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; +SELECT translate('abcdefabcdef', 'CD'::citext, 'XX') = 'abXXefabXXef' AS t; + +-- Table 9-20. Formatting Functions +SELECT to_date('05 Dec 2000'::citext, 'DD Mon YYYY'::citext) + = to_date('05 Dec 2000', 'DD Mon YYYY') AS t; +SELECT to_date('05 Dec 2000'::citext, 'DD Mon YYYY') + = to_date('05 Dec 2000', 'DD Mon YYYY') AS t; +SELECT to_date('05 Dec 2000', 'DD Mon YYYY'::citext) + = to_date('05 Dec 2000', 'DD Mon YYYY') AS t; + +SELECT to_number('12,454.8-'::citext, '99G999D9S'::citext) + = to_number('12,454.8-', '99G999D9S') AS t; +SELECT to_number('12,454.8-'::citext, '99G999D9S') + = to_number('12,454.8-', '99G999D9S') AS t; +SELECT to_number('12,454.8-', '99G999D9S'::citext) + = to_number('12,454.8-', '99G999D9S') AS t; + +SELECT to_timestamp('05 Dec 2000'::citext, 'DD Mon YYYY'::citext) + = to_timestamp('05 Dec 2000', 'DD Mon YYYY') AS t; +SELECT to_timestamp('05 Dec 2000'::citext, 'DD Mon YYYY') + = to_timestamp('05 Dec 2000', 'DD Mon YYYY') AS t; +SELECT to_timestamp('05 Dec 2000', 'DD Mon YYYY'::citext) + = to_timestamp('05 Dec 2000', 'DD Mon YYYY') AS t; + +-- Try assigning function results to a column. +SELECT COUNT(*) = 8::bigint AS t FROM try; +INSERT INTO try +VALUES ( to_char( now()::timestamp, 'HH12:MI:SS') ), + ( to_char( now() + '1 sec'::interval, 'HH12:MI:SS') ), -- timestamptz + ( to_char( '15h 2m 12s'::interval, 'HH24:MI:SS') ), + ( to_char( current_date, '999') ), + ( to_char( 125::int, '999') ), + ( to_char( 127::int4, '999') ), + ( to_char( 126::int8, '999') ), + ( to_char( 128.8::real, '999D9') ), + ( to_char( 125.7::float4, '999D9') ), + ( to_char( 125.9::float8, '999D9') ), + ( to_char( -125.8::numeric, '999D99S') ); + +SELECT COUNT(*) = 19::bigint AS t FROM try; + +SELECT like_escape( name, '' ) = like_escape( name::text, '' ) AS t FROM srt; +SELECT like_escape( name::text, ''::citext ) = like_escape( name::text, '' ) AS t FROM srt; + +-- Ensure correct behavior for citext with materialized views. +CREATE TABLE citext_table ( + id serial primary key, + name citext +); +INSERT INTO citext_table (name) + VALUES ('one'), ('two'), ('three'), (NULL), (NULL); +CREATE MATERIALIZED VIEW citext_matview AS + SELECT * FROM citext_table; +CREATE UNIQUE INDEX citext_matview_id + ON citext_matview (id); +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; +UPDATE citext_table SET name = 'Two' WHERE name = 'TWO'; +SELECT * + FROM citext_matview m + FULL JOIN citext_table t ON (t.id = m.id AND t *= m) + WHERE t.id IS NULL OR m.id IS NULL; +REFRESH MATERIALIZED VIEW CONCURRENTLY citext_matview; +SELECT * FROM citext_matview ORDER BY id; + +-- test citext_pattern_cmp() function explicitly. +SELECT citext_pattern_cmp('aardvark'::citext, 'aardvark'::citext) AS zero; +SELECT citext_pattern_cmp('aardvark'::citext, 'aardVark'::citext) AS zero; +SELECT citext_pattern_cmp('AARDVARK'::citext, 'AARDVARK'::citext) AS zero; +SELECT citext_pattern_cmp('B'::citext, 'a'::citext) > 0 AS true; +SELECT citext_pattern_cmp('a'::citext, 'B'::citext) < 0 AS true; +SELECT citext_pattern_cmp('A'::citext, 'b'::citext) < 0 AS true; +SELECT citext_pattern_cmp('ABCD'::citext, 'abc'::citext) > 0 AS true; +SELECT citext_pattern_cmp('ABC'::citext, 'abcd'::citext) < 0 AS true; + +-- test operator functions +-- lt +SELECT citext_pattern_lt('a'::citext, 'b'::citext) AS true; +SELECT citext_pattern_lt('A'::citext, 'b'::citext) AS true; +SELECT citext_pattern_lt('a'::citext, 'B'::citext) AS true; +SELECT citext_pattern_lt('b'::citext, 'a'::citext) AS false; +SELECT citext_pattern_lt('B'::citext, 'a'::citext) AS false; +SELECT citext_pattern_lt('b'::citext, 'A'::citext) AS false; +-- le +SELECT citext_pattern_le('a'::citext, 'a'::citext) AS true; +SELECT citext_pattern_le('a'::citext, 'A'::citext) AS true; +SELECT citext_pattern_le('A'::citext, 'a'::citext) AS true; +SELECT citext_pattern_le('A'::citext, 'A'::citext) AS true; +SELECT citext_pattern_le('a'::citext, 'B'::citext) AS true; +SELECT citext_pattern_le('A'::citext, 'b'::citext) AS true; +SELECT citext_pattern_le('a'::citext, 'B'::citext) AS true; +SELECT citext_pattern_le('b'::citext, 'a'::citext) AS false; +SELECT citext_pattern_le('B'::citext, 'a'::citext) AS false; +SELECT citext_pattern_le('b'::citext, 'A'::citext) AS false; +-- gt +SELECT citext_pattern_gt('a'::citext, 'b'::citext) AS false; +SELECT citext_pattern_gt('A'::citext, 'b'::citext) AS false; +SELECT citext_pattern_gt('a'::citext, 'B'::citext) AS false; +SELECT citext_pattern_gt('b'::citext, 'a'::citext) AS true; +SELECT citext_pattern_gt('B'::citext, 'a'::citext) AS true; +SELECT citext_pattern_gt('b'::citext, 'A'::citext) AS true; +-- ge +SELECT citext_pattern_ge('a'::citext, 'a'::citext) AS true; +SELECT citext_pattern_ge('a'::citext, 'A'::citext) AS true; +SELECT citext_pattern_ge('A'::citext, 'a'::citext) AS true; +SELECT citext_pattern_ge('A'::citext, 'A'::citext) AS true; +SELECT citext_pattern_ge('a'::citext, 'B'::citext) AS false; +SELECT citext_pattern_ge('A'::citext, 'b'::citext) AS false; +SELECT citext_pattern_ge('a'::citext, 'B'::citext) AS false; +SELECT citext_pattern_ge('b'::citext, 'a'::citext) AS true; +SELECT citext_pattern_ge('B'::citext, 'a'::citext) AS true; +SELECT citext_pattern_ge('b'::citext, 'A'::citext) AS true; + +-- Test ~<~ and ~<=~ +SELECT 'a'::citext ~<~ 'B'::citext AS t; +SELECT 'b'::citext ~<~ 'A'::citext AS f; +SELECT 'a'::citext ~<=~ 'B'::citext AS t; +SELECT 'a'::citext ~<=~ 'A'::citext AS t; + +-- Test ~>~ and ~>=~ +SELECT 'B'::citext ~>~ 'a'::citext AS t; +SELECT 'b'::citext ~>~ 'A'::citext AS t; +SELECT 'B'::citext ~>~ 'b'::citext AS f; +SELECT 'B'::citext ~>=~ 'b'::citext AS t; + +-- Test implicit casting. citext casts to text, but not vice-versa. +SELECT 'B'::citext ~<~ 'a'::text AS t; -- text wins. +SELECT 'B'::citext ~<=~ 'a'::text AS t; -- text wins. + +SELECT 'a'::citext ~>~ 'B'::text AS t; -- text wins. +SELECT 'a'::citext ~>=~ 'B'::text AS t; -- text wins. + +-- Test implicit casting. citext casts to varchar, but not vice-versa. +SELECT 'B'::citext ~<~ 'a'::varchar AS t; -- varchar wins. +SELECT 'B'::citext ~<=~ 'a'::varchar AS t; -- varchar wins. + +SELECT 'a'::citext ~>~ 'B'::varchar AS t; -- varchar wins. +SELECT 'a'::citext ~>=~ 'B'::varchar AS t; -- varchar wins. diff --git a/contrib/citext/sql/citext_utf8.sql b/contrib/citext/sql/citext_utf8.sql new file mode 100644 index 0000000..1f51df1 --- /dev/null +++ b/contrib/citext/sql/citext_utf8.sql @@ -0,0 +1,58 @@ +/* + * This test must be run in a database with UTF-8 encoding + * and a Unicode-aware locale. + * + * Also disable this file for ICU, because the test for the the + * Turkish dotted I is not correct for many ICU locales. citext always + * uses the default collation, so it's not easy to restrict the test + * to the "tr-TR-x-icu" collation where it will succeed. + */ + +SELECT getdatabaseencoding() <> 'UTF8' OR + (SELECT (datlocprovider = 'c' AND datctype = 'C') OR datlocprovider = 'i' + FROM pg_database + WHERE datname=current_database()) + AS skip_test \gset +\if :skip_test +\quit +\endif + +set client_encoding = utf8; + +-- CREATE EXTENSION IF NOT EXISTS citext; + +-- Multibyte sanity tests. +SELECT 'À'::citext = 'À'::citext AS t; +SELECT 'À'::citext = 'à'::citext AS t; +SELECT 'À'::text = 'à'::text AS f; -- text wins. +SELECT 'À'::citext <> 'B'::citext AS t; + +-- Test combining characters making up canonically equivalent strings. +SELECT 'Ä'::text <> 'Ä'::text AS t; +SELECT 'Ä'::citext <> 'Ä'::citext AS t; + +-- Test the Turkish dotted I. The lowercase is a single byte while the +-- uppercase is multibyte. This is why the comparison code can't be optimized +-- to compare string lengths. +SELECT 'i'::citext = 'İ'::citext AS t; + +-- Regression. +SELECT 'láska'::citext <> 'laská'::citext AS t; + +SELECT 'Ask Bjørn Hansen'::citext = 'Ask Bjørn Hansen'::citext AS t; +SELECT 'Ask Bjørn Hansen'::citext = 'ASK BJØRN HANSEN'::citext AS t; +SELECT 'Ask Bjørn Hansen'::citext <> 'Ask Bjorn Hansen'::citext AS t; +SELECT 'Ask Bjørn Hansen'::citext <> 'ASK BJORN HANSEN'::citext AS t; +SELECT citext_cmp('Ask Bjørn Hansen'::citext, 'Ask Bjørn Hansen'::citext) = 0 AS t; +SELECT citext_cmp('Ask Bjørn Hansen'::citext, 'ask bjørn hansen'::citext) = 0 AS t; +SELECT citext_cmp('Ask Bjørn Hansen'::citext, 'ASK BJØRN HANSEN'::citext) = 0 AS t; +SELECT citext_cmp('Ask Bjørn Hansen'::citext, 'Ask Bjorn Hansen'::citext) > 0 AS t; +SELECT citext_cmp('Ask Bjorn Hansen'::citext, 'Ask Bjørn Hansen'::citext) < 0 AS t; + +-- Test ~<~ and ~<=~ +SELECT 'à'::citext ~<~ 'À'::citext AS f; +SELECT 'à'::citext ~<=~ 'À'::citext AS t; + +-- Test ~>~ and ~>=~ +SELECT 'à'::citext ~>~ 'À'::citext AS f; +SELECT 'à'::citext ~>=~ 'À'::citext AS t; diff --git a/contrib/citext/sql/create_index_acl.sql b/contrib/citext/sql/create_index_acl.sql new file mode 100644 index 0000000..10b5225 --- /dev/null +++ b/contrib/citext/sql/create_index_acl.sql @@ -0,0 +1,88 @@ +-- Each DefineIndex() ACL check uses either the original userid or the table +-- owner userid; see its header comment. Here, confirm that DefineIndex() +-- uses its original userid where necessary. The test works by creating +-- indexes that refer to as many sorts of objects as possible, with the table +-- owner having as few applicable privileges as possible. (The privileges.sql +-- regress_sro_user tests look for the opposite defect; they confirm that +-- DefineIndex() uses the table owner userid where necessary.) + +SET allow_in_place_tablespaces = true; +CREATE TABLESPACE regress_create_idx_tblspace LOCATION ''; +RESET allow_in_place_tablespaces; + +BEGIN; +CREATE ROLE regress_minimal; +CREATE SCHEMA s; +CREATE EXTENSION citext SCHEMA s; +-- Revoke all conceivably-relevant ACLs within the extension. The system +-- doesn't check all these ACLs, but this will provide some coverage if that +-- ever changes. +REVOKE ALL ON TYPE s.citext FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_pattern_lt FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_pattern_le FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_eq FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_pattern_ge FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_pattern_gt FROM PUBLIC; +REVOKE ALL ON FUNCTION s.citext_pattern_cmp FROM PUBLIC; +-- Functions sufficient for making an index column that has the side effect of +-- changing search_path at expression planning time. +CREATE FUNCTION public.setter() RETURNS bool VOLATILE + LANGUAGE SQL AS $$SET search_path = s; SELECT true$$; +CREATE FUNCTION s.const() RETURNS bool IMMUTABLE + LANGUAGE SQL AS $$SELECT public.setter()$$; +CREATE FUNCTION s.index_this_expr(s.citext, bool) RETURNS s.citext IMMUTABLE + LANGUAGE SQL AS $$SELECT $1$$; +REVOKE ALL ON FUNCTION public.setter FROM PUBLIC; +REVOKE ALL ON FUNCTION s.const FROM PUBLIC; +REVOKE ALL ON FUNCTION s.index_this_expr FROM PUBLIC; +-- Even for an empty table, expression planning calls s.const & public.setter. +GRANT EXECUTE ON FUNCTION public.setter TO regress_minimal; +GRANT EXECUTE ON FUNCTION s.const TO regress_minimal; +-- Function for index predicate. +CREATE FUNCTION s.index_row_if(s.citext) RETURNS bool IMMUTABLE + LANGUAGE SQL AS $$SELECT $1 IS NOT NULL$$; +REVOKE ALL ON FUNCTION s.index_row_if FROM PUBLIC; +-- Even for an empty table, CREATE INDEX checks ii_Predicate permissions. +GRANT EXECUTE ON FUNCTION s.index_row_if TO regress_minimal; +-- Non-extension, non-function objects. +CREATE COLLATION s.coll (LOCALE="C"); +CREATE TABLE s.x (y s.citext); +ALTER TABLE s.x OWNER TO regress_minimal; +-- Empty-table DefineIndex() +CREATE UNIQUE INDEX u0rows ON s.x USING btree + ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_pattern_ops) + TABLESPACE regress_create_idx_tblspace + WHERE s.index_row_if(y); +ALTER TABLE s.x ADD CONSTRAINT e0rows EXCLUDE USING btree + ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=) + USING INDEX TABLESPACE regress_create_idx_tblspace + WHERE (s.index_row_if(y)); +-- Make the table nonempty. +INSERT INTO s.x VALUES ('foo'), ('bar'); +-- If the INSERT runs the planner on index expressions, a search_path change +-- survives. As of 2022-06, the INSERT reuses a cached plan. It does so even +-- under debug_discard_caches, since each index is new-in-transaction. If +-- future work changes a cache lifecycle, this RESET may become necessary. +RESET search_path; +-- For a nonempty table, owner needs permissions throughout ii_Expressions. +GRANT EXECUTE ON FUNCTION s.index_this_expr TO regress_minimal; +CREATE UNIQUE INDEX u2rows ON s.x USING btree + ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_pattern_ops) + TABLESPACE regress_create_idx_tblspace + WHERE s.index_row_if(y); +ALTER TABLE s.x ADD CONSTRAINT e2rows EXCLUDE USING btree + ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=) + USING INDEX TABLESPACE regress_create_idx_tblspace + WHERE (s.index_row_if(y)); +-- Shall not find s.coll via search_path, despite the s.const->public.setter +-- call having set search_path=s during expression planning. Suppress the +-- message itself, which depends on the database encoding. +\set VERBOSITY sqlstate +ALTER TABLE s.x ADD CONSTRAINT underqualified EXCLUDE USING btree + ((s.index_this_expr(y, s.const())) COLLATE coll WITH s.=) + USING INDEX TABLESPACE regress_create_idx_tblspace + WHERE (s.index_row_if(y)); +\set VERBOSITY default +ROLLBACK; + +DROP TABLESPACE regress_create_idx_tblspace; diff --git a/contrib/contrib-global.mk b/contrib/contrib-global.mk new file mode 100644 index 0000000..6ac8e9b --- /dev/null +++ b/contrib/contrib-global.mk @@ -0,0 +1,4 @@ +# contrib/contrib-global.mk + +NO_PGXS = 1 +include $(top_srcdir)/src/makefiles/pgxs.mk diff --git a/contrib/cube/.gitignore b/contrib/cube/.gitignore new file mode 100644 index 0000000..f788440 --- /dev/null +++ b/contrib/cube/.gitignore @@ -0,0 +1,7 @@ +/cubeparse.h +/cubeparse.c +/cubescan.c +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/cube/CHANGES b/contrib/cube/CHANGES new file mode 100644 index 0000000..7c5590c --- /dev/null +++ b/contrib/cube/CHANGES @@ -0,0 +1,130 @@ +******************************************************************************** +Changes that were made in July 2006 by Joshua Reich I. +******************************************************************************** + +Code Cleanup: + +Update the calling convention for all external facing functions. By external +facing, I mean all functions that are directly referenced in cube.sql. Prior +to my update, all functions used the older V0 calling convention. They now +use V1. + +New Functions: + +cube(float[]), which makes a zero volume cube from a float array + +cube(float[], float[]), which allows the user to create a cube from +two float arrays; one for the upper right and one for the lower left +coordinate. + +cube_subset(cube, int4[]), to allow you to reorder or choose a subset of +dimensions from a cube, using index values specified in the array. + +******************************************************************************** +Changes that were made in August/September 2002 by Bruno Wolff III. +******************************************************************************** + +Note that this was based on a 7.3 development version and changes may not +directly work with earlier versions. + +I fixed a bug in cubescan.pl that prevented signed numbers with no digits +before a decimal point from being accepted. This was submitted as a separate +patch and may already be applied. + +cube_inter should really return NULL if the two cubes don't overlap. However +this requires changing to the new calling sequence and I don't know enough +about how to do it to make the change. + +I changed all floats to doubles except for g_cube_penalty which I don't +think can be changed to return double. This might cause the penalty to +overflow sooner than one might expect, but overflow could have happened +even with floats. + +I changed the output format (in cube_out) to use %.16g instead of %g, since the +default is only 6 digits of precision. + +I changed all of the functions declared with (isstrict) to use the current +method of declaring this. + +I changed all of the externally visible functions to be immutable which +they are. I don't think this matters for the gist functions and didn't +try to declare them immutable in case there was something tricky about them +that I don't understand. + +I changed the regression tests to use some larger exponents to test output +in exponential form. 1e7 was too small for this. + +I added some regression tests to check for 16 digits of precision. This +may or may not be a good idea. + +I got rid of the swap_corners function. It created scratch boxes that +were iterated through and deleted. This is slower than just getting the +larger or smaller coordinate as needed, since swap_corners was doing the +same thing with the overhead of a function call and memory allocation. + +I added memset calls to zero out newly allocated NDBOXes as the documentation +on functions indicates should be done. This still doesn't allow a hash +index for equality since there are multiple representations of the +same cube. + +I got rid of a call to cube_same in cube_lt and cube_gt since the test +was redundant with other checks being made. The call to cube_same would +only be faster if most of the time you were comparing equivalent cubes. + +In cube_lt and cube_gt, the second (UR) for loop for comparing +extra coordinates to 0 had the wrong range. + +Note that the cube_distance function wasn't mentioned in the README.cube file. + +I added regression tests for the cube_distance function. + +I added the following new functions: +cube +cube_dim +cube_ll_coord +cube_ur_coord +cube_is_point +cube_enlarge + +cube takes text input and returns a cube. This is useful for making cubes +from computed strings. + +cube_dim returns the number of dimensions stored in the data structure +for a cube. This is useful for constraints on the dimensions of a cube. + +cube_ll_coord returns the nth coordinate value for the lower left corner +of a cube. This is useful for doing coordinate transformations. + +cube_ur_coord returns the nth coordinate value for the upper right corner +of a cube. This is useful for doing coordinate transformations. + +cube_is_point returns true if a cube is also a point. This is true when the +two defining corners are the same. + +cube_enlarge increases the size of a cube by a specified radius in at least +n dimensions. If the radius is negative the box is shrunk instead. This +is useful for creating bounding boxes around a point for searching for +nearby points. All defined dimensions are changed by the radius. If n +is greater than the number of defined dimensions and the cube is being +increased (r >= 0) then 0 is used as the base for the extra coordinates. +LL coordinates are decreased by r and UR coordinates are increased by r. If a +LL coordinate is increased to larger than the corresponding UR coordinate +(this can only happen when r < 0) than both coordinates are set to their +average. + +I added regression tests for the new functions. + +I added documentation for cube_distance and the new functions to README.cube +as well as making a few other minor changes. + +I changed create function to create or replace function in the install +script. + +I limited the number of dimensions allowed in cube_enlarge and cube_in +to 100 to make it harder for people to mess up the database. The constant +is defined in cubedata.h and can be increased if you need something larger. + +I added grant statements to the install script to make the functions +executable to everyone. + +Bruno Wolff III diff --git a/contrib/cube/Makefile b/contrib/cube/Makefile new file mode 100644 index 0000000..4fd19aa --- /dev/null +++ b/contrib/cube/Makefile @@ -0,0 +1,44 @@ +# contrib/cube/Makefile + +MODULE_big = cube +OBJS = \ + $(WIN32RES) \ + cube.o \ + cubeparse.o \ + cubescan.o + +EXTENSION = cube +DATA = cube--1.2.sql cube--1.2--1.3.sql cube--1.3--1.4.sql cube--1.4--1.5.sql \ + cube--1.1--1.2.sql cube--1.0--1.1.sql +PGFILEDESC = "cube - multidimensional cube data type" + +HEADERS = cubedata.h + +REGRESS = cube cube_sci + +SHLIB_LINK += $(filter -lm, $(LIBS)) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/cube +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +# See notes in src/backend/parser/Makefile about the following two rules +cubeparse.h: cubeparse.c + touch $@ + +cubeparse.c: BISONFLAGS += -d + +# Force these dependencies to be known even without dependency info built: +cubeparse.o cubescan.o: cubeparse.h + +distprep: cubeparse.c cubescan.c + +maintainer-clean: + rm -f cubeparse.h cubeparse.c cubescan.c diff --git a/contrib/cube/cube--1.0--1.1.sql b/contrib/cube/cube--1.0--1.1.sql new file mode 100644 index 0000000..fbe61e7 --- /dev/null +++ b/contrib/cube/cube--1.0--1.1.sql @@ -0,0 +1,59 @@ +/* contrib/cube/cube--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION cube UPDATE TO '1.1'" to load this file. \quit + +CREATE FUNCTION distance_chebyshev(cube, cube) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION distance_taxicab(cube, cube) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION cube_coord(cube, int4) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION cube_coord_llur(cube, int4) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +CREATE OPERATOR -> ( + LEFTARG = cube, RIGHTARG = int, PROCEDURE = cube_coord +); + +CREATE OPERATOR ~> ( + LEFTARG = cube, RIGHTARG = int, PROCEDURE = cube_coord_llur +); + +CREATE OPERATOR <#> ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = distance_taxicab, + COMMUTATOR = '<#>' +); + +CREATE OPERATOR <-> ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_distance, + COMMUTATOR = '<->' +); + +CREATE OPERATOR <=> ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = distance_chebyshev, + COMMUTATOR = '<=>' +); + +CREATE FUNCTION g_cube_distance (internal, cube, smallint, oid) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +ALTER OPERATOR FAMILY gist_cube_ops USING gist ADD + OPERATOR 15 ~> (cube, int) FOR ORDER BY float_ops, + OPERATOR 16 <#> (cube, cube) FOR ORDER BY float_ops, + OPERATOR 17 <-> (cube, cube) FOR ORDER BY float_ops, + OPERATOR 18 <=> (cube, cube) FOR ORDER BY float_ops, + FUNCTION 8 (cube, cube) g_cube_distance (internal, cube, smallint, oid); diff --git a/contrib/cube/cube--1.1--1.2.sql b/contrib/cube/cube--1.1--1.2.sql new file mode 100644 index 0000000..76aba23 --- /dev/null +++ b/contrib/cube/cube--1.1--1.2.sql @@ -0,0 +1,75 @@ +/* contrib/cube/cube--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION cube UPDATE TO '1.2'" to load this file. \quit + +-- Update procedure signatures the hard way. +-- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, +-- wherein the signatures have been updated already. In that case to_regprocedure() will +-- return NULL and no updates will happen. +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) +FROM (VALUES +(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types +('g_cube_consistent(internal,SCH.cube,int4,oid,internal)', '{internal,SCH.cube,int2,oid,internal}'), +('g_cube_distance(internal,SCH.cube,smallint,oid)', '{internal,SCH.cube,smallint,oid,internal}') +) AS update_data (oldproc, newtypestext), +LATERAL ( + SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ +) ls +WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; + +ALTER FUNCTION cube_in(cstring) PARALLEL SAFE; +ALTER FUNCTION cube(float8[], float8[]) PARALLEL SAFE; +ALTER FUNCTION cube(float8[]) PARALLEL SAFE; +ALTER FUNCTION cube_out(cube) PARALLEL SAFE; +ALTER FUNCTION cube_eq(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_ne(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_lt(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_gt(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_le(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_ge(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_cmp(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_contains(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_contained(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_overlap(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_union(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_inter(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_size(cube) PARALLEL SAFE; +ALTER FUNCTION cube_subset(cube, int4[]) PARALLEL SAFE; +ALTER FUNCTION cube_distance(cube, cube) PARALLEL SAFE; +ALTER FUNCTION distance_chebyshev(cube, cube) PARALLEL SAFE; +ALTER FUNCTION distance_taxicab(cube, cube) PARALLEL SAFE; +ALTER FUNCTION cube_dim(cube) PARALLEL SAFE; +ALTER FUNCTION cube_ll_coord(cube, int4) PARALLEL SAFE; +ALTER FUNCTION cube_ur_coord(cube, int4) PARALLEL SAFE; +ALTER FUNCTION cube_coord(cube, int4) PARALLEL SAFE; +ALTER FUNCTION cube_coord_llur(cube, int4) PARALLEL SAFE; +ALTER FUNCTION cube(float8) PARALLEL SAFE; +ALTER FUNCTION cube(float8, float8) PARALLEL SAFE; +ALTER FUNCTION cube(cube, float8) PARALLEL SAFE; +ALTER FUNCTION cube(cube, float8, float8) PARALLEL SAFE; +ALTER FUNCTION cube_is_point(cube) PARALLEL SAFE; +ALTER FUNCTION cube_enlarge(cube, float8, int4) PARALLEL SAFE; +ALTER FUNCTION g_cube_consistent(internal, cube, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION g_cube_compress(internal) PARALLEL SAFE; +ALTER FUNCTION g_cube_decompress(internal) PARALLEL SAFE; +ALTER FUNCTION g_cube_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION g_cube_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION g_cube_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION g_cube_same(cube, cube, internal) PARALLEL SAFE; +ALTER FUNCTION g_cube_distance(internal, cube, smallint, oid, internal) PARALLEL SAFE; diff --git a/contrib/cube/cube--1.2--1.3.sql b/contrib/cube/cube--1.2--1.3.sql new file mode 100644 index 0000000..a688f19 --- /dev/null +++ b/contrib/cube/cube--1.2--1.3.sql @@ -0,0 +1,12 @@ +/* contrib/cube/cube--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION cube UPDATE TO '1.3'" to load this file. \quit + +ALTER OPERATOR <= (cube, cube) SET ( + RESTRICT = scalarlesel, JOIN = scalarlejoinsel +); + +ALTER OPERATOR >= (cube, cube) SET ( + RESTRICT = scalargesel, JOIN = scalargejoinsel +); diff --git a/contrib/cube/cube--1.2.sql b/contrib/cube/cube--1.2.sql new file mode 100644 index 0000000..dea2614 --- /dev/null +++ b/contrib/cube/cube--1.2.sql @@ -0,0 +1,378 @@ +/* contrib/cube/cube--1.2.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION cube" to load this file. \quit + +-- Create the user-defined type for N-dimensional boxes + +CREATE FUNCTION cube_in(cstring) +RETURNS cube +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube(float8[], float8[]) RETURNS cube +AS 'MODULE_PATHNAME', 'cube_a_f8_f8' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube(float8[]) RETURNS cube +AS 'MODULE_PATHNAME', 'cube_a_f8' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube_out(cube) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE TYPE cube ( + INTERNALLENGTH = variable, + INPUT = cube_in, + OUTPUT = cube_out, + ALIGNMENT = double +); + +COMMENT ON TYPE cube IS 'multi-dimensional cube ''(FLOAT-1, FLOAT-2, ..., FLOAT-N), (FLOAT-1, FLOAT-2, ..., FLOAT-N)'''; + +-- +-- External C-functions for R-tree methods +-- + +-- Comparison methods + +CREATE FUNCTION cube_eq(cube, cube) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +COMMENT ON FUNCTION cube_eq(cube, cube) IS 'same as'; + +CREATE FUNCTION cube_ne(cube, cube) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +COMMENT ON FUNCTION cube_ne(cube, cube) IS 'different'; + +CREATE FUNCTION cube_lt(cube, cube) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +COMMENT ON FUNCTION cube_lt(cube, cube) IS 'lower than'; + +CREATE FUNCTION cube_gt(cube, cube) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +COMMENT ON FUNCTION cube_gt(cube, cube) IS 'greater than'; + +CREATE FUNCTION cube_le(cube, cube) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +COMMENT ON FUNCTION cube_le(cube, cube) IS 'lower than or equal to'; + +CREATE FUNCTION cube_ge(cube, cube) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +COMMENT ON FUNCTION cube_ge(cube, cube) IS 'greater than or equal to'; + +CREATE FUNCTION cube_cmp(cube, cube) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +COMMENT ON FUNCTION cube_cmp(cube, cube) IS 'btree comparison function'; + +CREATE FUNCTION cube_contains(cube, cube) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +COMMENT ON FUNCTION cube_contains(cube, cube) IS 'contains'; + +CREATE FUNCTION cube_contained(cube, cube) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +COMMENT ON FUNCTION cube_contained(cube, cube) IS 'contained in'; + +CREATE FUNCTION cube_overlap(cube, cube) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +COMMENT ON FUNCTION cube_overlap(cube, cube) IS 'overlaps'; + +-- support routines for indexing + +CREATE FUNCTION cube_union(cube, cube) +RETURNS cube +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube_inter(cube, cube) +RETURNS cube +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube_size(cube) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + + +-- Misc N-dimensional functions + +CREATE FUNCTION cube_subset(cube, int4[]) +RETURNS cube +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- proximity routines + +CREATE FUNCTION cube_distance(cube, cube) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION distance_chebyshev(cube, cube) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION distance_taxicab(cube, cube) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- Extracting elements functions + +CREATE FUNCTION cube_dim(cube) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube_ll_coord(cube, int4) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube_ur_coord(cube, int4) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube_coord(cube, int4) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube_coord_llur(cube, int4) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube(float8) RETURNS cube +AS 'MODULE_PATHNAME', 'cube_f8' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube(float8, float8) RETURNS cube +AS 'MODULE_PATHNAME', 'cube_f8_f8' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube(cube, float8) RETURNS cube +AS 'MODULE_PATHNAME', 'cube_c_f8' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube(cube, float8, float8) RETURNS cube +AS 'MODULE_PATHNAME', 'cube_c_f8_f8' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- Test if cube is also a point + +CREATE FUNCTION cube_is_point(cube) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- Increasing the size of a cube by a radius in at least n dimensions + +CREATE FUNCTION cube_enlarge(cube, float8, int4) +RETURNS cube +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- +-- OPERATORS +-- + +CREATE OPERATOR < ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_lt, + COMMUTATOR = '>', NEGATOR = '>=', + RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_gt, + COMMUTATOR = '<', NEGATOR = '<=', + RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_le, + COMMUTATOR = '>=', NEGATOR = '>', + RESTRICT = scalarltsel, JOIN = scalarltjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_ge, + COMMUTATOR = '<=', NEGATOR = '<', + RESTRICT = scalargtsel, JOIN = scalargtjoinsel +); + +CREATE OPERATOR && ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_overlap, + COMMUTATOR = '&&', + RESTRICT = areasel, JOIN = areajoinsel +); + +CREATE OPERATOR = ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_eq, + COMMUTATOR = '=', NEGATOR = '<>', + RESTRICT = eqsel, JOIN = eqjoinsel, + MERGES +); + +CREATE OPERATOR <> ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_ne, + COMMUTATOR = '<>', NEGATOR = '=', + RESTRICT = neqsel, JOIN = neqjoinsel +); + +CREATE OPERATOR @> ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_contains, + COMMUTATOR = '<@', + RESTRICT = contsel, JOIN = contjoinsel +); + +CREATE OPERATOR <@ ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_contained, + COMMUTATOR = '@>', + RESTRICT = contsel, JOIN = contjoinsel +); + +CREATE OPERATOR -> ( + LEFTARG = cube, RIGHTARG = int, PROCEDURE = cube_coord +); + +CREATE OPERATOR ~> ( + LEFTARG = cube, RIGHTARG = int, PROCEDURE = cube_coord_llur +); + +CREATE OPERATOR <#> ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = distance_taxicab, + COMMUTATOR = '<#>' +); + +CREATE OPERATOR <-> ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_distance, + COMMUTATOR = '<->' +); + +CREATE OPERATOR <=> ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = distance_chebyshev, + COMMUTATOR = '<=>' +); + +-- these are obsolete/deprecated: +CREATE OPERATOR @ ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_contains, + COMMUTATOR = '~', + RESTRICT = contsel, JOIN = contjoinsel +); + +CREATE OPERATOR ~ ( + LEFTARG = cube, RIGHTARG = cube, PROCEDURE = cube_contained, + COMMUTATOR = '@', + RESTRICT = contsel, JOIN = contjoinsel +); + + +-- define the GiST support methods +CREATE FUNCTION g_cube_consistent(internal,cube,smallint,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_cube_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_cube_decompress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_cube_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_cube_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_cube_union(internal, internal) +RETURNS cube +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_cube_same(cube, cube, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_cube_distance (internal, cube, smallint, oid, internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- Create the operator classes for indexing + +CREATE OPERATOR CLASS cube_ops + DEFAULT FOR TYPE cube USING btree AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 cube_cmp(cube, cube); + +CREATE OPERATOR CLASS gist_cube_ops + DEFAULT FOR TYPE cube USING gist AS + OPERATOR 3 && , + OPERATOR 6 = , + OPERATOR 7 @> , + OPERATOR 8 <@ , + OPERATOR 13 @ , + OPERATOR 14 ~ , + OPERATOR 15 ~> (cube, int) FOR ORDER BY float_ops, + OPERATOR 16 <#> (cube, cube) FOR ORDER BY float_ops, + OPERATOR 17 <-> (cube, cube) FOR ORDER BY float_ops, + OPERATOR 18 <=> (cube, cube) FOR ORDER BY float_ops, + + FUNCTION 1 g_cube_consistent (internal, cube, smallint, oid, internal), + FUNCTION 2 g_cube_union (internal, internal), + FUNCTION 3 g_cube_compress (internal), + FUNCTION 4 g_cube_decompress (internal), + FUNCTION 5 g_cube_penalty (internal, internal, internal), + FUNCTION 6 g_cube_picksplit (internal, internal), + FUNCTION 7 g_cube_same (cube, cube, internal), + FUNCTION 8 g_cube_distance (internal, cube, smallint, oid, internal); diff --git a/contrib/cube/cube--1.3--1.4.sql b/contrib/cube/cube--1.3--1.4.sql new file mode 100644 index 0000000..4162939 --- /dev/null +++ b/contrib/cube/cube--1.3--1.4.sql @@ -0,0 +1,58 @@ +/* contrib/cube/cube--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION cube UPDATE TO '1.4'" to load this file. \quit + +-- +-- Get rid of unnecessary compress and decompress support functions. +-- +-- To be allowed to drop the opclass entry for a support function, +-- we must change the entry's dependency type from 'internal' to 'auto', +-- as though it were a loose member of the opfamily rather than being +-- bound into a particular opclass. There's no SQL command for that, +-- so fake it with a manual update on pg_depend. +-- +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_catalog.pg_depend +SET deptype = 'a' +WHERE classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass + AND objid = + (SELECT objid + FROM pg_catalog.pg_depend + WHERE classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass + AND refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass + AND (refobjid = (my_schema || '.g_cube_compress(pg_catalog.internal)')::pg_catalog.regprocedure)) + AND refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass + AND deptype = 'i'; + +UPDATE pg_catalog.pg_depend +SET deptype = 'a' +WHERE classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass + AND objid = + (SELECT objid + FROM pg_catalog.pg_depend + WHERE classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass + AND refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass + AND (refobjid = (my_schema || '.g_cube_decompress(pg_catalog.internal)')::pg_catalog.regprocedure)) + AND refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass + AND deptype = 'i'; + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; + +ALTER OPERATOR FAMILY gist_cube_ops USING gist drop function 3 (cube); +ALTER EXTENSION cube DROP function g_cube_compress(pg_catalog.internal); +DROP FUNCTION g_cube_compress(pg_catalog.internal); + +ALTER OPERATOR FAMILY gist_cube_ops USING gist drop function 4 (cube); +ALTER EXTENSION cube DROP function g_cube_decompress(pg_catalog.internal); +DROP FUNCTION g_cube_decompress(pg_catalog.internal); diff --git a/contrib/cube/cube--1.4--1.5.sql b/contrib/cube/cube--1.4--1.5.sql new file mode 100644 index 0000000..4b5bf8d --- /dev/null +++ b/contrib/cube/cube--1.4--1.5.sql @@ -0,0 +1,21 @@ +/* contrib/cube/cube--1.4--1.5.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION cube UPDATE TO '1.5'" to load this file. \quit + +-- Remove @ and ~ +DROP OPERATOR @ (cube, cube); +DROP OPERATOR ~ (cube, cube); + +-- Add binary input/output handlers +CREATE FUNCTION cube_recv(internal) +RETURNS cube +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION cube_send(cube) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +ALTER TYPE cube SET ( RECEIVE = cube_recv, SEND = cube_send ); diff --git a/contrib/cube/cube.c b/contrib/cube/cube.c new file mode 100644 index 0000000..1fc4475 --- /dev/null +++ b/contrib/cube/cube.c @@ -0,0 +1,1909 @@ +/****************************************************************************** + contrib/cube/cube.c + + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include + +#include "access/gist.h" +#include "access/stratnum.h" +#include "cubedata.h" +#include "libpq/pqformat.h" +#include "utils/array.h" +#include "utils/float.h" + +PG_MODULE_MAGIC; + +/* + * Taken from the intarray contrib header + */ +#define ARRPTR(x) ( (double *) ARR_DATA_PTR(x) ) +#define ARRNELEMS(x) ArrayGetNItems( ARR_NDIM(x), ARR_DIMS(x)) + +/* +** Input/Output routines +*/ +PG_FUNCTION_INFO_V1(cube_in); +PG_FUNCTION_INFO_V1(cube_a_f8_f8); +PG_FUNCTION_INFO_V1(cube_a_f8); +PG_FUNCTION_INFO_V1(cube_out); +PG_FUNCTION_INFO_V1(cube_send); +PG_FUNCTION_INFO_V1(cube_recv); +PG_FUNCTION_INFO_V1(cube_f8); +PG_FUNCTION_INFO_V1(cube_f8_f8); +PG_FUNCTION_INFO_V1(cube_c_f8); +PG_FUNCTION_INFO_V1(cube_c_f8_f8); +PG_FUNCTION_INFO_V1(cube_dim); +PG_FUNCTION_INFO_V1(cube_ll_coord); +PG_FUNCTION_INFO_V1(cube_ur_coord); +PG_FUNCTION_INFO_V1(cube_coord); +PG_FUNCTION_INFO_V1(cube_coord_llur); +PG_FUNCTION_INFO_V1(cube_subset); + +/* +** GiST support methods +*/ + +PG_FUNCTION_INFO_V1(g_cube_consistent); +PG_FUNCTION_INFO_V1(g_cube_compress); +PG_FUNCTION_INFO_V1(g_cube_decompress); +PG_FUNCTION_INFO_V1(g_cube_penalty); +PG_FUNCTION_INFO_V1(g_cube_picksplit); +PG_FUNCTION_INFO_V1(g_cube_union); +PG_FUNCTION_INFO_V1(g_cube_same); +PG_FUNCTION_INFO_V1(g_cube_distance); + +/* +** B-tree support functions +*/ +PG_FUNCTION_INFO_V1(cube_eq); +PG_FUNCTION_INFO_V1(cube_ne); +PG_FUNCTION_INFO_V1(cube_lt); +PG_FUNCTION_INFO_V1(cube_gt); +PG_FUNCTION_INFO_V1(cube_le); +PG_FUNCTION_INFO_V1(cube_ge); +PG_FUNCTION_INFO_V1(cube_cmp); + +/* +** R-tree support functions +*/ + +PG_FUNCTION_INFO_V1(cube_contains); +PG_FUNCTION_INFO_V1(cube_contained); +PG_FUNCTION_INFO_V1(cube_overlap); +PG_FUNCTION_INFO_V1(cube_union); +PG_FUNCTION_INFO_V1(cube_inter); +PG_FUNCTION_INFO_V1(cube_size); + +/* +** miscellaneous +*/ +PG_FUNCTION_INFO_V1(distance_taxicab); +PG_FUNCTION_INFO_V1(cube_distance); +PG_FUNCTION_INFO_V1(distance_chebyshev); +PG_FUNCTION_INFO_V1(cube_is_point); +PG_FUNCTION_INFO_V1(cube_enlarge); + +/* +** For internal use only +*/ +int32 cube_cmp_v0(NDBOX *a, NDBOX *b); +bool cube_contains_v0(NDBOX *a, NDBOX *b); +bool cube_overlap_v0(NDBOX *a, NDBOX *b); +NDBOX *cube_union_v0(NDBOX *a, NDBOX *b); +void rt_cube_size(NDBOX *a, double *size); +NDBOX *g_cube_binary_union(NDBOX *r1, NDBOX *r2, int *sizep); +bool g_cube_leaf_consistent(NDBOX *key, NDBOX *query, StrategyNumber strategy); +bool g_cube_internal_consistent(NDBOX *key, NDBOX *query, StrategyNumber strategy); + +/* +** Auxiliary functions +*/ +static double distance_1D(double a1, double a2, double b1, double b2); +static bool cube_is_point_internal(NDBOX *cube); + + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +/* NdBox = [(lowerleft),(upperright)] */ +/* [(xLL(1)...xLL(N)),(xUR(1)...xUR(n))] */ +Datum +cube_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + NDBOX *result; + Size scanbuflen; + + cube_scanner_init(str, &scanbuflen); + + cube_yyparse(&result, scanbuflen, fcinfo->context); + + /* We might as well run this even on failure. */ + cube_scanner_finish(); + + PG_RETURN_NDBOX_P(result); +} + + +/* +** Allows the construction of a cube from 2 float[]'s +*/ +Datum +cube_a_f8_f8(PG_FUNCTION_ARGS) +{ + ArrayType *ur = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *ll = PG_GETARG_ARRAYTYPE_P(1); + NDBOX *result; + int i; + int dim; + int size; + bool point; + double *dur, + *dll; + + if (array_contains_nulls(ur) || array_contains_nulls(ll)) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("cannot work with arrays containing NULLs"))); + + dim = ARRNELEMS(ur); + if (dim > CUBE_MAX_DIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("can't extend cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + + if (ARRNELEMS(ll) != dim) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("UR and LL arrays must be of same length"))); + + dur = ARRPTR(ur); + dll = ARRPTR(ll); + + /* Check if it's a point */ + point = true; + for (i = 0; i < dim; i++) + { + if (dur[i] != dll[i]) + { + point = false; + break; + } + } + + size = point ? POINT_SIZE(dim) : CUBE_SIZE(dim); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, dim); + + for (i = 0; i < dim; i++) + result->x[i] = dur[i]; + + if (!point) + { + for (i = 0; i < dim; i++) + result->x[i + dim] = dll[i]; + } + else + SET_POINT_BIT(result); + + PG_RETURN_NDBOX_P(result); +} + +/* +** Allows the construction of a zero-volume cube from a float[] +*/ +Datum +cube_a_f8(PG_FUNCTION_ARGS) +{ + ArrayType *ur = PG_GETARG_ARRAYTYPE_P(0); + NDBOX *result; + int i; + int dim; + int size; + double *dur; + + if (array_contains_nulls(ur)) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("cannot work with arrays containing NULLs"))); + + dim = ARRNELEMS(ur); + if (dim > CUBE_MAX_DIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array is too long"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + + dur = ARRPTR(ur); + + size = POINT_SIZE(dim); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, dim); + SET_POINT_BIT(result); + + for (i = 0; i < dim; i++) + result->x[i] = dur[i]; + + PG_RETURN_NDBOX_P(result); +} + +Datum +cube_subset(PG_FUNCTION_ARGS) +{ + NDBOX *c = PG_GETARG_NDBOX_P(0); + ArrayType *idx = PG_GETARG_ARRAYTYPE_P(1); + NDBOX *result; + int size, + dim, + i; + int *dx; + + if (array_contains_nulls(idx)) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("cannot work with arrays containing NULLs"))); + + dx = (int32 *) ARR_DATA_PTR(idx); + + dim = ARRNELEMS(idx); + if (dim > CUBE_MAX_DIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("array is too long"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + + size = IS_POINT(c) ? POINT_SIZE(dim) : CUBE_SIZE(dim); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, dim); + + if (IS_POINT(c)) + SET_POINT_BIT(result); + + for (i = 0; i < dim; i++) + { + if ((dx[i] <= 0) || (dx[i] > DIM(c))) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("Index out of bounds"))); + result->x[i] = c->x[dx[i] - 1]; + if (!IS_POINT(c)) + result->x[i + dim] = c->x[dx[i] + DIM(c) - 1]; + } + + PG_FREE_IF_COPY(c, 0); + PG_RETURN_NDBOX_P(result); +} + +Datum +cube_out(PG_FUNCTION_ARGS) +{ + NDBOX *cube = PG_GETARG_NDBOX_P(0); + StringInfoData buf; + int dim = DIM(cube); + int i; + + initStringInfo(&buf); + + appendStringInfoChar(&buf, '('); + for (i = 0; i < dim; i++) + { + if (i > 0) + appendStringInfoString(&buf, ", "); + appendStringInfoString(&buf, float8out_internal(LL_COORD(cube, i))); + } + appendStringInfoChar(&buf, ')'); + + if (!cube_is_point_internal(cube)) + { + appendStringInfoString(&buf, ",("); + for (i = 0; i < dim; i++) + { + if (i > 0) + appendStringInfoString(&buf, ", "); + appendStringInfoString(&buf, float8out_internal(UR_COORD(cube, i))); + } + appendStringInfoChar(&buf, ')'); + } + + PG_FREE_IF_COPY(cube, 0); + PG_RETURN_CSTRING(buf.data); +} + +/* + * cube_send - a binary output handler for cube type + */ +Datum +cube_send(PG_FUNCTION_ARGS) +{ + NDBOX *cube = PG_GETARG_NDBOX_P(0); + StringInfoData buf; + int32 i, + nitems = DIM(cube); + + pq_begintypsend(&buf); + pq_sendint32(&buf, cube->header); + if (!IS_POINT(cube)) + nitems += nitems; + /* for symmetry with cube_recv, we don't use LL_COORD/UR_COORD here */ + for (i = 0; i < nitems; i++) + pq_sendfloat8(&buf, cube->x[i]); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * cube_recv - a binary input handler for cube type + */ +Datum +cube_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + int32 header; + int32 i, + nitems; + NDBOX *cube; + + header = pq_getmsgint(buf, sizeof(int32)); + nitems = (header & DIM_MASK); + if (nitems > CUBE_MAX_DIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("cube dimension is too large"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + if ((header & POINT_BIT) == 0) + nitems += nitems; + cube = palloc(offsetof(NDBOX, x) + sizeof(double) * nitems); + SET_VARSIZE(cube, offsetof(NDBOX, x) + sizeof(double) * nitems); + cube->header = header; + for (i = 0; i < nitems; i++) + cube->x[i] = pq_getmsgfloat8(buf); + + PG_RETURN_NDBOX_P(cube); +} + + +/***************************************************************************** + * GiST functions + *****************************************************************************/ + +/* +** The GiST Consistent method for boxes +** Should return false if for all data items x below entry, +** the predicate x op query == false, where op is the oper +** corresponding to strategy in the pg_amop table. +*/ +Datum +g_cube_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + NDBOX *query = PG_GETARG_NDBOX_P(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + bool res; + + /* All cases served by this function are exact */ + *recheck = false; + + /* + * if entry is not leaf, use g_cube_internal_consistent, else use + * g_cube_leaf_consistent + */ + if (GIST_LEAF(entry)) + res = g_cube_leaf_consistent(DatumGetNDBOXP(entry->key), + query, strategy); + else + res = g_cube_internal_consistent(DatumGetNDBOXP(entry->key), + query, strategy); + + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(res); +} + + +/* +** The GiST Union method for boxes +** returns the minimal bounding box that encloses all the entries in entryvec +*/ +Datum +g_cube_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int *sizep = (int *) PG_GETARG_POINTER(1); + NDBOX *out = (NDBOX *) NULL; + NDBOX *tmp; + int i; + + tmp = DatumGetNDBOXP(entryvec->vector[0].key); + + /* + * sizep = sizeof(NDBOX); -- NDBOX has variable size + */ + *sizep = VARSIZE(tmp); + + for (i = 1; i < entryvec->n; i++) + { + out = g_cube_binary_union(tmp, + DatumGetNDBOXP(entryvec->vector[i].key), + sizep); + tmp = out; + } + + PG_RETURN_POINTER(out); +} + +/* +** GiST Compress and Decompress methods for boxes +** do not do anything. +*/ + +Datum +g_cube_compress(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); +} + +Datum +g_cube_decompress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + NDBOX *key = DatumGetNDBOXP(entry->key); + + if (key != DatumGetNDBOXP(entry->key)) + { + GISTENTRY *retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + + gistentryinit(*retval, PointerGetDatum(key), + entry->rel, entry->page, + entry->offset, false); + PG_RETURN_POINTER(retval); + } + PG_RETURN_POINTER(entry); +} + + +/* +** The GiST Penalty method for boxes +** As in the R-tree paper, we use change in area as our penalty metric +*/ +Datum +g_cube_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *result = (float *) PG_GETARG_POINTER(2); + NDBOX *ud; + double tmp1, + tmp2; + + ud = cube_union_v0(DatumGetNDBOXP(origentry->key), + DatumGetNDBOXP(newentry->key)); + rt_cube_size(ud, &tmp1); + rt_cube_size(DatumGetNDBOXP(origentry->key), &tmp2); + *result = (float) (tmp1 - tmp2); + + PG_RETURN_FLOAT8(*result); +} + + + +/* +** The GiST PickSplit method for boxes +** We use Guttman's poly time split algorithm +*/ +Datum +g_cube_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + OffsetNumber i, + j; + NDBOX *datum_alpha, + *datum_beta; + NDBOX *datum_l, + *datum_r; + NDBOX *union_d, + *union_dl, + *union_dr; + NDBOX *inter_d; + bool firsttime; + double size_alpha, + size_beta, + size_union, + size_inter; + double size_waste, + waste; + double size_l, + size_r; + int nbytes; + OffsetNumber seed_1 = 1, + seed_2 = 2; + OffsetNumber *left, + *right; + OffsetNumber maxoff; + + maxoff = entryvec->n - 2; + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + firsttime = true; + waste = 0.0; + + for (i = FirstOffsetNumber; i < maxoff; i = OffsetNumberNext(i)) + { + datum_alpha = DatumGetNDBOXP(entryvec->vector[i].key); + for (j = OffsetNumberNext(i); j <= maxoff; j = OffsetNumberNext(j)) + { + datum_beta = DatumGetNDBOXP(entryvec->vector[j].key); + + /* compute the wasted space by unioning these guys */ + /* size_waste = size_union - size_inter; */ + union_d = cube_union_v0(datum_alpha, datum_beta); + rt_cube_size(union_d, &size_union); + inter_d = DatumGetNDBOXP(DirectFunctionCall2(cube_inter, + entryvec->vector[i].key, + entryvec->vector[j].key)); + rt_cube_size(inter_d, &size_inter); + size_waste = size_union - size_inter; + + /* + * are these a more promising split than what we've already seen? + */ + + if (size_waste > waste || firsttime) + { + waste = size_waste; + seed_1 = i; + seed_2 = j; + firsttime = false; + } + } + } + + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + + datum_alpha = DatumGetNDBOXP(entryvec->vector[seed_1].key); + datum_l = cube_union_v0(datum_alpha, datum_alpha); + rt_cube_size(datum_l, &size_l); + datum_beta = DatumGetNDBOXP(entryvec->vector[seed_2].key); + datum_r = cube_union_v0(datum_beta, datum_beta); + rt_cube_size(datum_r, &size_r); + + /* + * Now split up the regions between the two seeds. An important property + * of this split algorithm is that the split vector v has the indices of + * items to be split in order in its left and right vectors. We exploit + * this property by doing a merge in the code that actually splits the + * page. + * + * For efficiency, we also place the new index tuple in this loop. This is + * handled at the very end, when we have placed all the existing tuples + * and i == maxoff + 1. + */ + + maxoff = OffsetNumberNext(maxoff); + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + /* + * If we've already decided where to place this item, just put it on + * the right list. Otherwise, we need to figure out which page needs + * the least enlargement in order to store the item. + */ + + if (i == seed_1) + { + *left++ = i; + v->spl_nleft++; + continue; + } + else if (i == seed_2) + { + *right++ = i; + v->spl_nright++; + continue; + } + + /* okay, which page needs least enlargement? */ + datum_alpha = DatumGetNDBOXP(entryvec->vector[i].key); + union_dl = cube_union_v0(datum_l, datum_alpha); + union_dr = cube_union_v0(datum_r, datum_alpha); + rt_cube_size(union_dl, &size_alpha); + rt_cube_size(union_dr, &size_beta); + + /* pick which page to add it to */ + if (size_alpha - size_l < size_beta - size_r) + { + datum_l = union_dl; + size_l = size_alpha; + *left++ = i; + v->spl_nleft++; + } + else + { + datum_r = union_dr; + size_r = size_beta; + *right++ = i; + v->spl_nright++; + } + } + *left = *right = FirstOffsetNumber; /* sentinel value */ + + v->spl_ldatum = PointerGetDatum(datum_l); + v->spl_rdatum = PointerGetDatum(datum_r); + + PG_RETURN_POINTER(v); +} + +/* +** Equality method +*/ +Datum +g_cube_same(PG_FUNCTION_ARGS) +{ + NDBOX *b1 = PG_GETARG_NDBOX_P(0); + NDBOX *b2 = PG_GETARG_NDBOX_P(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + + if (cube_cmp_v0(b1, b2) == 0) + *result = true; + else + *result = false; + + PG_RETURN_NDBOX_P(result); +} + +/* +** SUPPORT ROUTINES +*/ +bool +g_cube_leaf_consistent(NDBOX *key, + NDBOX *query, + StrategyNumber strategy) +{ + bool retval; + + switch (strategy) + { + case RTOverlapStrategyNumber: + retval = cube_overlap_v0(key, query); + break; + case RTSameStrategyNumber: + retval = (cube_cmp_v0(key, query) == 0); + break; + case RTContainsStrategyNumber: + case RTOldContainsStrategyNumber: + retval = cube_contains_v0(key, query); + break; + case RTContainedByStrategyNumber: + case RTOldContainedByStrategyNumber: + retval = cube_contains_v0(query, key); + break; + default: + retval = false; + } + return retval; +} + +bool +g_cube_internal_consistent(NDBOX *key, + NDBOX *query, + StrategyNumber strategy) +{ + bool retval; + + switch (strategy) + { + case RTOverlapStrategyNumber: + retval = (bool) cube_overlap_v0(key, query); + break; + case RTSameStrategyNumber: + case RTContainsStrategyNumber: + case RTOldContainsStrategyNumber: + retval = (bool) cube_contains_v0(key, query); + break; + case RTContainedByStrategyNumber: + case RTOldContainedByStrategyNumber: + retval = (bool) cube_overlap_v0(key, query); + break; + default: + retval = false; + } + return retval; +} + +NDBOX * +g_cube_binary_union(NDBOX *r1, NDBOX *r2, int *sizep) +{ + NDBOX *retval; + + retval = cube_union_v0(r1, r2); + *sizep = VARSIZE(retval); + + return retval; +} + + +/* cube_union_v0 */ +NDBOX * +cube_union_v0(NDBOX *a, NDBOX *b) +{ + int i; + NDBOX *result; + int dim; + int size; + + /* trivial case */ + if (a == b) + return a; + + /* swap the arguments if needed, so that 'a' is always larger than 'b' */ + if (DIM(a) < DIM(b)) + { + NDBOX *tmp = b; + + b = a; + a = tmp; + } + dim = DIM(a); + + size = CUBE_SIZE(dim); + result = palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, dim); + + /* First compute the union of the dimensions present in both args */ + for (i = 0; i < DIM(b); i++) + { + result->x[i] = Min(Min(LL_COORD(a, i), UR_COORD(a, i)), + Min(LL_COORD(b, i), UR_COORD(b, i))); + result->x[i + DIM(a)] = Max(Max(LL_COORD(a, i), UR_COORD(a, i)), + Max(LL_COORD(b, i), UR_COORD(b, i))); + } + /* continue on the higher dimensions only present in 'a' */ + for (; i < DIM(a); i++) + { + result->x[i] = Min(0, + Min(LL_COORD(a, i), UR_COORD(a, i)) + ); + result->x[i + dim] = Max(0, + Max(LL_COORD(a, i), UR_COORD(a, i)) + ); + } + + /* + * Check if the result was in fact a point, and set the flag in the datum + * accordingly. (we don't bother to repalloc it smaller) + */ + if (cube_is_point_internal(result)) + { + size = POINT_SIZE(dim); + SET_VARSIZE(result, size); + SET_POINT_BIT(result); + } + + return result; +} + +Datum +cube_union(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0); + NDBOX *b = PG_GETARG_NDBOX_P(1); + NDBOX *res; + + res = cube_union_v0(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_NDBOX_P(res); +} + +/* cube_inter */ +Datum +cube_inter(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0); + NDBOX *b = PG_GETARG_NDBOX_P(1); + NDBOX *result; + bool swapped = false; + int i; + int dim; + int size; + + /* swap the arguments if needed, so that 'a' is always larger than 'b' */ + if (DIM(a) < DIM(b)) + { + NDBOX *tmp = b; + + b = a; + a = tmp; + swapped = true; + } + dim = DIM(a); + + size = CUBE_SIZE(dim); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, dim); + + /* First compute intersection of the dimensions present in both args */ + for (i = 0; i < DIM(b); i++) + { + result->x[i] = Max(Min(LL_COORD(a, i), UR_COORD(a, i)), + Min(LL_COORD(b, i), UR_COORD(b, i))); + result->x[i + DIM(a)] = Min(Max(LL_COORD(a, i), UR_COORD(a, i)), + Max(LL_COORD(b, i), UR_COORD(b, i))); + } + /* continue on the higher dimensions only present in 'a' */ + for (; i < DIM(a); i++) + { + result->x[i] = Max(0, + Min(LL_COORD(a, i), UR_COORD(a, i)) + ); + result->x[i + DIM(a)] = Min(0, + Max(LL_COORD(a, i), UR_COORD(a, i)) + ); + } + + /* + * Check if the result was in fact a point, and set the flag in the datum + * accordingly. (we don't bother to repalloc it smaller) + */ + if (cube_is_point_internal(result)) + { + size = POINT_SIZE(dim); + result = repalloc(result, size); + SET_VARSIZE(result, size); + SET_POINT_BIT(result); + } + + if (swapped) + { + PG_FREE_IF_COPY(b, 0); + PG_FREE_IF_COPY(a, 1); + } + else + { + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + } + + /* + * Is it OK to return a non-null intersection for non-overlapping boxes? + */ + PG_RETURN_NDBOX_P(result); +} + +/* cube_size */ +Datum +cube_size(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0); + double result; + + rt_cube_size(a, &result); + PG_FREE_IF_COPY(a, 0); + PG_RETURN_FLOAT8(result); +} + +void +rt_cube_size(NDBOX *a, double *size) +{ + double result; + int i; + + if (a == (NDBOX *) NULL) + { + /* special case for GiST */ + result = 0.0; + } + else if (IS_POINT(a) || DIM(a) == 0) + { + /* necessarily has zero size */ + result = 0.0; + } + else + { + result = 1.0; + for (i = 0; i < DIM(a); i++) + result *= fabs(UR_COORD(a, i) - LL_COORD(a, i)); + } + *size = result; +} + +/* make up a metric in which one box will be 'lower' than the other + -- this can be useful for sorting and to determine uniqueness */ +int32 +cube_cmp_v0(NDBOX *a, NDBOX *b) +{ + int i; + int dim; + + dim = Min(DIM(a), DIM(b)); + + /* compare the common dimensions */ + for (i = 0; i < dim; i++) + { + if (Min(LL_COORD(a, i), UR_COORD(a, i)) > + Min(LL_COORD(b, i), UR_COORD(b, i))) + return 1; + if (Min(LL_COORD(a, i), UR_COORD(a, i)) < + Min(LL_COORD(b, i), UR_COORD(b, i))) + return -1; + } + for (i = 0; i < dim; i++) + { + if (Max(LL_COORD(a, i), UR_COORD(a, i)) > + Max(LL_COORD(b, i), UR_COORD(b, i))) + return 1; + if (Max(LL_COORD(a, i), UR_COORD(a, i)) < + Max(LL_COORD(b, i), UR_COORD(b, i))) + return -1; + } + + /* compare extra dimensions to zero */ + if (DIM(a) > DIM(b)) + { + for (i = dim; i < DIM(a); i++) + { + if (Min(LL_COORD(a, i), UR_COORD(a, i)) > 0) + return 1; + if (Min(LL_COORD(a, i), UR_COORD(a, i)) < 0) + return -1; + } + for (i = dim; i < DIM(a); i++) + { + if (Max(LL_COORD(a, i), UR_COORD(a, i)) > 0) + return 1; + if (Max(LL_COORD(a, i), UR_COORD(a, i)) < 0) + return -1; + } + + /* + * if all common dimensions are equal, the cube with more dimensions + * wins + */ + return 1; + } + if (DIM(a) < DIM(b)) + { + for (i = dim; i < DIM(b); i++) + { + if (Min(LL_COORD(b, i), UR_COORD(b, i)) > 0) + return -1; + if (Min(LL_COORD(b, i), UR_COORD(b, i)) < 0) + return 1; + } + for (i = dim; i < DIM(b); i++) + { + if (Max(LL_COORD(b, i), UR_COORD(b, i)) > 0) + return -1; + if (Max(LL_COORD(b, i), UR_COORD(b, i)) < 0) + return 1; + } + + /* + * if all common dimensions are equal, the cube with more dimensions + * wins + */ + return -1; + } + + /* They're really equal */ + return 0; +} + +Datum +cube_cmp(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + int32 res; + + res = cube_cmp_v0(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_INT32(res); +} + + +Datum +cube_eq(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + int32 res; + + res = cube_cmp_v0(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_BOOL(res == 0); +} + + +Datum +cube_ne(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + int32 res; + + res = cube_cmp_v0(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_BOOL(res != 0); +} + + +Datum +cube_lt(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + int32 res; + + res = cube_cmp_v0(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_BOOL(res < 0); +} + + +Datum +cube_gt(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + int32 res; + + res = cube_cmp_v0(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_BOOL(res > 0); +} + + +Datum +cube_le(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + int32 res; + + res = cube_cmp_v0(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_BOOL(res <= 0); +} + + +Datum +cube_ge(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + int32 res; + + res = cube_cmp_v0(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_BOOL(res >= 0); +} + + +/* Contains */ +/* Box(A) CONTAINS Box(B) IFF pt(A) < pt(B) */ +bool +cube_contains_v0(NDBOX *a, NDBOX *b) +{ + int i; + + if ((a == NULL) || (b == NULL)) + return false; + + if (DIM(a) < DIM(b)) + { + /* + * the further comparisons will make sense if the excess dimensions of + * (b) were zeroes Since both UL and UR coordinates must be zero, we + * can check them all without worrying about which is which. + */ + for (i = DIM(a); i < DIM(b); i++) + { + if (LL_COORD(b, i) != 0) + return false; + if (UR_COORD(b, i) != 0) + return false; + } + } + + /* Can't care less about the excess dimensions of (a), if any */ + for (i = 0; i < Min(DIM(a), DIM(b)); i++) + { + if (Min(LL_COORD(a, i), UR_COORD(a, i)) > + Min(LL_COORD(b, i), UR_COORD(b, i))) + return false; + if (Max(LL_COORD(a, i), UR_COORD(a, i)) < + Max(LL_COORD(b, i), UR_COORD(b, i))) + return false; + } + + return true; +} + +Datum +cube_contains(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + bool res; + + res = cube_contains_v0(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_BOOL(res); +} + +/* Contained */ +/* Box(A) Contained by Box(B) IFF Box(B) Contains Box(A) */ +Datum +cube_contained(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + bool res; + + res = cube_contains_v0(b, a); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_BOOL(res); +} + +/* Overlap */ +/* Box(A) Overlap Box(B) IFF (pt(a)LL < pt(B)UR) && (pt(b)LL < pt(a)UR) */ +bool +cube_overlap_v0(NDBOX *a, NDBOX *b) +{ + int i; + + if ((a == NULL) || (b == NULL)) + return false; + + /* swap the box pointers if needed */ + if (DIM(a) < DIM(b)) + { + NDBOX *tmp = b; + + b = a; + a = tmp; + } + + /* compare within the dimensions of (b) */ + for (i = 0; i < DIM(b); i++) + { + if (Min(LL_COORD(a, i), UR_COORD(a, i)) > Max(LL_COORD(b, i), UR_COORD(b, i))) + return false; + if (Max(LL_COORD(a, i), UR_COORD(a, i)) < Min(LL_COORD(b, i), UR_COORD(b, i))) + return false; + } + + /* compare to zero those dimensions in (a) absent in (b) */ + for (i = DIM(b); i < DIM(a); i++) + { + if (Min(LL_COORD(a, i), UR_COORD(a, i)) > 0) + return false; + if (Max(LL_COORD(a, i), UR_COORD(a, i)) < 0) + return false; + } + + return true; +} + + +Datum +cube_overlap(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + bool res; + + res = cube_overlap_v0(a, b); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_BOOL(res); +} + + +/* Distance */ +/* The distance is computed as a per axis sum of the squared distances + between 1D projections of the boxes onto Cartesian axes. Assuming zero + distance between overlapping projections, this metric coincides with the + "common sense" geometric distance */ +Datum +cube_distance(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + bool swapped = false; + double d, + distance; + int i; + + /* swap the box pointers if needed */ + if (DIM(a) < DIM(b)) + { + NDBOX *tmp = b; + + b = a; + a = tmp; + swapped = true; + } + + distance = 0.0; + /* compute within the dimensions of (b) */ + for (i = 0; i < DIM(b); i++) + { + d = distance_1D(LL_COORD(a, i), UR_COORD(a, i), LL_COORD(b, i), UR_COORD(b, i)); + distance += d * d; + } + + /* compute distance to zero for those dimensions in (a) absent in (b) */ + for (i = DIM(b); i < DIM(a); i++) + { + d = distance_1D(LL_COORD(a, i), UR_COORD(a, i), 0.0, 0.0); + distance += d * d; + } + + if (swapped) + { + PG_FREE_IF_COPY(b, 0); + PG_FREE_IF_COPY(a, 1); + } + else + { + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + } + + PG_RETURN_FLOAT8(sqrt(distance)); +} + +Datum +distance_taxicab(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + bool swapped = false; + double distance; + int i; + + /* swap the box pointers if needed */ + if (DIM(a) < DIM(b)) + { + NDBOX *tmp = b; + + b = a; + a = tmp; + swapped = true; + } + + distance = 0.0; + /* compute within the dimensions of (b) */ + for (i = 0; i < DIM(b); i++) + distance += fabs(distance_1D(LL_COORD(a, i), UR_COORD(a, i), + LL_COORD(b, i), UR_COORD(b, i))); + + /* compute distance to zero for those dimensions in (a) absent in (b) */ + for (i = DIM(b); i < DIM(a); i++) + distance += fabs(distance_1D(LL_COORD(a, i), UR_COORD(a, i), + 0.0, 0.0)); + + if (swapped) + { + PG_FREE_IF_COPY(b, 0); + PG_FREE_IF_COPY(a, 1); + } + else + { + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + } + + PG_RETURN_FLOAT8(distance); +} + +Datum +distance_chebyshev(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0), + *b = PG_GETARG_NDBOX_P(1); + bool swapped = false; + double d, + distance; + int i; + + /* swap the box pointers if needed */ + if (DIM(a) < DIM(b)) + { + NDBOX *tmp = b; + + b = a; + a = tmp; + swapped = true; + } + + distance = 0.0; + /* compute within the dimensions of (b) */ + for (i = 0; i < DIM(b); i++) + { + d = fabs(distance_1D(LL_COORD(a, i), UR_COORD(a, i), + LL_COORD(b, i), UR_COORD(b, i))); + if (d > distance) + distance = d; + } + + /* compute distance to zero for those dimensions in (a) absent in (b) */ + for (i = DIM(b); i < DIM(a); i++) + { + d = fabs(distance_1D(LL_COORD(a, i), UR_COORD(a, i), 0.0, 0.0)); + if (d > distance) + distance = d; + } + + if (swapped) + { + PG_FREE_IF_COPY(b, 0); + PG_FREE_IF_COPY(a, 1); + } + else + { + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + } + + PG_RETURN_FLOAT8(distance); +} + +Datum +g_cube_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + NDBOX *cube = DatumGetNDBOXP(entry->key); + double retval; + + if (strategy == CubeKNNDistanceCoord) + { + /* + * Handle ordering by ~> operator. See comments of cube_coord_llur() + * for details + */ + int coord = PG_GETARG_INT32(1); + bool isLeaf = GistPageIsLeaf(entry->page); + bool inverse = false; + + /* 0 is the only unsupported coordinate value */ + if (coord == 0) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("zero cube index is not defined"))); + + /* Return inversed value for negative coordinate */ + if (coord < 0) + { + coord = -coord; + inverse = true; + } + + if (coord <= 2 * DIM(cube)) + { + /* dimension index */ + int index = (coord - 1) / 2; + + /* whether this is upper bound (lower bound otherwise) */ + bool upper = ((coord - 1) % 2 == 1); + + if (IS_POINT(cube)) + { + retval = cube->x[index]; + } + else + { + if (isLeaf) + { + /* For leaf just return required upper/lower bound */ + if (upper) + retval = Max(cube->x[index], cube->x[index + DIM(cube)]); + else + retval = Min(cube->x[index], cube->x[index + DIM(cube)]); + } + else + { + /* + * For non-leaf we should always return lower bound, + * because even upper bound of a child in the subtree can + * be as small as our lower bound. For inversed case we + * return upper bound because it becomes lower bound for + * inversed value. + */ + if (!inverse) + retval = Min(cube->x[index], cube->x[index + DIM(cube)]); + else + retval = Max(cube->x[index], cube->x[index + DIM(cube)]); + } + } + } + else + { + retval = 0.0; + } + + /* Inverse return value if needed */ + if (inverse) + retval = -retval; + } + else + { + NDBOX *query = PG_GETARG_NDBOX_P(1); + + switch (strategy) + { + case CubeKNNDistanceTaxicab: + retval = DatumGetFloat8(DirectFunctionCall2(distance_taxicab, + PointerGetDatum(cube), PointerGetDatum(query))); + break; + case CubeKNNDistanceEuclid: + retval = DatumGetFloat8(DirectFunctionCall2(cube_distance, + PointerGetDatum(cube), PointerGetDatum(query))); + break; + case CubeKNNDistanceChebyshev: + retval = DatumGetFloat8(DirectFunctionCall2(distance_chebyshev, + PointerGetDatum(cube), PointerGetDatum(query))); + break; + default: + elog(ERROR, "unrecognized cube strategy number: %d", strategy); + retval = 0; /* keep compiler quiet */ + break; + } + } + PG_RETURN_FLOAT8(retval); +} + +static double +distance_1D(double a1, double a2, double b1, double b2) +{ + /* interval (a) is entirely on the left of (b) */ + if ((a1 <= b1) && (a2 <= b1) && (a1 <= b2) && (a2 <= b2)) + return (Min(b1, b2) - Max(a1, a2)); + + /* interval (a) is entirely on the right of (b) */ + if ((a1 > b1) && (a2 > b1) && (a1 > b2) && (a2 > b2)) + return (Min(a1, a2) - Max(b1, b2)); + + /* the rest are all sorts of intersections */ + return 0.0; +} + +/* Test if a box is also a point */ +Datum +cube_is_point(PG_FUNCTION_ARGS) +{ + NDBOX *cube = PG_GETARG_NDBOX_P(0); + bool result; + + result = cube_is_point_internal(cube); + PG_FREE_IF_COPY(cube, 0); + PG_RETURN_BOOL(result); +} + +static bool +cube_is_point_internal(NDBOX *cube) +{ + int i; + + if (IS_POINT(cube)) + return true; + + /* + * Even if the point-flag is not set, all the lower-left coordinates might + * match the upper-right coordinates, so that the value is in fact a + * point. Such values don't arise with current code - the point flag is + * always set if appropriate - but they might be present on-disk in + * clusters upgraded from pre-9.4 versions. + */ + for (i = 0; i < DIM(cube); i++) + { + if (LL_COORD(cube, i) != UR_COORD(cube, i)) + return false; + } + return true; +} + +/* Return dimensions in use in the data structure */ +Datum +cube_dim(PG_FUNCTION_ARGS) +{ + NDBOX *c = PG_GETARG_NDBOX_P(0); + int dim = DIM(c); + + PG_FREE_IF_COPY(c, 0); + PG_RETURN_INT32(dim); +} + +/* Return a specific normalized LL coordinate */ +Datum +cube_ll_coord(PG_FUNCTION_ARGS) +{ + NDBOX *c = PG_GETARG_NDBOX_P(0); + int n = PG_GETARG_INT32(1); + double result; + + if (DIM(c) >= n && n > 0) + result = Min(LL_COORD(c, n - 1), UR_COORD(c, n - 1)); + else + result = 0; + + PG_FREE_IF_COPY(c, 0); + PG_RETURN_FLOAT8(result); +} + +/* Return a specific normalized UR coordinate */ +Datum +cube_ur_coord(PG_FUNCTION_ARGS) +{ + NDBOX *c = PG_GETARG_NDBOX_P(0); + int n = PG_GETARG_INT32(1); + double result; + + if (DIM(c) >= n && n > 0) + result = Max(LL_COORD(c, n - 1), UR_COORD(c, n - 1)); + else + result = 0; + + PG_FREE_IF_COPY(c, 0); + PG_RETURN_FLOAT8(result); +} + +/* + * Function returns cube coordinate. + * Numbers from 1 to DIM denotes first corner coordinates. + * Numbers from DIM+1 to 2*DIM denotes second corner coordinates. + */ +Datum +cube_coord(PG_FUNCTION_ARGS) +{ + NDBOX *cube = PG_GETARG_NDBOX_P(0); + int coord = PG_GETARG_INT32(1); + + if (coord <= 0 || coord > 2 * DIM(cube)) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("cube index %d is out of bounds", coord))); + + if (IS_POINT(cube)) + PG_RETURN_FLOAT8(cube->x[(coord - 1) % DIM(cube)]); + else + PG_RETURN_FLOAT8(cube->x[coord - 1]); +} + + +/*---- + * This function works like cube_coord(), but rearranges coordinates in the + * way suitable to support coordinate ordering using KNN-GiST. For historical + * reasons this extension allows us to create cubes in form ((2,1),(1,2)) and + * instead of normalizing such cube to ((1,1),(2,2)) it stores cube in original + * way. But in order to get cubes ordered by one of dimensions from the index + * without explicit sort step we need this representation-independent coordinate + * getter. Moreover, indexed dataset may contain cubes of different dimensions + * number. Accordingly, this coordinate getter should be able to return + * lower/upper bound for particular dimension independently on number of cube + * dimensions. Also, KNN-GiST supports only ascending sorting. In order to + * support descending sorting, this function returns inverse of value when + * negative coordinate is given. + * + * Long story short, this function uses following meaning of coordinates: + * # (2 * N - 1) -- lower bound of Nth dimension, + * # (2 * N) -- upper bound of Nth dimension, + * # - (2 * N - 1) -- negative of lower bound of Nth dimension, + * # - (2 * N) -- negative of upper bound of Nth dimension. + * + * When given coordinate exceeds number of cube dimensions, then 0 returned + * (reproducing logic of GiST indexing of variable-length cubes). + */ +Datum +cube_coord_llur(PG_FUNCTION_ARGS) +{ + NDBOX *cube = PG_GETARG_NDBOX_P(0); + int coord = PG_GETARG_INT32(1); + bool inverse = false; + float8 result; + + /* 0 is the only unsupported coordinate value */ + if (coord == 0) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("zero cube index is not defined"))); + + /* Return inversed value for negative coordinate */ + if (coord < 0) + { + coord = -coord; + inverse = true; + } + + if (coord <= 2 * DIM(cube)) + { + /* dimension index */ + int index = (coord - 1) / 2; + + /* whether this is upper bound (lower bound otherwise) */ + bool upper = ((coord - 1) % 2 == 1); + + if (IS_POINT(cube)) + { + result = cube->x[index]; + } + else + { + if (upper) + result = Max(cube->x[index], cube->x[index + DIM(cube)]); + else + result = Min(cube->x[index], cube->x[index + DIM(cube)]); + } + } + else + { + /* + * Return zero if coordinate is out of bound. That reproduces logic + * of how cubes with low dimension number are expanded during GiST + * indexing. + */ + result = 0.0; + } + + /* Inverse value if needed */ + if (inverse) + result = -result; + + PG_RETURN_FLOAT8(result); +} + +/* Increase or decrease box size by a radius in at least n dimensions. */ +Datum +cube_enlarge(PG_FUNCTION_ARGS) +{ + NDBOX *a = PG_GETARG_NDBOX_P(0); + double r = PG_GETARG_FLOAT8(1); + int32 n = PG_GETARG_INT32(2); + NDBOX *result; + int dim = 0; + int size; + int i, + j; + + if (n > CUBE_MAX_DIM) + n = CUBE_MAX_DIM; + if (r > 0 && n > 0) + dim = n; + if (DIM(a) > dim) + dim = DIM(a); + + size = CUBE_SIZE(dim); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, dim); + + for (i = 0, j = dim; i < DIM(a); i++, j++) + { + if (LL_COORD(a, i) >= UR_COORD(a, i)) + { + result->x[i] = UR_COORD(a, i) - r; + result->x[j] = LL_COORD(a, i) + r; + } + else + { + result->x[i] = LL_COORD(a, i) - r; + result->x[j] = UR_COORD(a, i) + r; + } + if (result->x[i] > result->x[j]) + { + result->x[i] = (result->x[i] + result->x[j]) / 2; + result->x[j] = result->x[i]; + } + } + /* dim > a->dim only if r > 0 */ + for (; i < dim; i++, j++) + { + result->x[i] = -r; + result->x[j] = r; + } + + /* + * Check if the result was in fact a point, and set the flag in the datum + * accordingly. (we don't bother to repalloc it smaller) + */ + if (cube_is_point_internal(result)) + { + size = POINT_SIZE(dim); + SET_VARSIZE(result, size); + SET_POINT_BIT(result); + } + + PG_FREE_IF_COPY(a, 0); + PG_RETURN_NDBOX_P(result); +} + +/* Create a one dimensional box with identical upper and lower coordinates */ +Datum +cube_f8(PG_FUNCTION_ARGS) +{ + double x = PG_GETARG_FLOAT8(0); + NDBOX *result; + int size; + + size = POINT_SIZE(1); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, 1); + SET_POINT_BIT(result); + result->x[0] = x; + + PG_RETURN_NDBOX_P(result); +} + +/* Create a one dimensional box */ +Datum +cube_f8_f8(PG_FUNCTION_ARGS) +{ + double x0 = PG_GETARG_FLOAT8(0); + double x1 = PG_GETARG_FLOAT8(1); + NDBOX *result; + int size; + + if (x0 == x1) + { + size = POINT_SIZE(1); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, 1); + SET_POINT_BIT(result); + result->x[0] = x0; + } + else + { + size = CUBE_SIZE(1); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, 1); + result->x[0] = x0; + result->x[1] = x1; + } + + PG_RETURN_NDBOX_P(result); +} + +/* Add a dimension to an existing cube with the same values for the new + coordinate */ +Datum +cube_c_f8(PG_FUNCTION_ARGS) +{ + NDBOX *cube = PG_GETARG_NDBOX_P(0); + double x = PG_GETARG_FLOAT8(1); + NDBOX *result; + int size; + int i; + + if (DIM(cube) + 1 > CUBE_MAX_DIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("can't extend cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + + if (IS_POINT(cube)) + { + size = POINT_SIZE((DIM(cube) + 1)); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, DIM(cube) + 1); + SET_POINT_BIT(result); + for (i = 0; i < DIM(cube); i++) + result->x[i] = cube->x[i]; + result->x[DIM(result) - 1] = x; + } + else + { + size = CUBE_SIZE((DIM(cube) + 1)); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, DIM(cube) + 1); + for (i = 0; i < DIM(cube); i++) + { + result->x[i] = cube->x[i]; + result->x[DIM(result) + i] = cube->x[DIM(cube) + i]; + } + result->x[DIM(result) - 1] = x; + result->x[2 * DIM(result) - 1] = x; + } + + PG_FREE_IF_COPY(cube, 0); + PG_RETURN_NDBOX_P(result); +} + +/* Add a dimension to an existing cube */ +Datum +cube_c_f8_f8(PG_FUNCTION_ARGS) +{ + NDBOX *cube = PG_GETARG_NDBOX_P(0); + double x1 = PG_GETARG_FLOAT8(1); + double x2 = PG_GETARG_FLOAT8(2); + NDBOX *result; + int size; + int i; + + if (DIM(cube) + 1 > CUBE_MAX_DIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("can't extend cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + + if (IS_POINT(cube) && (x1 == x2)) + { + size = POINT_SIZE((DIM(cube) + 1)); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, DIM(cube) + 1); + SET_POINT_BIT(result); + for (i = 0; i < DIM(cube); i++) + result->x[i] = cube->x[i]; + result->x[DIM(result) - 1] = x1; + } + else + { + size = CUBE_SIZE((DIM(cube) + 1)); + result = (NDBOX *) palloc0(size); + SET_VARSIZE(result, size); + SET_DIM(result, DIM(cube) + 1); + for (i = 0; i < DIM(cube); i++) + { + result->x[i] = LL_COORD(cube, i); + result->x[DIM(result) + i] = UR_COORD(cube, i); + } + result->x[DIM(result) - 1] = x1; + result->x[2 * DIM(result) - 1] = x2; + } + + PG_FREE_IF_COPY(cube, 0); + PG_RETURN_NDBOX_P(result); +} diff --git a/contrib/cube/cube.control b/contrib/cube/cube.control new file mode 100644 index 0000000..50427ec --- /dev/null +++ b/contrib/cube/cube.control @@ -0,0 +1,6 @@ +# cube extension +comment = 'data type for multidimensional cubes' +default_version = '1.5' +module_pathname = '$libdir/cube' +relocatable = true +trusted = true diff --git a/contrib/cube/cubedata.h b/contrib/cube/cubedata.h new file mode 100644 index 0000000..96fa41a --- /dev/null +++ b/contrib/cube/cubedata.h @@ -0,0 +1,72 @@ +/* contrib/cube/cubedata.h */ + +/* + * This limit is pretty arbitrary, but don't make it so large that you + * risk overflow in sizing calculations. + */ +#define CUBE_MAX_DIM (100) + +typedef struct NDBOX +{ + /* varlena header (do not touch directly!) */ + int32 vl_len_; + + /*---------- + * Header contains info about NDBOX. For binary compatibility with old + * versions, it is defined as "unsigned int". + * + * Following information is stored: + * + * bits 0-7 : number of cube dimensions; + * bits 8-30 : unused, initialize to zero; + * bit 31 : point flag. If set, the upper right coordinates are not + * stored, and are implicitly the same as the lower left + * coordinates. + *---------- + */ + unsigned int header; + + /* + * The lower left coordinates for each dimension come first, followed by + * upper right coordinates unless the point flag is set. + */ + double x[FLEXIBLE_ARRAY_MEMBER]; +} NDBOX; + +/* NDBOX access macros */ +#define POINT_BIT 0x80000000 +#define DIM_MASK 0x7fffffff + +#define IS_POINT(cube) ( ((cube)->header & POINT_BIT) != 0 ) +#define SET_POINT_BIT(cube) ( (cube)->header |= POINT_BIT ) +#define DIM(cube) ( (cube)->header & DIM_MASK ) +#define SET_DIM(cube, _dim) ( (cube)->header = ((cube)->header & ~DIM_MASK) | (_dim) ) + +#define LL_COORD(cube, i) ( (cube)->x[i] ) +#define UR_COORD(cube, i) ( IS_POINT(cube) ? (cube)->x[i] : (cube)->x[(i) + DIM(cube)] ) + +#define POINT_SIZE(_dim) (offsetof(NDBOX, x) + sizeof(double)*(_dim)) +#define CUBE_SIZE(_dim) (offsetof(NDBOX, x) + sizeof(double)*(_dim)*2) + +/* fmgr interface macros */ +#define DatumGetNDBOXP(x) ((NDBOX *) PG_DETOAST_DATUM(x)) +#define PG_GETARG_NDBOX_P(x) DatumGetNDBOXP(PG_GETARG_DATUM(x)) +#define PG_RETURN_NDBOX_P(x) PG_RETURN_POINTER(x) + +/* GiST operator strategy numbers */ +#define CubeKNNDistanceCoord 15 /* ~> */ +#define CubeKNNDistanceTaxicab 16 /* <#> */ +#define CubeKNNDistanceEuclid 17 /* <-> */ +#define CubeKNNDistanceChebyshev 18 /* <=> */ + +/* in cubescan.l */ +extern int cube_yylex(void); +extern void cube_yyerror(NDBOX **result, Size scanbuflen, + struct Node *escontext, + const char *message); +extern void cube_scanner_init(const char *str, Size *scanbuflen); +extern void cube_scanner_finish(void); + +/* in cubeparse.y */ +extern int cube_yyparse(NDBOX **result, Size scanbuflen, + struct Node *escontext); diff --git a/contrib/cube/cubeparse.c b/contrib/cube/cubeparse.c new file mode 100644 index 0000000..4c99c56 --- /dev/null +++ b/contrib/cube/cubeparse.c @@ -0,0 +1,1575 @@ +/* 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 . */ + +/* 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 cube_yyparse +#define yylex cube_yylex +#define yyerror cube_yyerror +#define yydebug cube_yydebug +#define yynerrs cube_yynerrs +#define yylval cube_yylval +#define yychar cube_yychar + +/* First part of user prologue. */ +#line 1 "cubeparse.y" + +/* contrib/cube/cubeparse.y */ + +/* NdBox = [(lowerleft),(upperright)] */ +/* [(xLL(1)...xLL(N)),(xUR(1)...xUR(n))] */ + +#include "postgres.h" + +#include "cubedata.h" +#include "nodes/miscnodes.h" +#include "utils/float.h" +#include "varatt.h" + +/* All grammar constructs return strings */ +#define YYSTYPE char * + +/* + * Bison doesn't allocate anything that needs to live across parser calls, + * so we can easily have it use palloc instead of malloc. This prevents + * memory leaks if we error out during parsing. + */ +#define YYMALLOC palloc +#define YYFREE pfree + +static int item_count(const char *s, char delim); +static bool write_box(int dim, char *str1, char *str2, + NDBOX **result, struct Node *escontext); +static bool write_point_as_box(int dim, char *str, + NDBOX **result, struct Node *escontext); + + +#line 110 "cubeparse.c" + +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (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 + +#include "cubeparse.h" +/* 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_CUBEFLOAT = 3, /* CUBEFLOAT */ + YYSYMBOL_O_PAREN = 4, /* O_PAREN */ + YYSYMBOL_C_PAREN = 5, /* C_PAREN */ + YYSYMBOL_O_BRACKET = 6, /* O_BRACKET */ + YYSYMBOL_C_BRACKET = 7, /* C_BRACKET */ + YYSYMBOL_COMMA = 8, /* COMMA */ + YYSYMBOL_YYACCEPT = 9, /* $accept */ + YYSYMBOL_box = 10, /* box */ + YYSYMBOL_paren_list = 11, /* paren_list */ + YYSYMBOL_list = 12 /* list */ +}; +typedef enum yysymbol_kind_t yysymbol_kind_t; + + + + +#ifdef short +# undef short +#endif + +/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure + and (if available) are included + so that the code can choose integer types of a good width. */ + +#ifndef __PTRDIFF_MAX__ +# include /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* 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 + . */ +#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 /* 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 /* 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 /* 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 /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include /* 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 /* 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 10 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 17 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 9 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 4 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 9 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 19 + +/* YYMAXUTOK -- Last valid token kind. */ +#define YYMAXUTOK 263 + + +/* 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, + 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, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8 +}; + +#if YYDEBUG + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 46, 46, 74, 102, 121, 141, 145, 151, 157 +}; +#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\"", "CUBEFLOAT", "O_PAREN", + "C_PAREN", "O_BRACKET", "C_BRACKET", "COMMA", "$accept", "box", + "paren_list", "list", 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 +}; +#endif + +#define YYPACT_NINF (-4) + +#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[] = +{ + -2, -4, 0, 3, 10, 4, 5, -4, 1, 6, + -4, 3, 12, -4, 3, -4, -4, 9, -4 +}; + + /* 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[] = +{ + 0, 8, 0, 0, 0, 4, 5, 7, 0, 0, + 1, 0, 0, 6, 0, 3, 9, 0, 2 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -4, -4, -3, 15 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + 0, 4, 5, 6 +}; + + /* 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[] = +{ + 9, 1, 2, 1, 3, 7, 13, 2, 15, 12, + 10, 17, 11, 12, 14, 16, 18, 8 +}; + +static const yytype_int8 yycheck[] = +{ + 3, 3, 4, 3, 6, 5, 5, 4, 11, 8, + 0, 14, 8, 8, 8, 3, 7, 2 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_int8 yystos[] = +{ + 0, 3, 4, 6, 10, 11, 12, 5, 12, 11, + 0, 8, 8, 5, 8, 11, 3, 11, 7 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_int8 yyr1[] = +{ + 0, 9, 10, 10, 10, 10, 11, 11, 12, 12 +}; + + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ +static const yytype_int8 yyr2[] = +{ + 0, 2, 5, 3, 1, 1, 3, 2, 1, 3 +}; + + +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 (result, scanbuflen, escontext, 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 /* 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, result, scanbuflen, escontext); \ + 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, NDBOX **result, Size scanbuflen, struct Node *escontext) +{ + FILE *yyoutput = yyo; + YY_USE (yyoutput); + YY_USE (result); + YY_USE (scanbuflen); + YY_USE (escontext); + 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, NDBOX **result, Size scanbuflen, struct Node *escontext) +{ + YYFPRINTF (yyo, "%s %s (", + yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); + + yy_symbol_value_print (yyo, yykind, yyvaluep, result, scanbuflen, escontext); + 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, NDBOX **result, Size scanbuflen, struct Node *escontext) +{ + 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)], result, scanbuflen, escontext); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule, result, scanbuflen, escontext); \ +} 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, NDBOX **result, Size scanbuflen, struct Node *escontext) +{ + YY_USE (yyvaluep); + YY_USE (result); + YY_USE (scanbuflen); + YY_USE (escontext); + 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 (NDBOX **result, Size scanbuflen, struct Node *escontext) +{ + 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: /* box: O_BRACKET paren_list COMMA paren_list C_BRACKET */ +#line 47 "cubeparse.y" + { + int dim; + + dim = item_count(yyvsp[-3], ','); + if (item_count(yyvsp[-1], ',') != dim) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("Different point dimensions in (%s) and (%s).", + yyvsp[-3], yyvsp[-1]))); + YYABORT; + } + if (dim > CUBE_MAX_DIM) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + YYABORT; + } + + if (!write_box(dim, yyvsp[-3], yyvsp[-1], result, escontext)) + YYABORT; + } +#line 1140 "cubeparse.c" + break; + + case 3: /* box: paren_list COMMA paren_list */ +#line 75 "cubeparse.y" + { + int dim; + + dim = item_count(yyvsp[-2], ','); + if (item_count(yyvsp[0], ',') != dim) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("Different point dimensions in (%s) and (%s).", + yyvsp[-2], yyvsp[0]))); + YYABORT; + } + if (dim > CUBE_MAX_DIM) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + YYABORT; + } + + if (!write_box(dim, yyvsp[-2], yyvsp[0], result, escontext)) + YYABORT; + } +#line 1171 "cubeparse.c" + break; + + case 4: /* box: paren_list */ +#line 103 "cubeparse.y" + { + int dim; + + dim = item_count(yyvsp[0], ','); + if (dim > CUBE_MAX_DIM) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + YYABORT; + } + + if (!write_point_as_box(dim, yyvsp[0], result, escontext)) + YYABORT; + } +#line 1193 "cubeparse.c" + break; + + case 5: /* box: list */ +#line 122 "cubeparse.y" + { + int dim; + + dim = item_count(yyvsp[0], ','); + if (dim > CUBE_MAX_DIM) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + YYABORT; + } + + if (!write_point_as_box(dim, yyvsp[0], result, escontext)) + YYABORT; + } +#line 1215 "cubeparse.c" + break; + + case 6: /* paren_list: O_PAREN list C_PAREN */ +#line 142 "cubeparse.y" + { + yyval = yyvsp[-1]; + } +#line 1223 "cubeparse.c" + break; + + case 7: /* paren_list: O_PAREN C_PAREN */ +#line 146 "cubeparse.y" + { + yyval = pstrdup(""); + } +#line 1231 "cubeparse.c" + break; + + case 8: /* list: CUBEFLOAT */ +#line 152 "cubeparse.y" + { + /* alloc enough space to be sure whole list will fit */ + yyval = palloc(scanbuflen + 1); + strcpy(yyval, yyvsp[0]); + } +#line 1241 "cubeparse.c" + break; + + case 9: /* list: list COMMA CUBEFLOAT */ +#line 158 "cubeparse.y" + { + yyval = yyvsp[-2]; + strcat(yyval, ","); + strcat(yyval, yyvsp[0]); + } +#line 1251 "cubeparse.c" + break; + + +#line 1255 "cubeparse.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 (result, scanbuflen, escontext, 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, result, scanbuflen, escontext); + 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, result, scanbuflen, escontext); + 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 (result, scanbuflen, escontext, 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, result, scanbuflen, escontext); + } + /* 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, result, scanbuflen, escontext); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + + return yyresult; +} + +#line 165 "cubeparse.y" + + +/* This assumes the string has been normalized by productions above */ +static int +item_count(const char *s, char delim) +{ + int nitems = 0; + + if (s[0] != '\0') + { + nitems++; + while ((s = strchr(s, delim)) != NULL) + { + nitems++; + s++; + } + } + return nitems; +} + +static bool +write_box(int dim, char *str1, char *str2, + NDBOX **result, struct Node *escontext) +{ + NDBOX *bp; + char *s; + char *endptr; + int i; + int size = CUBE_SIZE(dim); + bool point = true; + + bp = palloc0(size); + SET_VARSIZE(bp, size); + SET_DIM(bp, dim); + + s = str1; + i = 0; + if (dim > 0) + { + bp->x[i++] = float8in_internal(s, &endptr, "cube", str1, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } + while ((s = strchr(s, ',')) != NULL) + { + s++; + bp->x[i++] = float8in_internal(s, &endptr, "cube", str1, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } + Assert(i == dim); + + s = str2; + if (dim > 0) + { + bp->x[i] = float8in_internal(s, &endptr, "cube", str2, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + /* code this way to do right thing with NaN */ + point &= (bp->x[i] == bp->x[0]); + i++; + } + while ((s = strchr(s, ',')) != NULL) + { + s++; + bp->x[i] = float8in_internal(s, &endptr, "cube", str2, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + point &= (bp->x[i] == bp->x[i - dim]); + i++; + } + Assert(i == dim * 2); + + if (point) + { + /* + * The value turned out to be a point, ie. all the upper-right + * coordinates were equal to the lower-left coordinates. Resize the + * cube we constructed. Note: we don't bother to repalloc() it + * smaller, as it's unlikely that the tiny amount of memory freed + * that way would be useful, and the output is always short-lived. + */ + size = POINT_SIZE(dim); + SET_VARSIZE(bp, size); + SET_POINT_BIT(bp); + } + + *result = bp; + return true; +} + +static bool +write_point_as_box(int dim, char *str, + NDBOX **result, struct Node *escontext) +{ + NDBOX *bp; + int i, + size; + char *s; + char *endptr; + + size = POINT_SIZE(dim); + bp = palloc0(size); + SET_VARSIZE(bp, size); + SET_DIM(bp, dim); + SET_POINT_BIT(bp); + + s = str; + i = 0; + if (dim > 0) + { + bp->x[i++] = float8in_internal(s, &endptr, "cube", str, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } + while ((s = strchr(s, ',')) != NULL) + { + s++; + bp->x[i++] = float8in_internal(s, &endptr, "cube", str, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } + Assert(i == dim); + + *result = bp; + return true; +} diff --git a/contrib/cube/cubeparse.h b/contrib/cube/cubeparse.h new file mode 100644 index 0000000..bd52d8d --- /dev/null +++ b/contrib/cube/cubeparse.h @@ -0,0 +1,79 @@ +/* A Bison parser, made by GNU Bison 3.7.5. */ + +/* Bison interface 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 . */ + +/* 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. */ + +/* 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. */ + +#ifndef YY_CUBE_YY_CUBEPARSE_H_INCLUDED +# define YY_CUBE_YY_CUBEPARSE_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int cube_yydebug; +#endif + +/* Token kinds. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + YYEMPTY = -2, + YYEOF = 0, /* "end of file" */ + YYerror = 256, /* error */ + YYUNDEF = 257, /* "invalid token" */ + CUBEFLOAT = 258, /* CUBEFLOAT */ + O_PAREN = 259, /* O_PAREN */ + C_PAREN = 260, /* C_PAREN */ + O_BRACKET = 261, /* O_BRACKET */ + C_BRACKET = 262, /* C_BRACKET */ + COMMA = 263 /* COMMA */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +typedef int YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + +extern YYSTYPE cube_yylval; + +int cube_yyparse (NDBOX **result, Size scanbuflen, struct Node *escontext); + +#endif /* !YY_CUBE_YY_CUBEPARSE_H_INCLUDED */ diff --git a/contrib/cube/cubeparse.y b/contrib/cube/cubeparse.y new file mode 100644 index 0000000..b39fbe6 --- /dev/null +++ b/contrib/cube/cubeparse.y @@ -0,0 +1,291 @@ +%{ +/* contrib/cube/cubeparse.y */ + +/* NdBox = [(lowerleft),(upperright)] */ +/* [(xLL(1)...xLL(N)),(xUR(1)...xUR(n))] */ + +#include "postgres.h" + +#include "cubedata.h" +#include "nodes/miscnodes.h" +#include "utils/float.h" +#include "varatt.h" + +/* All grammar constructs return strings */ +#define YYSTYPE char * + +/* + * Bison doesn't allocate anything that needs to live across parser calls, + * so we can easily have it use palloc instead of malloc. This prevents + * memory leaks if we error out during parsing. + */ +#define YYMALLOC palloc +#define YYFREE pfree + +static int item_count(const char *s, char delim); +static bool write_box(int dim, char *str1, char *str2, + NDBOX **result, struct Node *escontext); +static bool write_point_as_box(int dim, char *str, + NDBOX **result, struct Node *escontext); + +%} + +/* BISON Declarations */ +%parse-param {NDBOX **result} +%parse-param {Size scanbuflen} +%parse-param {struct Node *escontext} +%expect 0 +%name-prefix="cube_yy" + +%token CUBEFLOAT O_PAREN C_PAREN O_BRACKET C_BRACKET COMMA +%start box + +/* Grammar follows */ +%% + +box: O_BRACKET paren_list COMMA paren_list C_BRACKET + { + int dim; + + dim = item_count($2, ','); + if (item_count($4, ',') != dim) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("Different point dimensions in (%s) and (%s).", + $2, $4))); + YYABORT; + } + if (dim > CUBE_MAX_DIM) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + YYABORT; + } + + if (!write_box(dim, $2, $4, result, escontext)) + YYABORT; + } + + | paren_list COMMA paren_list + { + int dim; + + dim = item_count($1, ','); + if (item_count($3, ',') != dim) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("Different point dimensions in (%s) and (%s).", + $1, $3))); + YYABORT; + } + if (dim > CUBE_MAX_DIM) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + YYABORT; + } + + if (!write_box(dim, $1, $3, result, escontext)) + YYABORT; + } + + | paren_list + { + int dim; + + dim = item_count($1, ','); + if (dim > CUBE_MAX_DIM) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + YYABORT; + } + + if (!write_point_as_box(dim, $1, result, escontext)) + YYABORT; + } + + | list + { + int dim; + + dim = item_count($1, ','); + if (dim > CUBE_MAX_DIM) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + errdetail("A cube cannot have more than %d dimensions.", + CUBE_MAX_DIM))); + YYABORT; + } + + if (!write_point_as_box(dim, $1, result, escontext)) + YYABORT; + } + ; + +paren_list: O_PAREN list C_PAREN + { + $$ = $2; + } + | O_PAREN C_PAREN + { + $$ = pstrdup(""); + } + ; + +list: CUBEFLOAT + { + /* alloc enough space to be sure whole list will fit */ + $$ = palloc(scanbuflen + 1); + strcpy($$, $1); + } + | list COMMA CUBEFLOAT + { + $$ = $1; + strcat($$, ","); + strcat($$, $3); + } + ; + +%% + +/* This assumes the string has been normalized by productions above */ +static int +item_count(const char *s, char delim) +{ + int nitems = 0; + + if (s[0] != '\0') + { + nitems++; + while ((s = strchr(s, delim)) != NULL) + { + nitems++; + s++; + } + } + return nitems; +} + +static bool +write_box(int dim, char *str1, char *str2, + NDBOX **result, struct Node *escontext) +{ + NDBOX *bp; + char *s; + char *endptr; + int i; + int size = CUBE_SIZE(dim); + bool point = true; + + bp = palloc0(size); + SET_VARSIZE(bp, size); + SET_DIM(bp, dim); + + s = str1; + i = 0; + if (dim > 0) + { + bp->x[i++] = float8in_internal(s, &endptr, "cube", str1, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } + while ((s = strchr(s, ',')) != NULL) + { + s++; + bp->x[i++] = float8in_internal(s, &endptr, "cube", str1, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } + Assert(i == dim); + + s = str2; + if (dim > 0) + { + bp->x[i] = float8in_internal(s, &endptr, "cube", str2, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + /* code this way to do right thing with NaN */ + point &= (bp->x[i] == bp->x[0]); + i++; + } + while ((s = strchr(s, ',')) != NULL) + { + s++; + bp->x[i] = float8in_internal(s, &endptr, "cube", str2, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + point &= (bp->x[i] == bp->x[i - dim]); + i++; + } + Assert(i == dim * 2); + + if (point) + { + /* + * The value turned out to be a point, ie. all the upper-right + * coordinates were equal to the lower-left coordinates. Resize the + * cube we constructed. Note: we don't bother to repalloc() it + * smaller, as it's unlikely that the tiny amount of memory freed + * that way would be useful, and the output is always short-lived. + */ + size = POINT_SIZE(dim); + SET_VARSIZE(bp, size); + SET_POINT_BIT(bp); + } + + *result = bp; + return true; +} + +static bool +write_point_as_box(int dim, char *str, + NDBOX **result, struct Node *escontext) +{ + NDBOX *bp; + int i, + size; + char *s; + char *endptr; + + size = POINT_SIZE(dim); + bp = palloc0(size); + SET_VARSIZE(bp, size); + SET_DIM(bp, dim); + SET_POINT_BIT(bp); + + s = str; + i = 0; + if (dim > 0) + { + bp->x[i++] = float8in_internal(s, &endptr, "cube", str, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } + while ((s = strchr(s, ',')) != NULL) + { + s++; + bp->x[i++] = float8in_internal(s, &endptr, "cube", str, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + } + Assert(i == dim); + + *result = bp; + return true; +} diff --git a/contrib/cube/cubescan.c b/contrib/cube/cubescan.c new file mode 100644 index 0000000..fd428e4 --- /dev/null +++ b/contrib/cube/cubescan.c @@ -0,0 +1,2125 @@ +#line 2 "cubescan.c" +/* + * A scanner for EMP-style numeric ranges + * contrib/cube/cubescan.l + */ + +#include "postgres.h" + +/* + * NB: include cubeparse.h only AFTER defining YYSTYPE (to match cubeparse.y) + * and cubedata.h for NDBOX. + */ +#include "cubedata.h" +#define YYSTYPE char * +#include "cubeparse.h" + +#line 18 "cubescan.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define yy_create_buffer cube_yy_create_buffer +#define yy_delete_buffer cube_yy_delete_buffer +#define yy_scan_buffer cube_yy_scan_buffer +#define yy_scan_string cube_yy_scan_string +#define yy_scan_bytes cube_yy_scan_bytes +#define yy_init_buffer cube_yy_init_buffer +#define yy_flush_buffer cube_yy_flush_buffer +#define yy_load_buffer_state cube_yy_load_buffer_state +#define yy_switch_to_buffer cube_yy_switch_to_buffer +#define yypush_buffer_state cube_yypush_buffer_state +#define yypop_buffer_state cube_yypop_buffer_state +#define yyensure_buffer_stack cube_yyensure_buffer_stack +#define yy_flex_debug cube_yy_flex_debug +#define yyin cube_yyin +#define yyleng cube_yyleng +#define yylex cube_yylex +#define yylineno cube_yylineno +#define yyout cube_yyout +#define yyrestart cube_yyrestart +#define yytext cube_yytext +#define yywrap cube_yywrap +#define yyalloc cube_yyalloc +#define yyrealloc cube_yyrealloc +#define yyfree cube_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 cube_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer cube_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define cube_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer cube_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define cube_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer cube_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define cube_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string cube_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define cube_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes cube_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define cube_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer cube_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define cube_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer cube_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define cube_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state cube_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define cube_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer cube_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define cube_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state cube_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define cube_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state cube_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define cube_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack cube_yyensure_buffer_stack +#endif + +#ifdef yylex +#define cube_yylex_ALREADY_DEFINED +#else +#define yylex cube_yylex +#endif + +#ifdef yyrestart +#define cube_yyrestart_ALREADY_DEFINED +#else +#define yyrestart cube_yyrestart +#endif + +#ifdef yylex_init +#define cube_yylex_init_ALREADY_DEFINED +#else +#define yylex_init cube_yylex_init +#endif + +#ifdef yylex_init_extra +#define cube_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra cube_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define cube_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy cube_yylex_destroy +#endif + +#ifdef yyget_debug +#define cube_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug cube_yyget_debug +#endif + +#ifdef yyset_debug +#define cube_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug cube_yyset_debug +#endif + +#ifdef yyget_extra +#define cube_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra cube_yyget_extra +#endif + +#ifdef yyset_extra +#define cube_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra cube_yyset_extra +#endif + +#ifdef yyget_in +#define cube_yyget_in_ALREADY_DEFINED +#else +#define yyget_in cube_yyget_in +#endif + +#ifdef yyset_in +#define cube_yyset_in_ALREADY_DEFINED +#else +#define yyset_in cube_yyset_in +#endif + +#ifdef yyget_out +#define cube_yyget_out_ALREADY_DEFINED +#else +#define yyget_out cube_yyget_out +#endif + +#ifdef yyset_out +#define cube_yyset_out_ALREADY_DEFINED +#else +#define yyset_out cube_yyset_out +#endif + +#ifdef yyget_leng +#define cube_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng cube_yyget_leng +#endif + +#ifdef yyget_text +#define cube_yyget_text_ALREADY_DEFINED +#else +#define yyget_text cube_yyget_text +#endif + +#ifdef yyget_lineno +#define cube_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno cube_yyget_lineno +#endif + +#ifdef yyset_lineno +#define cube_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno cube_yyset_lineno +#endif + +#ifdef yywrap +#define cube_yywrap_ALREADY_DEFINED +#else +#define yywrap cube_yywrap +#endif + +#ifdef yyalloc +#define cube_yyalloc_ALREADY_DEFINED +#else +#define yyalloc cube_yyalloc +#endif + +#ifdef yyrealloc +#define cube_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc cube_yyrealloc +#endif + +#ifdef yyfree +#define cube_yyfree_ALREADY_DEFINED +#else +#define yyfree cube_yyfree +#endif + +#ifdef yytext +#define cube_yytext_ALREADY_DEFINED +#else +#define yytext cube_yytext +#endif + +#ifdef yyleng +#define cube_yyleng_ALREADY_DEFINED +#else +#define yyleng cube_yyleng +#endif + +#ifdef yyin +#define cube_yyin_ALREADY_DEFINED +#else +#define yyin cube_yyin +#endif + +#ifdef yyout +#define cube_yyout_ALREADY_DEFINED +#else +#define yyout cube_yyout +#endif + +#ifdef yy_flex_debug +#define cube_yy_flex_debug_ALREADY_DEFINED +#else +#define yy_flex_debug cube_yy_flex_debug +#endif + +#ifdef yylineno +#define cube_yylineno_ALREADY_DEFINED +#else +#define yylineno cube_yylineno +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . 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 +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 cube_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 11 +#define YY_END_OF_BUFFER 12 +/* 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[37] = + { 0, + 0, 0, 12, 10, 9, 9, 6, 7, 10, 8, + 10, 1, 10, 10, 4, 5, 9, 0, 1, 0, + 1, 1, 0, 0, 0, 1, 0, 1, 2, 3, + 0, 0, 0, 0, 2, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 1, 4, + 5, 1, 6, 7, 6, 8, 1, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, + 1, 1, 1, 1, 10, 1, 1, 1, 11, 12, + 1, 1, 13, 1, 1, 1, 1, 14, 1, 1, + 1, 1, 1, 15, 1, 1, 1, 1, 16, 1, + 17, 1, 18, 1, 1, 1, 10, 1, 1, 1, + + 11, 12, 1, 1, 13, 1, 1, 1, 1, 14, + 1, 1, 1, 1, 1, 15, 1, 1, 1, 1, + 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static const YY_CHAR yy_meta[19] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1 + } ; + +static const flex_int16_t yy_base[37] = + { 0, + 0, 0, 53, 54, 17, 19, 54, 54, 15, 54, + 43, 21, 37, 40, 54, 54, 23, 40, 0, 34, + 22, 25, 29, 35, 32, 28, 36, 35, 30, 54, + 28, 28, 25, 11, 54, 54 + } ; + +static const flex_int16_t yy_def[37] = + { 0, + 36, 1, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 12, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 0 + } ; + +static const flex_int16_t yy_nxt[73] = + { 0, + 4, 5, 6, 7, 8, 9, 10, 11, 12, 4, + 4, 4, 13, 14, 4, 4, 15, 16, 17, 17, + 17, 17, 18, 19, 17, 17, 35, 20, 22, 19, + 21, 23, 23, 26, 27, 23, 26, 28, 23, 34, + 33, 32, 31, 28, 28, 30, 29, 24, 21, 25, + 24, 21, 36, 3, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36 + } ; + +static const flex_int16_t yy_chk[73] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, + 6, 6, 9, 9, 17, 17, 34, 9, 12, 12, + 21, 12, 21, 22, 23, 22, 26, 23, 26, 33, + 32, 31, 29, 28, 27, 25, 24, 20, 18, 14, + 13, 11, 3, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36 + } ; + +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 "cubescan.l" + +#line 19 "cubescan.l" +/* LCOV_EXCL_START */ + +/* No reason to constrain amount of data slurped */ +#define YY_READ_BUF_SIZE 16777216 + +/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ +#undef fprintf +#define fprintf(file, fmt, msg) fprintf_to_ereport(fmt, msg) + +static void +fprintf_to_ereport(const char *fmt, const char *msg) +{ + ereport(ERROR, (errmsg_internal("%s", msg))); +} + +/* Handles to the buffer that the lexer uses internally */ +static YY_BUFFER_STATE scanbufhandle; +static char *scanbuf; +#line 762 "cubescan.c" +#define YY_NO_INPUT 1 +#line 764 "cubescan.c" + +#define INITIAL 0 + +#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 +#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 56 "cubescan.l" + + +#line 982 "cubescan.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 >= 37 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_current_state != 36 ); + 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 58 "cubescan.l" +cube_yylval = yytext; return CUBEFLOAT; + YY_BREAK +case 2: +YY_RULE_SETUP +#line 59 "cubescan.l" +cube_yylval = yytext; return CUBEFLOAT; + YY_BREAK +case 3: +YY_RULE_SETUP +#line 60 "cubescan.l" +cube_yylval = yytext; return CUBEFLOAT; + YY_BREAK +case 4: +YY_RULE_SETUP +#line 61 "cubescan.l" +cube_yylval = "("; return O_BRACKET; + YY_BREAK +case 5: +YY_RULE_SETUP +#line 62 "cubescan.l" +cube_yylval = ")"; return C_BRACKET; + YY_BREAK +case 6: +YY_RULE_SETUP +#line 63 "cubescan.l" +cube_yylval = "("; return O_PAREN; + YY_BREAK +case 7: +YY_RULE_SETUP +#line 64 "cubescan.l" +cube_yylval = ")"; return C_PAREN; + YY_BREAK +case 8: +YY_RULE_SETUP +#line 65 "cubescan.l" +cube_yylval = ","; return COMMA; + YY_BREAK +case 9: +/* rule 9 can match eol */ +YY_RULE_SETUP +#line 66 "cubescan.l" +/* discard spaces */ + YY_BREAK +case 10: +YY_RULE_SETUP +#line 67 "cubescan.l" +return yytext[0]; /* alert parser of the garbage */ + YY_BREAK +case 11: +YY_RULE_SETUP +#line 69 "cubescan.l" +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +#line 1091 "cubescan.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 >= 37 ) + 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 >= 37 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 36); + + 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 69 "cubescan.l" + + +/* LCOV_EXCL_STOP */ + +/* result and scanbuflen are not used, but Bison expects this signature */ +void +cube_yyerror(NDBOX **result, Size scanbuflen, + struct Node *escontext, + const char *message) +{ + if (*yytext == YY_END_OF_BUFFER_CHAR) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + /* translator: %s is typically "syntax error" */ + errdetail("%s at end of input", message))); + } + else + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + /* translator: first %s is typically "syntax error" */ + errdetail("%s at or near \"%s\"", message, yytext))); + } +} + + +/* + * Called before any actual parsing is done + */ +void +cube_scanner_init(const char *str, Size *scanbuflen) +{ + Size slen = strlen(str); + + /* + * Might be left over after ereport() + */ + if (YY_CURRENT_BUFFER) + yy_delete_buffer(YY_CURRENT_BUFFER); + + /* + * Make a scan buffer with special termination needed by flex. + */ + *scanbuflen = slen; + scanbuf = palloc(slen + 2); + memcpy(scanbuf, str, slen); + scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR; + scanbufhandle = yy_scan_buffer(scanbuf, slen + 2); + + BEGIN(INITIAL); +} + + +/* + * Called after parsing is done to clean up after cube_scanner_init() + */ +void +cube_scanner_finish(void) +{ + yy_delete_buffer(scanbufhandle); + pfree(scanbuf); +} + diff --git a/contrib/cube/cubescan.l b/contrib/cube/cubescan.l new file mode 100644 index 0000000..49cb699 --- /dev/null +++ b/contrib/cube/cubescan.l @@ -0,0 +1,133 @@ +%top{ +/* + * A scanner for EMP-style numeric ranges + * contrib/cube/cubescan.l + */ + +#include "postgres.h" + +/* + * NB: include cubeparse.h only AFTER defining YYSTYPE (to match cubeparse.y) + * and cubedata.h for NDBOX. + */ +#include "cubedata.h" +#define YYSTYPE char * +#include "cubeparse.h" +} + +%{ +/* LCOV_EXCL_START */ + +/* No reason to constrain amount of data slurped */ +#define YY_READ_BUF_SIZE 16777216 + +/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ +#undef fprintf +#define fprintf(file, fmt, msg) fprintf_to_ereport(fmt, msg) + +static void +fprintf_to_ereport(const char *fmt, const char *msg) +{ + ereport(ERROR, (errmsg_internal("%s", msg))); +} + +/* Handles to the buffer that the lexer uses internally */ +static YY_BUFFER_STATE scanbufhandle; +static char *scanbuf; +%} + +%option 8bit +%option never-interactive +%option nodefault +%option noinput +%option nounput +%option noyywrap +%option warn +%option prefix="cube_yy" + + +n [0-9]+ +integer [+-]?{n} +real [+-]?({n}\.{n}?|\.{n}) +float ({integer}|{real})([eE]{integer})? +infinity [+-]?[iI][nN][fF]([iI][nN][iI][tT][yY])? +NaN [nN][aA][nN] + +%% + +{float} cube_yylval = yytext; return CUBEFLOAT; +{infinity} cube_yylval = yytext; return CUBEFLOAT; +{NaN} cube_yylval = yytext; return CUBEFLOAT; +\[ cube_yylval = "("; return O_BRACKET; +\] cube_yylval = ")"; return C_BRACKET; +\( cube_yylval = "("; return O_PAREN; +\) cube_yylval = ")"; return C_PAREN; +\, cube_yylval = ","; return COMMA; +[ \t\n\r\f]+ /* discard spaces */ +. return yytext[0]; /* alert parser of the garbage */ + +%% + +/* LCOV_EXCL_STOP */ + +/* result and scanbuflen are not used, but Bison expects this signature */ +void +cube_yyerror(NDBOX **result, Size scanbuflen, + struct Node *escontext, + const char *message) +{ + if (*yytext == YY_END_OF_BUFFER_CHAR) + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + /* translator: %s is typically "syntax error" */ + errdetail("%s at end of input", message))); + } + else + { + errsave(escontext, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for cube"), + /* translator: first %s is typically "syntax error" */ + errdetail("%s at or near \"%s\"", message, yytext))); + } +} + + +/* + * Called before any actual parsing is done + */ +void +cube_scanner_init(const char *str, Size *scanbuflen) +{ + Size slen = strlen(str); + + /* + * Might be left over after ereport() + */ + if (YY_CURRENT_BUFFER) + yy_delete_buffer(YY_CURRENT_BUFFER); + + /* + * Make a scan buffer with special termination needed by flex. + */ + *scanbuflen = slen; + scanbuf = palloc(slen + 2); + memcpy(scanbuf, str, slen); + scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR; + scanbufhandle = yy_scan_buffer(scanbuf, slen + 2); + + BEGIN(INITIAL); +} + + +/* + * Called after parsing is done to clean up after cube_scanner_init() + */ +void +cube_scanner_finish(void) +{ + yy_delete_buffer(scanbufhandle); + pfree(scanbuf); +} diff --git a/contrib/cube/data/test_cube.data b/contrib/cube/data/test_cube.data new file mode 100644 index 0000000..d67cd12 --- /dev/null +++ b/contrib/cube/data/test_cube.data @@ -0,0 +1,3100 @@ +(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) +(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) +(24555,40334),(24546,40330) +(46870,43762),(46789,43709) +(20030,2752),(19945,2687) +(30758,26754),(30718,26678) +(4320,44673),(4286,44625) +(1011,15576),(939,15574) +(41936,40699),(41854,40655) +(20594,19002),(20561,18995) +(9388,41056),(9325,41042) +(34771,46693),(34751,46645) +(49398,46359),(49332,46357) +(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) +(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) +(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) +(29884,9544),(29838,9520) +(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) +(12916,34327),(12891,34240) +(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) +(35276,1027),(35259,972) +(26337,17603),(26313,17579) +(35649,16777),(35626,16777) +(42454,5105),(42362,5101) +(21682,24951),(21646,24920) +(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) +(35523,40973),(35504,40932) +(32948,16962),(32857,16901) +(7808,13469),(7712,13469) +(13920,35203),(13870,35131) +(22731,31563),(22658,31557) +(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) +(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) +(30333,50),(30273,6) +(35567,9965),(35482,9912) +(49847,7128),(49798,7067) +(27685,36396),(27668,36384) +(43832,18491),(43825,18431) +(36849,34600),(36785,34589) +(2348,47938),(2307,47902) +(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) +(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) +(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) +(11590,8292),(11539,8272) +(8193,33323),(8106,33317) +(16043,14799),(16001,14710) +(19574,11395),(19514,11316) +(26290,41424),(26224,41342) +(22844,12516),(22807,12471) +(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) +(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) +(35495,25950),(35459,25912) +(41896,45014),(41881,44999) +(22654,41896),(22572,41801) +(18581,7087),(18524,6988) +(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) +(4917,34801),(4828,34766) +(39281,33659),(39268,33618) +(31706,7119),(31645,7063) +(3427,44006),(3422,44004) +(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) +(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) +(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) +(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) +(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) +(4599,45275),(4552,45254) +(33191,11404),(33176,11348) +(14245,18633),(14177,18540) +(32457,20705),(32393,20700) +(40052,10499),(40016,10457) +(29824,44065),(29785,44037) +(31613,12565),(31557,12543) +(42692,29000),(42652,28996) +(40680,22219),(40603,22140) +(33575,27661),(33488,27644) +(46194,1385),(46184,1355) +(38442,48501),(38407,48426) +(25305,21544),(25236,21523) +(15562,8226),(15561,8208) +(20844,43614),(20752,43558) +(22566,30541),(22554,30532) +(2760,47802),(2672,47789) +(25515,30745),(25433,30675) +(48382,45134),(48382,45093) +(9940,27094),(9871,27087) +(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) +(45275,8420),(45244,8418) +(13514,45251),(13491,45249) +(42112,2748),(42047,2668) +(7810,21907),(7806,21878) +(48378,36029),(48303,35979) +(32568,48605),(32510,48563) +(859,18915),(810,18915) +(41963,17950),(41939,17915) +(42723,8031),(42685,7955) +(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) +(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) +(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) +(39471,23837),(39390,23749) +(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) +(49853,18504),(49848,18503) +(11528,25165),(11476,25080) +(49902,41752),(49818,41746) +(1956,47506),(1922,47424) +(21834,22058),(21802,21964) +(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) +(14409,48602),(14320,48555) +(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) +(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) +(45383,16904),(45353,16888) +(7132,11408),(7091,11338) +(48262,43227),(48236,43159) +(31722,12861),(31675,12810) +(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) +(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) +(7749,32601),(7740,32515) +(26203,34991),(26159,34946) +(31856,39006),(31783,39003) +(45828,24767),(45788,24723) +(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) +(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) +(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) +(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) +(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) +(5760,49617),(5730,49546) +(794,27001),(777,26992) +(13518,45289),(13459,45235) +(34430,29754),(34363,29736) +(37912,24574),(37880,24543) +(8130,2270),(8083,2258) +(26930,21516),(26848,21455) +(3634,33511),(3592,33489) +(33080,5036),(33035,4972) +(48389,13942),(48316,13915) +(9231,5298),(9150,5232) +(1357,10601),(1321,10548) +(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) +(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) +(46833,38652),(46755,38612) +(47768,27202),(47681,27169) +(22792,1183),(22731,1152) +(25650,43310),(25562,43247) +(37084,20116),(37045,20057) +(47461,32556),(47423,32555) +(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) +(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) +(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) +(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) +(3512,32256),(3417,32242) +(31923,31225),(31832,31197) +(5144,4969),(5124,4937) +(34499,46164),(34430,46162) +(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) +(27442,33090),(27353,33064) +(23949,12046),(23949,12036) +(11399,377),(11360,294) +(47099,9989),(47023,9942) +(641,33118),(639,33084) +(13687,41308),(13682,41290) +(3682,17727),(3645,17660) +(13262,19396),(13185,19357) +(18791,389),(18774,366) +(12489,45384),(12403,45369) +(12065,6364),(12015,6325) +(32705,23886),(32619,23827) +(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) +(14110,49740),(14063,49695) +(4170,470),(4097,463) +(22219,17296),(22164,17221) +(2505,20879),(2446,20842) +(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) +(14410,30977),(14349,30944) +(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) +(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) +(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) +(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) +(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) +(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) +(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) +(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) +(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) +(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) +(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) +(33679,13468),(33628,13415) +(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) +(13387,37414),(13329,37318) +(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) +(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) +(15438,18788),(15435,18729) +(47115,5235),(47096,5143) +(33982,9002),(33915,8925) +(14206,37041),(14174,36955) +(24300,36616),(24232,36613) +(44658,1788),(44580,1769) +(31539,43550),(31463,43464) +(16722,9673),(16633,9652) +(44813,20573),(44733,20544) +(42114,32559),(42040,32552) +(41561,36244),(41477,36241) +(39589,33796),(39548,33716) +(20365,26770),(20329,26709) +(28511,208),(28479,114) +(10010,25524),(9930,25508) +(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) +(36643,18136),(36614,18106) +(1858,7457),(1851,7402) +(24452,44306),(24372,44252) +(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) +(35407,30177),(35345,30151) +(4071,40568),(4058,40544) +(25998,30513),(25965,30452) +(8195,45403),(8097,45310) +(8276,41689),(8183,41670) +(48435,28550),(48355,28455) +(8139,25449),(8136,25380) +(20302,25574),(20297,25531) +(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) +(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) +(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) +(48200,15377),(48110,15369) +(16546,22195),(16477,22142) +(21436,8460),(21378,8449) +(46598,17235),(46577,17138) +(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) +(32654,35183),(32606,35144) +(14336,11763),(14328,11712) +(30546,23808),(30463,23773) +(6813,21006),(6781,20924) +(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) +(21263,8203),(21242,8176) +(46537,3797),(46462,3735) +(18406,14579),(18393,14563) +(11583,16529),(11536,16471) +(10564,46257),(10478,46228) +(49769,34513),(49761,34458) +(9202,6482),(9138,6391) +(40387,37411),(40357,37360) +(11966,11802),(11888,11751) +(15551,47438),(15486,47406) +(12017,43288),(11969,43230) +(9717,22574),(9701,22495) +(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) +(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) +(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) +(12164,33871),(12075,33820) +(19598,43327),(19593,43314) +(3818,28584),(3815,28504) +(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) +(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) +(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) +(1402,13497),(1397,13465) +(24155,3075),(24083,3062) +(38378,1864),(38339,1849) +(29261,49910),(29247,49818) +(38139,37073),(38098,37057) +(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) +(23104,7700),(23062,7688) +(5619,2079),(5611,2075) +(31471,49632),(31375,49549) +(25793,12526),(25783,12456) +(3935,29528),(3866,29513) +(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) +(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) +(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) +(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) +(20133,37769),(20105,37679) +(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) +(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) +(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) +(21128,2384),(21101,2368) +(30209,16952),(30156,16858) +(39078,24963),(39045,24898) +(5598,1348),(5499,1294) +(38474,7436),(38450,7364) +(15117,45734),(15024,45693) +(23909,39853),(23888,39780) +(24292,30183),(24282,30148) +(48871,17661),(48868,17637) +(918,18752),(847,18708) +(43615,16162),(43606,16104) +(33763,47410),(33751,47409) +(4798,6485),(4773,6388) +(18524,41539),(18433,41518) +(47745,42449),(47651,42364) +(38936,21237),(38864,21204) +(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) +(8605,39015),(8605,38933) +(5764,43548),(5719,43496) +(20789,29902),(20696,29843) +(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) +(43689,8119),(43670,8028) +(41917,49601),(41835,49563) +(44295,13116),(44205,13078) +(22721,44772),(22667,44748) +(32640,11107),(32636,11050) +(20639,28851),(20613,28839) +(32479,10159),(32446,10061) +(27251,16978),(27196,16959) +(41401,33148),(41339,33074) +(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) +(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) +(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) +(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) +(43531,2412),(43447,2396) +(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) +(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) +(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) +(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) +(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) +(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) +(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) +(49944,25163),(49902,25153) +(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) +(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) +(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) +(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) +(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) +(5457,35844),(5376,35782) +(29423,3977),(29354,3959) +(18059,3001),(17965,2961) +(8509,5691),(8463,5620) +(27118,5762),(27083,5747) +(2991,48605),(2939,48559) +(44482,3484),(44425,3459) +(45143,16439),(45046,16365) +(2236,37531),(2147,37530) +(41561,3217),(41490,3210) +(6270,27200),(6171,27166) +(49195,24871),(49138,24798) +(46985,38881),(46897,38845) +(37486,23522),(37404,23441) +(26907,14490),(26900,14391) +(30829,16111),(30756,16056) +(3644,17291),(3587,17262) +(20508,49775),(20472,49680) +(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) +(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) +(29239,30554),(29202,30500) +(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) +(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) +(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) +(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) +(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) +(5424,34145),(5382,34106) +(14727,45600),(14699,45547) +(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) +(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) +(43938,7429),(43923,7391) +(2168,50012),(2108,49914) +(16022,8934),(15963,8928) +(24567,39147),(24561,39102) +(42781,14149),(42765,14088) +(39501,21084),(39468,21078) +(6697,29628),(6693,29584) +(11441,16164),(11364,16125) +(39946,1920),(39868,1844) +(18138,45512),(18111,45438) +(20799,41217),(20718,41138) +(30264,16697),(30240,16639) +(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) +(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) +(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) +(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) +(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) +(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) +(12786,8485),(12702,8435) +(13876,49473),(13813,49448) +(31585,46998),(31490,46929) +(30227,8768),(30206,8715) +(32062,39306),(32023,39292) +(25003,35753),(24921,35687) +(3281,6758),(3232,6704) +(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) +(7346,34235),(7330,34224) +(16417,48134),(16352,48066) +(41916,4971),(41849,4886) +(15856,1522),(15807,1521) +(41549,40218),(41494,40144) +(9978,16226),(9972,16181) +(14856,13312),(14808,13283) +(38490,41641),(38428,41583) +(25828,7438),(25807,7378) +(21876,30633),(21796,30587) +(1908,14279),(1825,14247) +(32207,10251),(32121,10184) +(370,9493),(328,9441) +(42072,17634),(41974,17600) +(47298,9910),(47235,9846) +(17856,11266),(17782,11225) +(35009,21400),(34956,21396) +(18337,11145),(18335,11133) +(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) +(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) +(2710,33521),(2675,33505) +(26067,19998),(26026,19989) +(12244,34509),(12202,34489) +(47162,598),(47119,499) +(33093,49382),(33068,49359) +(35170,26340),(35153,26264) +(22552,35785),(22490,35735) +(36791,23032),(36781,22976) +(22857,10857),(22833,10797) +(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) +(48478,36135),(48425,36092) +(5230,39250),(5206,39174) +(3488,18562),(3423,18489) +(39502,16331),(39460,16275) +(18296,1478),(18233,1471) +(28627,12430),(28559,12410) +(25257,21981),(25206,21954) +(2410,41192),(2325,41142) +(43681,9631),(43587,9538) +(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) +(40659,35541),(40659,35489) +(22231,26628),(22210,26579) +(37408,23016),(37375,22919) +(5920,15916),(5906,15895) +(33125,9952),(33037,9880) +(12142,29705),(12141,29670) +(3672,20995),(3649,20899) +(39147,31967),(39101,31907) +(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) +(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) +(388,21716),(296,21678) +(48782,11064),(48701,11005) +(40293,12997),(40213,12927) +(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) +(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) +(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) +(37262,26005),(37260,25947) +(14899,44069),(14814,44066) +(39635,18701),(39587,18698) +(28528,22948),(28457,22857) +(7755,36528),(7681,36454) +(32461,1172),(32427,1106) +(18775,27359),(18736,27329) +(15379,20031),(15337,19934) +(45888,33592),(45881,33544) +(44013,24694),(43962,24645) +(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) +(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) +(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) +(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) +(40316,1721),(40315,1658) +(40089,3454),(40074,3443) +(793,17897),(761,17897) +(6490,43552),(6434,43522) +(10825,487),(10820,405) +(47703,36067),(47641,36011) +(4480,11671),(4468,11653) +(37713,10642),(37711,10615) +(12315,5302),(12273,5203) +(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) +(19650,142),(19630,51) +(39769,17276),(39743,17229) +(5171,24562),(5119,24470) +(32976,35249),(32917,35199) +(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) +(154,4019),(138,3990) +(20447,4998),(20383,4970) +(38867,35757),(38795,35659) +(32322,15845),(32227,15804) +(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) +(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) +(29239,9962),(29208,9875) +(40062,44554),(39973,44460) +(19135,20729),(19059,20643) +(31969,40664),(31896,40643) +(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) +(37913,42300),(37814,42269) +(18216,8177),(18161,8173) +(32967,8258),(32899,8244) +(14978,40230),(14971,40149) +(30343,39152),(30266,39101) +(25917,5835),(25843,5806) +(5169,45366),(5141,45314) +(16221,20898),(16209,20875) +(13151,19869),(13145,19811) +(44399,2801),(44337,2713) +(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) +(11042,33125),(11011,33071) +(22347,13460),(22290,13375) +(3508,20538),(3483,20536) +(5331,42945),(5272,42875) +(2368,15537),(2339,15503) +(45314,31830),(45254,31817) +(34358,2649),(34319,2589) +(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) +(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) +(27526,15096),(27450,15088) +(26947,33409),(26853,33333) +(1537,43572),(1471,43499) +(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) +(1623,8350),(1578,8254) +(21220,43808),(21137,43748) +(40397,16471),(40358,16434) +(34839,1377),(34744,1327) +(17096,5730),(17090,5637) +(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) +(10423,33548),(10364,33498) +(39187,23274),(39136,23197) +(21882,37247),(21835,37167) +(11343,16957),(11281,16914) +(38279,43400),(38264,43352) +(23167,30271),(23086,30224) +(46278,6037),(46180,5964) +(28626,31165),(28605,31095) +(31018,367),(30946,333) +(23541,12541),(23530,12523) +(49741,14535),(49691,14511) +(31444,12702),(31425,12612) +(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) +(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) +(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) +(6527,49356),(6508,49315) +(43891,29789),(43856,29723) +(6146,37192),(6085,37107) +(42012,28897),(41939,28808) +(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) +(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) +(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) +(46513,46780),(46425,46780) +(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) +(31608,6215),(31575,6168) +(12788,49510),(12784,49468) +(46071,13013),(46035,12991) +(27647,8218),(27582,8201) +(49580,11076),(49537,11050) +(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) +(11737,18808),(11714,18788) +(13019,30534),(13005,30481) +(39224,31729),(39191,31683) +(4942,41680),(4907,41596) +(12287,37187),(12188,37172) +(30758,29579),(30725,29531) +(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) +(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) +(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) +(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) +(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) +(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) +(26917,45698),(26864,45689) +(23933,41617),(23909,41539) +(8912,8471),(8862,8401) +(9625,4747),(9558,4692) +(34743,35056),(34721,34969) +(39544,21762),(39475,21717) +(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) +(21650,23955),(21605,23883) +(13260,3234),(13193,3214) +(25361,10896),(25305,10806) +(25051,25042),(25011,25001) +(25044,25088),(25015,25005) +(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) +(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) +(25076,25098),(25002,25016) +(25030,25077),(25012,25006) diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out new file mode 100644 index 0000000..47787c5 --- /dev/null +++ b/contrib/cube/expected/cube.out @@ -0,0 +1,1975 @@ +-- +-- Test cube datatype +-- +CREATE EXTENSION cube; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + amname | opcname +--------+--------- +(0 rows) + +-- +-- testing the input and output functions +-- +-- Any number (a one-dimensional point) +SELECT '1'::cube AS cube; + cube +------ + (1) +(1 row) + +SELECT '-1'::cube AS cube; + cube +------ + (-1) +(1 row) + +SELECT '1.'::cube AS cube; + cube +------ + (1) +(1 row) + +SELECT '-1.'::cube AS cube; + cube +------ + (-1) +(1 row) + +SELECT '.1'::cube AS cube; + cube +------- + (0.1) +(1 row) + +SELECT '-.1'::cube AS cube; + cube +-------- + (-0.1) +(1 row) + +SELECT '1.0'::cube AS cube; + cube +------ + (1) +(1 row) + +SELECT '-1.0'::cube AS cube; + cube +------ + (-1) +(1 row) + +SELECT 'infinity'::cube AS cube; + cube +------------ + (Infinity) +(1 row) + +SELECT '-infinity'::cube AS cube; + cube +------------- + (-Infinity) +(1 row) + +SELECT 'NaN'::cube AS cube; + cube +------- + (NaN) +(1 row) + +SELECT '.1234567890123456'::cube AS cube; + cube +---------------------- + (0.1234567890123456) +(1 row) + +SELECT '+.1234567890123456'::cube AS cube; + cube +---------------------- + (0.1234567890123456) +(1 row) + +SELECT '-.1234567890123456'::cube AS cube; + cube +----------------------- + (-0.1234567890123456) +(1 row) + +-- simple lists (points) +SELECT '()'::cube AS cube; + cube +------ + () +(1 row) + +SELECT '1,2'::cube AS cube; + cube +-------- + (1, 2) +(1 row) + +SELECT '(1,2)'::cube AS cube; + cube +-------- + (1, 2) +(1 row) + +SELECT '1,2,3,4,5'::cube AS cube; + cube +----------------- + (1, 2, 3, 4, 5) +(1 row) + +SELECT '(1,2,3,4,5)'::cube AS cube; + cube +----------------- + (1, 2, 3, 4, 5) +(1 row) + +-- double lists (cubes) +SELECT '(),()'::cube AS cube; + cube +------ + () +(1 row) + +SELECT '(0),(0)'::cube AS cube; + cube +------ + (0) +(1 row) + +SELECT '(0),(1)'::cube AS cube; + cube +--------- + (0),(1) +(1 row) + +SELECT '[(0),(0)]'::cube AS cube; + cube +------ + (0) +(1 row) + +SELECT '[(0),(1)]'::cube AS cube; + cube +--------- + (0),(1) +(1 row) + +SELECT '(0,0,0,0),(0,0,0,0)'::cube AS cube; + cube +-------------- + (0, 0, 0, 0) +(1 row) + +SELECT '(0,0,0,0),(1,0,0,0)'::cube AS cube; + cube +--------------------------- + (0, 0, 0, 0),(1, 0, 0, 0) +(1 row) + +SELECT '[(0,0,0,0),(0,0,0,0)]'::cube AS cube; + cube +-------------- + (0, 0, 0, 0) +(1 row) + +SELECT '[(0,0,0,0),(1,0,0,0)]'::cube AS cube; + cube +--------------------------- + (0, 0, 0, 0),(1, 0, 0, 0) +(1 row) + +-- invalid input: parse errors +SELECT ''::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT ''::cube AS cube; + ^ +DETAIL: syntax error at end of input +SELECT 'ABC'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT 'ABC'::cube AS cube; + ^ +DETAIL: syntax error at or near "A" +SELECT '[]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[]'::cube AS cube; + ^ +DETAIL: syntax error at or near "]" +SELECT '[()]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[()]'::cube AS cube; + ^ +DETAIL: syntax error at or near "]" +SELECT '[(1)]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1)]'::cube AS cube; + ^ +DETAIL: syntax error at or near "]" +SELECT '[(1),]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1),]'::cube AS cube; + ^ +DETAIL: syntax error at or near "]" +SELECT '[(1),2]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1),2]'::cube AS cube; + ^ +DETAIL: syntax error at or near "2" +SELECT '[(1),(2),(3)]'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1),(2),(3)]'::cube AS cube; + ^ +DETAIL: syntax error at or near "," +SELECT '1,'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '1,'::cube AS cube; + ^ +DETAIL: syntax error at end of input +SELECT '1,2,'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '1,2,'::cube AS cube; + ^ +DETAIL: syntax error at end of input +SELECT '1,,2'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '1,,2'::cube AS cube; + ^ +DETAIL: syntax error at or near "," +SELECT '(1,)'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,)'::cube AS cube; + ^ +DETAIL: syntax error at or near ")" +SELECT '(1,2,)'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2,)'::cube AS cube; + ^ +DETAIL: syntax error at or near ")" +SELECT '(1,,2)'::cube AS cube; +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,,2)'::cube AS cube; + ^ +DETAIL: syntax error at or near "," +-- invalid input: semantic errors and trailing garbage +SELECT '[(1),(2)],'::cube AS cube; -- 0 +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1),(2)],'::cube AS cube; + ^ +DETAIL: syntax error at or near "," +SELECT '[(1,2,3),(2,3)]'::cube AS cube; -- 1 +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1,2,3),(2,3)]'::cube AS cube; + ^ +DETAIL: Different point dimensions in (1,2,3) and (2,3). +SELECT '[(1,2),(1,2,3)]'::cube AS cube; -- 1 +ERROR: invalid input syntax for cube +LINE 1: SELECT '[(1,2),(1,2,3)]'::cube AS cube; + ^ +DETAIL: Different point dimensions in (1,2) and (1,2,3). +SELECT '(1),(2),'::cube AS cube; -- 2 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1),(2),'::cube AS cube; + ^ +DETAIL: syntax error at or near "," +SELECT '(1,2,3),(2,3)'::cube AS cube; -- 3 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2,3),(2,3)'::cube AS cube; + ^ +DETAIL: Different point dimensions in (1,2,3) and (2,3). +SELECT '(1,2),(1,2,3)'::cube AS cube; -- 3 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2),(1,2,3)'::cube AS cube; + ^ +DETAIL: Different point dimensions in (1,2) and (1,2,3). +SELECT '(1,2,3)ab'::cube AS cube; -- 4 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2,3)ab'::cube AS cube; + ^ +DETAIL: syntax error at or near "a" +SELECT '(1,2,3)a'::cube AS cube; -- 5 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2,3)a'::cube AS cube; + ^ +DETAIL: syntax error at or near "a" +SELECT '(1,2)('::cube AS cube; -- 5 +ERROR: invalid input syntax for cube +LINE 1: SELECT '(1,2)('::cube AS cube; + ^ +DETAIL: syntax error at or near "(" +SELECT '1,2ab'::cube AS cube; -- 6 +ERROR: invalid input syntax for cube +LINE 1: SELECT '1,2ab'::cube AS cube; + ^ +DETAIL: syntax error at or near "a" +SELECT '1 e7'::cube AS cube; -- 6 +ERROR: invalid input syntax for cube +LINE 1: SELECT '1 e7'::cube AS cube; + ^ +DETAIL: syntax error at or near "e" +SELECT '1,2a'::cube AS cube; -- 7 +ERROR: invalid input syntax for cube +LINE 1: SELECT '1,2a'::cube AS cube; + ^ +DETAIL: syntax error at or near "a" +SELECT '1..2'::cube AS cube; -- 7 +ERROR: invalid input syntax for cube +LINE 1: SELECT '1..2'::cube AS cube; + ^ +DETAIL: syntax error at or near ".2" +SELECT '-1e-700'::cube AS cube; -- out of range +ERROR: "-1e-700" is out of range for type double precision +LINE 1: SELECT '-1e-700'::cube AS cube; + ^ +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('(1,2)', 'cube'); + pg_input_is_valid +------------------- + t +(1 row) + +SELECT pg_input_is_valid('[(1),]', 'cube'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT pg_input_is_valid('-1e-700', 'cube'); + pg_input_is_valid +------------------- + f +(1 row) + +SELECT * FROM pg_input_error_info('-1e-700', 'cube'); + message | detail | hint | sql_error_code +-----------------------------------------------------+--------+------+---------------- + "-1e-700" is out of range for type double precision | | | 22003 +(1 row) + +-- +-- Testing building cubes from float8 values +-- +SELECT cube(0::float8); + cube +------ + (0) +(1 row) + +SELECT cube(1::float8); + cube +------ + (1) +(1 row) + +SELECT cube(1,2); + cube +--------- + (1),(2) +(1 row) + +SELECT cube(cube(1,2),3); + cube +--------------- + (1, 3),(2, 3) +(1 row) + +SELECT cube(cube(1,2),3,4); + cube +--------------- + (1, 3),(2, 4) +(1 row) + +SELECT cube(cube(cube(1,2),3,4),5); + cube +--------------------- + (1, 3, 5),(2, 4, 5) +(1 row) + +SELECT cube(cube(cube(1,2),3,4),5,6); + cube +--------------------- + (1, 3, 5),(2, 4, 6) +(1 row) + +-- +-- Test that the text -> cube cast was installed. +-- +SELECT '(0)'::text::cube; + cube +------ + (0) +(1 row) + +-- +-- Test the float[] -> cube cast +-- +SELECT cube('{0,1,2}'::float[], '{3,4,5}'::float[]); + cube +--------------------- + (0, 1, 2),(3, 4, 5) +(1 row) + +SELECT cube('{0,1,2}'::float[], '{3}'::float[]); +ERROR: UR and LL arrays must be of same length +SELECT cube(NULL::float[], '{3}'::float[]); + cube +------ + +(1 row) + +SELECT cube('{0,1,2}'::float[]); + cube +----------- + (0, 1, 2) +(1 row) + +SELECT cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[3,2,1,1]); + cube_subset +--------------------------- + (5, 3, 1, 1),(8, 7, 6, 6) +(1 row) + +SELECT cube_subset(cube('(1,3,5),(1,3,5)'), ARRAY[3,2,1,1]); + cube_subset +-------------- + (5, 3, 1, 1) +(1 row) + +SELECT cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[4,0]); +ERROR: Index out of bounds +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), ARRAY[4,0]); +ERROR: Index out of bounds +-- test for limits: this should pass +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_series(1,100))); + cube_subset +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + (6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6) +(1 row) + +-- and this should fail +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_series(1,101))); +ERROR: array is too long +DETAIL: A cube cannot have more than 100 dimensions. +-- +-- Test point processing +-- +SELECT cube('(1,2),(1,2)'); -- cube_in + cube +-------- + (1, 2) +(1 row) + +SELECT cube('{0,1,2}'::float[], '{0,1,2}'::float[]); -- cube_a_f8_f8 + cube +----------- + (0, 1, 2) +(1 row) + +SELECT cube('{5,6,7,8}'::float[]); -- cube_a_f8 + cube +-------------- + (5, 6, 7, 8) +(1 row) + +SELECT cube(1.37); -- cube_f8 + cube +-------- + (1.37) +(1 row) + +SELECT cube(1.37, 1.37); -- cube_f8_f8 + cube +-------- + (1.37) +(1 row) + +SELECT cube(cube(1,1), 42); -- cube_c_f8 + cube +--------- + (1, 42) +(1 row) + +SELECT cube(cube(1,2), 42); -- cube_c_f8 + cube +----------------- + (1, 42),(2, 42) +(1 row) + +SELECT cube(cube(1,1), 42, 42); -- cube_c_f8_f8 + cube +--------- + (1, 42) +(1 row) + +SELECT cube(cube(1,1), 42, 24); -- cube_c_f8_f8 + cube +----------------- + (1, 42),(1, 24) +(1 row) + +SELECT cube(cube(1,2), 42, 42); -- cube_c_f8_f8 + cube +----------------- + (1, 42),(2, 42) +(1 row) + +SELECT cube(cube(1,2), 42, 24); -- cube_c_f8_f8 + cube +----------------- + (1, 42),(2, 24) +(1 row) + +-- +-- Testing limit of CUBE_MAX_DIM dimensions check in cube_in. +-- +-- create too big cube from literal +select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)'::cube; +ERROR: invalid input syntax for cube +LINE 1: select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0... + ^ +DETAIL: A cube cannot have more than 100 dimensions. +select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)'::cube; +ERROR: invalid input syntax for cube +LINE 1: select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0... + ^ +DETAIL: A cube cannot have more than 100 dimensions. +-- from an array +select cube(array(SELECT 0 as a FROM generate_series(1,101))); +ERROR: array is too long +DETAIL: A cube cannot have more than 100 dimensions. +select cube(array(SELECT 0 as a FROM generate_series(1,101)),array(SELECT 0 as a FROM generate_series(1,101))); +ERROR: can't extend cube +DETAIL: A cube cannot have more than 100 dimensions. +-- extend cube beyond limit +-- this should work +select cube(array(SELECT 0 as a FROM generate_series(1,100))); + cube +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +(1 row) + +select cube(array(SELECT 0 as a FROM generate_series(1,100)),array(SELECT 0 as a FROM generate_series(1,100))); + cube +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +(1 row) + +-- this should fail +select cube(cube(array(SELECT 0 as a FROM generate_series(1,100))), 0); +ERROR: can't extend cube +DETAIL: A cube cannot have more than 100 dimensions. +select cube(cube(array(SELECT 0 as a FROM generate_series(1,100)),array(SELECT 0 as a FROM generate_series(1,100))), 0, 0); +ERROR: can't extend cube +DETAIL: A cube cannot have more than 100 dimensions. +-- +-- testing the operators +-- +-- equality/inequality: +-- +SELECT '24, 33.20'::cube = '24, 33.20'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '24, 33.20'::cube != '24, 33.20'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '24, 33.20'::cube = '24, 33.21'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '24, 33.20'::cube != '24, 33.21'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0),(3,1)'::cube = '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0),(3,1)'::cube = '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; + bool +------ + f +(1 row) + +-- "lower than" / "greater than" +-- (these operators are not useful for anything but ordering) +-- +SELECT '1'::cube > '2'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '1'::cube < '2'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '1,1'::cube > '1,2'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '1,1'::cube < '1,2'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,1),(3,1,0,0,0)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,1),(3,1,0,0,0)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0,0,0,0),(3,1,0,0,1)'::cube > '(2,0),(3,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0,0,0,0),(3,1,0,0,1)'::cube < '(2,0),(3,1)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; + bool +------ + f +(1 row) + +-- "overlap" +-- +SELECT '1'::cube && '1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '1'::cube && '2'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '1,1,1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(1,1,1),(2,2,2)]'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(1,1),(2,2)]'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(2,1,1),(2,2,2)]'::cube AS bool; + bool +------ + f +(1 row) + +-- "contained in" (the left operand is the cube entirely enclosed by +-- the right operand): +-- +SELECT '0'::cube <@ '0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0,0'::cube <@ '0,0,0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0'::cube <@ '0,0,1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0,0'::cube <@ '0,0,1'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '1,0,0'::cube <@ '0,0,1'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(1,0,0),(0,0,1)'::cube <@ '(1,0,0),(0,0,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(1,0,0),(0,0,1)'::cube <@ '(-1,-1,-1),(1,1,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(1,0,0),(0,0,1)'::cube <@ '(-1,-1,-1,-1),(1,1,1,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0'::cube <@ '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '1'::cube <@ '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '-1'::cube <@ '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube <@ '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube <@ '(-1,-1),(1,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-2),(1)'::cube <@ '(-1),(1)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(-2),(1)'::cube <@ '(-1,-1),(1,1)'::cube AS bool; + bool +------ + f +(1 row) + +-- "contains" (the left operand is the cube that entirely encloses the +-- right operand) +-- +SELECT '0'::cube @> '0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0,0'::cube @> '0,0,0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0,1'::cube @> '0,0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '0,0,1'::cube @> '0,0,0'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '0,0,1'::cube @> '1,0,0'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(1,0,0),(0,0,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1,-1,-1),(1,1,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1,-1,-1,-1),(1,1,1,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube @> '0'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube @> '1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube @> '-1'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube @> '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1,-1),(1,1)'::cube @> '(-1),(1)'::cube AS bool; + bool +------ + t +(1 row) + +SELECT '(-1),(1)'::cube @> '(-2),(1)'::cube AS bool; + bool +------ + f +(1 row) + +SELECT '(-1,-1),(1,1)'::cube @> '(-2),(1)'::cube AS bool; + bool +------ + f +(1 row) + +-- Test of distance function +-- +SELECT cube_distance('(0)'::cube,'(2,2,2,2)'::cube); + cube_distance +--------------- + 4 +(1 row) + +SELECT cube_distance('(0)'::cube,'(.3,.4)'::cube); + cube_distance +--------------- + 0.5 +(1 row) + +SELECT cube_distance('(2,3,4)'::cube,'(2,3,4)'::cube); + cube_distance +--------------- + 0 +(1 row) + +SELECT cube_distance('(42,42,42,42)'::cube,'(137,137,137,137)'::cube); + cube_distance +--------------- + 190 +(1 row) + +SELECT cube_distance('(42,42,42)'::cube,'(137,137)'::cube); + cube_distance +-------------------- + 140.76221083799445 +(1 row) + +-- Test of cube function (text to cube) +-- +SELECT cube('(1,1.2)'::text); + cube +---------- + (1, 1.2) +(1 row) + +SELECT cube(NULL); + cube +------ + +(1 row) + +-- Test of cube_dim function (dimensions stored in cube) +-- +SELECT cube_dim('(0)'::cube); + cube_dim +---------- + 1 +(1 row) + +SELECT cube_dim('(0,0)'::cube); + cube_dim +---------- + 2 +(1 row) + +SELECT cube_dim('(0,0,0)'::cube); + cube_dim +---------- + 3 +(1 row) + +SELECT cube_dim('(42,42,42),(42,42,42)'::cube); + cube_dim +---------- + 3 +(1 row) + +SELECT cube_dim('(4,8,15,16,23),(4,8,15,16,23)'::cube); + cube_dim +---------- + 5 +(1 row) + +-- Test of cube_ll_coord function (retrieves LL coordinate values) +-- +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 1); + cube_ll_coord +--------------- + -1 +(1 row) + +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 2); + cube_ll_coord +--------------- + -2 +(1 row) + +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 3); + cube_ll_coord +--------------- + 0 +(1 row) + +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 1); + cube_ll_coord +--------------- + 1 +(1 row) + +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 2); + cube_ll_coord +--------------- + 2 +(1 row) + +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 3); + cube_ll_coord +--------------- + 0 +(1 row) + +SELECT cube_ll_coord('(42,137)'::cube, 1); + cube_ll_coord +--------------- + 42 +(1 row) + +SELECT cube_ll_coord('(42,137)'::cube, 2); + cube_ll_coord +--------------- + 137 +(1 row) + +SELECT cube_ll_coord('(42,137)'::cube, 3); + cube_ll_coord +--------------- + 0 +(1 row) + +-- Test of cube_ur_coord function (retrieves UR coordinate values) +-- +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 1); + cube_ur_coord +--------------- + 2 +(1 row) + +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 2); + cube_ur_coord +--------------- + 1 +(1 row) + +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 3); + cube_ur_coord +--------------- + 0 +(1 row) + +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 1); + cube_ur_coord +--------------- + 1 +(1 row) + +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 2); + cube_ur_coord +--------------- + 2 +(1 row) + +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 3); + cube_ur_coord +--------------- + 0 +(1 row) + +SELECT cube_ur_coord('(42,137)'::cube, 1); + cube_ur_coord +--------------- + 42 +(1 row) + +SELECT cube_ur_coord('(42,137)'::cube, 2); + cube_ur_coord +--------------- + 137 +(1 row) + +SELECT cube_ur_coord('(42,137)'::cube, 3); + cube_ur_coord +--------------- + 0 +(1 row) + +-- Test of cube_is_point +-- +SELECT cube_is_point('(0)'::cube); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_is_point('(0,1,2)'::cube); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_is_point('(0,1,2),(0,1,2)'::cube); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_is_point('(0,1,2),(-1,1,2)'::cube); + cube_is_point +--------------- + f +(1 row) + +SELECT cube_is_point('(0,1,2),(0,-1,2)'::cube); + cube_is_point +--------------- + f +(1 row) + +SELECT cube_is_point('(0,1,2),(0,1,-2)'::cube); + cube_is_point +--------------- + f +(1 row) + +-- Test of cube_enlarge (enlarging and shrinking cubes) +-- +SELECT cube_enlarge('(0)'::cube, 0, 0); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(0)'::cube, 0, 1); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(0)'::cube, 0, 2); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(2),(-2)'::cube, 0, 4); + cube_enlarge +-------------- + (-2),(2) +(1 row) + +SELECT cube_enlarge('(0)'::cube, 1, 0); + cube_enlarge +-------------- + (-1),(1) +(1 row) + +SELECT cube_enlarge('(0)'::cube, 1, 1); + cube_enlarge +-------------- + (-1),(1) +(1 row) + +SELECT cube_enlarge('(0)'::cube, 1, 2); + cube_enlarge +----------------- + (-1, -1),(1, 1) +(1 row) + +SELECT cube_enlarge('(2),(-2)'::cube, 1, 4); + cube_enlarge +------------------------------- + (-3, -1, -1, -1),(3, 1, 1, 1) +(1 row) + +SELECT cube_enlarge('(0)'::cube, -1, 0); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(0)'::cube, -1, 1); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(0)'::cube, -1, 2); + cube_enlarge +-------------- + (0) +(1 row) + +SELECT cube_enlarge('(2),(-2)'::cube, -1, 4); + cube_enlarge +-------------- + (-1),(1) +(1 row) + +SELECT cube_enlarge('(0,0,0)'::cube, 1, 0); + cube_enlarge +------------------------ + (-1, -1, -1),(1, 1, 1) +(1 row) + +SELECT cube_enlarge('(0,0,0)'::cube, 1, 2); + cube_enlarge +------------------------ + (-1, -1, -1),(1, 1, 1) +(1 row) + +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, 1, 2); + cube_enlarge +----------------- + (-4, -3),(3, 8) +(1 row) + +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, 3, 2); + cube_enlarge +------------------ + (-6, -5),(5, 10) +(1 row) + +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, -1, 2); + cube_enlarge +----------------- + (-2, -1),(1, 6) +(1 row) + +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, -3, 2); + cube_enlarge +--------------------- + (-0.5, 1),(-0.5, 4) +(1 row) + +SELECT cube_enlarge('(42,-23,-23),(42,23,23)'::cube, -23, 5); + cube_enlarge +-------------- + (42, 0, 0) +(1 row) + +SELECT cube_enlarge('(42,-23,-23),(42,23,23)'::cube, -24, 5); + cube_enlarge +-------------- + (42, 0, 0) +(1 row) + +-- Test of cube_union (MBR for two cubes) +-- +SELECT cube_union('(1,2),(3,4)'::cube, '(5,6,7),(8,9,10)'::cube); + cube_union +---------------------- + (1, 2, 0),(8, 9, 10) +(1 row) + +SELECT cube_union('(1,2)'::cube, '(4,2,0,0)'::cube); + cube_union +--------------------------- + (1, 2, 0, 0),(4, 2, 0, 0) +(1 row) + +SELECT cube_union('(1,2),(1,2)'::cube, '(4,2),(4,2)'::cube); + cube_union +--------------- + (1, 2),(4, 2) +(1 row) + +SELECT cube_union('(1,2),(1,2)'::cube, '(1,2),(1,2)'::cube); + cube_union +------------ + (1, 2) +(1 row) + +SELECT cube_union('(1,2),(1,2)'::cube, '(1,2,0),(1,2,0)'::cube); + cube_union +------------ + (1, 2, 0) +(1 row) + +-- Test of cube_inter +-- +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,4), (16,15)'::cube); -- intersects + cube_inter +----------------- + (3, 4),(10, 11) +(1 row) + +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,4), (6,5)'::cube); -- includes + cube_inter +--------------- + (3, 4),(6, 5) +(1 row) + +SELECT cube_inter('(1,2),(10,11)'::cube, '(13,14), (16,15)'::cube); -- no intersection + cube_inter +------------------- + (13, 14),(10, 11) +(1 row) + +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,14), (16,15)'::cube); -- no intersection, but one dimension intersects + cube_inter +------------------ + (3, 14),(10, 11) +(1 row) + +SELECT cube_inter('(1,2),(10,11)'::cube, '(10,11), (16,15)'::cube); -- point intersection + cube_inter +------------ + (10, 11) +(1 row) + +SELECT cube_inter('(1,2,3)'::cube, '(1,2,3)'::cube); -- point args + cube_inter +------------ + (1, 2, 3) +(1 row) + +SELECT cube_inter('(1,2,3)'::cube, '(5,6,3)'::cube); -- point args + cube_inter +--------------------- + (5, 6, 3),(1, 2, 3) +(1 row) + +-- Test of cube_size +-- +SELECT cube_size('(4,8),(15,16)'::cube); + cube_size +----------- + 88 +(1 row) + +SELECT cube_size('(42,137)'::cube); + cube_size +----------- + 0 +(1 row) + +-- Test of distances (euclidean distance may not be bit-exact) +-- +SET extra_float_digits = 0; +SELECT cube_distance('(1,1)'::cube, '(4,5)'::cube); + cube_distance +--------------- + 5 +(1 row) + +SELECT '(1,1)'::cube <-> '(4,5)'::cube as d_e; + d_e +----- + 5 +(1 row) + +RESET extra_float_digits; +SELECT distance_chebyshev('(1,1)'::cube, '(4,5)'::cube); + distance_chebyshev +-------------------- + 4 +(1 row) + +SELECT '(1,1)'::cube <=> '(4,5)'::cube as d_c; + d_c +----- + 4 +(1 row) + +SELECT distance_taxicab('(1,1)'::cube, '(4,5)'::cube); + distance_taxicab +------------------ + 7 +(1 row) + +SELECT '(1,1)'::cube <#> '(4,5)'::cube as d_t; + d_t +----- + 7 +(1 row) + +-- zero for overlapping +SELECT cube_distance('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); + cube_distance +--------------- + 0 +(1 row) + +SELECT distance_chebyshev('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); + distance_chebyshev +-------------------- + 0 +(1 row) + +SELECT distance_taxicab('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); + distance_taxicab +------------------ + 0 +(1 row) + +-- coordinate access +SELECT cube(array[10,20,30], array[40,50,60])->1; + ?column? +---------- + 10 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])->1; + ?column? +---------- + 40 +(1 row) + +SELECT cube(array[10,20,30], array[40,50,60])->6; + ?column? +---------- + 60 +(1 row) + +SELECT cube(array[10,20,30], array[40,50,60])->0; +ERROR: cube index 0 is out of bounds +SELECT cube(array[10,20,30], array[40,50,60])->7; +ERROR: cube index 7 is out of bounds +SELECT cube(array[10,20,30], array[40,50,60])->-1; +ERROR: cube index -1 is out of bounds +SELECT cube(array[10,20,30], array[40,50,60])->-6; +ERROR: cube index -6 is out of bounds +SELECT cube(array[10,20,30])->3; + ?column? +---------- + 30 +(1 row) + +SELECT cube(array[10,20,30])->6; + ?column? +---------- + 30 +(1 row) + +SELECT cube(array[10,20,30])->-6; +ERROR: cube index -6 is out of bounds +-- "normalized" coordinate access +SELECT cube(array[10,20,30], array[40,50,60])~>1; + ?column? +---------- + 10 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])~>1; + ?column? +---------- + 10 +(1 row) + +SELECT cube(array[10,20,30], array[40,50,60])~>2; + ?column? +---------- + 40 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])~>2; + ?column? +---------- + 40 +(1 row) + +SELECT cube(array[10,20,30], array[40,50,60])~>3; + ?column? +---------- + 20 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])~>3; + ?column? +---------- + 20 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])~>0; +ERROR: zero cube index is not defined +SELECT cube(array[40,50,60], array[10,20,30])~>4; + ?column? +---------- + 50 +(1 row) + +SELECT cube(array[40,50,60], array[10,20,30])~>(-1); + ?column? +---------- + -10 +(1 row) + +-- Load some example data and build the index +-- +CREATE TABLE test_cube (c cube); +\copy test_cube from 'data/test_cube.data' +CREATE INDEX test_cube_ix ON test_cube USING gist (c); +SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' ORDER BY c; + c +-------------------------- + (337, 455),(240, 359) + (759, 187),(662, 163) + (1444, 403),(1346, 344) + (1594, 1043),(1517, 971) + (2424, 160),(2424, 81) +(5 rows) + +-- Test sorting +SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c; + c +-------------------------- + (337, 455),(240, 359) + (759, 187),(662, 163) + (1444, 403),(1346, 344) + (1594, 1043),(1517, 971) + (2424, 160),(2424, 81) +(5 rows) + +-- Test index-only scans +SET enable_bitmapscan = false; +EXPLAIN (COSTS OFF) +SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c; + QUERY PLAN +-------------------------------------------------------- + Sort + Sort Key: c + -> Index Only Scan using test_cube_ix on test_cube + Index Cond: (c <@ '(3000, 1000),(0, 0)'::cube) +(4 rows) + +SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c; + c +------------------------- + (337, 455),(240, 359) + (759, 187),(662, 163) + (1444, 403),(1346, 344) + (2424, 160),(2424, 81) +(4 rows) + +RESET enable_bitmapscan; +-- Test kNN +INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases +SET enable_seqscan = false; +-- Test different metrics +SET extra_float_digits = 0; +SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------------------ + (337, 455),(240, 359) | 0 + (1, 1) | 140.007142674936 + (759, 187),(662, 163) | 162 + (948, 1201),(907, 1156) | 772.000647668122 + (1444, 403),(1346, 344) | 846 +(5 rows) + +RESET extra_float_digits; +SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------ + (337, 455),(240, 359) | 0 + (1, 1) | 99 + (759, 187),(662, 163) | 162 + (948, 1201),(907, 1156) | 656 + (1444, 403),(1346, 344) | 846 +(5 rows) + +SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------ + (337, 455),(240, 359) | 0 + (759, 187),(662, 163) | 162 + (1, 1) | 198 + (1444, 403),(1346, 344) | 846 + (369, 1457),(278, 1409) | 909 +(5 rows) + +-- Test sorting by coordinates +SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound + ?column? | c +----------+--------------------------- + 0 | (0, 100000) + 1 | (1, 1) + 3 | (54, 38679),(3, 38602) + 15 | (83, 10271),(15, 10265) + 64 | (122, 46832),(64, 46762) + 92 | (167, 17214),(92, 17184) + 107 | (161, 24465),(107, 24374) + 120 | (162, 26040),(120, 25963) + 138 | (154, 4019),(138, 3990) + 175 | (259, 1850),(175, 1820) + 179 | (207, 40886),(179, 40879) + 204 | (288, 49588),(204, 49571) + 226 | (270, 32616),(226, 32607) + 235 | (318, 31489),(235, 31404) + 240 | (337, 455),(240, 359) +(15 rows) + +SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound + ?column? | c +----------+--------------------------- + 0 | (0, 100000) + 1 | (1, 1) + 54 | (54, 38679),(3, 38602) + 83 | (83, 10271),(15, 10265) + 122 | (122, 46832),(64, 46762) + 154 | (154, 4019),(138, 3990) + 161 | (161, 24465),(107, 24374) + 162 | (162, 26040),(120, 25963) + 167 | (167, 17214),(92, 17184) + 207 | (207, 40886),(179, 40879) + 259 | (259, 1850),(175, 1820) + 270 | (270, 29508),(264, 29440) + 270 | (270, 32616),(226, 32607) + 288 | (288, 49588),(204, 49571) + 318 | (318, 31489),(235, 31404) +(15 rows) + +SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound + ?column? | c +----------+--------------------------- + 0 | (100000) + 1 | (1, 1) + 6 | (30333, 50),(30273, 6) + 43 | (43301, 75),(43227, 43) + 51 | (19650, 142),(19630, 51) + 81 | (2424, 160),(2424, 81) + 108 | (3449, 171),(3354, 108) + 109 | (18037, 155),(17941, 109) + 114 | (28511, 208),(28479, 114) + 118 | (19946, 217),(19941, 118) + 139 | (16906, 191),(16816, 139) + 163 | (759, 187),(662, 163) + 181 | (22684, 266),(22656, 181) + 213 | (24423, 255),(24360, 213) + 222 | (45989, 249),(45910, 222) +(15 rows) + +SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound + ?column? | c +----------+--------------------------- + 0 | (100000) + 1 | (1, 1) + 50 | (30333, 50),(30273, 6) + 75 | (43301, 75),(43227, 43) + 142 | (19650, 142),(19630, 51) + 155 | (18037, 155),(17941, 109) + 160 | (2424, 160),(2424, 81) + 171 | (3449, 171),(3354, 108) + 187 | (759, 187),(662, 163) + 191 | (16906, 191),(16816, 139) + 208 | (28511, 208),(28479, 114) + 217 | (19946, 217),(19941, 118) + 249 | (45989, 249),(45910, 222) + 255 | (24423, 255),(24360, 213) + 266 | (22684, 266),(22656, 181) +(15 rows) + +SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound + ?column? | c +----------+------------------------------- + -100000 | (100000) + -49951 | (50027, 49230),(49951, 49214) + -49937 | (49980, 35004),(49937, 34963) + -49927 | (49985, 6436),(49927, 6338) + -49908 | (49999, 27218),(49908, 27176) + -49905 | (49954, 1340),(49905, 1294) + -49902 | (49944, 25163),(49902, 25153) + -49898 | (49981, 34876),(49898, 34786) + -49897 | (49957, 43390),(49897, 43384) + -49848 | (49853, 18504),(49848, 18503) + -49818 | (49902, 41752),(49818, 41746) + -49810 | (49907, 30225),(49810, 30158) + -49808 | (49843, 5175),(49808, 5145) + -49805 | (49887, 24274),(49805, 24184) + -49798 | (49847, 7128),(49798, 7067) +(15 rows) + +SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound + ?column? | c +----------+------------------------------- + -100000 | (100000) + -50027 | (50027, 49230),(49951, 49214) + -49999 | (49999, 27218),(49908, 27176) + -49985 | (49985, 6436),(49927, 6338) + -49981 | (49981, 34876),(49898, 34786) + -49980 | (49980, 35004),(49937, 34963) + -49957 | (49957, 43390),(49897, 43384) + -49954 | (49954, 1340),(49905, 1294) + -49944 | (49944, 25163),(49902, 25153) + -49907 | (49907, 30225),(49810, 30158) + -49902 | (49902, 41752),(49818, 41746) + -49887 | (49887, 24274),(49805, 24184) + -49853 | (49853, 18504),(49848, 18503) + -49847 | (49847, 7128),(49798, 7067) + -49843 | (49843, 5175),(49808, 5145) +(15 rows) + +SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound + ?column? | c +----------+------------------------------- + -100000 | (0, 100000) + -49992 | (30746, 50040),(30727, 49992) + -49987 | (36311, 50073),(36258, 49987) + -49934 | (3531, 49962),(3463, 49934) + -49915 | (17954, 49975),(17865, 49915) + -49914 | (2168, 50012),(2108, 49914) + -49913 | (31287, 49923),(31236, 49913) + -49885 | (21551, 49983),(21492, 49885) + -49878 | (43925, 49912),(43888, 49878) + -49849 | (19128, 49932),(19112, 49849) + -49844 | (38266, 49852),(38233, 49844) + -49836 | (14913, 49873),(14849, 49836) + -49834 | (37595, 49849),(37581, 49834) + -49830 | (46151, 49848),(46058, 49830) + -49818 | (29261, 49910),(29247, 49818) +(15 rows) + +SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound + ?column? | c +----------+------------------------------- + -100000 | (0, 100000) + -50073 | (36311, 50073),(36258, 49987) + -50040 | (30746, 50040),(30727, 49992) + -50012 | (2168, 50012),(2108, 49914) + -49983 | (21551, 49983),(21492, 49885) + -49975 | (17954, 49975),(17865, 49915) + -49962 | (3531, 49962),(3463, 49934) + -49932 | (19128, 49932),(19112, 49849) + -49923 | (31287, 49923),(31236, 49913) + -49912 | (43925, 49912),(43888, 49878) + -49910 | (29261, 49910),(29247, 49818) + -49873 | (14913, 49873),(14849, 49836) + -49858 | (20007, 49858),(19921, 49778) + -49852 | (38266, 49852),(38233, 49844) + -49849 | (37595, 49849),(37581, 49834) +(15 rows) + +-- Same queries with sequential scan (should give the same results as above) +RESET enable_seqscan; +SET enable_indexscan = OFF; +SET extra_float_digits = 0; +SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------------------ + (337, 455),(240, 359) | 0 + (1, 1) | 140.007142674936 + (759, 187),(662, 163) | 162 + (948, 1201),(907, 1156) | 772.000647668122 + (1444, 403),(1346, 344) | 846 +(5 rows) + +RESET extra_float_digits; +SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------ + (337, 455),(240, 359) | 0 + (1, 1) | 99 + (759, 187),(662, 163) | 162 + (948, 1201),(907, 1156) | 656 + (1444, 403),(1346, 344) | 846 +(5 rows) + +SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5; + c | dist +-------------------------+------ + (337, 455),(240, 359) | 0 + (759, 187),(662, 163) | 162 + (1, 1) | 198 + (1444, 403),(1346, 344) | 846 + (369, 1457),(278, 1409) | 909 +(5 rows) + +SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound + ?column? | c +----------+--------------------------- + 0 | (0, 100000) + 1 | (1, 1) + 3 | (54, 38679),(3, 38602) + 15 | (83, 10271),(15, 10265) + 64 | (122, 46832),(64, 46762) + 92 | (167, 17214),(92, 17184) + 107 | (161, 24465),(107, 24374) + 120 | (162, 26040),(120, 25963) + 138 | (154, 4019),(138, 3990) + 175 | (259, 1850),(175, 1820) + 179 | (207, 40886),(179, 40879) + 204 | (288, 49588),(204, 49571) + 226 | (270, 32616),(226, 32607) + 235 | (318, 31489),(235, 31404) + 240 | (337, 455),(240, 359) +(15 rows) + +SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound + ?column? | c +----------+--------------------------- + 0 | (0, 100000) + 1 | (1, 1) + 54 | (54, 38679),(3, 38602) + 83 | (83, 10271),(15, 10265) + 122 | (122, 46832),(64, 46762) + 154 | (154, 4019),(138, 3990) + 161 | (161, 24465),(107, 24374) + 162 | (162, 26040),(120, 25963) + 167 | (167, 17214),(92, 17184) + 207 | (207, 40886),(179, 40879) + 259 | (259, 1850),(175, 1820) + 270 | (270, 29508),(264, 29440) + 270 | (270, 32616),(226, 32607) + 288 | (288, 49588),(204, 49571) + 318 | (318, 31489),(235, 31404) +(15 rows) + +SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound + ?column? | c +----------+--------------------------- + 0 | (100000) + 1 | (1, 1) + 6 | (30333, 50),(30273, 6) + 43 | (43301, 75),(43227, 43) + 51 | (19650, 142),(19630, 51) + 81 | (2424, 160),(2424, 81) + 108 | (3449, 171),(3354, 108) + 109 | (18037, 155),(17941, 109) + 114 | (28511, 208),(28479, 114) + 118 | (19946, 217),(19941, 118) + 139 | (16906, 191),(16816, 139) + 163 | (759, 187),(662, 163) + 181 | (22684, 266),(22656, 181) + 213 | (24423, 255),(24360, 213) + 222 | (45989, 249),(45910, 222) +(15 rows) + +SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound + ?column? | c +----------+--------------------------- + 0 | (100000) + 1 | (1, 1) + 50 | (30333, 50),(30273, 6) + 75 | (43301, 75),(43227, 43) + 142 | (19650, 142),(19630, 51) + 155 | (18037, 155),(17941, 109) + 160 | (2424, 160),(2424, 81) + 171 | (3449, 171),(3354, 108) + 187 | (759, 187),(662, 163) + 191 | (16906, 191),(16816, 139) + 208 | (28511, 208),(28479, 114) + 217 | (19946, 217),(19941, 118) + 249 | (45989, 249),(45910, 222) + 255 | (24423, 255),(24360, 213) + 266 | (22684, 266),(22656, 181) +(15 rows) + +SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound + ?column? | c +----------+------------------------------- + -100000 | (100000) + -49951 | (50027, 49230),(49951, 49214) + -49937 | (49980, 35004),(49937, 34963) + -49927 | (49985, 6436),(49927, 6338) + -49908 | (49999, 27218),(49908, 27176) + -49905 | (49954, 1340),(49905, 1294) + -49902 | (49944, 25163),(49902, 25153) + -49898 | (49981, 34876),(49898, 34786) + -49897 | (49957, 43390),(49897, 43384) + -49848 | (49853, 18504),(49848, 18503) + -49818 | (49902, 41752),(49818, 41746) + -49810 | (49907, 30225),(49810, 30158) + -49808 | (49843, 5175),(49808, 5145) + -49805 | (49887, 24274),(49805, 24184) + -49798 | (49847, 7128),(49798, 7067) +(15 rows) + +SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound + ?column? | c +----------+------------------------------- + -100000 | (100000) + -50027 | (50027, 49230),(49951, 49214) + -49999 | (49999, 27218),(49908, 27176) + -49985 | (49985, 6436),(49927, 6338) + -49981 | (49981, 34876),(49898, 34786) + -49980 | (49980, 35004),(49937, 34963) + -49957 | (49957, 43390),(49897, 43384) + -49954 | (49954, 1340),(49905, 1294) + -49944 | (49944, 25163),(49902, 25153) + -49907 | (49907, 30225),(49810, 30158) + -49902 | (49902, 41752),(49818, 41746) + -49887 | (49887, 24274),(49805, 24184) + -49853 | (49853, 18504),(49848, 18503) + -49847 | (49847, 7128),(49798, 7067) + -49843 | (49843, 5175),(49808, 5145) +(15 rows) + +SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound + ?column? | c +----------+------------------------------- + -100000 | (0, 100000) + -49992 | (30746, 50040),(30727, 49992) + -49987 | (36311, 50073),(36258, 49987) + -49934 | (3531, 49962),(3463, 49934) + -49915 | (17954, 49975),(17865, 49915) + -49914 | (2168, 50012),(2108, 49914) + -49913 | (31287, 49923),(31236, 49913) + -49885 | (21551, 49983),(21492, 49885) + -49878 | (43925, 49912),(43888, 49878) + -49849 | (19128, 49932),(19112, 49849) + -49844 | (38266, 49852),(38233, 49844) + -49836 | (14913, 49873),(14849, 49836) + -49834 | (37595, 49849),(37581, 49834) + -49830 | (46151, 49848),(46058, 49830) + -49818 | (29261, 49910),(29247, 49818) +(15 rows) + +SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound + ?column? | c +----------+------------------------------- + -100000 | (0, 100000) + -50073 | (36311, 50073),(36258, 49987) + -50040 | (30746, 50040),(30727, 49992) + -50012 | (2168, 50012),(2108, 49914) + -49983 | (21551, 49983),(21492, 49885) + -49975 | (17954, 49975),(17865, 49915) + -49962 | (3531, 49962),(3463, 49934) + -49932 | (19128, 49932),(19112, 49849) + -49923 | (31287, 49923),(31236, 49913) + -49912 | (43925, 49912),(43888, 49878) + -49910 | (29261, 49910),(29247, 49818) + -49873 | (14913, 49873),(14849, 49836) + -49858 | (20007, 49858),(19921, 49778) + -49852 | (38266, 49852),(38233, 49844) + -49849 | (37595, 49849),(37581, 49834) +(15 rows) + +RESET enable_indexscan; diff --git a/contrib/cube/expected/cube_sci.out b/contrib/cube/expected/cube_sci.out new file mode 100644 index 0000000..488499a --- /dev/null +++ b/contrib/cube/expected/cube_sci.out @@ -0,0 +1,106 @@ +--- +--- Testing cube output in scientific notation. This was put into separate +--- test, because has platform-depending output. +--- +SELECT '1e27'::cube AS cube; + cube +--------- + (1e+27) +(1 row) + +SELECT '-1e27'::cube AS cube; + cube +---------- + (-1e+27) +(1 row) + +SELECT '1.0e27'::cube AS cube; + cube +--------- + (1e+27) +(1 row) + +SELECT '-1.0e27'::cube AS cube; + cube +---------- + (-1e+27) +(1 row) + +SELECT '1e+27'::cube AS cube; + cube +--------- + (1e+27) +(1 row) + +SELECT '-1e+27'::cube AS cube; + cube +---------- + (-1e+27) +(1 row) + +SELECT '1.0e+27'::cube AS cube; + cube +--------- + (1e+27) +(1 row) + +SELECT '-1.0e+27'::cube AS cube; + cube +---------- + (-1e+27) +(1 row) + +SELECT '1e-7'::cube AS cube; + cube +--------- + (1e-07) +(1 row) + +SELECT '-1e-7'::cube AS cube; + cube +---------- + (-1e-07) +(1 row) + +SELECT '1.0e-7'::cube AS cube; + cube +--------- + (1e-07) +(1 row) + +SELECT '-1.0e-7'::cube AS cube; + cube +---------- + (-1e-07) +(1 row) + +SELECT '1e-300'::cube AS cube; + cube +---------- + (1e-300) +(1 row) + +SELECT '-1e-300'::cube AS cube; + cube +----------- + (-1e-300) +(1 row) + +SELECT '1234567890123456'::cube AS cube; + cube +------------------------- + (1.234567890123456e+15) +(1 row) + +SELECT '+1234567890123456'::cube AS cube; + cube +------------------------- + (1.234567890123456e+15) +(1 row) + +SELECT '-1234567890123456'::cube AS cube; + cube +-------------------------- + (-1.234567890123456e+15) +(1 row) + diff --git a/contrib/cube/meson.build b/contrib/cube/meson.build new file mode 100644 index 0000000..80ad516 --- /dev/null +++ b/contrib/cube/meson.build @@ -0,0 +1,61 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +cube_sources = files( + 'cube.c', +) + +cube_scan = custom_target('cubescan', + input: 'cubescan.l', + output: 'cubescan.c', + command: flex_cmd, +) +generated_sources += cube_scan +cube_sources += cube_scan + +cube_parse = custom_target('cubeparse', + input: 'cubeparse.y', + kwargs: bison_kw, +) +generated_sources += cube_parse.to_list() +cube_sources += cube_parse + +if host_system == 'windows' + cube_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'cube', + '--FILEDESC', 'cube - multidimensional cube data type',]) +endif + +cube = shared_module('cube', + cube_sources, + include_directories: include_directories('.'), + kwargs: contrib_mod_args, +) +contrib_targets += cube + +install_data( + 'cube.control', + 'cube--1.0--1.1.sql', + 'cube--1.1--1.2.sql', + 'cube--1.2.sql', + 'cube--1.2--1.3.sql', + 'cube--1.3--1.4.sql', + 'cube--1.4--1.5.sql', + kwargs: contrib_data_args, +) + +install_headers( + 'cubedata.h', + install_dir: dir_include_extension / 'cube', +) + +tests += { + 'name': 'cube', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'cube', + 'cube_sci', + ], + }, +} diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql new file mode 100644 index 0000000..eec90d2 --- /dev/null +++ b/contrib/cube/sql/cube.sql @@ -0,0 +1,438 @@ +-- +-- Test cube datatype +-- + +CREATE EXTENSION cube; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +-- +-- testing the input and output functions +-- + +-- Any number (a one-dimensional point) +SELECT '1'::cube AS cube; +SELECT '-1'::cube AS cube; +SELECT '1.'::cube AS cube; +SELECT '-1.'::cube AS cube; +SELECT '.1'::cube AS cube; +SELECT '-.1'::cube AS cube; +SELECT '1.0'::cube AS cube; +SELECT '-1.0'::cube AS cube; +SELECT 'infinity'::cube AS cube; +SELECT '-infinity'::cube AS cube; +SELECT 'NaN'::cube AS cube; +SELECT '.1234567890123456'::cube AS cube; +SELECT '+.1234567890123456'::cube AS cube; +SELECT '-.1234567890123456'::cube AS cube; + +-- simple lists (points) +SELECT '()'::cube AS cube; +SELECT '1,2'::cube AS cube; +SELECT '(1,2)'::cube AS cube; +SELECT '1,2,3,4,5'::cube AS cube; +SELECT '(1,2,3,4,5)'::cube AS cube; + +-- double lists (cubes) +SELECT '(),()'::cube AS cube; +SELECT '(0),(0)'::cube AS cube; +SELECT '(0),(1)'::cube AS cube; +SELECT '[(0),(0)]'::cube AS cube; +SELECT '[(0),(1)]'::cube AS cube; +SELECT '(0,0,0,0),(0,0,0,0)'::cube AS cube; +SELECT '(0,0,0,0),(1,0,0,0)'::cube AS cube; +SELECT '[(0,0,0,0),(0,0,0,0)]'::cube AS cube; +SELECT '[(0,0,0,0),(1,0,0,0)]'::cube AS cube; + +-- invalid input: parse errors +SELECT ''::cube AS cube; +SELECT 'ABC'::cube AS cube; +SELECT '[]'::cube AS cube; +SELECT '[()]'::cube AS cube; +SELECT '[(1)]'::cube AS cube; +SELECT '[(1),]'::cube AS cube; +SELECT '[(1),2]'::cube AS cube; +SELECT '[(1),(2),(3)]'::cube AS cube; +SELECT '1,'::cube AS cube; +SELECT '1,2,'::cube AS cube; +SELECT '1,,2'::cube AS cube; +SELECT '(1,)'::cube AS cube; +SELECT '(1,2,)'::cube AS cube; +SELECT '(1,,2)'::cube AS cube; + +-- invalid input: semantic errors and trailing garbage +SELECT '[(1),(2)],'::cube AS cube; -- 0 +SELECT '[(1,2,3),(2,3)]'::cube AS cube; -- 1 +SELECT '[(1,2),(1,2,3)]'::cube AS cube; -- 1 +SELECT '(1),(2),'::cube AS cube; -- 2 +SELECT '(1,2,3),(2,3)'::cube AS cube; -- 3 +SELECT '(1,2),(1,2,3)'::cube AS cube; -- 3 +SELECT '(1,2,3)ab'::cube AS cube; -- 4 +SELECT '(1,2,3)a'::cube AS cube; -- 5 +SELECT '(1,2)('::cube AS cube; -- 5 +SELECT '1,2ab'::cube AS cube; -- 6 +SELECT '1 e7'::cube AS cube; -- 6 +SELECT '1,2a'::cube AS cube; -- 7 +SELECT '1..2'::cube AS cube; -- 7 +SELECT '-1e-700'::cube AS cube; -- out of range + +-- Also try it with non-error-throwing API +SELECT pg_input_is_valid('(1,2)', 'cube'); +SELECT pg_input_is_valid('[(1),]', 'cube'); +SELECT pg_input_is_valid('-1e-700', 'cube'); +SELECT * FROM pg_input_error_info('-1e-700', 'cube'); + +-- +-- Testing building cubes from float8 values +-- + +SELECT cube(0::float8); +SELECT cube(1::float8); +SELECT cube(1,2); +SELECT cube(cube(1,2),3); +SELECT cube(cube(1,2),3,4); +SELECT cube(cube(cube(1,2),3,4),5); +SELECT cube(cube(cube(1,2),3,4),5,6); + +-- +-- Test that the text -> cube cast was installed. +-- + +SELECT '(0)'::text::cube; + +-- +-- Test the float[] -> cube cast +-- +SELECT cube('{0,1,2}'::float[], '{3,4,5}'::float[]); +SELECT cube('{0,1,2}'::float[], '{3}'::float[]); +SELECT cube(NULL::float[], '{3}'::float[]); +SELECT cube('{0,1,2}'::float[]); +SELECT cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[3,2,1,1]); +SELECT cube_subset(cube('(1,3,5),(1,3,5)'), ARRAY[3,2,1,1]); +SELECT cube_subset(cube('(1,3,5),(6,7,8)'), ARRAY[4,0]); +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), ARRAY[4,0]); +-- test for limits: this should pass +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_series(1,100))); +-- and this should fail +SELECT cube_subset(cube('(6,7,8),(6,7,8)'), array(SELECT 1 as a FROM generate_series(1,101))); + + + +-- +-- Test point processing +-- +SELECT cube('(1,2),(1,2)'); -- cube_in +SELECT cube('{0,1,2}'::float[], '{0,1,2}'::float[]); -- cube_a_f8_f8 +SELECT cube('{5,6,7,8}'::float[]); -- cube_a_f8 +SELECT cube(1.37); -- cube_f8 +SELECT cube(1.37, 1.37); -- cube_f8_f8 +SELECT cube(cube(1,1), 42); -- cube_c_f8 +SELECT cube(cube(1,2), 42); -- cube_c_f8 +SELECT cube(cube(1,1), 42, 42); -- cube_c_f8_f8 +SELECT cube(cube(1,1), 42, 24); -- cube_c_f8_f8 +SELECT cube(cube(1,2), 42, 42); -- cube_c_f8_f8 +SELECT cube(cube(1,2), 42, 24); -- cube_c_f8_f8 + +-- +-- Testing limit of CUBE_MAX_DIM dimensions check in cube_in. +-- +-- create too big cube from literal +select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)'::cube; +select '(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)'::cube; +-- from an array +select cube(array(SELECT 0 as a FROM generate_series(1,101))); +select cube(array(SELECT 0 as a FROM generate_series(1,101)),array(SELECT 0 as a FROM generate_series(1,101))); + +-- extend cube beyond limit +-- this should work +select cube(array(SELECT 0 as a FROM generate_series(1,100))); +select cube(array(SELECT 0 as a FROM generate_series(1,100)),array(SELECT 0 as a FROM generate_series(1,100))); +-- this should fail +select cube(cube(array(SELECT 0 as a FROM generate_series(1,100))), 0); +select cube(cube(array(SELECT 0 as a FROM generate_series(1,100)),array(SELECT 0 as a FROM generate_series(1,100))), 0, 0); + + +-- +-- testing the operators +-- + +-- equality/inequality: +-- +SELECT '24, 33.20'::cube = '24, 33.20'::cube AS bool; +SELECT '24, 33.20'::cube != '24, 33.20'::cube AS bool; +SELECT '24, 33.20'::cube = '24, 33.21'::cube AS bool; +SELECT '24, 33.20'::cube != '24, 33.21'::cube AS bool; +SELECT '(2,0),(3,1)'::cube = '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube = '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; + +-- "lower than" / "greater than" +-- (these operators are not useful for anything but ordering) +-- +SELECT '1'::cube > '2'::cube AS bool; +SELECT '1'::cube < '2'::cube AS bool; +SELECT '1,1'::cube > '1,2'::cube AS bool; +SELECT '1,1'::cube < '1,2'::cube AS bool; + +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,0),(3,1,0,0,1)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,1),(3,1,0,0,0)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,1),(3,1,0,0,0)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube > '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; +SELECT '(2,0),(3,1)'::cube < '(2,0,0,0,0),(3,1,0,0,0)'::cube AS bool; +SELECT '(2,0,0,0,0),(3,1,0,0,1)'::cube > '(2,0),(3,1)'::cube AS bool; +SELECT '(2,0,0,0,0),(3,1,0,0,1)'::cube < '(2,0),(3,1)'::cube AS bool; +SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool; +SELECT '(2,0,0,0,1),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; +SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube > '(2,0),(3,1)'::cube AS bool; +SELECT '(2,0,0,0,0),(3,1,0,0,0)'::cube < '(2,0),(3,1)'::cube AS bool; + + +-- "overlap" +-- +SELECT '1'::cube && '1'::cube AS bool; +SELECT '1'::cube && '2'::cube AS bool; + +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '0'::cube AS bool; +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '1'::cube AS bool; +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '1,1,1'::cube AS bool; +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(1,1,1),(2,2,2)]'::cube AS bool; +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(1,1),(2,2)]'::cube AS bool; +SELECT '[(-1,-1,-1),(1,1,1)]'::cube && '[(2,1,1),(2,2,2)]'::cube AS bool; + + +-- "contained in" (the left operand is the cube entirely enclosed by +-- the right operand): +-- +SELECT '0'::cube <@ '0'::cube AS bool; +SELECT '0,0,0'::cube <@ '0,0,0'::cube AS bool; +SELECT '0,0'::cube <@ '0,0,1'::cube AS bool; +SELECT '0,0,0'::cube <@ '0,0,1'::cube AS bool; +SELECT '1,0,0'::cube <@ '0,0,1'::cube AS bool; +SELECT '(1,0,0),(0,0,1)'::cube <@ '(1,0,0),(0,0,1)'::cube AS bool; +SELECT '(1,0,0),(0,0,1)'::cube <@ '(-1,-1,-1),(1,1,1)'::cube AS bool; +SELECT '(1,0,0),(0,0,1)'::cube <@ '(-1,-1,-1,-1),(1,1,1,1)'::cube AS bool; +SELECT '0'::cube <@ '(-1),(1)'::cube AS bool; +SELECT '1'::cube <@ '(-1),(1)'::cube AS bool; +SELECT '-1'::cube <@ '(-1),(1)'::cube AS bool; +SELECT '(-1),(1)'::cube <@ '(-1),(1)'::cube AS bool; +SELECT '(-1),(1)'::cube <@ '(-1,-1),(1,1)'::cube AS bool; +SELECT '(-2),(1)'::cube <@ '(-1),(1)'::cube AS bool; +SELECT '(-2),(1)'::cube <@ '(-1,-1),(1,1)'::cube AS bool; + + +-- "contains" (the left operand is the cube that entirely encloses the +-- right operand) +-- +SELECT '0'::cube @> '0'::cube AS bool; +SELECT '0,0,0'::cube @> '0,0,0'::cube AS bool; +SELECT '0,0,1'::cube @> '0,0'::cube AS bool; +SELECT '0,0,1'::cube @> '0,0,0'::cube AS bool; +SELECT '0,0,1'::cube @> '1,0,0'::cube AS bool; +SELECT '(1,0,0),(0,0,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; +SELECT '(-1,-1,-1),(1,1,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; +SELECT '(-1,-1,-1,-1),(1,1,1,1)'::cube @> '(1,0,0),(0,0,1)'::cube AS bool; +SELECT '(-1),(1)'::cube @> '0'::cube AS bool; +SELECT '(-1),(1)'::cube @> '1'::cube AS bool; +SELECT '(-1),(1)'::cube @> '-1'::cube AS bool; +SELECT '(-1),(1)'::cube @> '(-1),(1)'::cube AS bool; +SELECT '(-1,-1),(1,1)'::cube @> '(-1),(1)'::cube AS bool; +SELECT '(-1),(1)'::cube @> '(-2),(1)'::cube AS bool; +SELECT '(-1,-1),(1,1)'::cube @> '(-2),(1)'::cube AS bool; + +-- Test of distance function +-- +SELECT cube_distance('(0)'::cube,'(2,2,2,2)'::cube); +SELECT cube_distance('(0)'::cube,'(.3,.4)'::cube); +SELECT cube_distance('(2,3,4)'::cube,'(2,3,4)'::cube); +SELECT cube_distance('(42,42,42,42)'::cube,'(137,137,137,137)'::cube); +SELECT cube_distance('(42,42,42)'::cube,'(137,137)'::cube); + +-- Test of cube function (text to cube) +-- +SELECT cube('(1,1.2)'::text); +SELECT cube(NULL); + +-- Test of cube_dim function (dimensions stored in cube) +-- +SELECT cube_dim('(0)'::cube); +SELECT cube_dim('(0,0)'::cube); +SELECT cube_dim('(0,0,0)'::cube); +SELECT cube_dim('(42,42,42),(42,42,42)'::cube); +SELECT cube_dim('(4,8,15,16,23),(4,8,15,16,23)'::cube); + +-- Test of cube_ll_coord function (retrieves LL coordinate values) +-- +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 1); +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 2); +SELECT cube_ll_coord('(-1,1),(2,-2)'::cube, 3); +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 1); +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 2); +SELECT cube_ll_coord('(1,2),(1,2)'::cube, 3); +SELECT cube_ll_coord('(42,137)'::cube, 1); +SELECT cube_ll_coord('(42,137)'::cube, 2); +SELECT cube_ll_coord('(42,137)'::cube, 3); + +-- Test of cube_ur_coord function (retrieves UR coordinate values) +-- +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 1); +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 2); +SELECT cube_ur_coord('(-1,1),(2,-2)'::cube, 3); +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 1); +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 2); +SELECT cube_ur_coord('(1,2),(1,2)'::cube, 3); +SELECT cube_ur_coord('(42,137)'::cube, 1); +SELECT cube_ur_coord('(42,137)'::cube, 2); +SELECT cube_ur_coord('(42,137)'::cube, 3); + +-- Test of cube_is_point +-- +SELECT cube_is_point('(0)'::cube); +SELECT cube_is_point('(0,1,2)'::cube); +SELECT cube_is_point('(0,1,2),(0,1,2)'::cube); +SELECT cube_is_point('(0,1,2),(-1,1,2)'::cube); +SELECT cube_is_point('(0,1,2),(0,-1,2)'::cube); +SELECT cube_is_point('(0,1,2),(0,1,-2)'::cube); + +-- Test of cube_enlarge (enlarging and shrinking cubes) +-- +SELECT cube_enlarge('(0)'::cube, 0, 0); +SELECT cube_enlarge('(0)'::cube, 0, 1); +SELECT cube_enlarge('(0)'::cube, 0, 2); +SELECT cube_enlarge('(2),(-2)'::cube, 0, 4); +SELECT cube_enlarge('(0)'::cube, 1, 0); +SELECT cube_enlarge('(0)'::cube, 1, 1); +SELECT cube_enlarge('(0)'::cube, 1, 2); +SELECT cube_enlarge('(2),(-2)'::cube, 1, 4); +SELECT cube_enlarge('(0)'::cube, -1, 0); +SELECT cube_enlarge('(0)'::cube, -1, 1); +SELECT cube_enlarge('(0)'::cube, -1, 2); +SELECT cube_enlarge('(2),(-2)'::cube, -1, 4); +SELECT cube_enlarge('(0,0,0)'::cube, 1, 0); +SELECT cube_enlarge('(0,0,0)'::cube, 1, 2); +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, 1, 2); +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, 3, 2); +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, -1, 2); +SELECT cube_enlarge('(2,-2),(-3,7)'::cube, -3, 2); +SELECT cube_enlarge('(42,-23,-23),(42,23,23)'::cube, -23, 5); +SELECT cube_enlarge('(42,-23,-23),(42,23,23)'::cube, -24, 5); + +-- Test of cube_union (MBR for two cubes) +-- +SELECT cube_union('(1,2),(3,4)'::cube, '(5,6,7),(8,9,10)'::cube); +SELECT cube_union('(1,2)'::cube, '(4,2,0,0)'::cube); +SELECT cube_union('(1,2),(1,2)'::cube, '(4,2),(4,2)'::cube); +SELECT cube_union('(1,2),(1,2)'::cube, '(1,2),(1,2)'::cube); +SELECT cube_union('(1,2),(1,2)'::cube, '(1,2,0),(1,2,0)'::cube); + +-- Test of cube_inter +-- +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,4), (16,15)'::cube); -- intersects +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,4), (6,5)'::cube); -- includes +SELECT cube_inter('(1,2),(10,11)'::cube, '(13,14), (16,15)'::cube); -- no intersection +SELECT cube_inter('(1,2),(10,11)'::cube, '(3,14), (16,15)'::cube); -- no intersection, but one dimension intersects +SELECT cube_inter('(1,2),(10,11)'::cube, '(10,11), (16,15)'::cube); -- point intersection +SELECT cube_inter('(1,2,3)'::cube, '(1,2,3)'::cube); -- point args +SELECT cube_inter('(1,2,3)'::cube, '(5,6,3)'::cube); -- point args + +-- Test of cube_size +-- +SELECT cube_size('(4,8),(15,16)'::cube); +SELECT cube_size('(42,137)'::cube); + +-- Test of distances (euclidean distance may not be bit-exact) +-- +SET extra_float_digits = 0; +SELECT cube_distance('(1,1)'::cube, '(4,5)'::cube); +SELECT '(1,1)'::cube <-> '(4,5)'::cube as d_e; +RESET extra_float_digits; +SELECT distance_chebyshev('(1,1)'::cube, '(4,5)'::cube); +SELECT '(1,1)'::cube <=> '(4,5)'::cube as d_c; +SELECT distance_taxicab('(1,1)'::cube, '(4,5)'::cube); +SELECT '(1,1)'::cube <#> '(4,5)'::cube as d_t; +-- zero for overlapping +SELECT cube_distance('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); +SELECT distance_chebyshev('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); +SELECT distance_taxicab('(2,2),(10,10)'::cube, '(0,0),(5,5)'::cube); +-- coordinate access +SELECT cube(array[10,20,30], array[40,50,60])->1; +SELECT cube(array[40,50,60], array[10,20,30])->1; +SELECT cube(array[10,20,30], array[40,50,60])->6; +SELECT cube(array[10,20,30], array[40,50,60])->0; +SELECT cube(array[10,20,30], array[40,50,60])->7; +SELECT cube(array[10,20,30], array[40,50,60])->-1; +SELECT cube(array[10,20,30], array[40,50,60])->-6; +SELECT cube(array[10,20,30])->3; +SELECT cube(array[10,20,30])->6; +SELECT cube(array[10,20,30])->-6; +-- "normalized" coordinate access +SELECT cube(array[10,20,30], array[40,50,60])~>1; +SELECT cube(array[40,50,60], array[10,20,30])~>1; +SELECT cube(array[10,20,30], array[40,50,60])~>2; +SELECT cube(array[40,50,60], array[10,20,30])~>2; +SELECT cube(array[10,20,30], array[40,50,60])~>3; +SELECT cube(array[40,50,60], array[10,20,30])~>3; + +SELECT cube(array[40,50,60], array[10,20,30])~>0; +SELECT cube(array[40,50,60], array[10,20,30])~>4; +SELECT cube(array[40,50,60], array[10,20,30])~>(-1); + +-- Load some example data and build the index +-- +CREATE TABLE test_cube (c cube); + +\copy test_cube from 'data/test_cube.data' + +CREATE INDEX test_cube_ix ON test_cube USING gist (c); +SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' ORDER BY c; + +-- Test sorting +SELECT * FROM test_cube WHERE c && '(3000,1000),(0,0)' GROUP BY c ORDER BY c; + +-- Test index-only scans +SET enable_bitmapscan = false; +EXPLAIN (COSTS OFF) +SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c; +SELECT c FROM test_cube WHERE c <@ '(3000,1000),(0,0)' ORDER BY c; +RESET enable_bitmapscan; + +-- Test kNN +INSERT INTO test_cube VALUES ('(1,1)'), ('(100000)'), ('(0, 100000)'); -- Some corner cases +SET enable_seqscan = false; + +-- Test different metrics +SET extra_float_digits = 0; +SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5; +RESET extra_float_digits; +SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5; +SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5; + +-- Test sorting by coordinates +SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound +SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound +SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound +SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound +SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound +SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound +SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound +SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound + +-- Same queries with sequential scan (should give the same results as above) +RESET enable_seqscan; +SET enable_indexscan = OFF; +SET extra_float_digits = 0; +SELECT *, c <-> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <-> '(100, 100),(500, 500)'::cube LIMIT 5; +RESET extra_float_digits; +SELECT *, c <=> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <=> '(100, 100),(500, 500)'::cube LIMIT 5; +SELECT *, c <#> '(100, 100),(500, 500)'::cube as dist FROM test_cube ORDER BY c <#> '(100, 100),(500, 500)'::cube LIMIT 5; +SELECT c~>1, c FROM test_cube ORDER BY c~>1 LIMIT 15; -- ascending by left bound +SELECT c~>2, c FROM test_cube ORDER BY c~>2 LIMIT 15; -- ascending by right bound +SELECT c~>3, c FROM test_cube ORDER BY c~>3 LIMIT 15; -- ascending by lower bound +SELECT c~>4, c FROM test_cube ORDER BY c~>4 LIMIT 15; -- ascending by upper bound +SELECT c~>(-1), c FROM test_cube ORDER BY c~>(-1) LIMIT 15; -- descending by left bound +SELECT c~>(-2), c FROM test_cube ORDER BY c~>(-2) LIMIT 15; -- descending by right bound +SELECT c~>(-3), c FROM test_cube ORDER BY c~>(-3) LIMIT 15; -- descending by lower bound +SELECT c~>(-4), c FROM test_cube ORDER BY c~>(-4) LIMIT 15; -- descending by upper bound +RESET enable_indexscan; diff --git a/contrib/cube/sql/cube_sci.sql b/contrib/cube/sql/cube_sci.sql new file mode 100644 index 0000000..35a5407 --- /dev/null +++ b/contrib/cube/sql/cube_sci.sql @@ -0,0 +1,22 @@ +--- +--- Testing cube output in scientific notation. This was put into separate +--- test, because has platform-depending output. +--- + +SELECT '1e27'::cube AS cube; +SELECT '-1e27'::cube AS cube; +SELECT '1.0e27'::cube AS cube; +SELECT '-1.0e27'::cube AS cube; +SELECT '1e+27'::cube AS cube; +SELECT '-1e+27'::cube AS cube; +SELECT '1.0e+27'::cube AS cube; +SELECT '-1.0e+27'::cube AS cube; +SELECT '1e-7'::cube AS cube; +SELECT '-1e-7'::cube AS cube; +SELECT '1.0e-7'::cube AS cube; +SELECT '-1.0e-7'::cube AS cube; +SELECT '1e-300'::cube AS cube; +SELECT '-1e-300'::cube AS cube; +SELECT '1234567890123456'::cube AS cube; +SELECT '+1234567890123456'::cube AS cube; +SELECT '-1234567890123456'::cube AS cube; diff --git a/contrib/dblink/.gitignore b/contrib/dblink/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/dblink/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/dblink/Makefile b/contrib/dblink/Makefile new file mode 100644 index 0000000..d4c7ed6 --- /dev/null +++ b/contrib/dblink/Makefile @@ -0,0 +1,27 @@ +# contrib/dblink/Makefile + +MODULE_big = dblink +OBJS = \ + $(WIN32RES) \ + dblink.o +PG_CPPFLAGS = -I$(libpq_srcdir) +SHLIB_LINK_INTERNAL = $(libpq) + +EXTENSION = dblink +DATA = dblink--1.2.sql dblink--1.1--1.2.sql dblink--1.0--1.1.sql +PGFILEDESC = "dblink - connect to other PostgreSQL databases" + +REGRESS = dblink +REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +SHLIB_PREREQS = submake-libpq +subdir = contrib/dblink +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/dblink/dblink--1.0--1.1.sql b/contrib/dblink/dblink--1.0--1.1.sql new file mode 100644 index 0000000..67293f0 --- /dev/null +++ b/contrib/dblink/dblink--1.0--1.1.sql @@ -0,0 +1,14 @@ +/* contrib/dblink/dblink--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION dblink UPDATE TO '1.1'" to load this file. \quit + +CREATE FUNCTION dblink_fdw_validator( + options text[], + catalog oid +) +RETURNS void +AS 'MODULE_PATHNAME', 'dblink_fdw_validator' +LANGUAGE C STRICT; + +CREATE FOREIGN DATA WRAPPER dblink_fdw VALIDATOR dblink_fdw_validator; diff --git a/contrib/dblink/dblink--1.1--1.2.sql b/contrib/dblink/dblink--1.1--1.2.sql new file mode 100644 index 0000000..69d58af --- /dev/null +++ b/contrib/dblink/dblink--1.1--1.2.sql @@ -0,0 +1,46 @@ +/* contrib/dblink/dblink--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION dblink UPDATE TO '1.2'" to load this file. \quit + +ALTER FUNCTION dblink_connect(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_connect(text, text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_connect_u(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_connect_u(text, text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_disconnect() PARALLEL RESTRICTED; +ALTER FUNCTION dblink_disconnect(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_open(text, text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_open(text, text, boolean) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_open(text, text, text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_open(text, text, text, boolean) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_fetch(text, int) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_fetch(text, int, boolean) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_fetch(text, text, int) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_fetch(text, text, int, boolean) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_close(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_close(text, boolean) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_close(text, text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_close(text, text, boolean) PARALLEL RESTRICTED; +ALTER FUNCTION dblink(text, text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink(text, text, boolean) PARALLEL RESTRICTED; +ALTER FUNCTION dblink(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink(text, boolean) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_exec(text, text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_exec(text, text, boolean) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_exec(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_exec(text, boolean) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_get_pkey(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_build_sql_insert(text, int2vector, int, _text, _text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_build_sql_delete(text, int2vector, int, _text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_build_sql_update(text, int2vector, int, _text, _text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_current_query() PARALLEL RESTRICTED; +ALTER FUNCTION dblink_send_query(text, text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_is_busy(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_get_result(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_get_result(text, bool) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_get_connections() PARALLEL RESTRICTED; +ALTER FUNCTION dblink_cancel_query(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_error_message(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_get_notify() PARALLEL RESTRICTED; +ALTER FUNCTION dblink_get_notify(text) PARALLEL RESTRICTED; +ALTER FUNCTION dblink_fdw_validator(text[], oid) PARALLEL SAFE; diff --git a/contrib/dblink/dblink--1.2.sql b/contrib/dblink/dblink--1.2.sql new file mode 100644 index 0000000..405eccb --- /dev/null +++ b/contrib/dblink/dblink--1.2.sql @@ -0,0 +1,235 @@ +/* contrib/dblink/dblink--1.2.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION dblink" to load this file. \quit + +-- dblink_connect now restricts non-superusers to password +-- authenticated connections +CREATE FUNCTION dblink_connect (text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_connect' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_connect (text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_connect' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +-- dblink_connect_u allows non-superusers to use +-- non-password authenticated connections, but initially +-- privileges are revoked from public +CREATE FUNCTION dblink_connect_u (text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_connect' +LANGUAGE C STRICT PARALLEL RESTRICTED SECURITY DEFINER; + +CREATE FUNCTION dblink_connect_u (text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_connect' +LANGUAGE C STRICT PARALLEL RESTRICTED SECURITY DEFINER; + +REVOKE ALL ON FUNCTION dblink_connect_u (text) FROM public; +REVOKE ALL ON FUNCTION dblink_connect_u (text, text) FROM public; + +CREATE FUNCTION dblink_disconnect () +RETURNS text +AS 'MODULE_PATHNAME','dblink_disconnect' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_disconnect (text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_disconnect' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_open (text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_open' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_open (text, text, boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_open' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_open (text, text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_open' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_open (text, text, text, boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_open' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_fetch (text, int) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_fetch (text, int, boolean) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_fetch (text, text, int) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_fetch (text, text, int, boolean) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_close (text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_close' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_close (text, boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_close' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_close (text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_close' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_close (text, text, boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_close' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink (text, text) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_record' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink (text, text, boolean) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_record' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink (text) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_record' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink (text, boolean) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_record' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_exec (text, text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_exec' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_exec (text, text, boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_exec' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_exec (text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_exec' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_exec (text,boolean) +RETURNS text +AS 'MODULE_PATHNAME','dblink_exec' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE TYPE dblink_pkey_results AS (position int, colname text); + +CREATE FUNCTION dblink_get_pkey (text) +RETURNS setof dblink_pkey_results +AS 'MODULE_PATHNAME','dblink_get_pkey' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_build_sql_insert (text, int2vector, int, _text, _text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_build_sql_insert' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_build_sql_delete (text, int2vector, int, _text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_build_sql_delete' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_build_sql_update (text, int2vector, int, _text, _text) +RETURNS text +AS 'MODULE_PATHNAME','dblink_build_sql_update' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_current_query () +RETURNS text +AS 'MODULE_PATHNAME','dblink_current_query' +LANGUAGE C PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_send_query(text, text) +RETURNS int4 +AS 'MODULE_PATHNAME', 'dblink_send_query' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_is_busy(text) +RETURNS int4 +AS 'MODULE_PATHNAME', 'dblink_is_busy' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_get_result(text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'dblink_get_result' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_get_result(text, bool) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'dblink_get_result' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_get_connections() +RETURNS text[] +AS 'MODULE_PATHNAME', 'dblink_get_connections' +LANGUAGE C PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_cancel_query(text) +RETURNS text +AS 'MODULE_PATHNAME', 'dblink_cancel_query' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_error_message(text) +RETURNS text +AS 'MODULE_PATHNAME', 'dblink_error_message' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_get_notify( + OUT notify_name TEXT, + OUT be_pid INT4, + OUT extra TEXT +) +RETURNS setof record +AS 'MODULE_PATHNAME', 'dblink_get_notify' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION dblink_get_notify( + conname TEXT, + OUT notify_name TEXT, + OUT be_pid INT4, + OUT extra TEXT +) +RETURNS setof record +AS 'MODULE_PATHNAME', 'dblink_get_notify' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +/* New stuff in 1.1 begins here */ + +CREATE FUNCTION dblink_fdw_validator( + options text[], + catalog oid +) +RETURNS void +AS 'MODULE_PATHNAME', 'dblink_fdw_validator' +LANGUAGE C STRICT PARALLEL SAFE; + +CREATE FOREIGN DATA WRAPPER dblink_fdw VALIDATOR dblink_fdw_validator; diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c new file mode 100644 index 0000000..1ff65d1 --- /dev/null +++ b/contrib/dblink/dblink.c @@ -0,0 +1,3077 @@ +/* + * dblink.c + * + * Functions returning results from a remote database + * + * Joe Conway + * And contributors: + * Darko Prenosil + * Shridhar Daithankar + * + * contrib/dblink/dblink.c + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * ALL RIGHTS RESERVED; + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + */ +#include "postgres.h" + +#include + +#include "access/htup_details.h" +#include "access/relation.h" +#include "access/reloptions.h" +#include "access/table.h" +#include "catalog/namespace.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_type.h" +#include "catalog/pg_user_mapping.h" +#include "executor/spi.h" +#include "foreign/foreign.h" +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "libpq-fe.h" +#include "libpq/libpq-be.h" +#include "libpq/libpq-be-fe-helpers.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "parser/scansup.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/varlena.h" + +PG_MODULE_MAGIC; + +typedef struct remoteConn +{ + PGconn *conn; /* Hold the remote connection */ + int openCursorCount; /* The number of open cursors */ + bool newXactForCursor; /* Opened a transaction for a cursor */ +} remoteConn; + +typedef struct storeInfo +{ + FunctionCallInfo fcinfo; + Tuplestorestate *tuplestore; + AttInMetadata *attinmeta; + MemoryContext tmpcontext; + char **cstrs; + /* temp storage for results to avoid leaks on exception */ + PGresult *last_res; + PGresult *cur_res; +} storeInfo; + +/* + * Internal declarations + */ +static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async); +static void prepTuplestoreResult(FunctionCallInfo fcinfo); +static void materializeResult(FunctionCallInfo fcinfo, PGconn *conn, + PGresult *res); +static void materializeQueryResult(FunctionCallInfo fcinfo, + PGconn *conn, + const char *conname, + const char *sql, + bool fail); +static PGresult *storeQueryResult(volatile storeInfo *sinfo, PGconn *conn, const char *sql); +static void storeRow(volatile storeInfo *sinfo, PGresult *res, bool first); +static remoteConn *getConnectionByName(const char *name); +static HTAB *createConnHash(void); +static void createNewConnection(const char *name, remoteConn *rconn); +static void deleteConnection(const char *name); +static char **get_pkey_attnames(Relation rel, int16 *indnkeyatts); +static char **get_text_array_contents(ArrayType *array, int *numitems); +static char *get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals); +static char *get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **tgt_pkattvals); +static char *get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals); +static char *quote_ident_cstr(char *rawstr); +static int get_attnum_pk_pos(int *pkattnums, int pknumatts, int key); +static HeapTuple get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals); +static Relation get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclMode aclmode); +static char *generate_relation_name(Relation rel); +static void dblink_connstr_check(const char *connstr); +static bool dblink_connstr_has_pw(const char *connstr); +static void dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr); +static void dblink_res_error(PGconn *conn, const char *conname, PGresult *res, + bool fail, const char *fmt,...) pg_attribute_printf(5, 6); +static char *get_connect_string(const char *servername); +static char *escape_param_str(const char *str); +static void validate_pkattnums(Relation rel, + int2vector *pkattnums_arg, int32 pknumatts_arg, + int **pkattnums, int *pknumatts); +static bool is_valid_dblink_option(const PQconninfoOption *options, + const char *option, Oid context); +static int applyRemoteGucs(PGconn *conn); +static void restoreLocalGucs(int nestlevel); + +/* Global */ +static remoteConn *pconn = NULL; +static HTAB *remoteConnHash = NULL; + +/* + * Following is list that holds multiple remote connections. + * Calling convention of each dblink function changes to accept + * connection name as the first parameter. The connection list is + * much like ecpg e.g. a mapping between a name and a PGconn object. + */ + +typedef struct remoteConnHashEnt +{ + char name[NAMEDATALEN]; + remoteConn *rconn; +} remoteConnHashEnt; + +/* initial number of connection hashes */ +#define NUMCONN 16 + +static char * +xpstrdup(const char *in) +{ + if (in == NULL) + return NULL; + return pstrdup(in); +} + +static void +pg_attribute_noreturn() +dblink_res_internalerror(PGconn *conn, PGresult *res, const char *p2) +{ + char *msg = pchomp(PQerrorMessage(conn)); + + PQclear(res); + elog(ERROR, "%s: %s", p2, msg); +} + +static void +pg_attribute_noreturn() +dblink_conn_not_avail(const char *conname) +{ + if (conname) + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST), + errmsg("connection \"%s\" not available", conname))); + else + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST), + errmsg("connection not available"))); +} + +static void +dblink_get_conn(char *conname_or_str, + PGconn *volatile *conn_p, char **conname_p, volatile bool *freeconn_p) +{ + remoteConn *rconn = getConnectionByName(conname_or_str); + PGconn *conn; + char *conname; + bool freeconn; + + if (rconn) + { + conn = rconn->conn; + conname = conname_or_str; + freeconn = false; + } + else + { + const char *connstr; + + connstr = get_connect_string(conname_or_str); + if (connstr == NULL) + connstr = conname_or_str; + dblink_connstr_check(connstr); + + /* OK to make connection */ + conn = libpqsrv_connect(connstr, PG_WAIT_EXTENSION); + + if (PQstatus(conn) == CONNECTION_BAD) + { + char *msg = pchomp(PQerrorMessage(conn)); + + libpqsrv_disconnect(conn); + ereport(ERROR, + (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), + errmsg("could not establish connection"), + errdetail_internal("%s", msg))); + } + dblink_security_check(conn, rconn, connstr); + if (PQclientEncoding(conn) != GetDatabaseEncoding()) + PQsetClientEncoding(conn, GetDatabaseEncodingName()); + freeconn = true; + conname = NULL; + } + + *conn_p = conn; + *conname_p = conname; + *freeconn_p = freeconn; +} + +static PGconn * +dblink_get_named_conn(const char *conname) +{ + remoteConn *rconn = getConnectionByName(conname); + + if (rconn) + return rconn->conn; + + dblink_conn_not_avail(conname); + return NULL; /* keep compiler quiet */ +} + +static void +dblink_init(void) +{ + if (!pconn) + { + pconn = (remoteConn *) MemoryContextAlloc(TopMemoryContext, sizeof(remoteConn)); + pconn->conn = NULL; + pconn->openCursorCount = 0; + pconn->newXactForCursor = false; + } +} + +/* + * Create a persistent connection to another database + */ +PG_FUNCTION_INFO_V1(dblink_connect); +Datum +dblink_connect(PG_FUNCTION_ARGS) +{ + char *conname_or_str = NULL; + char *connstr = NULL; + char *connname = NULL; + char *msg; + PGconn *conn = NULL; + remoteConn *rconn = NULL; + + dblink_init(); + + if (PG_NARGS() == 2) + { + conname_or_str = text_to_cstring(PG_GETARG_TEXT_PP(1)); + connname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + } + else if (PG_NARGS() == 1) + conname_or_str = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + if (connname) + { + rconn = (remoteConn *) MemoryContextAlloc(TopMemoryContext, + sizeof(remoteConn)); + rconn->conn = NULL; + rconn->openCursorCount = 0; + rconn->newXactForCursor = false; + } + + /* first check for valid foreign data server */ + connstr = get_connect_string(conname_or_str); + if (connstr == NULL) + connstr = conname_or_str; + + /* check password in connection string if not superuser */ + dblink_connstr_check(connstr); + + /* OK to make connection */ + conn = libpqsrv_connect(connstr, PG_WAIT_EXTENSION); + + if (PQstatus(conn) == CONNECTION_BAD) + { + msg = pchomp(PQerrorMessage(conn)); + libpqsrv_disconnect(conn); + if (rconn) + pfree(rconn); + + ereport(ERROR, + (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), + errmsg("could not establish connection"), + errdetail_internal("%s", msg))); + } + + /* check password actually used if not superuser */ + dblink_security_check(conn, rconn, connstr); + + /* attempt to set client encoding to match server encoding, if needed */ + if (PQclientEncoding(conn) != GetDatabaseEncoding()) + PQsetClientEncoding(conn, GetDatabaseEncodingName()); + + if (connname) + { + rconn->conn = conn; + createNewConnection(connname, rconn); + } + else + { + if (pconn->conn) + libpqsrv_disconnect(pconn->conn); + pconn->conn = conn; + } + + PG_RETURN_TEXT_P(cstring_to_text("OK")); +} + +/* + * Clear a persistent connection to another database + */ +PG_FUNCTION_INFO_V1(dblink_disconnect); +Datum +dblink_disconnect(PG_FUNCTION_ARGS) +{ + char *conname = NULL; + remoteConn *rconn = NULL; + PGconn *conn = NULL; + + dblink_init(); + + if (PG_NARGS() == 1) + { + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + rconn = getConnectionByName(conname); + if (rconn) + conn = rconn->conn; + } + else + conn = pconn->conn; + + if (!conn) + dblink_conn_not_avail(conname); + + libpqsrv_disconnect(conn); + if (rconn) + { + deleteConnection(conname); + pfree(rconn); + } + else + pconn->conn = NULL; + + PG_RETURN_TEXT_P(cstring_to_text("OK")); +} + +/* + * opens a cursor using a persistent connection + */ +PG_FUNCTION_INFO_V1(dblink_open); +Datum +dblink_open(PG_FUNCTION_ARGS) +{ + PGresult *res = NULL; + PGconn *conn; + char *curname = NULL; + char *sql = NULL; + char *conname = NULL; + StringInfoData buf; + remoteConn *rconn = NULL; + bool fail = true; /* default to backward compatible behavior */ + + dblink_init(); + initStringInfo(&buf); + + if (PG_NARGS() == 2) + { + /* text,text */ + curname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); + rconn = pconn; + } + else if (PG_NARGS() == 3) + { + /* might be text,text,text or text,text,bool */ + if (get_fn_expr_argtype(fcinfo->flinfo, 2) == BOOLOID) + { + curname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); + fail = PG_GETARG_BOOL(2); + rconn = pconn; + } + else + { + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + curname = text_to_cstring(PG_GETARG_TEXT_PP(1)); + sql = text_to_cstring(PG_GETARG_TEXT_PP(2)); + rconn = getConnectionByName(conname); + } + } + else if (PG_NARGS() == 4) + { + /* text,text,text,bool */ + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + curname = text_to_cstring(PG_GETARG_TEXT_PP(1)); + sql = text_to_cstring(PG_GETARG_TEXT_PP(2)); + fail = PG_GETARG_BOOL(3); + rconn = getConnectionByName(conname); + } + + if (!rconn || !rconn->conn) + dblink_conn_not_avail(conname); + + conn = rconn->conn; + + /* If we are not in a transaction, start one */ + if (PQtransactionStatus(conn) == PQTRANS_IDLE) + { + res = PQexec(conn, "BEGIN"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + dblink_res_internalerror(conn, res, "begin error"); + PQclear(res); + rconn->newXactForCursor = true; + + /* + * Since transaction state was IDLE, we force cursor count to + * initially be 0. This is needed as a previous ABORT might have wiped + * out our transaction without maintaining the cursor count for us. + */ + rconn->openCursorCount = 0; + } + + /* if we started a transaction, increment cursor count */ + if (rconn->newXactForCursor) + (rconn->openCursorCount)++; + + appendStringInfo(&buf, "DECLARE %s CURSOR FOR %s", curname, sql); + res = PQexec(conn, buf.data); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) + { + dblink_res_error(conn, conname, res, fail, + "while opening cursor \"%s\"", curname); + PG_RETURN_TEXT_P(cstring_to_text("ERROR")); + } + + PQclear(res); + PG_RETURN_TEXT_P(cstring_to_text("OK")); +} + +/* + * closes a cursor + */ +PG_FUNCTION_INFO_V1(dblink_close); +Datum +dblink_close(PG_FUNCTION_ARGS) +{ + PGconn *conn; + PGresult *res = NULL; + char *curname = NULL; + char *conname = NULL; + StringInfoData buf; + remoteConn *rconn = NULL; + bool fail = true; /* default to backward compatible behavior */ + + dblink_init(); + initStringInfo(&buf); + + if (PG_NARGS() == 1) + { + /* text */ + curname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + rconn = pconn; + } + else if (PG_NARGS() == 2) + { + /* might be text,text or text,bool */ + if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID) + { + curname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + fail = PG_GETARG_BOOL(1); + rconn = pconn; + } + else + { + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + curname = text_to_cstring(PG_GETARG_TEXT_PP(1)); + rconn = getConnectionByName(conname); + } + } + if (PG_NARGS() == 3) + { + /* text,text,bool */ + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + curname = text_to_cstring(PG_GETARG_TEXT_PP(1)); + fail = PG_GETARG_BOOL(2); + rconn = getConnectionByName(conname); + } + + if (!rconn || !rconn->conn) + dblink_conn_not_avail(conname); + + conn = rconn->conn; + + appendStringInfo(&buf, "CLOSE %s", curname); + + /* close the cursor */ + res = PQexec(conn, buf.data); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) + { + dblink_res_error(conn, conname, res, fail, + "while closing cursor \"%s\"", curname); + PG_RETURN_TEXT_P(cstring_to_text("ERROR")); + } + + PQclear(res); + + /* if we started a transaction, decrement cursor count */ + if (rconn->newXactForCursor) + { + (rconn->openCursorCount)--; + + /* if count is zero, commit the transaction */ + if (rconn->openCursorCount == 0) + { + rconn->newXactForCursor = false; + + res = PQexec(conn, "COMMIT"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + dblink_res_internalerror(conn, res, "commit error"); + PQclear(res); + } + } + + PG_RETURN_TEXT_P(cstring_to_text("OK")); +} + +/* + * Fetch results from an open cursor + */ +PG_FUNCTION_INFO_V1(dblink_fetch); +Datum +dblink_fetch(PG_FUNCTION_ARGS) +{ + PGresult *res = NULL; + char *conname = NULL; + remoteConn *rconn = NULL; + PGconn *conn = NULL; + StringInfoData buf; + char *curname = NULL; + int howmany = 0; + bool fail = true; /* default to backward compatible */ + + prepTuplestoreResult(fcinfo); + + dblink_init(); + + if (PG_NARGS() == 4) + { + /* text,text,int,bool */ + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + curname = text_to_cstring(PG_GETARG_TEXT_PP(1)); + howmany = PG_GETARG_INT32(2); + fail = PG_GETARG_BOOL(3); + + rconn = getConnectionByName(conname); + if (rconn) + conn = rconn->conn; + } + else if (PG_NARGS() == 3) + { + /* text,text,int or text,int,bool */ + if (get_fn_expr_argtype(fcinfo->flinfo, 2) == BOOLOID) + { + curname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + howmany = PG_GETARG_INT32(1); + fail = PG_GETARG_BOOL(2); + conn = pconn->conn; + } + else + { + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + curname = text_to_cstring(PG_GETARG_TEXT_PP(1)); + howmany = PG_GETARG_INT32(2); + + rconn = getConnectionByName(conname); + if (rconn) + conn = rconn->conn; + } + } + else if (PG_NARGS() == 2) + { + /* text,int */ + curname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + howmany = PG_GETARG_INT32(1); + conn = pconn->conn; + } + + if (!conn) + dblink_conn_not_avail(conname); + + initStringInfo(&buf); + appendStringInfo(&buf, "FETCH %d FROM %s", howmany, curname); + + /* + * Try to execute the query. Note that since libpq uses malloc, the + * PGresult will be long-lived even though we are still in a short-lived + * memory context. + */ + res = PQexec(conn, buf.data); + if (!res || + (PQresultStatus(res) != PGRES_COMMAND_OK && + PQresultStatus(res) != PGRES_TUPLES_OK)) + { + dblink_res_error(conn, conname, res, fail, + "while fetching from cursor \"%s\"", curname); + return (Datum) 0; + } + else if (PQresultStatus(res) == PGRES_COMMAND_OK) + { + /* cursor does not exist - closed already or bad name */ + PQclear(res); + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_NAME), + errmsg("cursor \"%s\" does not exist", curname))); + } + + materializeResult(fcinfo, conn, res); + return (Datum) 0; +} + +/* + * Note: this is the new preferred version of dblink + */ +PG_FUNCTION_INFO_V1(dblink_record); +Datum +dblink_record(PG_FUNCTION_ARGS) +{ + return dblink_record_internal(fcinfo, false); +} + +PG_FUNCTION_INFO_V1(dblink_send_query); +Datum +dblink_send_query(PG_FUNCTION_ARGS) +{ + PGconn *conn; + char *sql; + int retval; + + if (PG_NARGS() == 2) + { + conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0))); + sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); + } + else + /* shouldn't happen */ + elog(ERROR, "wrong number of arguments"); + + /* async query send */ + retval = PQsendQuery(conn, sql); + if (retval != 1) + elog(NOTICE, "could not send query: %s", pchomp(PQerrorMessage(conn))); + + PG_RETURN_INT32(retval); +} + +PG_FUNCTION_INFO_V1(dblink_get_result); +Datum +dblink_get_result(PG_FUNCTION_ARGS) +{ + return dblink_record_internal(fcinfo, true); +} + +static Datum +dblink_record_internal(FunctionCallInfo fcinfo, bool is_async) +{ + PGconn *volatile conn = NULL; + volatile bool freeconn = false; + + prepTuplestoreResult(fcinfo); + + dblink_init(); + + PG_TRY(); + { + char *sql = NULL; + char *conname = NULL; + bool fail = true; /* default to backward compatible */ + + if (!is_async) + { + if (PG_NARGS() == 3) + { + /* text,text,bool */ + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); + fail = PG_GETARG_BOOL(2); + dblink_get_conn(conname, &conn, &conname, &freeconn); + } + else if (PG_NARGS() == 2) + { + /* text,text or text,bool */ + if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID) + { + sql = text_to_cstring(PG_GETARG_TEXT_PP(0)); + fail = PG_GETARG_BOOL(1); + conn = pconn->conn; + } + else + { + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); + dblink_get_conn(conname, &conn, &conname, &freeconn); + } + } + else if (PG_NARGS() == 1) + { + /* text */ + conn = pconn->conn; + sql = text_to_cstring(PG_GETARG_TEXT_PP(0)); + } + else + /* shouldn't happen */ + elog(ERROR, "wrong number of arguments"); + } + else /* is_async */ + { + /* get async result */ + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + if (PG_NARGS() == 2) + { + /* text,bool */ + fail = PG_GETARG_BOOL(1); + conn = dblink_get_named_conn(conname); + } + else if (PG_NARGS() == 1) + { + /* text */ + conn = dblink_get_named_conn(conname); + } + else + /* shouldn't happen */ + elog(ERROR, "wrong number of arguments"); + } + + if (!conn) + dblink_conn_not_avail(conname); + + if (!is_async) + { + /* synchronous query, use efficient tuple collection method */ + materializeQueryResult(fcinfo, conn, conname, sql, fail); + } + else + { + /* async result retrieval, do it the old way */ + PGresult *res = PQgetResult(conn); + + /* NULL means we're all done with the async results */ + if (res) + { + if (PQresultStatus(res) != PGRES_COMMAND_OK && + PQresultStatus(res) != PGRES_TUPLES_OK) + { + dblink_res_error(conn, conname, res, fail, + "while executing query"); + /* if fail isn't set, we'll return an empty query result */ + } + else + { + materializeResult(fcinfo, conn, res); + } + } + } + } + PG_FINALLY(); + { + /* if needed, close the connection to the database */ + if (freeconn) + libpqsrv_disconnect(conn); + } + PG_END_TRY(); + + return (Datum) 0; +} + +/* + * Verify function caller can handle a tuplestore result, and set up for that. + * + * Note: if the caller returns without actually creating a tuplestore, the + * executor will treat the function result as an empty set. + */ +static void +prepTuplestoreResult(FunctionCallInfo fcinfo) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + /* check to see if query supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* let the executor know we're sending back a tuplestore */ + rsinfo->returnMode = SFRM_Materialize; + + /* caller must fill these to return a non-empty result */ + rsinfo->setResult = NULL; + rsinfo->setDesc = NULL; +} + +/* + * Copy the contents of the PGresult into a tuplestore to be returned + * as the result of the current function. + * The PGresult will be released in this function. + */ +static void +materializeResult(FunctionCallInfo fcinfo, PGconn *conn, PGresult *res) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + /* prepTuplestoreResult must have been called previously */ + Assert(rsinfo->returnMode == SFRM_Materialize); + + PG_TRY(); + { + TupleDesc tupdesc; + bool is_sql_cmd; + int ntuples; + int nfields; + + if (PQresultStatus(res) == PGRES_COMMAND_OK) + { + is_sql_cmd = true; + + /* + * need a tuple descriptor representing one TEXT column to return + * the command status string as our result tuple + */ + tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status", + TEXTOID, -1, 0); + ntuples = 1; + nfields = 1; + } + else + { + Assert(PQresultStatus(res) == PGRES_TUPLES_OK); + + is_sql_cmd = false; + + /* get a tuple descriptor for our result type */ + switch (get_call_result_type(fcinfo, NULL, &tupdesc)) + { + case TYPEFUNC_COMPOSITE: + /* success */ + break; + case TYPEFUNC_RECORD: + /* failed to determine actual type of RECORD */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + break; + default: + /* result type isn't composite */ + elog(ERROR, "return type must be a row type"); + break; + } + + /* make sure we have a persistent copy of the tupdesc */ + tupdesc = CreateTupleDescCopy(tupdesc); + ntuples = PQntuples(res); + nfields = PQnfields(res); + } + + /* + * check result and tuple descriptor have the same number of columns + */ + if (nfields != tupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("remote query result rowtype does not match " + "the specified FROM clause rowtype"))); + + if (ntuples > 0) + { + AttInMetadata *attinmeta; + int nestlevel = -1; + Tuplestorestate *tupstore; + MemoryContext oldcontext; + int row; + char **values; + + attinmeta = TupleDescGetAttInMetadata(tupdesc); + + /* Set GUCs to ensure we read GUC-sensitive data types correctly */ + if (!is_sql_cmd) + nestlevel = applyRemoteGucs(conn); + + oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + values = palloc_array(char *, nfields); + + /* put all tuples into the tuplestore */ + for (row = 0; row < ntuples; row++) + { + HeapTuple tuple; + + if (!is_sql_cmd) + { + int i; + + for (i = 0; i < nfields; i++) + { + if (PQgetisnull(res, row, i)) + values[i] = NULL; + else + values[i] = PQgetvalue(res, row, i); + } + } + else + { + values[0] = PQcmdStatus(res); + } + + /* build the tuple and put it into the tuplestore. */ + tuple = BuildTupleFromCStrings(attinmeta, values); + tuplestore_puttuple(tupstore, tuple); + } + + /* clean up GUC settings, if we changed any */ + restoreLocalGucs(nestlevel); + } + } + PG_FINALLY(); + { + /* be sure to release the libpq result */ + PQclear(res); + } + PG_END_TRY(); +} + +/* + * Execute the given SQL command and store its results into a tuplestore + * to be returned as the result of the current function. + * + * This is equivalent to PQexec followed by materializeResult, but we make + * use of libpq's single-row mode to avoid accumulating the whole result + * inside libpq before it gets transferred to the tuplestore. + */ +static void +materializeQueryResult(FunctionCallInfo fcinfo, + PGconn *conn, + const char *conname, + const char *sql, + bool fail) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + PGresult *volatile res = NULL; + volatile storeInfo sinfo = {0}; + + /* prepTuplestoreResult must have been called previously */ + Assert(rsinfo->returnMode == SFRM_Materialize); + + sinfo.fcinfo = fcinfo; + + PG_TRY(); + { + /* Create short-lived memory context for data conversions */ + sinfo.tmpcontext = AllocSetContextCreate(CurrentMemoryContext, + "dblink temporary context", + ALLOCSET_DEFAULT_SIZES); + + /* execute query, collecting any tuples into the tuplestore */ + res = storeQueryResult(&sinfo, conn, sql); + + if (!res || + (PQresultStatus(res) != PGRES_COMMAND_OK && + PQresultStatus(res) != PGRES_TUPLES_OK)) + { + /* + * dblink_res_error will clear the passed PGresult, so we need + * this ugly dance to avoid doing so twice during error exit + */ + PGresult *res1 = res; + + res = NULL; + dblink_res_error(conn, conname, res1, fail, + "while executing query"); + /* if fail isn't set, we'll return an empty query result */ + } + else if (PQresultStatus(res) == PGRES_COMMAND_OK) + { + /* + * storeRow didn't get called, so we need to convert the command + * status string to a tuple manually + */ + TupleDesc tupdesc; + AttInMetadata *attinmeta; + Tuplestorestate *tupstore; + HeapTuple tuple; + char *values[1]; + MemoryContext oldcontext; + + /* + * need a tuple descriptor representing one TEXT column to return + * the command status string as our result tuple + */ + tupdesc = CreateTemplateTupleDesc(1); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status", + TEXTOID, -1, 0); + attinmeta = TupleDescGetAttInMetadata(tupdesc); + + oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + values[0] = PQcmdStatus(res); + + /* build the tuple and put it into the tuplestore. */ + tuple = BuildTupleFromCStrings(attinmeta, values); + tuplestore_puttuple(tupstore, tuple); + + PQclear(res); + res = NULL; + } + else + { + Assert(PQresultStatus(res) == PGRES_TUPLES_OK); + /* storeRow should have created a tuplestore */ + Assert(rsinfo->setResult != NULL); + + PQclear(res); + res = NULL; + } + + /* clean up data conversion short-lived memory context */ + if (sinfo.tmpcontext != NULL) + MemoryContextDelete(sinfo.tmpcontext); + sinfo.tmpcontext = NULL; + + PQclear(sinfo.last_res); + sinfo.last_res = NULL; + PQclear(sinfo.cur_res); + sinfo.cur_res = NULL; + } + PG_CATCH(); + { + /* be sure to release any libpq result we collected */ + PQclear(res); + PQclear(sinfo.last_res); + PQclear(sinfo.cur_res); + /* and clear out any pending data in libpq */ + while ((res = PQgetResult(conn)) != NULL) + PQclear(res); + PG_RE_THROW(); + } + PG_END_TRY(); +} + +/* + * Execute query, and send any result rows to sinfo->tuplestore. + */ +static PGresult * +storeQueryResult(volatile storeInfo *sinfo, PGconn *conn, const char *sql) +{ + bool first = true; + int nestlevel = -1; + PGresult *res; + + if (!PQsendQuery(conn, sql)) + elog(ERROR, "could not send query: %s", pchomp(PQerrorMessage(conn))); + + if (!PQsetSingleRowMode(conn)) /* shouldn't fail */ + elog(ERROR, "failed to set single-row mode for dblink query"); + + for (;;) + { + CHECK_FOR_INTERRUPTS(); + + sinfo->cur_res = PQgetResult(conn); + if (!sinfo->cur_res) + break; + + if (PQresultStatus(sinfo->cur_res) == PGRES_SINGLE_TUPLE) + { + /* got one row from possibly-bigger resultset */ + + /* + * Set GUCs to ensure we read GUC-sensitive data types correctly. + * We shouldn't do this until we have a row in hand, to ensure + * libpq has seen any earlier ParameterStatus protocol messages. + */ + if (first && nestlevel < 0) + nestlevel = applyRemoteGucs(conn); + + storeRow(sinfo, sinfo->cur_res, first); + + PQclear(sinfo->cur_res); + sinfo->cur_res = NULL; + first = false; + } + else + { + /* if empty resultset, fill tuplestore header */ + if (first && PQresultStatus(sinfo->cur_res) == PGRES_TUPLES_OK) + storeRow(sinfo, sinfo->cur_res, first); + + /* store completed result at last_res */ + PQclear(sinfo->last_res); + sinfo->last_res = sinfo->cur_res; + sinfo->cur_res = NULL; + first = true; + } + } + + /* clean up GUC settings, if we changed any */ + restoreLocalGucs(nestlevel); + + /* return last_res */ + res = sinfo->last_res; + sinfo->last_res = NULL; + return res; +} + +/* + * Send single row to sinfo->tuplestore. + * + * If "first" is true, create the tuplestore using PGresult's metadata + * (in this case the PGresult might contain either zero or one row). + */ +static void +storeRow(volatile storeInfo *sinfo, PGresult *res, bool first) +{ + int nfields = PQnfields(res); + HeapTuple tuple; + int i; + MemoryContext oldcontext; + + if (first) + { + /* Prepare for new result set */ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) sinfo->fcinfo->resultinfo; + TupleDesc tupdesc; + + /* + * It's possible to get more than one result set if the query string + * contained multiple SQL commands. In that case, we follow PQexec's + * traditional behavior of throwing away all but the last result. + */ + if (sinfo->tuplestore) + tuplestore_end(sinfo->tuplestore); + sinfo->tuplestore = NULL; + + /* get a tuple descriptor for our result type */ + switch (get_call_result_type(sinfo->fcinfo, NULL, &tupdesc)) + { + case TYPEFUNC_COMPOSITE: + /* success */ + break; + case TYPEFUNC_RECORD: + /* failed to determine actual type of RECORD */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + break; + default: + /* result type isn't composite */ + elog(ERROR, "return type must be a row type"); + break; + } + + /* make sure we have a persistent copy of the tupdesc */ + tupdesc = CreateTupleDescCopy(tupdesc); + + /* check result and tuple descriptor have the same number of columns */ + if (nfields != tupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("remote query result rowtype does not match " + "the specified FROM clause rowtype"))); + + /* Prepare attinmeta for later data conversions */ + sinfo->attinmeta = TupleDescGetAttInMetadata(tupdesc); + + /* Create a new, empty tuplestore */ + oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); + sinfo->tuplestore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->setResult = sinfo->tuplestore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + /* Done if empty resultset */ + if (PQntuples(res) == 0) + return; + + /* + * Set up sufficiently-wide string pointers array; this won't change + * in size so it's easy to preallocate. + */ + if (sinfo->cstrs) + pfree(sinfo->cstrs); + sinfo->cstrs = palloc_array(char *, nfields); + } + + /* Should have a single-row result if we get here */ + Assert(PQntuples(res) == 1); + + /* + * Do the following work in a temp context that we reset after each tuple. + * This cleans up not only the data we have direct access to, but any + * cruft the I/O functions might leak. + */ + oldcontext = MemoryContextSwitchTo(sinfo->tmpcontext); + + /* + * Fill cstrs with null-terminated strings of column values. + */ + for (i = 0; i < nfields; i++) + { + if (PQgetisnull(res, 0, i)) + sinfo->cstrs[i] = NULL; + else + sinfo->cstrs[i] = PQgetvalue(res, 0, i); + } + + /* Convert row to a tuple, and add it to the tuplestore */ + tuple = BuildTupleFromCStrings(sinfo->attinmeta, sinfo->cstrs); + + tuplestore_puttuple(sinfo->tuplestore, tuple); + + /* Clean up */ + MemoryContextSwitchTo(oldcontext); + MemoryContextReset(sinfo->tmpcontext); +} + +/* + * List all open dblink connections by name. + * Returns an array of all connection names. + * Takes no params + */ +PG_FUNCTION_INFO_V1(dblink_get_connections); +Datum +dblink_get_connections(PG_FUNCTION_ARGS) +{ + HASH_SEQ_STATUS status; + remoteConnHashEnt *hentry; + ArrayBuildState *astate = NULL; + + if (remoteConnHash) + { + hash_seq_init(&status, remoteConnHash); + while ((hentry = (remoteConnHashEnt *) hash_seq_search(&status)) != NULL) + { + /* stash away current value */ + astate = accumArrayResult(astate, + CStringGetTextDatum(hentry->name), + false, TEXTOID, CurrentMemoryContext); + } + } + + if (astate) + PG_RETURN_DATUM(makeArrayResult(astate, + CurrentMemoryContext)); + else + PG_RETURN_NULL(); +} + +/* + * Checks if a given remote connection is busy + * + * Returns 1 if the connection is busy, 0 otherwise + * Params: + * text connection_name - name of the connection to check + * + */ +PG_FUNCTION_INFO_V1(dblink_is_busy); +Datum +dblink_is_busy(PG_FUNCTION_ARGS) +{ + PGconn *conn; + + dblink_init(); + conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0))); + + PQconsumeInput(conn); + PG_RETURN_INT32(PQisBusy(conn)); +} + +/* + * Cancels a running request on a connection + * + * Returns text: + * "OK" if the cancel request has been sent correctly, + * an error message otherwise + * + * Params: + * text connection_name - name of the connection to check + * + */ +PG_FUNCTION_INFO_V1(dblink_cancel_query); +Datum +dblink_cancel_query(PG_FUNCTION_ARGS) +{ + int res; + PGconn *conn; + PGcancel *cancel; + char errbuf[256]; + + dblink_init(); + conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0))); + cancel = PQgetCancel(conn); + + res = PQcancel(cancel, errbuf, 256); + PQfreeCancel(cancel); + + if (res == 1) + PG_RETURN_TEXT_P(cstring_to_text("OK")); + else + PG_RETURN_TEXT_P(cstring_to_text(errbuf)); +} + + +/* + * Get error message from a connection + * + * Returns text: + * "OK" if no error, an error message otherwise + * + * Params: + * text connection_name - name of the connection to check + * + */ +PG_FUNCTION_INFO_V1(dblink_error_message); +Datum +dblink_error_message(PG_FUNCTION_ARGS) +{ + char *msg; + PGconn *conn; + + dblink_init(); + conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0))); + + msg = PQerrorMessage(conn); + if (msg == NULL || msg[0] == '\0') + PG_RETURN_TEXT_P(cstring_to_text("OK")); + else + PG_RETURN_TEXT_P(cstring_to_text(pchomp(msg))); +} + +/* + * Execute an SQL non-SELECT command + */ +PG_FUNCTION_INFO_V1(dblink_exec); +Datum +dblink_exec(PG_FUNCTION_ARGS) +{ + text *volatile sql_cmd_status = NULL; + PGconn *volatile conn = NULL; + volatile bool freeconn = false; + + dblink_init(); + + PG_TRY(); + { + PGresult *res = NULL; + char *sql = NULL; + char *conname = NULL; + bool fail = true; /* default to backward compatible behavior */ + + if (PG_NARGS() == 3) + { + /* must be text,text,bool */ + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); + fail = PG_GETARG_BOOL(2); + dblink_get_conn(conname, &conn, &conname, &freeconn); + } + else if (PG_NARGS() == 2) + { + /* might be text,text or text,bool */ + if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID) + { + sql = text_to_cstring(PG_GETARG_TEXT_PP(0)); + fail = PG_GETARG_BOOL(1); + conn = pconn->conn; + } + else + { + conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); + dblink_get_conn(conname, &conn, &conname, &freeconn); + } + } + else if (PG_NARGS() == 1) + { + /* must be single text argument */ + conn = pconn->conn; + sql = text_to_cstring(PG_GETARG_TEXT_PP(0)); + } + else + /* shouldn't happen */ + elog(ERROR, "wrong number of arguments"); + + if (!conn) + dblink_conn_not_avail(conname); + + res = PQexec(conn, sql); + if (!res || + (PQresultStatus(res) != PGRES_COMMAND_OK && + PQresultStatus(res) != PGRES_TUPLES_OK)) + { + dblink_res_error(conn, conname, res, fail, + "while executing command"); + + /* + * and save a copy of the command status string to return as our + * result tuple + */ + sql_cmd_status = cstring_to_text("ERROR"); + } + else if (PQresultStatus(res) == PGRES_COMMAND_OK) + { + /* + * and save a copy of the command status string to return as our + * result tuple + */ + sql_cmd_status = cstring_to_text(PQcmdStatus(res)); + PQclear(res); + } + else + { + PQclear(res); + ereport(ERROR, + (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED), + errmsg("statement returning results not allowed"))); + } + } + PG_FINALLY(); + { + /* if needed, close the connection to the database */ + if (freeconn) + libpqsrv_disconnect(conn); + } + PG_END_TRY(); + + PG_RETURN_TEXT_P(sql_cmd_status); +} + + +/* + * dblink_get_pkey + * + * Return list of primary key fields for the supplied relation, + * or NULL if none exists. + */ +PG_FUNCTION_INFO_V1(dblink_get_pkey); +Datum +dblink_get_pkey(PG_FUNCTION_ARGS) +{ + int16 indnkeyatts; + char **results; + FuncCallContext *funcctx; + int32 call_cntr; + int32 max_calls; + AttInMetadata *attinmeta; + MemoryContext oldcontext; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + Relation rel; + TupleDesc tupdesc; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* open target relation */ + rel = get_rel_from_relname(PG_GETARG_TEXT_PP(0), AccessShareLock, ACL_SELECT); + + /* get the array of attnums */ + results = get_pkey_attnames(rel, &indnkeyatts); + + relation_close(rel, AccessShareLock); + + /* + * need a tuple descriptor representing one INT and one TEXT column + */ + tupdesc = CreateTemplateTupleDesc(2); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "position", + INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "colname", + TEXTOID, -1, 0); + + /* + * Generate attribute metadata needed later to produce tuples from raw + * C strings + */ + attinmeta = TupleDescGetAttInMetadata(tupdesc); + funcctx->attinmeta = attinmeta; + + if ((results != NULL) && (indnkeyatts > 0)) + { + funcctx->max_calls = indnkeyatts; + + /* got results, keep track of them */ + funcctx->user_fctx = results; + } + else + { + /* fast track when no results */ + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + /* + * initialize per-call variables + */ + call_cntr = funcctx->call_cntr; + max_calls = funcctx->max_calls; + + results = (char **) funcctx->user_fctx; + attinmeta = funcctx->attinmeta; + + if (call_cntr < max_calls) /* do when there is more left to send */ + { + char **values; + HeapTuple tuple; + Datum result; + + values = palloc_array(char *, 2); + values[0] = psprintf("%d", call_cntr + 1); + values[1] = results[call_cntr]; + + /* build the tuple */ + tuple = BuildTupleFromCStrings(attinmeta, values); + + /* make the tuple into a datum */ + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + else + { + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); + } +} + + +/* + * dblink_build_sql_insert + * + * Used to generate an SQL insert statement + * based on an existing tuple in a local relation. + * This is useful for selectively replicating data + * to another server via dblink. + * + * API: + * - name of local table of interest + * - an int2vector of attnums which will be used + * to identify the local tuple of interest + * - number of attnums in pkattnums + * - text array of key values which will be used + * to identify the local tuple of interest + * - text array of key values which will be used + * to build the string for execution remotely. These are substituted + * for their counterparts in src_pkattvals_arry + */ +PG_FUNCTION_INFO_V1(dblink_build_sql_insert); +Datum +dblink_build_sql_insert(PG_FUNCTION_ARGS) +{ + text *relname_text = PG_GETARG_TEXT_PP(0); + int2vector *pkattnums_arg = (int2vector *) PG_GETARG_POINTER(1); + int32 pknumatts_arg = PG_GETARG_INT32(2); + ArrayType *src_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3); + ArrayType *tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(4); + Relation rel; + int *pkattnums; + int pknumatts; + char **src_pkattvals; + char **tgt_pkattvals; + int src_nitems; + int tgt_nitems; + char *sql; + + /* + * Open target relation. + */ + rel = get_rel_from_relname(relname_text, AccessShareLock, ACL_SELECT); + + /* + * Process pkattnums argument. + */ + validate_pkattnums(rel, pkattnums_arg, pknumatts_arg, + &pkattnums, &pknumatts); + + /* + * Source array is made up of key values that will be used to locate the + * tuple of interest from the local system. + */ + src_pkattvals = get_text_array_contents(src_pkattvals_arry, &src_nitems); + + /* + * There should be one source array key value for each key attnum + */ + if (src_nitems != pknumatts) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("source key array length must match number of key attributes"))); + + /* + * Target array is made up of key values that will be used to build the + * SQL string for use on the remote system. + */ + tgt_pkattvals = get_text_array_contents(tgt_pkattvals_arry, &tgt_nitems); + + /* + * There should be one target array key value for each key attnum + */ + if (tgt_nitems != pknumatts) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("target key array length must match number of key attributes"))); + + /* + * Prep work is finally done. Go get the SQL string. + */ + sql = get_sql_insert(rel, pkattnums, pknumatts, src_pkattvals, tgt_pkattvals); + + /* + * Now we can close the relation. + */ + relation_close(rel, AccessShareLock); + + /* + * And send it + */ + PG_RETURN_TEXT_P(cstring_to_text(sql)); +} + + +/* + * dblink_build_sql_delete + * + * Used to generate an SQL delete statement. + * This is useful for selectively replicating a + * delete to another server via dblink. + * + * API: + * - name of remote table of interest + * - an int2vector of attnums which will be used + * to identify the remote tuple of interest + * - number of attnums in pkattnums + * - text array of key values which will be used + * to build the string for execution remotely. + */ +PG_FUNCTION_INFO_V1(dblink_build_sql_delete); +Datum +dblink_build_sql_delete(PG_FUNCTION_ARGS) +{ + text *relname_text = PG_GETARG_TEXT_PP(0); + int2vector *pkattnums_arg = (int2vector *) PG_GETARG_POINTER(1); + int32 pknumatts_arg = PG_GETARG_INT32(2); + ArrayType *tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3); + Relation rel; + int *pkattnums; + int pknumatts; + char **tgt_pkattvals; + int tgt_nitems; + char *sql; + + /* + * Open target relation. + */ + rel = get_rel_from_relname(relname_text, AccessShareLock, ACL_SELECT); + + /* + * Process pkattnums argument. + */ + validate_pkattnums(rel, pkattnums_arg, pknumatts_arg, + &pkattnums, &pknumatts); + + /* + * Target array is made up of key values that will be used to build the + * SQL string for use on the remote system. + */ + tgt_pkattvals = get_text_array_contents(tgt_pkattvals_arry, &tgt_nitems); + + /* + * There should be one target array key value for each key attnum + */ + if (tgt_nitems != pknumatts) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("target key array length must match number of key attributes"))); + + /* + * Prep work is finally done. Go get the SQL string. + */ + sql = get_sql_delete(rel, pkattnums, pknumatts, tgt_pkattvals); + + /* + * Now we can close the relation. + */ + relation_close(rel, AccessShareLock); + + /* + * And send it + */ + PG_RETURN_TEXT_P(cstring_to_text(sql)); +} + + +/* + * dblink_build_sql_update + * + * Used to generate an SQL update statement + * based on an existing tuple in a local relation. + * This is useful for selectively replicating data + * to another server via dblink. + * + * API: + * - name of local table of interest + * - an int2vector of attnums which will be used + * to identify the local tuple of interest + * - number of attnums in pkattnums + * - text array of key values which will be used + * to identify the local tuple of interest + * - text array of key values which will be used + * to build the string for execution remotely. These are substituted + * for their counterparts in src_pkattvals_arry + */ +PG_FUNCTION_INFO_V1(dblink_build_sql_update); +Datum +dblink_build_sql_update(PG_FUNCTION_ARGS) +{ + text *relname_text = PG_GETARG_TEXT_PP(0); + int2vector *pkattnums_arg = (int2vector *) PG_GETARG_POINTER(1); + int32 pknumatts_arg = PG_GETARG_INT32(2); + ArrayType *src_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3); + ArrayType *tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(4); + Relation rel; + int *pkattnums; + int pknumatts; + char **src_pkattvals; + char **tgt_pkattvals; + int src_nitems; + int tgt_nitems; + char *sql; + + /* + * Open target relation. + */ + rel = get_rel_from_relname(relname_text, AccessShareLock, ACL_SELECT); + + /* + * Process pkattnums argument. + */ + validate_pkattnums(rel, pkattnums_arg, pknumatts_arg, + &pkattnums, &pknumatts); + + /* + * Source array is made up of key values that will be used to locate the + * tuple of interest from the local system. + */ + src_pkattvals = get_text_array_contents(src_pkattvals_arry, &src_nitems); + + /* + * There should be one source array key value for each key attnum + */ + if (src_nitems != pknumatts) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("source key array length must match number of key attributes"))); + + /* + * Target array is made up of key values that will be used to build the + * SQL string for use on the remote system. + */ + tgt_pkattvals = get_text_array_contents(tgt_pkattvals_arry, &tgt_nitems); + + /* + * There should be one target array key value for each key attnum + */ + if (tgt_nitems != pknumatts) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("target key array length must match number of key attributes"))); + + /* + * Prep work is finally done. Go get the SQL string. + */ + sql = get_sql_update(rel, pkattnums, pknumatts, src_pkattvals, tgt_pkattvals); + + /* + * Now we can close the relation. + */ + relation_close(rel, AccessShareLock); + + /* + * And send it + */ + PG_RETURN_TEXT_P(cstring_to_text(sql)); +} + +/* + * dblink_current_query + * return the current query string + * to allow its use in (among other things) + * rewrite rules + */ +PG_FUNCTION_INFO_V1(dblink_current_query); +Datum +dblink_current_query(PG_FUNCTION_ARGS) +{ + /* This is now just an alias for the built-in function current_query() */ + PG_RETURN_DATUM(current_query(fcinfo)); +} + +/* + * Retrieve async notifications for a connection. + * + * Returns a setof record of notifications, or an empty set if none received. + * Can optionally take a named connection as parameter, but uses the unnamed + * connection per default. + * + */ +#define DBLINK_NOTIFY_COLS 3 + +PG_FUNCTION_INFO_V1(dblink_get_notify); +Datum +dblink_get_notify(PG_FUNCTION_ARGS) +{ + PGconn *conn; + PGnotify *notify; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + dblink_init(); + if (PG_NARGS() == 1) + conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0))); + else + conn = pconn->conn; + + InitMaterializedSRF(fcinfo, 0); + + PQconsumeInput(conn); + while ((notify = PQnotifies(conn)) != NULL) + { + Datum values[DBLINK_NOTIFY_COLS]; + bool nulls[DBLINK_NOTIFY_COLS]; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + if (notify->relname != NULL) + values[0] = CStringGetTextDatum(notify->relname); + else + nulls[0] = true; + + values[1] = Int32GetDatum(notify->be_pid); + + if (notify->extra != NULL) + values[2] = CStringGetTextDatum(notify->extra); + else + nulls[2] = true; + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + PQfreemem(notify); + PQconsumeInput(conn); + } + + return (Datum) 0; +} + +/* + * Validate the options given to a dblink foreign server or user mapping. + * Raise an error if any option is invalid. + * + * We just check the names of options here, so semantic errors in options, + * such as invalid numeric format, will be detected at the attempt to connect. + */ +PG_FUNCTION_INFO_V1(dblink_fdw_validator); +Datum +dblink_fdw_validator(PG_FUNCTION_ARGS) +{ + List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + Oid context = PG_GETARG_OID(1); + ListCell *cell; + + static const PQconninfoOption *options = NULL; + + /* + * Get list of valid libpq options. + * + * To avoid unnecessary work, we get the list once and use it throughout + * the lifetime of this backend process. We don't need to care about + * memory context issues, because PQconndefaults allocates with malloc. + */ + if (!options) + { + options = PQconndefaults(); + if (!options) /* assume reason for failure is OOM */ + ereport(ERROR, + (errcode(ERRCODE_FDW_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Could not get libpq's default connection options."))); + } + + /* Validate each supplied option. */ + foreach(cell, options_list) + { + DefElem *def = (DefElem *) lfirst(cell); + + if (!is_valid_dblink_option(options, def->defname, context)) + { + /* + * Unknown option, or invalid option for the context specified, so + * complain about it. Provide a hint with a valid option that + * looks similar, if there is one. + */ + const PQconninfoOption *opt; + const char *closest_match; + ClosestMatchState match_state; + bool has_valid_options = false; + + initClosestMatch(&match_state, def->defname, 4); + for (opt = options; opt->keyword; opt++) + { + if (is_valid_dblink_option(options, opt->keyword, context)) + { + has_valid_options = true; + updateClosestMatch(&match_state, opt->keyword); + } + } + + closest_match = getClosestMatch(&match_state); + ereport(ERROR, + (errcode(ERRCODE_FDW_OPTION_NAME_NOT_FOUND), + errmsg("invalid option \"%s\"", def->defname), + has_valid_options ? closest_match ? + errhint("Perhaps you meant the option \"%s\".", + closest_match) : 0 : + errhint("There are no valid options in this context."))); + } + } + + PG_RETURN_VOID(); +} + + +/************************************************************* + * internal functions + */ + + +/* + * get_pkey_attnames + * + * Get the primary key attnames for the given relation. + * Return NULL, and set indnkeyatts = 0, if no primary key exists. + */ +static char ** +get_pkey_attnames(Relation rel, int16 *indnkeyatts) +{ + Relation indexRelation; + ScanKeyData skey; + SysScanDesc scan; + HeapTuple indexTuple; + int i; + char **result = NULL; + TupleDesc tupdesc; + + /* initialize indnkeyatts to 0 in case no primary key exists */ + *indnkeyatts = 0; + + tupdesc = rel->rd_att; + + /* Prepare to scan pg_index for entries having indrelid = this rel. */ + indexRelation = table_open(IndexRelationId, AccessShareLock); + ScanKeyInit(&skey, + Anum_pg_index_indrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(rel))); + + scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true, + NULL, 1, &skey); + + while (HeapTupleIsValid(indexTuple = systable_getnext(scan))) + { + Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple); + + /* we're only interested if it is the primary key */ + if (index->indisprimary) + { + *indnkeyatts = index->indnkeyatts; + if (*indnkeyatts > 0) + { + result = palloc_array(char *, *indnkeyatts); + + for (i = 0; i < *indnkeyatts; i++) + result[i] = SPI_fname(tupdesc, index->indkey.values[i]); + } + break; + } + } + + systable_endscan(scan); + table_close(indexRelation, AccessShareLock); + + return result; +} + +/* + * Deconstruct a text[] into C-strings (note any NULL elements will be + * returned as NULL pointers) + */ +static char ** +get_text_array_contents(ArrayType *array, int *numitems) +{ + int ndim = ARR_NDIM(array); + int *dims = ARR_DIMS(array); + int nitems; + int16 typlen; + bool typbyval; + char typalign; + char **values; + char *ptr; + bits8 *bitmap; + int bitmask; + int i; + + Assert(ARR_ELEMTYPE(array) == TEXTOID); + + *numitems = nitems = ArrayGetNItems(ndim, dims); + + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &typlen, &typbyval, &typalign); + + values = palloc_array(char *, nitems); + + ptr = ARR_DATA_PTR(array); + bitmap = ARR_NULLBITMAP(array); + bitmask = 1; + + for (i = 0; i < nitems; i++) + { + if (bitmap && (*bitmap & bitmask) == 0) + { + values[i] = NULL; + } + else + { + values[i] = TextDatumGetCString(PointerGetDatum(ptr)); + ptr = att_addlength_pointer(ptr, typlen, ptr); + ptr = (char *) att_align_nominal(ptr, typalign); + } + + /* advance bitmap pointer if any */ + if (bitmap) + { + bitmask <<= 1; + if (bitmask == 0x100) + { + bitmap++; + bitmask = 1; + } + } + } + + return values; +} + +static char * +get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals) +{ + char *relname; + HeapTuple tuple; + TupleDesc tupdesc; + int natts; + StringInfoData buf; + char *val; + int key; + int i; + bool needComma; + + initStringInfo(&buf); + + /* get relation name including any needed schema prefix and quoting */ + relname = generate_relation_name(rel); + + tupdesc = rel->rd_att; + natts = tupdesc->natts; + + tuple = get_tuple_of_interest(rel, pkattnums, pknumatts, src_pkattvals); + if (!tuple) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("source row not found"))); + + appendStringInfo(&buf, "INSERT INTO %s(", relname); + + needComma = false; + for (i = 0; i < natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + if (att->attisdropped) + continue; + + if (needComma) + appendStringInfoChar(&buf, ','); + + appendStringInfoString(&buf, + quote_ident_cstr(NameStr(att->attname))); + needComma = true; + } + + appendStringInfoString(&buf, ") VALUES("); + + /* + * Note: i is physical column number (counting from 0). + */ + needComma = false; + for (i = 0; i < natts; i++) + { + if (TupleDescAttr(tupdesc, i)->attisdropped) + continue; + + if (needComma) + appendStringInfoChar(&buf, ','); + + key = get_attnum_pk_pos(pkattnums, pknumatts, i); + + if (key >= 0) + val = tgt_pkattvals[key] ? pstrdup(tgt_pkattvals[key]) : NULL; + else + val = SPI_getvalue(tuple, tupdesc, i + 1); + + if (val != NULL) + { + appendStringInfoString(&buf, quote_literal_cstr(val)); + pfree(val); + } + else + appendStringInfoString(&buf, "NULL"); + needComma = true; + } + appendStringInfoChar(&buf, ')'); + + return buf.data; +} + +static char * +get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **tgt_pkattvals) +{ + char *relname; + TupleDesc tupdesc; + StringInfoData buf; + int i; + + initStringInfo(&buf); + + /* get relation name including any needed schema prefix and quoting */ + relname = generate_relation_name(rel); + + tupdesc = rel->rd_att; + + appendStringInfo(&buf, "DELETE FROM %s WHERE ", relname); + for (i = 0; i < pknumatts; i++) + { + int pkattnum = pkattnums[i]; + Form_pg_attribute attr = TupleDescAttr(tupdesc, pkattnum); + + if (i > 0) + appendStringInfoString(&buf, " AND "); + + appendStringInfoString(&buf, + quote_ident_cstr(NameStr(attr->attname))); + + if (tgt_pkattvals[i] != NULL) + appendStringInfo(&buf, " = %s", + quote_literal_cstr(tgt_pkattvals[i])); + else + appendStringInfoString(&buf, " IS NULL"); + } + + return buf.data; +} + +static char * +get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals) +{ + char *relname; + HeapTuple tuple; + TupleDesc tupdesc; + int natts; + StringInfoData buf; + char *val; + int key; + int i; + bool needComma; + + initStringInfo(&buf); + + /* get relation name including any needed schema prefix and quoting */ + relname = generate_relation_name(rel); + + tupdesc = rel->rd_att; + natts = tupdesc->natts; + + tuple = get_tuple_of_interest(rel, pkattnums, pknumatts, src_pkattvals); + if (!tuple) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("source row not found"))); + + appendStringInfo(&buf, "UPDATE %s SET ", relname); + + /* + * Note: i is physical column number (counting from 0). + */ + needComma = false; + for (i = 0; i < natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + if (attr->attisdropped) + continue; + + if (needComma) + appendStringInfoString(&buf, ", "); + + appendStringInfo(&buf, "%s = ", + quote_ident_cstr(NameStr(attr->attname))); + + key = get_attnum_pk_pos(pkattnums, pknumatts, i); + + if (key >= 0) + val = tgt_pkattvals[key] ? pstrdup(tgt_pkattvals[key]) : NULL; + else + val = SPI_getvalue(tuple, tupdesc, i + 1); + + if (val != NULL) + { + appendStringInfoString(&buf, quote_literal_cstr(val)); + pfree(val); + } + else + appendStringInfoString(&buf, "NULL"); + needComma = true; + } + + appendStringInfoString(&buf, " WHERE "); + + for (i = 0; i < pknumatts; i++) + { + int pkattnum = pkattnums[i]; + Form_pg_attribute attr = TupleDescAttr(tupdesc, pkattnum); + + if (i > 0) + appendStringInfoString(&buf, " AND "); + + appendStringInfoString(&buf, + quote_ident_cstr(NameStr(attr->attname))); + + val = tgt_pkattvals[i]; + + if (val != NULL) + appendStringInfo(&buf, " = %s", quote_literal_cstr(val)); + else + appendStringInfoString(&buf, " IS NULL"); + } + + return buf.data; +} + +/* + * Return a properly quoted identifier. + * Uses quote_ident in quote.c + */ +static char * +quote_ident_cstr(char *rawstr) +{ + text *rawstr_text; + text *result_text; + char *result; + + rawstr_text = cstring_to_text(rawstr); + result_text = DatumGetTextPP(DirectFunctionCall1(quote_ident, + PointerGetDatum(rawstr_text))); + result = text_to_cstring(result_text); + + return result; +} + +static int +get_attnum_pk_pos(int *pkattnums, int pknumatts, int key) +{ + int i; + + /* + * Not likely a long list anyway, so just scan for the value + */ + for (i = 0; i < pknumatts; i++) + if (key == pkattnums[i]) + return i; + + return -1; +} + +static HeapTuple +get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals) +{ + char *relname; + TupleDesc tupdesc; + int natts; + StringInfoData buf; + int ret; + HeapTuple tuple; + int i; + + /* + * Connect to SPI manager + */ + if ((ret = SPI_connect()) < 0) + /* internal error */ + elog(ERROR, "SPI connect failure - returned %d", ret); + + initStringInfo(&buf); + + /* get relation name including any needed schema prefix and quoting */ + relname = generate_relation_name(rel); + + tupdesc = rel->rd_att; + natts = tupdesc->natts; + + /* + * Build sql statement to look up tuple of interest, ie, the one matching + * src_pkattvals. We used to use "SELECT *" here, but it's simpler to + * generate a result tuple that matches the table's physical structure, + * with NULLs for any dropped columns. Otherwise we have to deal with two + * different tupdescs and everything's very confusing. + */ + appendStringInfoString(&buf, "SELECT "); + + for (i = 0; i < natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + if (i > 0) + appendStringInfoString(&buf, ", "); + + if (attr->attisdropped) + appendStringInfoString(&buf, "NULL"); + else + appendStringInfoString(&buf, + quote_ident_cstr(NameStr(attr->attname))); + } + + appendStringInfo(&buf, " FROM %s WHERE ", relname); + + for (i = 0; i < pknumatts; i++) + { + int pkattnum = pkattnums[i]; + Form_pg_attribute attr = TupleDescAttr(tupdesc, pkattnum); + + if (i > 0) + appendStringInfoString(&buf, " AND "); + + appendStringInfoString(&buf, + quote_ident_cstr(NameStr(attr->attname))); + + if (src_pkattvals[i] != NULL) + appendStringInfo(&buf, " = %s", + quote_literal_cstr(src_pkattvals[i])); + else + appendStringInfoString(&buf, " IS NULL"); + } + + /* + * Retrieve the desired tuple + */ + ret = SPI_exec(buf.data, 0); + pfree(buf.data); + + /* + * Only allow one qualifying tuple + */ + if ((ret == SPI_OK_SELECT) && (SPI_processed > 1)) + ereport(ERROR, + (errcode(ERRCODE_CARDINALITY_VIOLATION), + errmsg("source criteria matched more than one record"))); + + else if (ret == SPI_OK_SELECT && SPI_processed == 1) + { + SPITupleTable *tuptable = SPI_tuptable; + + tuple = SPI_copytuple(tuptable->vals[0]); + SPI_finish(); + + return tuple; + } + else + { + /* + * no qualifying tuples + */ + SPI_finish(); + + return NULL; + } + + /* + * never reached, but keep compiler quiet + */ + return NULL; +} + +/* + * Open the relation named by relname_text, acquire specified type of lock, + * verify we have specified permissions. + * Caller must close rel when done with it. + */ +static Relation +get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclMode aclmode) +{ + RangeVar *relvar; + Relation rel; + AclResult aclresult; + + relvar = makeRangeVarFromNameList(textToQualifiedNameList(relname_text)); + rel = table_openrv(relvar, lockmode); + + aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), + aclmode); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), + RelationGetRelationName(rel)); + + return rel; +} + +/* + * generate_relation_name - copied from ruleutils.c + * Compute the name to display for a relation + * + * The result includes all necessary quoting and schema-prefixing. + */ +static char * +generate_relation_name(Relation rel) +{ + char *nspname; + char *result; + + /* Qualify the name if not visible in search path */ + if (RelationIsVisible(RelationGetRelid(rel))) + nspname = NULL; + else + nspname = get_namespace_name(rel->rd_rel->relnamespace); + + result = quote_qualified_identifier(nspname, RelationGetRelationName(rel)); + + return result; +} + + +static remoteConn * +getConnectionByName(const char *name) +{ + remoteConnHashEnt *hentry; + char *key; + + if (!remoteConnHash) + remoteConnHash = createConnHash(); + + key = pstrdup(name); + truncate_identifier(key, strlen(key), false); + hentry = (remoteConnHashEnt *) hash_search(remoteConnHash, + key, HASH_FIND, NULL); + + if (hentry) + return hentry->rconn; + + return NULL; +} + +static HTAB * +createConnHash(void) +{ + HASHCTL ctl; + + ctl.keysize = NAMEDATALEN; + ctl.entrysize = sizeof(remoteConnHashEnt); + + return hash_create("Remote Con hash", NUMCONN, &ctl, + HASH_ELEM | HASH_STRINGS); +} + +static void +createNewConnection(const char *name, remoteConn *rconn) +{ + remoteConnHashEnt *hentry; + bool found; + char *key; + + if (!remoteConnHash) + remoteConnHash = createConnHash(); + + key = pstrdup(name); + truncate_identifier(key, strlen(key), true); + hentry = (remoteConnHashEnt *) hash_search(remoteConnHash, key, + HASH_ENTER, &found); + + if (found) + { + libpqsrv_disconnect(rconn->conn); + pfree(rconn); + + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("duplicate connection name"))); + } + + hentry->rconn = rconn; + strlcpy(hentry->name, name, sizeof(hentry->name)); +} + +static void +deleteConnection(const char *name) +{ + remoteConnHashEnt *hentry; + bool found; + char *key; + + if (!remoteConnHash) + remoteConnHash = createConnHash(); + + key = pstrdup(name); + truncate_identifier(key, strlen(key), false); + hentry = (remoteConnHashEnt *) hash_search(remoteConnHash, + key, HASH_REMOVE, &found); + + if (!hentry) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("undefined connection name"))); +} + +/* + * We need to make sure that the connection made used credentials + * which were provided by the user, so check what credentials were + * used to connect and then make sure that they came from the user. + */ +static void +dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr) +{ + /* Superuser bypasses security check */ + if (superuser()) + return; + + /* If password was used to connect, make sure it was one provided */ + if (PQconnectionUsedPassword(conn) && dblink_connstr_has_pw(connstr)) + return; + +#ifdef ENABLE_GSS + /* If GSSAPI creds used to connect, make sure it was one delegated */ + if (PQconnectionUsedGSSAPI(conn) && be_gssapi_get_delegation(MyProcPort)) + return; +#endif + + /* Otherwise, fail out */ + libpqsrv_disconnect(conn); + if (rconn) + pfree(rconn); + + ereport(ERROR, + (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED), + errmsg("password or GSSAPI delegated credentials required"), + errdetail("Non-superusers may only connect using credentials they provide, eg: password in connection string or delegated GSSAPI credentials"), + errhint("Ensure provided credentials match target server's authentication method."))); +} + +/* + * Function to check if the connection string includes an explicit + * password, needed to ensure that non-superuser password-based auth + * is using a provided password and not one picked up from the + * environment. + */ +static bool +dblink_connstr_has_pw(const char *connstr) +{ + PQconninfoOption *options; + PQconninfoOption *option; + bool connstr_gives_password = false; + + options = PQconninfoParse(connstr, NULL); + if (options) + { + for (option = options; option->keyword != NULL; option++) + { + if (strcmp(option->keyword, "password") == 0) + { + if (option->val != NULL && option->val[0] != '\0') + { + connstr_gives_password = true; + break; + } + } + } + PQconninfoFree(options); + } + + return connstr_gives_password; +} + +/* + * For non-superusers, insist that the connstr specify a password, except + * if GSSAPI credentials have been delegated (and we check that they are used + * for the connection in dblink_security_check later). This prevents a + * password or GSSAPI credentials from being picked up from .pgpass, a + * service file, the environment, etc. We don't want the postgres user's + * passwords or Kerberos credentials to be accessible to non-superusers. + */ +static void +dblink_connstr_check(const char *connstr) +{ + if (superuser()) + return; + + if (dblink_connstr_has_pw(connstr)) + return; + +#ifdef ENABLE_GSS + if (be_gssapi_get_delegation(MyProcPort)) + return; +#endif + + ereport(ERROR, + (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED), + errmsg("password or GSSAPI delegated credentials required"), + errdetail("Non-superusers must provide a password in the connection string or send delegated GSSAPI credentials."))); +} + +/* + * Report an error received from the remote server + * + * res: the received error result (will be freed) + * fail: true for ERROR ereport, false for NOTICE + * fmt and following args: sprintf-style format and values for errcontext; + * the resulting string should be worded like "while " + */ +static void +dblink_res_error(PGconn *conn, const char *conname, PGresult *res, + bool fail, const char *fmt,...) +{ + int level; + char *pg_diag_sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE); + char *pg_diag_message_primary = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY); + char *pg_diag_message_detail = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL); + char *pg_diag_message_hint = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT); + char *pg_diag_context = PQresultErrorField(res, PG_DIAG_CONTEXT); + int sqlstate; + char *message_primary; + char *message_detail; + char *message_hint; + char *message_context; + va_list ap; + char dblink_context_msg[512]; + + if (fail) + level = ERROR; + else + level = NOTICE; + + if (pg_diag_sqlstate) + sqlstate = MAKE_SQLSTATE(pg_diag_sqlstate[0], + pg_diag_sqlstate[1], + pg_diag_sqlstate[2], + pg_diag_sqlstate[3], + pg_diag_sqlstate[4]); + else + sqlstate = ERRCODE_CONNECTION_FAILURE; + + message_primary = xpstrdup(pg_diag_message_primary); + message_detail = xpstrdup(pg_diag_message_detail); + message_hint = xpstrdup(pg_diag_message_hint); + message_context = xpstrdup(pg_diag_context); + + /* + * If we don't get a message from the PGresult, try the PGconn. This is + * needed because for connection-level failures, PQexec may just return + * NULL, not a PGresult at all. + */ + if (message_primary == NULL) + message_primary = pchomp(PQerrorMessage(conn)); + + /* + * Now that we've copied all the data we need out of the PGresult, it's + * safe to free it. We must do this to avoid PGresult leakage. We're + * leaking all the strings too, but those are in palloc'd memory that will + * get cleaned up eventually. + */ + PQclear(res); + + /* + * Format the basic errcontext string. Below, we'll add on something + * about the connection name. That's a violation of the translatability + * guidelines about constructing error messages out of parts, but since + * there's no translation support for dblink, there's no need to worry + * about that (yet). + */ + va_start(ap, fmt); + vsnprintf(dblink_context_msg, sizeof(dblink_context_msg), fmt, ap); + va_end(ap); + + ereport(level, + (errcode(sqlstate), + (message_primary != NULL && message_primary[0] != '\0') ? + errmsg_internal("%s", message_primary) : + errmsg("could not obtain message string for remote error"), + message_detail ? errdetail_internal("%s", message_detail) : 0, + message_hint ? errhint("%s", message_hint) : 0, + message_context ? (errcontext("%s", message_context)) : 0, + conname ? + (errcontext("%s on dblink connection named \"%s\"", + dblink_context_msg, conname)) : + (errcontext("%s on unnamed dblink connection", + dblink_context_msg)))); +} + +/* + * Obtain connection string for a foreign server + */ +static char * +get_connect_string(const char *servername) +{ + ForeignServer *foreign_server = NULL; + UserMapping *user_mapping; + ListCell *cell; + StringInfoData buf; + ForeignDataWrapper *fdw; + AclResult aclresult; + char *srvname; + + static const PQconninfoOption *options = NULL; + + initStringInfo(&buf); + + /* + * Get list of valid libpq options. + * + * To avoid unnecessary work, we get the list once and use it throughout + * the lifetime of this backend process. We don't need to care about + * memory context issues, because PQconndefaults allocates with malloc. + */ + if (!options) + { + options = PQconndefaults(); + if (!options) /* assume reason for failure is OOM */ + ereport(ERROR, + (errcode(ERRCODE_FDW_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Could not get libpq's default connection options."))); + } + + /* first gather the server connstr options */ + srvname = pstrdup(servername); + truncate_identifier(srvname, strlen(srvname), false); + foreign_server = GetForeignServerByName(srvname, true); + + if (foreign_server) + { + Oid serverid = foreign_server->serverid; + Oid fdwid = foreign_server->fdwid; + Oid userid = GetUserId(); + + user_mapping = GetUserMapping(userid, serverid); + fdw = GetForeignDataWrapper(fdwid); + + /* Check permissions, user must have usage on the server. */ + aclresult = object_aclcheck(ForeignServerRelationId, serverid, userid, ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, foreign_server->servername); + + foreach(cell, fdw->options) + { + DefElem *def = lfirst(cell); + + if (is_valid_dblink_option(options, def->defname, ForeignDataWrapperRelationId)) + appendStringInfo(&buf, "%s='%s' ", def->defname, + escape_param_str(strVal(def->arg))); + } + + foreach(cell, foreign_server->options) + { + DefElem *def = lfirst(cell); + + if (is_valid_dblink_option(options, def->defname, ForeignServerRelationId)) + appendStringInfo(&buf, "%s='%s' ", def->defname, + escape_param_str(strVal(def->arg))); + } + + foreach(cell, user_mapping->options) + { + + DefElem *def = lfirst(cell); + + if (is_valid_dblink_option(options, def->defname, UserMappingRelationId)) + appendStringInfo(&buf, "%s='%s' ", def->defname, + escape_param_str(strVal(def->arg))); + } + + return buf.data; + } + else + return NULL; +} + +/* + * Escaping libpq connect parameter strings. + * + * Replaces "'" with "\'" and "\" with "\\". + */ +static char * +escape_param_str(const char *str) +{ + const char *cp; + StringInfoData buf; + + initStringInfo(&buf); + + for (cp = str; *cp; cp++) + { + if (*cp == '\\' || *cp == '\'') + appendStringInfoChar(&buf, '\\'); + appendStringInfoChar(&buf, *cp); + } + + return buf.data; +} + +/* + * Validate the PK-attnums argument for dblink_build_sql_insert() and related + * functions, and translate to the internal representation. + * + * The user supplies an int2vector of 1-based logical attnums, plus a count + * argument (the need for the separate count argument is historical, but we + * still check it). We check that each attnum corresponds to a valid, + * non-dropped attribute of the rel. We do *not* prevent attnums from being + * listed twice, though the actual use-case for such things is dubious. + * Note that before Postgres 9.0, the user's attnums were interpreted as + * physical not logical column numbers; this was changed for future-proofing. + * + * The internal representation is a palloc'd int array of 0-based physical + * attnums. + */ +static void +validate_pkattnums(Relation rel, + int2vector *pkattnums_arg, int32 pknumatts_arg, + int **pkattnums, int *pknumatts) +{ + TupleDesc tupdesc = rel->rd_att; + int natts = tupdesc->natts; + int i; + + /* Don't take more array elements than there are */ + pknumatts_arg = Min(pknumatts_arg, pkattnums_arg->dim1); + + /* Must have at least one pk attnum selected */ + if (pknumatts_arg <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("number of key attributes must be > 0"))); + + /* Allocate output array */ + *pkattnums = palloc_array(int, pknumatts_arg); + *pknumatts = pknumatts_arg; + + /* Validate attnums and convert to internal form */ + for (i = 0; i < pknumatts_arg; i++) + { + int pkattnum = pkattnums_arg->values[i]; + int lnum; + int j; + + /* Can throw error immediately if out of range */ + if (pkattnum <= 0 || pkattnum > natts) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid attribute number %d", pkattnum))); + + /* Identify which physical column has this logical number */ + lnum = 0; + for (j = 0; j < natts; j++) + { + /* dropped columns don't count */ + if (TupleDescAttr(tupdesc, j)->attisdropped) + continue; + + if (++lnum == pkattnum) + break; + } + + if (j < natts) + (*pkattnums)[i] = j; + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid attribute number %d", pkattnum))); + } +} + +/* + * Check if the specified connection option is valid. + * + * We basically allow whatever libpq thinks is an option, with these + * restrictions: + * debug options: disallowed + * "client_encoding": disallowed + * "user": valid only in USER MAPPING options + * secure options (eg password): valid only in USER MAPPING options + * others: valid only in FOREIGN SERVER options + * + * We disallow client_encoding because it would be overridden anyway via + * PQclientEncoding; allowing it to be specified would merely promote + * confusion. + */ +static bool +is_valid_dblink_option(const PQconninfoOption *options, const char *option, + Oid context) +{ + const PQconninfoOption *opt; + + /* Look up the option in libpq result */ + for (opt = options; opt->keyword; opt++) + { + if (strcmp(opt->keyword, option) == 0) + break; + } + if (opt->keyword == NULL) + return false; + + /* Disallow debug options (particularly "replication") */ + if (strchr(opt->dispchar, 'D')) + return false; + + /* Disallow "client_encoding" */ + if (strcmp(opt->keyword, "client_encoding") == 0) + return false; + + /* + * If the option is "user" or marked secure, it should be specified only + * in USER MAPPING. Others should be specified only in SERVER. + */ + if (strcmp(opt->keyword, "user") == 0 || strchr(opt->dispchar, '*')) + { + if (context != UserMappingRelationId) + return false; + } + else + { + if (context != ForeignServerRelationId) + return false; + } + + return true; +} + +/* + * Copy the remote session's values of GUCs that affect datatype I/O + * and apply them locally in a new GUC nesting level. Returns the new + * nestlevel (which is needed by restoreLocalGucs to undo the settings), + * or -1 if no new nestlevel was needed. + * + * We use the equivalent of a function SET option to allow the settings to + * persist only until the caller calls restoreLocalGucs. If an error is + * thrown in between, guc.c will take care of undoing the settings. + */ +static int +applyRemoteGucs(PGconn *conn) +{ + static const char *const GUCsAffectingIO[] = { + "DateStyle", + "IntervalStyle" + }; + + int nestlevel = -1; + int i; + + for (i = 0; i < lengthof(GUCsAffectingIO); i++) + { + const char *gucName = GUCsAffectingIO[i]; + const char *remoteVal = PQparameterStatus(conn, gucName); + const char *localVal; + + /* + * If the remote server is pre-8.4, it won't have IntervalStyle, but + * that's okay because its output format won't be ambiguous. So just + * skip the GUC if we don't get a value for it. (We might eventually + * need more complicated logic with remote-version checks here.) + */ + if (remoteVal == NULL) + continue; + + /* + * Avoid GUC-setting overhead if the remote and local GUCs already + * have the same value. + */ + localVal = GetConfigOption(gucName, false, false); + Assert(localVal != NULL); + + if (strcmp(remoteVal, localVal) == 0) + continue; + + /* Create new GUC nest level if we didn't already */ + if (nestlevel < 0) + nestlevel = NewGUCNestLevel(); + + /* Apply the option (this will throw error on failure) */ + (void) set_config_option(gucName, remoteVal, + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + } + + return nestlevel; +} + +/* + * Restore local GUCs after they have been overlaid with remote settings. + */ +static void +restoreLocalGucs(int nestlevel) +{ + /* Do nothing if no new nestlevel was created */ + if (nestlevel > 0) + AtEOXact_GUC(true, nestlevel); +} diff --git a/contrib/dblink/dblink.control b/contrib/dblink/dblink.control new file mode 100644 index 0000000..bdd17d2 --- /dev/null +++ b/contrib/dblink/dblink.control @@ -0,0 +1,5 @@ +# dblink extension +comment = 'connect to other PostgreSQL databases from within a database' +default_version = '1.2' +module_pathname = '$libdir/dblink' +relocatable = true diff --git a/contrib/dblink/expected/dblink.out b/contrib/dblink/expected/dblink.out new file mode 100644 index 0000000..7809f58 --- /dev/null +++ b/contrib/dblink/expected/dblink.out @@ -0,0 +1,1221 @@ +CREATE EXTENSION dblink; +-- directory paths and dlsuffix are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX +\set regresslib :libdir '/regress' :dlsuffix +-- create some functions needed for tests +CREATE FUNCTION setenv(text, text) + RETURNS void + AS :'regresslib', 'regress_setenv' + LANGUAGE C STRICT; +CREATE FUNCTION wait_pid(int) + RETURNS void + AS :'regresslib' + LANGUAGE C STRICT; +\set path :abs_srcdir '/' +\set fnbody 'SELECT setenv(''PGSERVICEFILE'', ' :'path' ' || $1)' +CREATE FUNCTION set_pgservicefile(text) RETURNS void LANGUAGE SQL + AS :'fnbody'; +-- want context for notices +\set SHOW_CONTEXT always +CREATE TABLE foo(f1 int, f2 text, f3 text[], primary key (f1,f2)); +INSERT INTO foo VALUES (0,'a','{"a0","b0","c0"}'); +INSERT INTO foo VALUES (1,'b','{"a1","b1","c1"}'); +INSERT INTO foo VALUES (2,'c','{"a2","b2","c2"}'); +INSERT INTO foo VALUES (3,'d','{"a3","b3","c3"}'); +INSERT INTO foo VALUES (4,'e','{"a4","b4","c4"}'); +INSERT INTO foo VALUES (5,'f','{"a5","b5","c5"}'); +INSERT INTO foo VALUES (6,'g','{"a6","b6","c6"}'); +INSERT INTO foo VALUES (7,'h','{"a7","b7","c7"}'); +INSERT INTO foo VALUES (8,'i','{"a8","b8","c8"}'); +INSERT INTO foo VALUES (9,'j','{"a9","b9","c9"}'); +-- misc utilities +-- list the primary key fields +SELECT * +FROM dblink_get_pkey('foo'); + position | colname +----------+--------- + 1 | f1 + 2 | f2 +(2 rows) + +-- build an insert statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_insert('foo','1 2',2,'{"0", "a"}','{"99", "xyz"}'); + dblink_build_sql_insert +----------------------------------------------------------- + INSERT INTO foo(f1,f2,f3) VALUES('99','xyz','{a0,b0,c0}') +(1 row) + +-- too many pk fields, should fail +SELECT dblink_build_sql_insert('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}'); +ERROR: invalid attribute number 4 +-- build an update statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_update('foo','1 2',2,'{"0", "a"}','{"99", "xyz"}'); + dblink_build_sql_update +---------------------------------------------------------------------------------------- + UPDATE foo SET f1 = '99', f2 = 'xyz', f3 = '{a0,b0,c0}' WHERE f1 = '99' AND f2 = 'xyz' +(1 row) + +-- too many pk fields, should fail +SELECT dblink_build_sql_update('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}'); +ERROR: invalid attribute number 4 +-- build a delete statement based on a local tuple, +SELECT dblink_build_sql_delete('foo','1 2',2,'{"0", "a"}'); + dblink_build_sql_delete +--------------------------------------------- + DELETE FROM foo WHERE f1 = '0' AND f2 = 'a' +(1 row) + +-- too many pk fields, should fail +SELECT dblink_build_sql_delete('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}'); +ERROR: invalid attribute number 4 +-- repeat the test for table with primary key index with included columns +CREATE TABLE foo_1(f1 int, f2 text, f3 text[], primary key (f1,f2) include (f3)); +INSERT INTO foo_1 VALUES (0,'a','{"a0","b0","c0"}'); +INSERT INTO foo_1 VALUES (1,'b','{"a1","b1","c1"}'); +INSERT INTO foo_1 VALUES (2,'c','{"a2","b2","c2"}'); +INSERT INTO foo_1 VALUES (3,'d','{"a3","b3","c3"}'); +INSERT INTO foo_1 VALUES (4,'e','{"a4","b4","c4"}'); +INSERT INTO foo_1 VALUES (5,'f','{"a5","b5","c5"}'); +INSERT INTO foo_1 VALUES (6,'g','{"a6","b6","c6"}'); +INSERT INTO foo_1 VALUES (7,'h','{"a7","b7","c7"}'); +INSERT INTO foo_1 VALUES (8,'i','{"a8","b8","c8"}'); +INSERT INTO foo_1 VALUES (9,'j','{"a9","b9","c9"}'); +-- misc utilities +-- list the primary key fields +SELECT * +FROM dblink_get_pkey('foo_1'); + position | colname +----------+--------- + 1 | f1 + 2 | f2 +(2 rows) + +-- build an insert statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_insert('foo_1','1 2',2,'{"0", "a"}','{"99", "xyz"}'); + dblink_build_sql_insert +------------------------------------------------------------- + INSERT INTO foo_1(f1,f2,f3) VALUES('99','xyz','{a0,b0,c0}') +(1 row) + +-- too many pk fields, should fail +SELECT dblink_build_sql_insert('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}'); +ERROR: invalid attribute number 4 +-- build an update statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_update('foo_1','1 2',2,'{"0", "a"}','{"99", "xyz"}'); + dblink_build_sql_update +------------------------------------------------------------------------------------------ + UPDATE foo_1 SET f1 = '99', f2 = 'xyz', f3 = '{a0,b0,c0}' WHERE f1 = '99' AND f2 = 'xyz' +(1 row) + +-- too many pk fields, should fail +SELECT dblink_build_sql_update('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}'); +ERROR: invalid attribute number 4 +-- build a delete statement based on a local tuple, +SELECT dblink_build_sql_delete('foo_1','1 2',2,'{"0", "a"}'); + dblink_build_sql_delete +----------------------------------------------- + DELETE FROM foo_1 WHERE f1 = '0' AND f2 = 'a' +(1 row) + +-- too many pk fields, should fail +SELECT dblink_build_sql_delete('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}'); +ERROR: invalid attribute number 4 +DROP TABLE foo_1; +-- retest using a quoted and schema qualified table +CREATE SCHEMA "MySchema"; +CREATE TABLE "MySchema"."Foo"(f1 int, f2 text, f3 text[], primary key (f1,f2)); +INSERT INTO "MySchema"."Foo" VALUES (0,'a','{"a0","b0","c0"}'); +-- list the primary key fields +SELECT * +FROM dblink_get_pkey('"MySchema"."Foo"'); + position | colname +----------+--------- + 1 | f1 + 2 | f2 +(2 rows) + +-- build an insert statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_insert('"MySchema"."Foo"','1 2',2,'{"0", "a"}','{"99", "xyz"}'); + dblink_build_sql_insert +------------------------------------------------------------------------ + INSERT INTO "MySchema"."Foo"(f1,f2,f3) VALUES('99','xyz','{a0,b0,c0}') +(1 row) + +-- build an update statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_update('"MySchema"."Foo"','1 2',2,'{"0", "a"}','{"99", "xyz"}'); + dblink_build_sql_update +----------------------------------------------------------------------------------------------------- + UPDATE "MySchema"."Foo" SET f1 = '99', f2 = 'xyz', f3 = '{a0,b0,c0}' WHERE f1 = '99' AND f2 = 'xyz' +(1 row) + +-- build a delete statement based on a local tuple, +SELECT dblink_build_sql_delete('"MySchema"."Foo"','1 2',2,'{"0", "a"}'); + dblink_build_sql_delete +---------------------------------------------------------- + DELETE FROM "MySchema"."Foo" WHERE f1 = '0' AND f2 = 'a' +(1 row) + +CREATE FUNCTION connection_parameters() RETURNS text LANGUAGE SQL AS $f$ + SELECT $$dbname='$$||current_database()||$$' port=$$||current_setting('port'); +$f$; +-- regular old dblink +SELECT * +FROM dblink(connection_parameters(),'SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + a | b | c +---+---+------------ + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} +(2 rows) + +-- should generate "connection not available" error +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; +ERROR: connection not available +-- The first-level connection's backend will crash on exit given OpenLDAP +-- [2.4.24, 2.4.31]. We won't see evidence of any crash until the victim +-- process terminates and the postmaster responds. If process termination +-- entails writing a core dump, that can take awhile. Wait for the process to +-- vanish. At that point, the postmaster has called waitpid() on the crashed +-- process, and it will accept no new connections until it has reinitialized +-- the cluster. (We can't exploit pg_stat_activity, because the crash happens +-- after the backend updates shared memory to reflect its impending exit.) +DO $pl$ +DECLARE + detail text; +BEGIN + PERFORM wait_pid(crash_pid) + FROM dblink(connection_parameters(), $$ + SELECT pg_backend_pid() FROM dblink( + 'service=test_ldap '||connection_parameters(), + -- This string concatenation is a hack to shoehorn a + -- set_pgservicefile call into the SQL statement. + 'SELECT 1' || set_pgservicefile('pg_service.conf') + ) t(c int) + $$) AS t(crash_pid int); +EXCEPTION WHEN OTHERS THEN + GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL; + -- Expected error in a non-LDAP build. + IF NOT detail LIKE 'syntax error in service file%' THEN RAISE; END IF; +END +$pl$; +-- create a persistent connection +SELECT dblink_connect(connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +-- use the persistent connection +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + a | b | c +---+---+------------ + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} +(2 rows) + +-- open a cursor with bad SQL and fail_on_error set to false +SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foobar',false); +NOTICE: relation "foobar" does not exist +CONTEXT: while opening cursor "rmt_foo_cursor" on unnamed dblink connection + dblink_open +------------- + ERROR +(1 row) + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + +-- open a cursor +SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo'); + dblink_open +------------- + OK +(1 row) + +-- close the cursor +SELECT dblink_close('rmt_foo_cursor',false); + dblink_close +-------------- + OK +(1 row) + +-- open the cursor again +SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo'); + dblink_open +------------- + OK +(1 row) + +-- fetch some data +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); + a | b | c +---+---+------------ + 0 | a | {a0,b0,c0} + 1 | b | {a1,b1,c1} + 2 | c | {a2,b2,c2} + 3 | d | {a3,b3,c3} +(4 rows) + +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); + a | b | c +---+---+------------ + 4 | e | {a4,b4,c4} + 5 | f | {a5,b5,c5} + 6 | g | {a6,b6,c6} + 7 | h | {a7,b7,c7} +(4 rows) + +-- this one only finds two rows left +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); + a | b | c +---+---+------------ + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} +(2 rows) + +-- intentionally botch a fetch +SELECT * +FROM dblink_fetch('rmt_foobar_cursor',4,false) AS t(a int, b text, c text[]); +NOTICE: cursor "rmt_foobar_cursor" does not exist +CONTEXT: while fetching from cursor "rmt_foobar_cursor" on unnamed dblink connection + a | b | c +---+---+--- +(0 rows) + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + +-- close the wrong cursor +SELECT dblink_close('rmt_foobar_cursor',false); +NOTICE: cursor "rmt_foobar_cursor" does not exist +CONTEXT: while closing cursor "rmt_foobar_cursor" on unnamed dblink connection + dblink_close +-------------- + ERROR +(1 row) + +-- should generate 'cursor "rmt_foo_cursor" not found' error +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); +ERROR: cursor "rmt_foo_cursor" does not exist +CONTEXT: while fetching from cursor "rmt_foo_cursor" on unnamed dblink connection +-- this time, 'cursor "rmt_foo_cursor" not found' as a notice +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4,false) AS t(a int, b text, c text[]); +NOTICE: cursor "rmt_foo_cursor" does not exist +CONTEXT: while fetching from cursor "rmt_foo_cursor" on unnamed dblink connection + a | b | c +---+---+--- +(0 rows) + +-- close the persistent connection +SELECT dblink_disconnect(); + dblink_disconnect +------------------- + OK +(1 row) + +-- should generate "connection not available" error +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; +ERROR: connection not available +-- put more data into our table, first using arbitrary connection syntax +-- but truncate the actual return value so we can use diff to check for success +SELECT substr(dblink_exec(connection_parameters(),'INSERT INTO foo VALUES(10,''k'',''{"a10","b10","c10"}'')'),1,6); + substr +-------- + INSERT +(1 row) + +-- create a persistent connection +SELECT dblink_connect(connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +-- put more data into our table, using persistent connection syntax +-- but truncate the actual return value so we can use diff to check for success +SELECT substr(dblink_exec('INSERT INTO foo VALUES(11,''l'',''{"a11","b11","c11"}'')'),1,6); + substr +-------- + INSERT +(1 row) + +-- let's see it +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]); + a | b | c +----+---+--------------- + 0 | a | {a0,b0,c0} + 1 | b | {a1,b1,c1} + 2 | c | {a2,b2,c2} + 3 | d | {a3,b3,c3} + 4 | e | {a4,b4,c4} + 5 | f | {a5,b5,c5} + 6 | g | {a6,b6,c6} + 7 | h | {a7,b7,c7} + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} + 10 | k | {a10,b10,c10} + 11 | l | {a11,b11,c11} +(12 rows) + +-- bad remote select +SELECT * +FROM dblink('SELECT * FROM foobar',false) AS t(a int, b text, c text[]); +NOTICE: relation "foobar" does not exist +CONTEXT: while executing query on unnamed dblink connection + a | b | c +---+---+--- +(0 rows) + +-- change some data +SELECT dblink_exec('UPDATE foo SET f3[2] = ''b99'' WHERE f1 = 11'); + dblink_exec +------------- + UPDATE 1 +(1 row) + +-- let's see it +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE a = 11; + a | b | c +----+---+--------------- + 11 | l | {a11,b99,c11} +(1 row) + +-- botch a change to some other data +SELECT dblink_exec('UPDATE foobar SET f3[2] = ''b99'' WHERE f1 = 11',false); +NOTICE: relation "foobar" does not exist +CONTEXT: while executing command on unnamed dblink connection + dblink_exec +------------- + ERROR +(1 row) + +-- delete some data +SELECT dblink_exec('DELETE FROM foo WHERE f1 = 11'); + dblink_exec +------------- + DELETE 1 +(1 row) + +-- let's see it +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE a = 11; + a | b | c +---+---+--- +(0 rows) + +-- close the persistent connection +SELECT dblink_disconnect(); + dblink_disconnect +------------------- + OK +(1 row) + +-- +-- tests for the new named persistent connection syntax +-- +-- should generate "missing "=" after "myconn" in connection info string" error +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; +ERROR: could not establish connection +DETAIL: missing "=" after "myconn" in connection info string +-- create a named persistent connection +SELECT dblink_connect('myconn',connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +-- use the named persistent connection +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + a | b | c +----+---+--------------- + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} + 10 | k | {a10,b10,c10} +(3 rows) + +-- use the named persistent connection, but get it wrong +SELECT * +FROM dblink('myconn','SELECT * FROM foobar',false) AS t(a int, b text, c text[]) +WHERE t.a > 7; +NOTICE: relation "foobar" does not exist +CONTEXT: while executing query on dblink connection named "myconn" + a | b | c +---+---+--- +(0 rows) + +-- create a second named persistent connection +-- should error with "duplicate connection name" +SELECT dblink_connect('myconn',connection_parameters()); +ERROR: duplicate connection name +-- create a second named persistent connection with a new name +SELECT dblink_connect('myconn2',connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +-- use the second named persistent connection +SELECT * +FROM dblink('myconn2','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + a | b | c +----+---+--------------- + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} + 10 | k | {a10,b10,c10} +(3 rows) + +-- close the second named persistent connection +SELECT dblink_disconnect('myconn2'); + dblink_disconnect +------------------- + OK +(1 row) + +-- open a cursor incorrectly +SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foobar',false); +NOTICE: relation "foobar" does not exist +CONTEXT: while opening cursor "rmt_foo_cursor" on dblink connection named "myconn" + dblink_open +------------- + ERROR +(1 row) + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + +-- test opening cursor in a transaction +SELECT dblink_exec('myconn','BEGIN'); + dblink_exec +------------- + BEGIN +(1 row) + +-- an open transaction will prevent dblink_open() from opening its own +SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo'); + dblink_open +------------- + OK +(1 row) + +-- this should not commit the transaction because the client opened it +SELECT dblink_close('myconn','rmt_foo_cursor'); + dblink_close +-------------- + OK +(1 row) + +-- this should succeed because we have an open transaction +SELECT dblink_exec('myconn','DECLARE xact_test CURSOR FOR SELECT * FROM foo'); + dblink_exec +---------------- + DECLARE CURSOR +(1 row) + +-- commit remote transaction +SELECT dblink_exec('myconn','COMMIT'); + dblink_exec +------------- + COMMIT +(1 row) + +-- test automatic transactions for multiple cursor opens +SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo'); + dblink_open +------------- + OK +(1 row) + +-- the second cursor +SELECT dblink_open('myconn','rmt_foo_cursor2','SELECT * FROM foo'); + dblink_open +------------- + OK +(1 row) + +-- this should not commit the transaction +SELECT dblink_close('myconn','rmt_foo_cursor2'); + dblink_close +-------------- + OK +(1 row) + +-- this should succeed because we have an open transaction +SELECT dblink_exec('myconn','DECLARE xact_test CURSOR FOR SELECT * FROM foo'); + dblink_exec +---------------- + DECLARE CURSOR +(1 row) + +-- this should commit the transaction +SELECT dblink_close('myconn','rmt_foo_cursor'); + dblink_close +-------------- + OK +(1 row) + +-- this should fail because there is no open transaction +SELECT dblink_exec('myconn','DECLARE xact_test CURSOR FOR SELECT * FROM foo'); +ERROR: DECLARE CURSOR can only be used in transaction blocks +CONTEXT: while executing command on dblink connection named "myconn" +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + +-- open a cursor +SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo'); + dblink_open +------------- + OK +(1 row) + +-- fetch some data +SELECT * +FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); + a | b | c +---+---+------------ + 0 | a | {a0,b0,c0} + 1 | b | {a1,b1,c1} + 2 | c | {a2,b2,c2} + 3 | d | {a3,b3,c3} +(4 rows) + +SELECT * +FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); + a | b | c +---+---+------------ + 4 | e | {a4,b4,c4} + 5 | f | {a5,b5,c5} + 6 | g | {a6,b6,c6} + 7 | h | {a7,b7,c7} +(4 rows) + +-- this one only finds three rows left +SELECT * +FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); + a | b | c +----+---+--------------- + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} + 10 | k | {a10,b10,c10} +(3 rows) + +-- fetch some data incorrectly +SELECT * +FROM dblink_fetch('myconn','rmt_foobar_cursor',4,false) AS t(a int, b text, c text[]); +NOTICE: cursor "rmt_foobar_cursor" does not exist +CONTEXT: while fetching from cursor "rmt_foobar_cursor" on dblink connection named "myconn" + a | b | c +---+---+--- +(0 rows) + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + +-- should generate 'cursor "rmt_foo_cursor" not found' error +SELECT * +FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); +ERROR: cursor "rmt_foo_cursor" does not exist +CONTEXT: while fetching from cursor "rmt_foo_cursor" on dblink connection named "myconn" +-- close the named persistent connection +SELECT dblink_disconnect('myconn'); + dblink_disconnect +------------------- + OK +(1 row) + +-- should generate "missing "=" after "myconn" in connection info string" error +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; +ERROR: could not establish connection +DETAIL: missing "=" after "myconn" in connection info string +-- create a named persistent connection +SELECT dblink_connect('myconn',connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +-- put more data into our table, using named persistent connection syntax +-- but truncate the actual return value so we can use diff to check for success +SELECT substr(dblink_exec('myconn','INSERT INTO foo VALUES(11,''l'',''{"a11","b11","c11"}'')'),1,6); + substr +-------- + INSERT +(1 row) + +-- let's see it +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]); + a | b | c +----+---+--------------- + 0 | a | {a0,b0,c0} + 1 | b | {a1,b1,c1} + 2 | c | {a2,b2,c2} + 3 | d | {a3,b3,c3} + 4 | e | {a4,b4,c4} + 5 | f | {a5,b5,c5} + 6 | g | {a6,b6,c6} + 7 | h | {a7,b7,c7} + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} + 10 | k | {a10,b10,c10} + 11 | l | {a11,b11,c11} +(12 rows) + +-- change some data +SELECT dblink_exec('myconn','UPDATE foo SET f3[2] = ''b99'' WHERE f1 = 11'); + dblink_exec +------------- + UPDATE 1 +(1 row) + +-- let's see it +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE a = 11; + a | b | c +----+---+--------------- + 11 | l | {a11,b99,c11} +(1 row) + +-- delete some data +SELECT dblink_exec('myconn','DELETE FROM foo WHERE f1 = 11'); + dblink_exec +------------- + DELETE 1 +(1 row) + +-- let's see it +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE a = 11; + a | b | c +---+---+--- +(0 rows) + +-- close the named persistent connection +SELECT dblink_disconnect('myconn'); + dblink_disconnect +------------------- + OK +(1 row) + +-- close the named persistent connection again +-- should get 'connection "myconn" not available' error +SELECT dblink_disconnect('myconn'); +ERROR: connection "myconn" not available +-- test asynchronous queries +SELECT dblink_connect('dtest1', connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +SELECT * from + dblink_send_query('dtest1', 'select * from foo where f1 < 3') as t1; + t1 +---- + 1 +(1 row) + +SELECT dblink_connect('dtest2', connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +SELECT * from + dblink_send_query('dtest2', 'select * from foo where f1 > 2 and f1 < 7') as t1; + t1 +---- + 1 +(1 row) + +SELECT dblink_connect('dtest3', connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +SELECT * from + dblink_send_query('dtest3', 'select * from foo where f1 > 6') as t1; + t1 +---- + 1 +(1 row) + +CREATE TEMPORARY TABLE result AS +(SELECT * from dblink_get_result('dtest1') as t1(f1 int, f2 text, f3 text[])) +UNION +(SELECT * from dblink_get_result('dtest2') as t2(f1 int, f2 text, f3 text[])) +UNION +(SELECT * from dblink_get_result('dtest3') as t3(f1 int, f2 text, f3 text[])) +ORDER by f1; +-- dblink_get_connections returns an array with elements in a machine-dependent +-- ordering, so we must resort to unnesting and sorting for a stable result +create function unnest(anyarray) returns setof anyelement +language sql strict immutable as $$ +select $1[i] from generate_series(array_lower($1,1), array_upper($1,1)) as i +$$; +SELECT * FROM unnest(dblink_get_connections()) ORDER BY 1; + unnest +-------- + dtest1 + dtest2 + dtest3 +(3 rows) + +SELECT dblink_is_busy('dtest1'); + dblink_is_busy +---------------- + 0 +(1 row) + +SELECT dblink_disconnect('dtest1'); + dblink_disconnect +------------------- + OK +(1 row) + +SELECT dblink_disconnect('dtest2'); + dblink_disconnect +------------------- + OK +(1 row) + +SELECT dblink_disconnect('dtest3'); + dblink_disconnect +------------------- + OK +(1 row) + +SELECT * from result; + f1 | f2 | f3 +----+----+--------------- + 0 | a | {a0,b0,c0} + 1 | b | {a1,b1,c1} + 2 | c | {a2,b2,c2} + 3 | d | {a3,b3,c3} + 4 | e | {a4,b4,c4} + 5 | f | {a5,b5,c5} + 6 | g | {a6,b6,c6} + 7 | h | {a7,b7,c7} + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} + 10 | k | {a10,b10,c10} +(11 rows) + +SELECT dblink_connect('dtest1', connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +SELECT * from + dblink_send_query('dtest1', 'select * from foo where f1 < 3') as t1; + t1 +---- + 1 +(1 row) + +SELECT dblink_cancel_query('dtest1'); + dblink_cancel_query +--------------------- + OK +(1 row) + +SELECT dblink_error_message('dtest1'); + dblink_error_message +---------------------- + OK +(1 row) + +SELECT dblink_disconnect('dtest1'); + dblink_disconnect +------------------- + OK +(1 row) + +-- test foreign data wrapper functionality +CREATE ROLE regress_dblink_user; +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; +CREATE USER MAPPING FOR public SERVER fdtest + OPTIONS (server 'localhost'); -- fail, can't specify server here +ERROR: invalid option "server" +CREATE USER MAPPING FOR public SERVER fdtest OPTIONS (user :'USER'); +GRANT USAGE ON FOREIGN SERVER fdtest TO regress_dblink_user; +GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO regress_dblink_user; +SET SESSION AUTHORIZATION regress_dblink_user; +-- should fail +SELECT dblink_connect('myconn', 'fdtest'); +ERROR: password or GSSAPI delegated credentials required +DETAIL: Non-superusers must provide a password in the connection string or send delegated GSSAPI credentials. +-- should succeed +SELECT dblink_connect_u('myconn', 'fdtest'); + dblink_connect_u +------------------ + OK +(1 row) + +SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]); + a | b | c +----+---+--------------- + 0 | a | {a0,b0,c0} + 1 | b | {a1,b1,c1} + 2 | c | {a2,b2,c2} + 3 | d | {a3,b3,c3} + 4 | e | {a4,b4,c4} + 5 | f | {a5,b5,c5} + 6 | g | {a6,b6,c6} + 7 | h | {a7,b7,c7} + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} + 10 | k | {a10,b10,c10} +(11 rows) + +\c - - +REVOKE USAGE ON FOREIGN SERVER fdtest FROM regress_dblink_user; +REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM regress_dblink_user; +DROP USER regress_dblink_user; +DROP USER MAPPING FOR public SERVER fdtest; +DROP SERVER fdtest; +-- should fail +ALTER FOREIGN DATA WRAPPER dblink_fdw OPTIONS (nonexistent 'fdw'); +ERROR: invalid option "nonexistent" +HINT: There are no valid options in this context. +-- test repeated calls to dblink_connect +SELECT dblink_connect(connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +SELECT dblink_connect(connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +SELECT dblink_disconnect(); + dblink_disconnect +------------------- + OK +(1 row) + +-- test asynchronous notifications +SELECT dblink_connect(connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +--should return listen +SELECT dblink_exec('LISTEN regression'); + dblink_exec +------------- + LISTEN +(1 row) + +--should return listen +SELECT dblink_exec('LISTEN foobar'); + dblink_exec +------------- + LISTEN +(1 row) + +SELECT dblink_exec('NOTIFY regression'); + dblink_exec +------------- + NOTIFY +(1 row) + +SELECT dblink_exec('NOTIFY foobar'); + dblink_exec +------------- + NOTIFY +(1 row) + +SELECT notify_name, be_pid = (select t.be_pid from dblink('select pg_backend_pid()') as t(be_pid int)) AS is_self_notify, extra from dblink_get_notify(); + notify_name | is_self_notify | extra +-------------+----------------+------- + regression | t | + foobar | t | +(2 rows) + +SELECT * from dblink_get_notify(); + notify_name | be_pid | extra +-------------+--------+------- +(0 rows) + +SELECT dblink_disconnect(); + dblink_disconnect +------------------- + OK +(1 row) + +-- test dropped columns in dblink_build_sql_insert, dblink_build_sql_update +CREATE TEMP TABLE test_dropped +( + col1 INT NOT NULL DEFAULT 111, + id SERIAL PRIMARY KEY, + col2 INT NOT NULL DEFAULT 112, + col2b INT NOT NULL DEFAULT 113 +); +INSERT INTO test_dropped VALUES(default); +ALTER TABLE test_dropped + DROP COLUMN col1, + DROP COLUMN col2, + ADD COLUMN col3 VARCHAR(10) NOT NULL DEFAULT 'foo', + ADD COLUMN col4 INT NOT NULL DEFAULT 42; +SELECT dblink_build_sql_insert('test_dropped', '1', 1, + ARRAY['1'::TEXT], ARRAY['2'::TEXT]); + dblink_build_sql_insert +--------------------------------------------------------------------------- + INSERT INTO test_dropped(id,col2b,col3,col4) VALUES('2','113','foo','42') +(1 row) + +SELECT dblink_build_sql_update('test_dropped', '1', 1, + ARRAY['1'::TEXT], ARRAY['2'::TEXT]); + dblink_build_sql_update +------------------------------------------------------------------------------------------- + UPDATE test_dropped SET id = '2', col2b = '113', col3 = 'foo', col4 = '42' WHERE id = '2' +(1 row) + +SELECT dblink_build_sql_delete('test_dropped', '1', 1, + ARRAY['2'::TEXT]); + dblink_build_sql_delete +----------------------------------------- + DELETE FROM test_dropped WHERE id = '2' +(1 row) + +-- test local mimicry of remote GUC values that affect datatype I/O +SET datestyle = ISO, MDY; +SET intervalstyle = postgres; +SET timezone = UTC; +SELECT dblink_connect('myconn',connection_parameters()); + dblink_connect +---------------- + OK +(1 row) + +SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;'); + dblink_exec +------------- + SET +(1 row) + +-- single row synchronous case +SELECT * +FROM dblink('myconn', + 'SELECT * FROM (VALUES (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz); + a +------------------------ + 2013-03-12 00:00:00+00 +(1 row) + +-- multi-row synchronous case +SELECT * +FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00''), + (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz); + a +------------------------ + 2013-03-12 00:00:00+00 + 2013-03-12 00:00:00+00 +(2 rows) + +-- single-row asynchronous case +SELECT * +FROM dblink_send_query('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00'')) t'); + dblink_send_query +------------------- + 1 +(1 row) + +CREATE TEMPORARY TABLE result AS +(SELECT * from dblink_get_result('myconn') as t(t timestamptz)) +UNION ALL +(SELECT * from dblink_get_result('myconn') as t(t timestamptz)); +SELECT * FROM result; + t +------------------------ + 2013-03-12 00:00:00+00 +(1 row) + +DROP TABLE result; +-- multi-row asynchronous case +SELECT * +FROM dblink_send_query('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00''), + (''12.03.2013 00:00:00+00'')) t'); + dblink_send_query +------------------- + 1 +(1 row) + +CREATE TEMPORARY TABLE result AS +(SELECT * from dblink_get_result('myconn') as t(t timestamptz)) +UNION ALL +(SELECT * from dblink_get_result('myconn') as t(t timestamptz)) +UNION ALL +(SELECT * from dblink_get_result('myconn') as t(t timestamptz)); +SELECT * FROM result; + t +------------------------ + 2013-03-12 00:00:00+00 + 2013-03-12 00:00:00+00 +(2 rows) + +DROP TABLE result; +-- Try an ambiguous interval +SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;'); + dblink_exec +------------- + SET +(1 row) + +SELECT * +FROM dblink('myconn', + 'SELECT * FROM (VALUES (''-1 2:03:04'')) i') + AS i(i interval); + i +------------------- + -1 days -02:03:04 +(1 row) + +-- Try swapping to another format to ensure the GUCs are tracked +-- properly through a change. +CREATE TEMPORARY TABLE result (t timestamptz); +SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;'); + dblink_exec +------------- + SET +(1 row) + +INSERT INTO result + SELECT * + FROM dblink('myconn', + 'SELECT * FROM (VALUES (''03.12.2013 00:00:00+00'')) t') + AS t(a timestamptz); +SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;'); + dblink_exec +------------- + SET +(1 row) + +INSERT INTO result + SELECT * + FROM dblink('myconn', + 'SELECT * FROM (VALUES (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz); +SELECT * FROM result; + t +------------------------ + 2013-03-12 00:00:00+00 + 2013-03-12 00:00:00+00 +(2 rows) + +DROP TABLE result; +-- Check error throwing in dblink_fetch +SELECT dblink_open('myconn','error_cursor', + 'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);'); + dblink_open +------------- + OK +(1 row) + +SELECT * +FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int); + i +--- + 1 +(1 row) + +SELECT * +FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int); +ERROR: invalid input syntax for type integer: "not an int" +-- Make sure that the local settings have retained their values in spite +-- of shenanigans on the connection. +SHOW datestyle; + DateStyle +----------- + ISO, MDY +(1 row) + +SHOW intervalstyle; + IntervalStyle +--------------- + postgres +(1 row) + +-- Clean up GUC-setting tests +SELECT dblink_disconnect('myconn'); + dblink_disconnect +------------------- + OK +(1 row) + +RESET datestyle; +RESET intervalstyle; +RESET timezone; diff --git a/contrib/dblink/meson.build b/contrib/dblink/meson.build new file mode 100644 index 0000000..4c288ce --- /dev/null +++ b/contrib/dblink/meson.build @@ -0,0 +1,39 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +dblink_sources = files( + 'dblink.c', +) + +if host_system == 'windows' + dblink_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'dblink', + '--FILEDESC', 'dblink - connect to other PostgreSQL databases',]) +endif + +dblink = shared_module('dblink', + dblink_sources, + kwargs: contrib_mod_args + { + 'dependencies': contrib_mod_args['dependencies'] + [libpq], + }, +) +contrib_targets += dblink + +install_data( + 'dblink.control', + 'dblink--1.0--1.1.sql', + 'dblink--1.1--1.2.sql', + 'dblink--1.2.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'dblink', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'dblink', + ], + 'regress_args': ['--dlpath', meson.build_root() / 'src/test/regress'], + }, +} diff --git a/contrib/dblink/pg_service.conf b/contrib/dblink/pg_service.conf new file mode 100644 index 0000000..92201f0 --- /dev/null +++ b/contrib/dblink/pg_service.conf @@ -0,0 +1,7 @@ +# pg_service.conf for minimally exercising libpq use of LDAP. + +# Having failed to reach an LDAP server, libpq essentially ignores the +# "service=test_ldap" in its connection string. Contact the "discard" +# service; the test works whether or not it answers. +[test_ldap] +ldap://127.0.0.1:9/base?attribute?one?filter diff --git a/contrib/dblink/sql/dblink.sql b/contrib/dblink/sql/dblink.sql new file mode 100644 index 0000000..7870ce5 --- /dev/null +++ b/contrib/dblink/sql/dblink.sql @@ -0,0 +1,634 @@ +CREATE EXTENSION dblink; + +-- directory paths and dlsuffix are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +\getenv libdir PG_LIBDIR +\getenv dlsuffix PG_DLSUFFIX + +\set regresslib :libdir '/regress' :dlsuffix + +-- create some functions needed for tests +CREATE FUNCTION setenv(text, text) + RETURNS void + AS :'regresslib', 'regress_setenv' + LANGUAGE C STRICT; + +CREATE FUNCTION wait_pid(int) + RETURNS void + AS :'regresslib' + LANGUAGE C STRICT; + +\set path :abs_srcdir '/' +\set fnbody 'SELECT setenv(''PGSERVICEFILE'', ' :'path' ' || $1)' +CREATE FUNCTION set_pgservicefile(text) RETURNS void LANGUAGE SQL + AS :'fnbody'; + +-- want context for notices +\set SHOW_CONTEXT always + +CREATE TABLE foo(f1 int, f2 text, f3 text[], primary key (f1,f2)); +INSERT INTO foo VALUES (0,'a','{"a0","b0","c0"}'); +INSERT INTO foo VALUES (1,'b','{"a1","b1","c1"}'); +INSERT INTO foo VALUES (2,'c','{"a2","b2","c2"}'); +INSERT INTO foo VALUES (3,'d','{"a3","b3","c3"}'); +INSERT INTO foo VALUES (4,'e','{"a4","b4","c4"}'); +INSERT INTO foo VALUES (5,'f','{"a5","b5","c5"}'); +INSERT INTO foo VALUES (6,'g','{"a6","b6","c6"}'); +INSERT INTO foo VALUES (7,'h','{"a7","b7","c7"}'); +INSERT INTO foo VALUES (8,'i','{"a8","b8","c8"}'); +INSERT INTO foo VALUES (9,'j','{"a9","b9","c9"}'); + +-- misc utilities + +-- list the primary key fields +SELECT * +FROM dblink_get_pkey('foo'); + +-- build an insert statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_insert('foo','1 2',2,'{"0", "a"}','{"99", "xyz"}'); +-- too many pk fields, should fail +SELECT dblink_build_sql_insert('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}'); + +-- build an update statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_update('foo','1 2',2,'{"0", "a"}','{"99", "xyz"}'); +-- too many pk fields, should fail +SELECT dblink_build_sql_update('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}'); + +-- build a delete statement based on a local tuple, +SELECT dblink_build_sql_delete('foo','1 2',2,'{"0", "a"}'); +-- too many pk fields, should fail +SELECT dblink_build_sql_delete('foo','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}'); + +-- repeat the test for table with primary key index with included columns +CREATE TABLE foo_1(f1 int, f2 text, f3 text[], primary key (f1,f2) include (f3)); +INSERT INTO foo_1 VALUES (0,'a','{"a0","b0","c0"}'); +INSERT INTO foo_1 VALUES (1,'b','{"a1","b1","c1"}'); +INSERT INTO foo_1 VALUES (2,'c','{"a2","b2","c2"}'); +INSERT INTO foo_1 VALUES (3,'d','{"a3","b3","c3"}'); +INSERT INTO foo_1 VALUES (4,'e','{"a4","b4","c4"}'); +INSERT INTO foo_1 VALUES (5,'f','{"a5","b5","c5"}'); +INSERT INTO foo_1 VALUES (6,'g','{"a6","b6","c6"}'); +INSERT INTO foo_1 VALUES (7,'h','{"a7","b7","c7"}'); +INSERT INTO foo_1 VALUES (8,'i','{"a8","b8","c8"}'); +INSERT INTO foo_1 VALUES (9,'j','{"a9","b9","c9"}'); + +-- misc utilities + +-- list the primary key fields +SELECT * +FROM dblink_get_pkey('foo_1'); + +-- build an insert statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_insert('foo_1','1 2',2,'{"0", "a"}','{"99", "xyz"}'); +-- too many pk fields, should fail +SELECT dblink_build_sql_insert('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}'); + +-- build an update statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_update('foo_1','1 2',2,'{"0", "a"}','{"99", "xyz"}'); +-- too many pk fields, should fail +SELECT dblink_build_sql_update('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}','{"99", "xyz", "{za0,zb0,zc0}"}'); + +-- build a delete statement based on a local tuple, +SELECT dblink_build_sql_delete('foo_1','1 2',2,'{"0", "a"}'); +-- too many pk fields, should fail +SELECT dblink_build_sql_delete('foo_1','1 2 3 4',4,'{"0", "a", "{a0,b0,c0}"}'); + +DROP TABLE foo_1; + +-- retest using a quoted and schema qualified table +CREATE SCHEMA "MySchema"; +CREATE TABLE "MySchema"."Foo"(f1 int, f2 text, f3 text[], primary key (f1,f2)); +INSERT INTO "MySchema"."Foo" VALUES (0,'a','{"a0","b0","c0"}'); + +-- list the primary key fields +SELECT * +FROM dblink_get_pkey('"MySchema"."Foo"'); + +-- build an insert statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_insert('"MySchema"."Foo"','1 2',2,'{"0", "a"}','{"99", "xyz"}'); + +-- build an update statement based on a local tuple, +-- replacing the primary key values with new ones +SELECT dblink_build_sql_update('"MySchema"."Foo"','1 2',2,'{"0", "a"}','{"99", "xyz"}'); + +-- build a delete statement based on a local tuple, +SELECT dblink_build_sql_delete('"MySchema"."Foo"','1 2',2,'{"0", "a"}'); + +CREATE FUNCTION connection_parameters() RETURNS text LANGUAGE SQL AS $f$ + SELECT $$dbname='$$||current_database()||$$' port=$$||current_setting('port'); +$f$; + +-- regular old dblink +SELECT * +FROM dblink(connection_parameters(),'SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + +-- should generate "connection not available" error +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + +-- The first-level connection's backend will crash on exit given OpenLDAP +-- [2.4.24, 2.4.31]. We won't see evidence of any crash until the victim +-- process terminates and the postmaster responds. If process termination +-- entails writing a core dump, that can take awhile. Wait for the process to +-- vanish. At that point, the postmaster has called waitpid() on the crashed +-- process, and it will accept no new connections until it has reinitialized +-- the cluster. (We can't exploit pg_stat_activity, because the crash happens +-- after the backend updates shared memory to reflect its impending exit.) +DO $pl$ +DECLARE + detail text; +BEGIN + PERFORM wait_pid(crash_pid) + FROM dblink(connection_parameters(), $$ + SELECT pg_backend_pid() FROM dblink( + 'service=test_ldap '||connection_parameters(), + -- This string concatenation is a hack to shoehorn a + -- set_pgservicefile call into the SQL statement. + 'SELECT 1' || set_pgservicefile('pg_service.conf') + ) t(c int) + $$) AS t(crash_pid int); +EXCEPTION WHEN OTHERS THEN + GET STACKED DIAGNOSTICS detail = PG_EXCEPTION_DETAIL; + -- Expected error in a non-LDAP build. + IF NOT detail LIKE 'syntax error in service file%' THEN RAISE; END IF; +END +$pl$; + +-- create a persistent connection +SELECT dblink_connect(connection_parameters()); + +-- use the persistent connection +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + +-- open a cursor with bad SQL and fail_on_error set to false +SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foobar',false); + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + +-- open a cursor +SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo'); + +-- close the cursor +SELECT dblink_close('rmt_foo_cursor',false); + +-- open the cursor again +SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo'); + +-- fetch some data +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); + +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); + +-- this one only finds two rows left +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); + +-- intentionally botch a fetch +SELECT * +FROM dblink_fetch('rmt_foobar_cursor',4,false) AS t(a int, b text, c text[]); + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + +-- close the wrong cursor +SELECT dblink_close('rmt_foobar_cursor',false); + +-- should generate 'cursor "rmt_foo_cursor" not found' error +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); + +-- this time, 'cursor "rmt_foo_cursor" not found' as a notice +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4,false) AS t(a int, b text, c text[]); + +-- close the persistent connection +SELECT dblink_disconnect(); + +-- should generate "connection not available" error +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + +-- put more data into our table, first using arbitrary connection syntax +-- but truncate the actual return value so we can use diff to check for success +SELECT substr(dblink_exec(connection_parameters(),'INSERT INTO foo VALUES(10,''k'',''{"a10","b10","c10"}'')'),1,6); + +-- create a persistent connection +SELECT dblink_connect(connection_parameters()); + +-- put more data into our table, using persistent connection syntax +-- but truncate the actual return value so we can use diff to check for success +SELECT substr(dblink_exec('INSERT INTO foo VALUES(11,''l'',''{"a11","b11","c11"}'')'),1,6); + +-- let's see it +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]); + +-- bad remote select +SELECT * +FROM dblink('SELECT * FROM foobar',false) AS t(a int, b text, c text[]); + +-- change some data +SELECT dblink_exec('UPDATE foo SET f3[2] = ''b99'' WHERE f1 = 11'); + +-- let's see it +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE a = 11; + +-- botch a change to some other data +SELECT dblink_exec('UPDATE foobar SET f3[2] = ''b99'' WHERE f1 = 11',false); + +-- delete some data +SELECT dblink_exec('DELETE FROM foo WHERE f1 = 11'); + +-- let's see it +SELECT * +FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE a = 11; + +-- close the persistent connection +SELECT dblink_disconnect(); + +-- +-- tests for the new named persistent connection syntax +-- + +-- should generate "missing "=" after "myconn" in connection info string" error +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + +-- create a named persistent connection +SELECT dblink_connect('myconn',connection_parameters()); + +-- use the named persistent connection +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + +-- use the named persistent connection, but get it wrong +SELECT * +FROM dblink('myconn','SELECT * FROM foobar',false) AS t(a int, b text, c text[]) +WHERE t.a > 7; + +-- create a second named persistent connection +-- should error with "duplicate connection name" +SELECT dblink_connect('myconn',connection_parameters()); + +-- create a second named persistent connection with a new name +SELECT dblink_connect('myconn2',connection_parameters()); + +-- use the second named persistent connection +SELECT * +FROM dblink('myconn2','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + +-- close the second named persistent connection +SELECT dblink_disconnect('myconn2'); + +-- open a cursor incorrectly +SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foobar',false); + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + +-- test opening cursor in a transaction +SELECT dblink_exec('myconn','BEGIN'); + +-- an open transaction will prevent dblink_open() from opening its own +SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo'); + +-- this should not commit the transaction because the client opened it +SELECT dblink_close('myconn','rmt_foo_cursor'); + +-- this should succeed because we have an open transaction +SELECT dblink_exec('myconn','DECLARE xact_test CURSOR FOR SELECT * FROM foo'); + +-- commit remote transaction +SELECT dblink_exec('myconn','COMMIT'); + +-- test automatic transactions for multiple cursor opens +SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo'); + +-- the second cursor +SELECT dblink_open('myconn','rmt_foo_cursor2','SELECT * FROM foo'); + +-- this should not commit the transaction +SELECT dblink_close('myconn','rmt_foo_cursor2'); + +-- this should succeed because we have an open transaction +SELECT dblink_exec('myconn','DECLARE xact_test CURSOR FOR SELECT * FROM foo'); + +-- this should commit the transaction +SELECT dblink_close('myconn','rmt_foo_cursor'); + +-- this should fail because there is no open transaction +SELECT dblink_exec('myconn','DECLARE xact_test CURSOR FOR SELECT * FROM foo'); + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + +-- open a cursor +SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo'); + +-- fetch some data +SELECT * +FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); + +SELECT * +FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); + +-- this one only finds three rows left +SELECT * +FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); + +-- fetch some data incorrectly +SELECT * +FROM dblink_fetch('myconn','rmt_foobar_cursor',4,false) AS t(a int, b text, c text[]); + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + +-- should generate 'cursor "rmt_foo_cursor" not found' error +SELECT * +FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); + +-- close the named persistent connection +SELECT dblink_disconnect('myconn'); + +-- should generate "missing "=" after "myconn" in connection info string" error +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE t.a > 7; + +-- create a named persistent connection +SELECT dblink_connect('myconn',connection_parameters()); + +-- put more data into our table, using named persistent connection syntax +-- but truncate the actual return value so we can use diff to check for success +SELECT substr(dblink_exec('myconn','INSERT INTO foo VALUES(11,''l'',''{"a11","b11","c11"}'')'),1,6); + +-- let's see it +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]); + +-- change some data +SELECT dblink_exec('myconn','UPDATE foo SET f3[2] = ''b99'' WHERE f1 = 11'); + +-- let's see it +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE a = 11; + +-- delete some data +SELECT dblink_exec('myconn','DELETE FROM foo WHERE f1 = 11'); + +-- let's see it +SELECT * +FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) +WHERE a = 11; + +-- close the named persistent connection +SELECT dblink_disconnect('myconn'); + +-- close the named persistent connection again +-- should get 'connection "myconn" not available' error +SELECT dblink_disconnect('myconn'); + +-- test asynchronous queries +SELECT dblink_connect('dtest1', connection_parameters()); +SELECT * from + dblink_send_query('dtest1', 'select * from foo where f1 < 3') as t1; + +SELECT dblink_connect('dtest2', connection_parameters()); +SELECT * from + dblink_send_query('dtest2', 'select * from foo where f1 > 2 and f1 < 7') as t1; + +SELECT dblink_connect('dtest3', connection_parameters()); +SELECT * from + dblink_send_query('dtest3', 'select * from foo where f1 > 6') as t1; + +CREATE TEMPORARY TABLE result AS +(SELECT * from dblink_get_result('dtest1') as t1(f1 int, f2 text, f3 text[])) +UNION +(SELECT * from dblink_get_result('dtest2') as t2(f1 int, f2 text, f3 text[])) +UNION +(SELECT * from dblink_get_result('dtest3') as t3(f1 int, f2 text, f3 text[])) +ORDER by f1; + +-- dblink_get_connections returns an array with elements in a machine-dependent +-- ordering, so we must resort to unnesting and sorting for a stable result +create function unnest(anyarray) returns setof anyelement +language sql strict immutable as $$ +select $1[i] from generate_series(array_lower($1,1), array_upper($1,1)) as i +$$; + +SELECT * FROM unnest(dblink_get_connections()) ORDER BY 1; + +SELECT dblink_is_busy('dtest1'); + +SELECT dblink_disconnect('dtest1'); +SELECT dblink_disconnect('dtest2'); +SELECT dblink_disconnect('dtest3'); + +SELECT * from result; + +SELECT dblink_connect('dtest1', connection_parameters()); +SELECT * from + dblink_send_query('dtest1', 'select * from foo where f1 < 3') as t1; + +SELECT dblink_cancel_query('dtest1'); +SELECT dblink_error_message('dtest1'); +SELECT dblink_disconnect('dtest1'); + +-- test foreign data wrapper functionality +CREATE ROLE regress_dblink_user; +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER fdtest FOREIGN DATA WRAPPER dblink_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; + +CREATE USER MAPPING FOR public SERVER fdtest + OPTIONS (server 'localhost'); -- fail, can't specify server here +CREATE USER MAPPING FOR public SERVER fdtest OPTIONS (user :'USER'); + +GRANT USAGE ON FOREIGN SERVER fdtest TO regress_dblink_user; +GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO regress_dblink_user; + +SET SESSION AUTHORIZATION regress_dblink_user; +-- should fail +SELECT dblink_connect('myconn', 'fdtest'); +-- should succeed +SELECT dblink_connect_u('myconn', 'fdtest'); +SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]); + +\c - - +REVOKE USAGE ON FOREIGN SERVER fdtest FROM regress_dblink_user; +REVOKE EXECUTE ON FUNCTION dblink_connect_u(text, text) FROM regress_dblink_user; +DROP USER regress_dblink_user; +DROP USER MAPPING FOR public SERVER fdtest; +DROP SERVER fdtest; + +-- should fail +ALTER FOREIGN DATA WRAPPER dblink_fdw OPTIONS (nonexistent 'fdw'); + +-- test repeated calls to dblink_connect +SELECT dblink_connect(connection_parameters()); +SELECT dblink_connect(connection_parameters()); +SELECT dblink_disconnect(); + +-- test asynchronous notifications +SELECT dblink_connect(connection_parameters()); + +--should return listen +SELECT dblink_exec('LISTEN regression'); +--should return listen +SELECT dblink_exec('LISTEN foobar'); + +SELECT dblink_exec('NOTIFY regression'); +SELECT dblink_exec('NOTIFY foobar'); + +SELECT notify_name, be_pid = (select t.be_pid from dblink('select pg_backend_pid()') as t(be_pid int)) AS is_self_notify, extra from dblink_get_notify(); + +SELECT * from dblink_get_notify(); + +SELECT dblink_disconnect(); + +-- test dropped columns in dblink_build_sql_insert, dblink_build_sql_update +CREATE TEMP TABLE test_dropped +( + col1 INT NOT NULL DEFAULT 111, + id SERIAL PRIMARY KEY, + col2 INT NOT NULL DEFAULT 112, + col2b INT NOT NULL DEFAULT 113 +); + +INSERT INTO test_dropped VALUES(default); + +ALTER TABLE test_dropped + DROP COLUMN col1, + DROP COLUMN col2, + ADD COLUMN col3 VARCHAR(10) NOT NULL DEFAULT 'foo', + ADD COLUMN col4 INT NOT NULL DEFAULT 42; + +SELECT dblink_build_sql_insert('test_dropped', '1', 1, + ARRAY['1'::TEXT], ARRAY['2'::TEXT]); + +SELECT dblink_build_sql_update('test_dropped', '1', 1, + ARRAY['1'::TEXT], ARRAY['2'::TEXT]); + +SELECT dblink_build_sql_delete('test_dropped', '1', 1, + ARRAY['2'::TEXT]); + +-- test local mimicry of remote GUC values that affect datatype I/O +SET datestyle = ISO, MDY; +SET intervalstyle = postgres; +SET timezone = UTC; +SELECT dblink_connect('myconn',connection_parameters()); +SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;'); + +-- single row synchronous case +SELECT * +FROM dblink('myconn', + 'SELECT * FROM (VALUES (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz); + +-- multi-row synchronous case +SELECT * +FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00''), + (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz); + +-- single-row asynchronous case +SELECT * +FROM dblink_send_query('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00'')) t'); +CREATE TEMPORARY TABLE result AS +(SELECT * from dblink_get_result('myconn') as t(t timestamptz)) +UNION ALL +(SELECT * from dblink_get_result('myconn') as t(t timestamptz)); +SELECT * FROM result; +DROP TABLE result; + +-- multi-row asynchronous case +SELECT * +FROM dblink_send_query('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00''), + (''12.03.2013 00:00:00+00'')) t'); +CREATE TEMPORARY TABLE result AS +(SELECT * from dblink_get_result('myconn') as t(t timestamptz)) +UNION ALL +(SELECT * from dblink_get_result('myconn') as t(t timestamptz)) +UNION ALL +(SELECT * from dblink_get_result('myconn') as t(t timestamptz)); +SELECT * FROM result; +DROP TABLE result; + +-- Try an ambiguous interval +SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;'); +SELECT * +FROM dblink('myconn', + 'SELECT * FROM (VALUES (''-1 2:03:04'')) i') + AS i(i interval); + +-- Try swapping to another format to ensure the GUCs are tracked +-- properly through a change. +CREATE TEMPORARY TABLE result (t timestamptz); + +SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;'); +INSERT INTO result + SELECT * + FROM dblink('myconn', + 'SELECT * FROM (VALUES (''03.12.2013 00:00:00+00'')) t') + AS t(a timestamptz); + +SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;'); +INSERT INTO result + SELECT * + FROM dblink('myconn', + 'SELECT * FROM (VALUES (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz); + +SELECT * FROM result; + +DROP TABLE result; + +-- Check error throwing in dblink_fetch +SELECT dblink_open('myconn','error_cursor', + 'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);'); +SELECT * +FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int); +SELECT * +FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int); + +-- Make sure that the local settings have retained their values in spite +-- of shenanigans on the connection. +SHOW datestyle; +SHOW intervalstyle; + +-- Clean up GUC-setting tests +SELECT dblink_disconnect('myconn'); +RESET datestyle; +RESET intervalstyle; +RESET timezone; diff --git a/contrib/dict_int/.gitignore b/contrib/dict_int/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/dict_int/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/dict_int/Makefile b/contrib/dict_int/Makefile new file mode 100644 index 0000000..2ff1bdb --- /dev/null +++ b/contrib/dict_int/Makefile @@ -0,0 +1,23 @@ +# contrib/dict_int/Makefile + +MODULE_big = dict_int +OBJS = \ + $(WIN32RES) \ + dict_int.o + +EXTENSION = dict_int +DATA = dict_int--1.0.sql +PGFILEDESC = "dict_int - add-on dictionary template for full-text search" + +REGRESS = dict_int + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/dict_int +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/dict_int/dict_int--1.0.sql b/contrib/dict_int/dict_int--1.0.sql new file mode 100644 index 0000000..acb1461 --- /dev/null +++ b/contrib/dict_int/dict_int--1.0.sql @@ -0,0 +1,25 @@ +/* contrib/dict_int/dict_int--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION dict_int" to load this file. \quit + +CREATE FUNCTION dintdict_init(internal) + RETURNS internal + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT; + +CREATE FUNCTION dintdict_lexize(internal, internal, internal, internal) + RETURNS internal + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT; + +CREATE TEXT SEARCH TEMPLATE intdict_template ( + LEXIZE = dintdict_lexize, + INIT = dintdict_init +); + +CREATE TEXT SEARCH DICTIONARY intdict ( + TEMPLATE = intdict_template +); + +COMMENT ON TEXT SEARCH DICTIONARY intdict IS 'dictionary for integers'; diff --git a/contrib/dict_int/dict_int.c b/contrib/dict_int/dict_int.c new file mode 100644 index 0000000..e44b8ee --- /dev/null +++ b/contrib/dict_int/dict_int.c @@ -0,0 +1,116 @@ +/*------------------------------------------------------------------------- + * + * dict_int.c + * Text search dictionary for integers + * + * Copyright (c) 2007-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/dict_int/dict_int.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "commands/defrem.h" +#include "tsearch/ts_public.h" + +PG_MODULE_MAGIC; + +typedef struct +{ + int maxlen; + bool rejectlong; + bool absval; +} DictInt; + + +PG_FUNCTION_INFO_V1(dintdict_init); +PG_FUNCTION_INFO_V1(dintdict_lexize); + +Datum +dintdict_init(PG_FUNCTION_ARGS) +{ + List *dictoptions = (List *) PG_GETARG_POINTER(0); + DictInt *d; + ListCell *l; + + d = (DictInt *) palloc0(sizeof(DictInt)); + d->maxlen = 6; + d->rejectlong = false; + d->absval = false; + + foreach(l, dictoptions) + { + DefElem *defel = (DefElem *) lfirst(l); + + if (strcmp(defel->defname, "maxlen") == 0) + { + d->maxlen = atoi(defGetString(defel)); + + if (d->maxlen < 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("maxlen value has to be >= 1"))); + } + else if (strcmp(defel->defname, "rejectlong") == 0) + { + d->rejectlong = defGetBoolean(defel); + } + else if (strcmp(defel->defname, "absval") == 0) + { + d->absval = defGetBoolean(defel); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized intdict parameter: \"%s\"", + defel->defname))); + } + } + + PG_RETURN_POINTER(d); +} + +Datum +dintdict_lexize(PG_FUNCTION_ARGS) +{ + DictInt *d = (DictInt *) PG_GETARG_POINTER(0); + char *in = (char *) PG_GETARG_POINTER(1); + int len = PG_GETARG_INT32(2); + char *txt; + TSLexeme *res = palloc0(sizeof(TSLexeme) * 2); + + res[1].lexeme = NULL; + + if (d->absval && (in[0] == '+' || in[0] == '-')) + { + len--; + txt = pnstrdup(in + 1, len); + } + else + txt = pnstrdup(in, len); + + if (len > d->maxlen) + { + if (d->rejectlong) + { + /* reject by returning void array */ + pfree(txt); + res[0].lexeme = NULL; + } + else + { + /* trim integer */ + txt[d->maxlen] = '\0'; + res[0].lexeme = txt; + } + } + else + { + res[0].lexeme = txt; + } + + PG_RETURN_POINTER(res); +} diff --git a/contrib/dict_int/dict_int.control b/contrib/dict_int/dict_int.control new file mode 100644 index 0000000..ec04cce --- /dev/null +++ b/contrib/dict_int/dict_int.control @@ -0,0 +1,6 @@ +# dict_int extension +comment = 'text search dictionary template for integers' +default_version = '1.0' +module_pathname = '$libdir/dict_int' +relocatable = true +trusted = true diff --git a/contrib/dict_int/expected/dict_int.out b/contrib/dict_int/expected/dict_int.out new file mode 100644 index 0000000..39906fc --- /dev/null +++ b/contrib/dict_int/expected/dict_int.out @@ -0,0 +1,356 @@ +CREATE EXTENSION dict_int; +--lexize +select ts_lexize('intdict', '511673'); + ts_lexize +----------- + {511673} +(1 row) + +select ts_lexize('intdict', '129'); + ts_lexize +----------- + {129} +(1 row) + +select ts_lexize('intdict', '40865854'); + ts_lexize +----------- + {408658} +(1 row) + +select ts_lexize('intdict', '952'); + ts_lexize +----------- + {952} +(1 row) + +select ts_lexize('intdict', '654980341'); + ts_lexize +----------- + {654980} +(1 row) + +select ts_lexize('intdict', '09810106'); + ts_lexize +----------- + {098101} +(1 row) + +select ts_lexize('intdict', '14262713'); + ts_lexize +----------- + {142627} +(1 row) + +select ts_lexize('intdict', '6532082986'); + ts_lexize +----------- + {653208} +(1 row) + +select ts_lexize('intdict', '0150061'); + ts_lexize +----------- + {015006} +(1 row) + +select ts_lexize('intdict', '7778'); + ts_lexize +----------- + {7778} +(1 row) + +select ts_lexize('intdict', '9547'); + ts_lexize +----------- + {9547} +(1 row) + +select ts_lexize('intdict', '753395478'); + ts_lexize +----------- + {753395} +(1 row) + +select ts_lexize('intdict', '647652'); + ts_lexize +----------- + {647652} +(1 row) + +select ts_lexize('intdict', '6988655574'); + ts_lexize +----------- + {698865} +(1 row) + +select ts_lexize('intdict', '1279'); + ts_lexize +----------- + {1279} +(1 row) + +select ts_lexize('intdict', '1266645909'); + ts_lexize +----------- + {126664} +(1 row) + +select ts_lexize('intdict', '7594193969'); + ts_lexize +----------- + {759419} +(1 row) + +select ts_lexize('intdict', '16928207'); + ts_lexize +----------- + {169282} +(1 row) + +select ts_lexize('intdict', '196850350328'); + ts_lexize +----------- + {196850} +(1 row) + +select ts_lexize('intdict', '22026985592'); + ts_lexize +----------- + {220269} +(1 row) + +select ts_lexize('intdict', '2063765'); + ts_lexize +----------- + {206376} +(1 row) + +select ts_lexize('intdict', '242387310'); + ts_lexize +----------- + {242387} +(1 row) + +select ts_lexize('intdict', '93595'); + ts_lexize +----------- + {93595} +(1 row) + +select ts_lexize('intdict', '9374'); + ts_lexize +----------- + {9374} +(1 row) + +select ts_lexize('intdict', '996969'); + ts_lexize +----------- + {996969} +(1 row) + +select ts_lexize('intdict', '353595982'); + ts_lexize +----------- + {353595} +(1 row) + +select ts_lexize('intdict', '925860'); + ts_lexize +----------- + {925860} +(1 row) + +select ts_lexize('intdict', '11848378337'); + ts_lexize +----------- + {118483} +(1 row) + +select ts_lexize('intdict', '333'); + ts_lexize +----------- + {333} +(1 row) + +select ts_lexize('intdict', '799287416765'); + ts_lexize +----------- + {799287} +(1 row) + +select ts_lexize('intdict', '745939'); + ts_lexize +----------- + {745939} +(1 row) + +select ts_lexize('intdict', '67601305734'); + ts_lexize +----------- + {676013} +(1 row) + +select ts_lexize('intdict', '3361113'); + ts_lexize +----------- + {336111} +(1 row) + +select ts_lexize('intdict', '9033778607'); + ts_lexize +----------- + {903377} +(1 row) + +select ts_lexize('intdict', '7507648'); + ts_lexize +----------- + {750764} +(1 row) + +select ts_lexize('intdict', '1166'); + ts_lexize +----------- + {1166} +(1 row) + +select ts_lexize('intdict', '9360498'); + ts_lexize +----------- + {936049} +(1 row) + +select ts_lexize('intdict', '917795'); + ts_lexize +----------- + {917795} +(1 row) + +select ts_lexize('intdict', '9387894'); + ts_lexize +----------- + {938789} +(1 row) + +select ts_lexize('intdict', '42764329'); + ts_lexize +----------- + {427643} +(1 row) + +select ts_lexize('intdict', '564062'); + ts_lexize +----------- + {564062} +(1 row) + +select ts_lexize('intdict', '5413377'); + ts_lexize +----------- + {541337} +(1 row) + +select ts_lexize('intdict', '060965'); + ts_lexize +----------- + {060965} +(1 row) + +select ts_lexize('intdict', '08273593'); + ts_lexize +----------- + {082735} +(1 row) + +select ts_lexize('intdict', '593556010144'); + ts_lexize +----------- + {593556} +(1 row) + +select ts_lexize('intdict', '17988843352'); + ts_lexize +----------- + {179888} +(1 row) + +select ts_lexize('intdict', '252281774'); + ts_lexize +----------- + {252281} +(1 row) + +select ts_lexize('intdict', '313425'); + ts_lexize +----------- + {313425} +(1 row) + +select ts_lexize('intdict', '641439323669'); + ts_lexize +----------- + {641439} +(1 row) + +select ts_lexize('intdict', '314532610153'); + ts_lexize +----------- + {314532} +(1 row) + +ALTER TEXT SEARCH DICTIONARY intdict (MAXLEN = -214783648); -- fail +ERROR: maxlen value has to be >= 1 +-- This ought to fail, perhaps, but historically it has not: +ALTER TEXT SEARCH DICTIONARY intdict (MAXLEN = 6.7); +select ts_lexize('intdict', '-40865854'); + ts_lexize +----------- + {-40865} +(1 row) + +select ts_lexize('intdict', '+40865854'); + ts_lexize +----------- + {+40865} +(1 row) + +ALTER TEXT SEARCH DICTIONARY intdict (ABSVAL = true); +select ts_lexize('intdict', '-40865854'); + ts_lexize +----------- + {408658} +(1 row) + +select ts_lexize('intdict', '+40865854'); + ts_lexize +----------- + {408658} +(1 row) + +ALTER TEXT SEARCH DICTIONARY intdict (REJECTLONG = 1); +select ts_lexize('intdict', '-40865854'); + ts_lexize +----------- + {} +(1 row) + +select ts_lexize('intdict', '-4086585'); + ts_lexize +----------- + {} +(1 row) + +select ts_lexize('intdict', '-408658'); + ts_lexize +----------- + {408658} +(1 row) + +SELECT dictinitoption FROM pg_ts_dict WHERE dictname = 'intdict'; + dictinitoption +----------------------------------------------- + maxlen = 6.7, absval = 'true', rejectlong = 1 +(1 row) + diff --git a/contrib/dict_int/meson.build b/contrib/dict_int/meson.build new file mode 100644 index 0000000..dcf601d --- /dev/null +++ b/contrib/dict_int/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +dict_int_sources = files( + 'dict_int.c', +) + +if host_system == 'windows' + dict_int_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'dict_int', + '--FILEDESC', 'dict_int - add-on dictionary template for full-text search',]) +endif + +dict_int = shared_module('dict_int', + dict_int_sources, + kwargs: contrib_mod_args, +) +contrib_targets += dict_int + +install_data( + 'dict_int.control', + 'dict_int--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'dict_int', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'dict_int', + ], + }, +} diff --git a/contrib/dict_int/sql/dict_int.sql b/contrib/dict_int/sql/dict_int.sql new file mode 100644 index 0000000..4e2543d --- /dev/null +++ b/contrib/dict_int/sql/dict_int.sql @@ -0,0 +1,69 @@ +CREATE EXTENSION dict_int; + +--lexize +select ts_lexize('intdict', '511673'); +select ts_lexize('intdict', '129'); +select ts_lexize('intdict', '40865854'); +select ts_lexize('intdict', '952'); +select ts_lexize('intdict', '654980341'); +select ts_lexize('intdict', '09810106'); +select ts_lexize('intdict', '14262713'); +select ts_lexize('intdict', '6532082986'); +select ts_lexize('intdict', '0150061'); +select ts_lexize('intdict', '7778'); +select ts_lexize('intdict', '9547'); +select ts_lexize('intdict', '753395478'); +select ts_lexize('intdict', '647652'); +select ts_lexize('intdict', '6988655574'); +select ts_lexize('intdict', '1279'); +select ts_lexize('intdict', '1266645909'); +select ts_lexize('intdict', '7594193969'); +select ts_lexize('intdict', '16928207'); +select ts_lexize('intdict', '196850350328'); +select ts_lexize('intdict', '22026985592'); +select ts_lexize('intdict', '2063765'); +select ts_lexize('intdict', '242387310'); +select ts_lexize('intdict', '93595'); +select ts_lexize('intdict', '9374'); +select ts_lexize('intdict', '996969'); +select ts_lexize('intdict', '353595982'); +select ts_lexize('intdict', '925860'); +select ts_lexize('intdict', '11848378337'); +select ts_lexize('intdict', '333'); +select ts_lexize('intdict', '799287416765'); +select ts_lexize('intdict', '745939'); +select ts_lexize('intdict', '67601305734'); +select ts_lexize('intdict', '3361113'); +select ts_lexize('intdict', '9033778607'); +select ts_lexize('intdict', '7507648'); +select ts_lexize('intdict', '1166'); +select ts_lexize('intdict', '9360498'); +select ts_lexize('intdict', '917795'); +select ts_lexize('intdict', '9387894'); +select ts_lexize('intdict', '42764329'); +select ts_lexize('intdict', '564062'); +select ts_lexize('intdict', '5413377'); +select ts_lexize('intdict', '060965'); +select ts_lexize('intdict', '08273593'); +select ts_lexize('intdict', '593556010144'); +select ts_lexize('intdict', '17988843352'); +select ts_lexize('intdict', '252281774'); +select ts_lexize('intdict', '313425'); +select ts_lexize('intdict', '641439323669'); +select ts_lexize('intdict', '314532610153'); + +ALTER TEXT SEARCH DICTIONARY intdict (MAXLEN = -214783648); -- fail +-- This ought to fail, perhaps, but historically it has not: +ALTER TEXT SEARCH DICTIONARY intdict (MAXLEN = 6.7); + +select ts_lexize('intdict', '-40865854'); +select ts_lexize('intdict', '+40865854'); +ALTER TEXT SEARCH DICTIONARY intdict (ABSVAL = true); +select ts_lexize('intdict', '-40865854'); +select ts_lexize('intdict', '+40865854'); +ALTER TEXT SEARCH DICTIONARY intdict (REJECTLONG = 1); +select ts_lexize('intdict', '-40865854'); +select ts_lexize('intdict', '-4086585'); +select ts_lexize('intdict', '-408658'); + +SELECT dictinitoption FROM pg_ts_dict WHERE dictname = 'intdict'; diff --git a/contrib/dict_xsyn/.gitignore b/contrib/dict_xsyn/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/dict_xsyn/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/dict_xsyn/Makefile b/contrib/dict_xsyn/Makefile new file mode 100644 index 0000000..b6bcfe6 --- /dev/null +++ b/contrib/dict_xsyn/Makefile @@ -0,0 +1,24 @@ +# contrib/dict_xsyn/Makefile + +MODULE_big = dict_xsyn +OBJS = \ + $(WIN32RES) \ + dict_xsyn.o + +EXTENSION = dict_xsyn +DATA = dict_xsyn--1.0.sql +DATA_TSEARCH = xsyn_sample.rules +PGFILEDESC = "dict_xsyn - add-on dictionary template for full-text search" + +REGRESS = dict_xsyn + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/dict_xsyn +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/dict_xsyn/dict_xsyn--1.0.sql b/contrib/dict_xsyn/dict_xsyn--1.0.sql new file mode 100644 index 0000000..3d6bb51 --- /dev/null +++ b/contrib/dict_xsyn/dict_xsyn--1.0.sql @@ -0,0 +1,25 @@ +/* contrib/dict_xsyn/dict_xsyn--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION dict_xsyn" to load this file. \quit + +CREATE FUNCTION dxsyn_init(internal) + RETURNS internal + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT; + +CREATE FUNCTION dxsyn_lexize(internal, internal, internal, internal) + RETURNS internal + AS 'MODULE_PATHNAME' + LANGUAGE C STRICT; + +CREATE TEXT SEARCH TEMPLATE xsyn_template ( + LEXIZE = dxsyn_lexize, + INIT = dxsyn_init +); + +CREATE TEXT SEARCH DICTIONARY xsyn ( + TEMPLATE = xsyn_template +); + +COMMENT ON TEXT SEARCH DICTIONARY xsyn IS 'eXtended synonym dictionary'; diff --git a/contrib/dict_xsyn/dict_xsyn.c b/contrib/dict_xsyn/dict_xsyn.c new file mode 100644 index 0000000..e538928 --- /dev/null +++ b/contrib/dict_xsyn/dict_xsyn.c @@ -0,0 +1,259 @@ +/*------------------------------------------------------------------------- + * + * dict_xsyn.c + * Extended synonym dictionary + * + * Copyright (c) 2007-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/dict_xsyn/dict_xsyn.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#include "commands/defrem.h" +#include "tsearch/ts_locale.h" +#include "tsearch/ts_utils.h" + +PG_MODULE_MAGIC; + +typedef struct +{ + char *key; /* Word */ + char *value; /* Unparsed list of synonyms, including the + * word itself */ +} Syn; + +typedef struct +{ + int len; + Syn *syn; + + bool matchorig; + bool keeporig; + bool matchsynonyms; + bool keepsynonyms; +} DictSyn; + + +PG_FUNCTION_INFO_V1(dxsyn_init); +PG_FUNCTION_INFO_V1(dxsyn_lexize); + +static char * +find_word(char *in, char **end) +{ + char *start; + + *end = NULL; + while (*in && t_isspace(in)) + in += pg_mblen(in); + + if (!*in || *in == '#') + return NULL; + start = in; + + while (*in && !t_isspace(in)) + in += pg_mblen(in); + + *end = in; + + return start; +} + +static int +compare_syn(const void *a, const void *b) +{ + return strcmp(((const Syn *) a)->key, ((const Syn *) b)->key); +} + +static void +read_dictionary(DictSyn *d, const char *filename) +{ + char *real_filename = get_tsearch_config_filename(filename, "rules"); + tsearch_readline_state trst; + char *line; + int cur = 0; + + if (!tsearch_readline_begin(&trst, real_filename)) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not open synonym file \"%s\": %m", + real_filename))); + + while ((line = tsearch_readline(&trst)) != NULL) + { + char *value; + char *key; + char *pos; + char *end; + + if (*line == '\0') + continue; + + value = lowerstr(line); + pfree(line); + + pos = value; + while ((key = find_word(pos, &end)) != NULL) + { + /* Enlarge syn structure if full */ + if (cur == d->len) + { + d->len = (d->len > 0) ? 2 * d->len : 16; + if (d->syn) + d->syn = (Syn *) repalloc(d->syn, sizeof(Syn) * d->len); + else + d->syn = (Syn *) palloc(sizeof(Syn) * d->len); + } + + /* Save first word only if we will match it */ + if (pos != value || d->matchorig) + { + d->syn[cur].key = pnstrdup(key, end - key); + d->syn[cur].value = pstrdup(value); + + cur++; + } + + pos = end; + + /* Don't bother scanning synonyms if we will not match them */ + if (!d->matchsynonyms) + break; + } + + pfree(value); + } + + tsearch_readline_end(&trst); + + d->len = cur; + if (cur > 1) + qsort(d->syn, d->len, sizeof(Syn), compare_syn); + + pfree(real_filename); +} + +Datum +dxsyn_init(PG_FUNCTION_ARGS) +{ + List *dictoptions = (List *) PG_GETARG_POINTER(0); + DictSyn *d; + ListCell *l; + char *filename = NULL; + + d = (DictSyn *) palloc0(sizeof(DictSyn)); + d->len = 0; + d->syn = NULL; + d->matchorig = true; + d->keeporig = true; + d->matchsynonyms = false; + d->keepsynonyms = true; + + foreach(l, dictoptions) + { + DefElem *defel = (DefElem *) lfirst(l); + + if (strcmp(defel->defname, "matchorig") == 0) + { + d->matchorig = defGetBoolean(defel); + } + else if (strcmp(defel->defname, "keeporig") == 0) + { + d->keeporig = defGetBoolean(defel); + } + else if (strcmp(defel->defname, "matchsynonyms") == 0) + { + d->matchsynonyms = defGetBoolean(defel); + } + else if (strcmp(defel->defname, "keepsynonyms") == 0) + { + d->keepsynonyms = defGetBoolean(defel); + } + else if (strcmp(defel->defname, "rules") == 0) + { + /* we can't read the rules before parsing all options! */ + filename = defGetString(defel); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized xsyn parameter: \"%s\"", + defel->defname))); + } + } + + if (filename) + read_dictionary(d, filename); + + PG_RETURN_POINTER(d); +} + +Datum +dxsyn_lexize(PG_FUNCTION_ARGS) +{ + DictSyn *d = (DictSyn *) PG_GETARG_POINTER(0); + char *in = (char *) PG_GETARG_POINTER(1); + int length = PG_GETARG_INT32(2); + Syn word; + Syn *found; + TSLexeme *res = NULL; + + if (!length || d->len == 0) + PG_RETURN_POINTER(NULL); + + /* Create search pattern */ + { + char *temp = pnstrdup(in, length); + + word.key = lowerstr(temp); + pfree(temp); + word.value = NULL; + } + + /* Look for matching syn */ + found = (Syn *) bsearch(&word, d->syn, d->len, sizeof(Syn), compare_syn); + pfree(word.key); + + if (!found) + PG_RETURN_POINTER(NULL); + + /* Parse string of synonyms and return array of words */ + { + char *value = found->value; + char *syn; + char *pos; + char *end; + int nsyns = 0; + + res = palloc(sizeof(TSLexeme)); + + pos = value; + while ((syn = find_word(pos, &end)) != NULL) + { + res = repalloc(res, sizeof(TSLexeme) * (nsyns + 2)); + + /* The first word is output only if keeporig=true */ + if (pos != value || d->keeporig) + { + res[nsyns].lexeme = pnstrdup(syn, end - syn); + res[nsyns].nvariant = 0; + res[nsyns].flags = 0; + nsyns++; + } + + pos = end; + + /* Stop if we are not to output the synonyms */ + if (!d->keepsynonyms) + break; + } + res[nsyns].lexeme = NULL; + } + + PG_RETURN_POINTER(res); +} diff --git a/contrib/dict_xsyn/dict_xsyn.control b/contrib/dict_xsyn/dict_xsyn.control new file mode 100644 index 0000000..3fd465a --- /dev/null +++ b/contrib/dict_xsyn/dict_xsyn.control @@ -0,0 +1,5 @@ +# dict_xsyn extension +comment = 'text search dictionary template for extended synonym processing' +default_version = '1.0' +module_pathname = '$libdir/dict_xsyn' +relocatable = true diff --git a/contrib/dict_xsyn/expected/dict_xsyn.out b/contrib/dict_xsyn/expected/dict_xsyn.out new file mode 100644 index 0000000..9b95e13 --- /dev/null +++ b/contrib/dict_xsyn/expected/dict_xsyn.out @@ -0,0 +1,142 @@ +CREATE EXTENSION dict_xsyn; +-- default configuration - match first word and return it among with all synonyms +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=true, MATCHORIG=true, KEEPSYNONYMS=true, MATCHSYNONYMS=false); +--lexize +SELECT ts_lexize('xsyn', 'supernova'); + ts_lexize +-------------------------- + {supernova,sn,sne,1987a} +(1 row) + +SELECT ts_lexize('xsyn', 'sn'); + ts_lexize +----------- + +(1 row) + +SELECT ts_lexize('xsyn', 'grb'); + ts_lexize +----------- + +(1 row) + +-- the same, but return only synonyms +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=false, MATCHORIG=true, KEEPSYNONYMS=true, MATCHSYNONYMS=false); +SELECT ts_lexize('xsyn', 'supernova'); + ts_lexize +---------------- + {sn,sne,1987a} +(1 row) + +SELECT ts_lexize('xsyn', 'sn'); + ts_lexize +----------- + +(1 row) + +SELECT ts_lexize('xsyn', 'grb'); + ts_lexize +----------- + +(1 row) + +-- match any word and return all words +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=true, MATCHORIG=true, KEEPSYNONYMS=true, MATCHSYNONYMS=true); +SELECT ts_lexize('xsyn', 'supernova'); + ts_lexize +-------------------------- + {supernova,sn,sne,1987a} +(1 row) + +SELECT ts_lexize('xsyn', 'sn'); + ts_lexize +-------------------------- + {supernova,sn,sne,1987a} +(1 row) + +SELECT ts_lexize('xsyn', 'grb'); + ts_lexize +----------- + +(1 row) + +-- match any word and return all words except first one +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=false, MATCHORIG=true, KEEPSYNONYMS=true, MATCHSYNONYMS=true); +SELECT ts_lexize('xsyn', 'supernova'); + ts_lexize +---------------- + {sn,sne,1987a} +(1 row) + +SELECT ts_lexize('xsyn', 'sn'); + ts_lexize +---------------- + {sn,sne,1987a} +(1 row) + +SELECT ts_lexize('xsyn', 'grb'); + ts_lexize +----------- + +(1 row) + +-- match any synonym but not first word, and return first word instead +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=true, MATCHORIG=false, KEEPSYNONYMS=false, MATCHSYNONYMS=true); +SELECT ts_lexize('xsyn', 'supernova'); + ts_lexize +----------- + +(1 row) + +SELECT ts_lexize('xsyn', 'sn'); + ts_lexize +------------- + {supernova} +(1 row) + +SELECT ts_lexize('xsyn', 'grb'); + ts_lexize +----------- + +(1 row) + +-- do not match or return anything +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=false, MATCHORIG=false, KEEPSYNONYMS=false, MATCHSYNONYMS=false); +SELECT ts_lexize('xsyn', 'supernova'); + ts_lexize +----------- + +(1 row) + +SELECT ts_lexize('xsyn', 'sn'); + ts_lexize +----------- + +(1 row) + +SELECT ts_lexize('xsyn', 'grb'); + ts_lexize +----------- + +(1 row) + +-- match any word but return nothing +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=false, MATCHORIG=true, KEEPSYNONYMS=false, MATCHSYNONYMS=true); +SELECT ts_lexize('xsyn', 'supernova'); + ts_lexize +----------- + {} +(1 row) + +SELECT ts_lexize('xsyn', 'sn'); + ts_lexize +----------- + {} +(1 row) + +SELECT ts_lexize('xsyn', 'grb'); + ts_lexize +----------- + +(1 row) + diff --git a/contrib/dict_xsyn/meson.build b/contrib/dict_xsyn/meson.build new file mode 100644 index 0000000..83bce52 --- /dev/null +++ b/contrib/dict_xsyn/meson.build @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +dict_xsyn_sources = files( + 'dict_xsyn.c', +) + +if host_system == 'windows' + dict_xsyn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'dict_xsyn', + '--FILEDESC', 'dict_xsyn - add-on dictionary template for full-text search',]) +endif + +dict_xsyn = shared_module('dict_xsyn', + dict_xsyn_sources, + kwargs: contrib_mod_args, +) +contrib_targets += dict_xsyn + +install_data( + 'dict_xsyn.control', + 'dict_xsyn--1.0.sql', + kwargs: contrib_data_args, +) + +install_data( + 'xsyn_sample.rules', + kwargs: contrib_data_args + { + 'install_dir': dir_data / 'tsearch_data' + } +) + +tests += { + 'name': 'dict_xsyn', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'dict_xsyn', + ], + }, +} diff --git a/contrib/dict_xsyn/sql/dict_xsyn.sql b/contrib/dict_xsyn/sql/dict_xsyn.sql new file mode 100644 index 0000000..4951106 --- /dev/null +++ b/contrib/dict_xsyn/sql/dict_xsyn.sql @@ -0,0 +1,45 @@ +CREATE EXTENSION dict_xsyn; + +-- default configuration - match first word and return it among with all synonyms +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=true, MATCHORIG=true, KEEPSYNONYMS=true, MATCHSYNONYMS=false); + +--lexize +SELECT ts_lexize('xsyn', 'supernova'); +SELECT ts_lexize('xsyn', 'sn'); +SELECT ts_lexize('xsyn', 'grb'); + +-- the same, but return only synonyms +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=false, MATCHORIG=true, KEEPSYNONYMS=true, MATCHSYNONYMS=false); +SELECT ts_lexize('xsyn', 'supernova'); +SELECT ts_lexize('xsyn', 'sn'); +SELECT ts_lexize('xsyn', 'grb'); + +-- match any word and return all words +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=true, MATCHORIG=true, KEEPSYNONYMS=true, MATCHSYNONYMS=true); +SELECT ts_lexize('xsyn', 'supernova'); +SELECT ts_lexize('xsyn', 'sn'); +SELECT ts_lexize('xsyn', 'grb'); + +-- match any word and return all words except first one +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=false, MATCHORIG=true, KEEPSYNONYMS=true, MATCHSYNONYMS=true); +SELECT ts_lexize('xsyn', 'supernova'); +SELECT ts_lexize('xsyn', 'sn'); +SELECT ts_lexize('xsyn', 'grb'); + +-- match any synonym but not first word, and return first word instead +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=true, MATCHORIG=false, KEEPSYNONYMS=false, MATCHSYNONYMS=true); +SELECT ts_lexize('xsyn', 'supernova'); +SELECT ts_lexize('xsyn', 'sn'); +SELECT ts_lexize('xsyn', 'grb'); + +-- do not match or return anything +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=false, MATCHORIG=false, KEEPSYNONYMS=false, MATCHSYNONYMS=false); +SELECT ts_lexize('xsyn', 'supernova'); +SELECT ts_lexize('xsyn', 'sn'); +SELECT ts_lexize('xsyn', 'grb'); + +-- match any word but return nothing +ALTER TEXT SEARCH DICTIONARY xsyn (RULES='xsyn_sample', KEEPORIG=false, MATCHORIG=true, KEEPSYNONYMS=false, MATCHSYNONYMS=true); +SELECT ts_lexize('xsyn', 'supernova'); +SELECT ts_lexize('xsyn', 'sn'); +SELECT ts_lexize('xsyn', 'grb'); diff --git a/contrib/dict_xsyn/xsyn_sample.rules b/contrib/dict_xsyn/xsyn_sample.rules new file mode 100644 index 0000000..203bec7 --- /dev/null +++ b/contrib/dict_xsyn/xsyn_sample.rules @@ -0,0 +1,6 @@ +# Sample rules file for eXtended Synonym (xsyn) dictionary +# format is as follows: +# +# word synonym1 synonym2 ... +# +supernova sn sne 1987a diff --git a/contrib/earthdistance/.gitignore b/contrib/earthdistance/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/earthdistance/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/earthdistance/Makefile b/contrib/earthdistance/Makefile new file mode 100644 index 0000000..f93b7a9 --- /dev/null +++ b/contrib/earthdistance/Makefile @@ -0,0 +1,23 @@ +# contrib/earthdistance/Makefile + +MODULES = earthdistance + +EXTENSION = earthdistance +DATA = earthdistance--1.1.sql earthdistance--1.0--1.1.sql +PGFILEDESC = "earthdistance - calculate distances on the surface of the Earth" + +REGRESS = earthdistance +EXTRA_INSTALL = contrib/cube + +LDFLAGS_SL += $(filter -lm, $(LIBS)) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/earthdistance +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/earthdistance/earthdistance--1.0--1.1.sql b/contrib/earthdistance/earthdistance--1.0--1.1.sql new file mode 100644 index 0000000..802f151 --- /dev/null +++ b/contrib/earthdistance/earthdistance--1.0--1.1.sql @@ -0,0 +1,14 @@ +/* contrib/earthdistance/earthdistance--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION earthdistance UPDATE TO '1.1'" to load this file. \quit + +ALTER FUNCTION earth() PARALLEL SAFE; +ALTER FUNCTION sec_to_gc(float8) PARALLEL SAFE; +ALTER FUNCTION gc_to_sec(float8) PARALLEL SAFE; +ALTER FUNCTION ll_to_earth(float8, float8) PARALLEL SAFE; +ALTER FUNCTION latitude(earth) PARALLEL SAFE; +ALTER FUNCTION longitude(earth) PARALLEL SAFE; +ALTER FUNCTION earth_distance(earth, earth) PARALLEL SAFE; +ALTER FUNCTION earth_box(earth, float8) PARALLEL SAFE; +ALTER FUNCTION geo_distance(point, point) PARALLEL SAFE; diff --git a/contrib/earthdistance/earthdistance--1.1.sql b/contrib/earthdistance/earthdistance--1.1.sql new file mode 100644 index 0000000..9ef20ab --- /dev/null +++ b/contrib/earthdistance/earthdistance--1.1.sql @@ -0,0 +1,98 @@ +/* contrib/earthdistance/earthdistance--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION earthdistance" to load this file. \quit + +-- earth() returns the radius of the earth in meters. This is the only +-- place you need to change things for the cube base distance functions +-- in order to use different units (or a better value for the Earth's radius). + +CREATE FUNCTION earth() RETURNS float8 +LANGUAGE SQL IMMUTABLE PARALLEL SAFE +AS 'SELECT ''6378168''::float8'; + +-- Astronomers may want to change the earth function so that distances will be +-- returned in degrees. To do this comment out the above definition and +-- uncomment the one below. Note that doing this will break the regression +-- tests. +-- +-- CREATE FUNCTION earth() RETURNS float8 +-- LANGUAGE SQL IMMUTABLE +-- AS 'SELECT 180/pi()'; + +-- Define domain for locations on the surface of the earth using a cube +-- datatype with constraints. cube provides 3D indexing. +-- The cube is restricted to be a point, no more than 3 dimensions +-- (for less than 3 dimensions 0 is assumed for the missing coordinates) +-- and that the point must be very near the surface of the sphere +-- centered about the origin with the radius of the earth. + +CREATE DOMAIN earth AS cube + CONSTRAINT not_point check(cube_is_point(value)) + CONSTRAINT not_3d check(cube_dim(value) <= 3) + CONSTRAINT on_surface check(abs(cube_distance(value, '(0)'::cube) / + earth() - '1'::float8) < '10e-7'::float8); + +CREATE FUNCTION sec_to_gc(float8) +RETURNS float8 +LANGUAGE SQL +IMMUTABLE STRICT +PARALLEL SAFE +AS 'SELECT CASE WHEN $1 < 0 THEN 0::float8 WHEN $1/(2*earth()) > 1 THEN pi()*earth() ELSE 2*earth()*asin($1/(2*earth())) END'; + +CREATE FUNCTION gc_to_sec(float8) +RETURNS float8 +LANGUAGE SQL +IMMUTABLE STRICT +PARALLEL SAFE +AS 'SELECT CASE WHEN $1 < 0 THEN 0::float8 WHEN $1/earth() > pi() THEN 2*earth() ELSE 2*earth()*sin($1/(2*earth())) END'; + +CREATE FUNCTION ll_to_earth(float8, float8) +RETURNS earth +LANGUAGE SQL +IMMUTABLE STRICT +PARALLEL SAFE +AS 'SELECT cube(cube(cube(earth()*cos(radians($1))*cos(radians($2))),earth()*cos(radians($1))*sin(radians($2))),earth()*sin(radians($1)))::earth'; + +CREATE FUNCTION latitude(earth) +RETURNS float8 +LANGUAGE SQL +IMMUTABLE STRICT +PARALLEL SAFE +AS 'SELECT CASE WHEN cube_ll_coord($1, 3)/earth() < -1 THEN -90::float8 WHEN cube_ll_coord($1, 3)/earth() > 1 THEN 90::float8 ELSE degrees(asin(cube_ll_coord($1, 3)/earth())) END'; + +CREATE FUNCTION longitude(earth) +RETURNS float8 +LANGUAGE SQL +IMMUTABLE STRICT +PARALLEL SAFE +AS 'SELECT degrees(atan2(cube_ll_coord($1, 2), cube_ll_coord($1, 1)))'; + +CREATE FUNCTION earth_distance(earth, earth) +RETURNS float8 +LANGUAGE SQL +IMMUTABLE STRICT +PARALLEL SAFE +AS 'SELECT sec_to_gc(cube_distance($1, $2))'; + +CREATE FUNCTION earth_box(earth, float8) +RETURNS cube +LANGUAGE SQL +IMMUTABLE STRICT +PARALLEL SAFE +AS 'SELECT cube_enlarge($1, gc_to_sec($2), 3)'; + +--------------- geo_distance + +CREATE FUNCTION geo_distance (point, point) +RETURNS float8 +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE AS 'MODULE_PATHNAME'; + +--------------- geo_distance as operator <@> + +CREATE OPERATOR <@> ( + LEFTARG = point, + RIGHTARG = point, + PROCEDURE = geo_distance, + COMMUTATOR = <@> +); diff --git a/contrib/earthdistance/earthdistance.c b/contrib/earthdistance/earthdistance.c new file mode 100644 index 0000000..ded048c --- /dev/null +++ b/contrib/earthdistance/earthdistance.c @@ -0,0 +1,105 @@ +/* contrib/earthdistance/earthdistance.c */ + +#include "postgres.h" + +#include + +#include "utils/geo_decls.h" /* for Point */ + +/* X/Open (XSI) requires to provide M_PI, but core POSIX does not */ +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +PG_MODULE_MAGIC; + +/* Earth's radius is in statute miles. */ +static const double EARTH_RADIUS = 3958.747716; +static const double TWO_PI = 2.0 * M_PI; + + +/****************************************************** + * + * degtorad - convert degrees to radians + * + * arg: double, angle in degrees + * + * returns: double, same angle in radians + ******************************************************/ + +static double +degtorad(double degrees) +{ + return (degrees / 360.0) * TWO_PI; +} + +/****************************************************** + * + * geo_distance_internal - distance between points + * + * args: + * a pair of points - for each point, + * x-coordinate is longitude in degrees west of Greenwich + * y-coordinate is latitude in degrees above equator + * + * returns: double + * distance between the points in miles on earth's surface + ******************************************************/ + +static double +geo_distance_internal(Point *pt1, Point *pt2) +{ + double long1, + lat1, + long2, + lat2; + double longdiff; + double sino; + + /* convert degrees to radians */ + + long1 = degtorad(pt1->x); + lat1 = degtorad(pt1->y); + + long2 = degtorad(pt2->x); + lat2 = degtorad(pt2->y); + + /* compute difference in longitudes - want < 180 degrees */ + longdiff = fabs(long1 - long2); + if (longdiff > M_PI) + longdiff = TWO_PI - longdiff; + + sino = sqrt(sin(fabs(lat1 - lat2) / 2.) * sin(fabs(lat1 - lat2) / 2.) + + cos(lat1) * cos(lat2) * sin(longdiff / 2.) * sin(longdiff / 2.)); + if (sino > 1.) + sino = 1.; + + return 2. * EARTH_RADIUS * asin(sino); +} + + +/****************************************************** + * + * geo_distance - distance between points + * + * args: + * a pair of points - for each point, + * x-coordinate is longitude in degrees west of Greenwich + * y-coordinate is latitude in degrees above equator + * + * returns: float8 + * distance between the points in miles on earth's surface + ******************************************************/ + +PG_FUNCTION_INFO_V1(geo_distance); + +Datum +geo_distance(PG_FUNCTION_ARGS) +{ + Point *pt1 = PG_GETARG_POINT_P(0); + Point *pt2 = PG_GETARG_POINT_P(1); + float8 result; + + result = geo_distance_internal(pt1, pt2); + PG_RETURN_FLOAT8(result); +} diff --git a/contrib/earthdistance/earthdistance.control b/contrib/earthdistance/earthdistance.control new file mode 100644 index 0000000..5816d22 --- /dev/null +++ b/contrib/earthdistance/earthdistance.control @@ -0,0 +1,6 @@ +# earthdistance extension +comment = 'calculate great-circle distances on the surface of the Earth' +default_version = '1.1' +module_pathname = '$libdir/earthdistance' +relocatable = true +requires = 'cube' diff --git a/contrib/earthdistance/expected/earthdistance.out b/contrib/earthdistance/expected/earthdistance.out new file mode 100644 index 0000000..26a843c --- /dev/null +++ b/contrib/earthdistance/expected/earthdistance.out @@ -0,0 +1,1098 @@ +-- +-- Test earthdistance extension +-- +-- In this file we also do some testing of extension create/drop scenarios. +-- That's really exercising the core database's dependency logic, so ideally +-- we'd do it in the core regression tests, but we can't for lack of suitable +-- guaranteed-available extensions. earthdistance is a good test case because +-- it has a dependency on the cube extension. +-- +CREATE EXTENSION earthdistance; -- fail, must install cube first +ERROR: required extension "cube" is not installed +HINT: Use CREATE EXTENSION ... CASCADE to install required extensions too. +CREATE EXTENSION cube; +CREATE EXTENSION earthdistance; +-- +-- The radius of the Earth we are using. +-- +SELECT earth()::numeric(20,5); + earth +--------------- + 6378168.00000 +(1 row) + +-- +-- Convert straight line distances to great circle distances. +-- +SELECT (pi()*earth())::numeric(20,5); + numeric +---------------- + 20037605.73216 +(1 row) + +SELECT sec_to_gc(0)::numeric(20,5); + sec_to_gc +----------- + 0.00000 +(1 row) + +SELECT sec_to_gc(2*earth())::numeric(20,5); + sec_to_gc +---------------- + 20037605.73216 +(1 row) + +SELECT sec_to_gc(10*earth())::numeric(20,5); + sec_to_gc +---------------- + 20037605.73216 +(1 row) + +SELECT sec_to_gc(-earth())::numeric(20,5); + sec_to_gc +----------- + 0.00000 +(1 row) + +SELECT sec_to_gc(1000)::numeric(20,5); + sec_to_gc +------------ + 1000.00000 +(1 row) + +SELECT sec_to_gc(10000)::numeric(20,5); + sec_to_gc +------------- + 10000.00102 +(1 row) + +SELECT sec_to_gc(100000)::numeric(20,5); + sec_to_gc +-------------- + 100001.02426 +(1 row) + +SELECT sec_to_gc(1000000)::numeric(20,5); + sec_to_gc +--------------- + 1001027.07131 +(1 row) + +-- +-- Convert great circle distances to straight line distances. +-- +SELECT gc_to_sec(0)::numeric(20,5); + gc_to_sec +----------- + 0.00000 +(1 row) + +SELECT gc_to_sec(sec_to_gc(2*earth()))::numeric(20,5); + gc_to_sec +---------------- + 12756336.00000 +(1 row) + +SELECT gc_to_sec(10*earth())::numeric(20,5); + gc_to_sec +---------------- + 12756336.00000 +(1 row) + +SELECT gc_to_sec(pi()*earth())::numeric(20,5); + gc_to_sec +---------------- + 12756336.00000 +(1 row) + +SELECT gc_to_sec(-1000)::numeric(20,5); + gc_to_sec +----------- + 0.00000 +(1 row) + +SELECT gc_to_sec(1000)::numeric(20,5); + gc_to_sec +------------ + 1000.00000 +(1 row) + +SELECT gc_to_sec(10000)::numeric(20,5); + gc_to_sec +------------ + 9999.99898 +(1 row) + +SELECT gc_to_sec(100000)::numeric(20,5); + gc_to_sec +------------- + 99998.97577 +(1 row) + +SELECT gc_to_sec(1000000)::numeric(20,5); + gc_to_sec +-------------- + 998976.08618 +(1 row) + +-- +-- Set coordinates using latitude and longitude. +-- Extract each coordinate separately so we can round them. +-- +SELECT cube_ll_coord(ll_to_earth(0,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,0),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+--------------- + 6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(360,360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(360,360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(360,360),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+--------------- + 6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(180,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,180),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+--------------- + 6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(180,360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,360),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +----------------+---------------+--------------- + -6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(-180,-360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-180,-360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-180,-360),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +----------------+---------------+--------------- + -6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(0,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,180),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +----------------+---------------+--------------- + -6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(0,-180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,-180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,-180),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +----------------+---------------+--------------- + -6378168.00000 | 0.00000 | 0.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(90,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,0),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+--------------- + 0.00000 | 0.00000 | 6378168.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(90,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,180),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+--------------- + 0.00000 | 0.00000 | 6378168.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(-90,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,0),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+---------------- + 0.00000 | 0.00000 | -6378168.00000 +(1 row) + +SELECT cube_ll_coord(ll_to_earth(-90,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,180),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord +---------------+---------------+---------------- + 0.00000 | 0.00000 | -6378168.00000 +(1 row) + +-- +-- Test getting the latitude of a location. +-- +SELECT latitude(ll_to_earth(0,0))::numeric(20,10); + latitude +-------------- + 0.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(45,0))::numeric(20,10); + latitude +--------------- + 45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(90,0))::numeric(20,10); + latitude +--------------- + 90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-45,0))::numeric(20,10); + latitude +---------------- + -45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-90,0))::numeric(20,10); + latitude +---------------- + -90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(0,90))::numeric(20,10); + latitude +-------------- + 0.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(45,90))::numeric(20,10); + latitude +--------------- + 45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(90,90))::numeric(20,10); + latitude +--------------- + 90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-45,90))::numeric(20,10); + latitude +---------------- + -45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-90,90))::numeric(20,10); + latitude +---------------- + -90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(0,180))::numeric(20,10); + latitude +-------------- + 0.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(45,180))::numeric(20,10); + latitude +--------------- + 45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(90,180))::numeric(20,10); + latitude +--------------- + 90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-45,180))::numeric(20,10); + latitude +---------------- + -45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-90,180))::numeric(20,10); + latitude +---------------- + -90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(0,-90))::numeric(20,10); + latitude +-------------- + 0.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(45,-90))::numeric(20,10); + latitude +--------------- + 45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(90,-90))::numeric(20,10); + latitude +--------------- + 90.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-45,-90))::numeric(20,10); + latitude +---------------- + -45.0000000000 +(1 row) + +SELECT latitude(ll_to_earth(-90,-90))::numeric(20,10); + latitude +---------------- + -90.0000000000 +(1 row) + +-- +-- Test getting the longitude of a location. +-- +SELECT longitude(ll_to_earth(0,0))::numeric(20,10); + longitude +-------------- + 0.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(45,0))::numeric(20,10); + longitude +-------------- + 0.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(90,0))::numeric(20,10); + longitude +-------------- + 0.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-45,0))::numeric(20,10); + longitude +-------------- + 0.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-90,0))::numeric(20,10); + longitude +-------------- + 0.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(0,90))::numeric(20,10); + longitude +--------------- + 90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(45,90))::numeric(20,10); + longitude +--------------- + 90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(90,90))::numeric(20,10); + longitude +--------------- + 90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-45,90))::numeric(20,10); + longitude +--------------- + 90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-90,90))::numeric(20,10); + longitude +--------------- + 90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(0,180))::numeric(20,10); + longitude +---------------- + 180.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(45,180))::numeric(20,10); + longitude +---------------- + 180.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(90,180))::numeric(20,10); + longitude +---------------- + 180.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-45,180))::numeric(20,10); + longitude +---------------- + 180.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-90,180))::numeric(20,10); + longitude +---------------- + 180.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(0,-90))::numeric(20,10); + longitude +---------------- + -90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(45,-90))::numeric(20,10); + longitude +---------------- + -90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(90,-90))::numeric(20,10); + longitude +---------------- + -90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-45,-90))::numeric(20,10); + longitude +---------------- + -90.0000000000 +(1 row) + +SELECT longitude(ll_to_earth(-90,-90))::numeric(20,10); + longitude +---------------- + -90.0000000000 +(1 row) + +-- +-- For the distance tests the following is some real life data. +-- +-- Chicago has a latitude of 41.8 and a longitude of 87.6. +-- Albuquerque has a latitude of 35.1 and a longitude of 106.7. +-- (Note that latitude and longitude are specified differently +-- in the cube based functions than for the point based functions.) +-- +-- +-- Test getting the distance between two points using earth_distance. +-- +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,0))::numeric(20,5); + earth_distance +---------------- + 0.00000 +(1 row) + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,180))::numeric(20,5); + earth_distance +---------------- + 20037605.73216 +(1 row) + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(90,0))::numeric(20,5); + earth_distance +---------------- + 10018802.86608 +(1 row) + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,90))::numeric(20,5); + earth_distance +---------------- + 10018802.86608 +(1 row) + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))::numeric(20,5); + earth_distance +---------------- + 111320.03185 +(1 row) + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(1,0))::numeric(20,5); + earth_distance +---------------- + 111320.03185 +(1 row) + +SELECT earth_distance(ll_to_earth(30,0),ll_to_earth(30,1))::numeric(20,5); + earth_distance +---------------- + 96405.66962 +(1 row) + +SELECT earth_distance(ll_to_earth(30,0),ll_to_earth(31,0))::numeric(20,5); + earth_distance +---------------- + 111320.03185 +(1 row) + +SELECT earth_distance(ll_to_earth(60,0),ll_to_earth(60,1))::numeric(20,5); + earth_distance +---------------- + 55659.48608 +(1 row) + +SELECT earth_distance(ll_to_earth(60,0),ll_to_earth(61,0))::numeric(20,5); + earth_distance +---------------- + 111320.03185 +(1 row) + +SELECT earth_distance(ll_to_earth(41.8,87.6),ll_to_earth(35.1,106.7))::numeric(20,5); + earth_distance +---------------- + 1819303.21265 +(1 row) + +SELECT (earth_distance(ll_to_earth(41.8,87.6),ll_to_earth(35.1,106.7))* + 100./2.54/12./5280.)::numeric(20,5); + numeric +------------ + 1130.46261 +(1 row) + +-- +-- Test getting the distance between two points using geo_distance. +-- +SELECT geo_distance('(0,0)'::point,'(0,0)'::point)::numeric(20,5); + geo_distance +-------------- + 0.00000 +(1 row) + +SELECT geo_distance('(0,0)'::point,'(180,0)'::point)::numeric(20,5); + geo_distance +-------------- + 12436.77274 +(1 row) + +SELECT geo_distance('(0,0)'::point,'(0,90)'::point)::numeric(20,5); + geo_distance +-------------- + 6218.38637 +(1 row) + +SELECT geo_distance('(0,0)'::point,'(90,0)'::point)::numeric(20,5); + geo_distance +-------------- + 6218.38637 +(1 row) + +SELECT geo_distance('(0,0)'::point,'(1,0)'::point)::numeric(20,5); + geo_distance +-------------- + 69.09318 +(1 row) + +SELECT geo_distance('(0,0)'::point,'(0,1)'::point)::numeric(20,5); + geo_distance +-------------- + 69.09318 +(1 row) + +SELECT geo_distance('(0,30)'::point,'(1,30)'::point)::numeric(20,5); + geo_distance +-------------- + 59.83626 +(1 row) + +SELECT geo_distance('(0,30)'::point,'(0,31)'::point)::numeric(20,5); + geo_distance +-------------- + 69.09318 +(1 row) + +SELECT geo_distance('(0,60)'::point,'(1,60)'::point)::numeric(20,5); + geo_distance +-------------- + 34.54626 +(1 row) + +SELECT geo_distance('(0,60)'::point,'(0,61)'::point)::numeric(20,5); + geo_distance +-------------- + 69.09318 +(1 row) + +SELECT geo_distance('(87.6,41.8)'::point,'(106.7,35.1)'::point)::numeric(20,5); + geo_distance +-------------- + 1129.18983 +(1 row) + +SELECT (geo_distance('(87.6,41.8)'::point,'(106.7,35.1)'::point)*5280.*12.*2.54/100.)::numeric(20,5); + numeric +--------------- + 1817254.87730 +(1 row) + +-- +-- Test getting the distance between two points using the <@> operator. +-- +SELECT ('(0,0)'::point <@> '(0,0)'::point)::numeric(20,5); + numeric +--------- + 0.00000 +(1 row) + +SELECT ('(0,0)'::point <@> '(180,0)'::point)::numeric(20,5); + numeric +------------- + 12436.77274 +(1 row) + +SELECT ('(0,0)'::point <@> '(0,90)'::point)::numeric(20,5); + numeric +------------ + 6218.38637 +(1 row) + +SELECT ('(0,0)'::point <@> '(90,0)'::point)::numeric(20,5); + numeric +------------ + 6218.38637 +(1 row) + +SELECT ('(0,0)'::point <@> '(1,0)'::point)::numeric(20,5); + numeric +---------- + 69.09318 +(1 row) + +SELECT ('(0,0)'::point <@> '(0,1)'::point)::numeric(20,5); + numeric +---------- + 69.09318 +(1 row) + +SELECT ('(0,30)'::point <@> '(1,30)'::point)::numeric(20,5); + numeric +---------- + 59.83626 +(1 row) + +SELECT ('(0,30)'::point <@> '(0,31)'::point)::numeric(20,5); + numeric +---------- + 69.09318 +(1 row) + +SELECT ('(0,60)'::point <@> '(1,60)'::point)::numeric(20,5); + numeric +---------- + 34.54626 +(1 row) + +SELECT ('(0,60)'::point <@> '(0,61)'::point)::numeric(20,5); + numeric +---------- + 69.09318 +(1 row) + +SELECT ('(87.6,41.8)'::point <@> '(106.7,35.1)'::point)::numeric(20,5); + numeric +------------ + 1129.18983 +(1 row) + +SELECT (('(87.6,41.8)'::point <@> '(106.7,35.1)'::point)*5280.*12.*2.54/100.)::numeric(20,5); + numeric +--------------- + 1817254.87730 +(1 row) + +-- +-- Test getting a bounding box around points. +-- +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),112000),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),112000),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),112000),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord | cube_ur_coord | cube_ur_coord | cube_ur_coord +---------------+---------------+---------------+---------------+---------------+--------------- + 6266169.43896 | -111998.56104 | -111998.56104 | 6490166.56104 | 111998.56104 | 111998.56104 +(1 row) + +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord | cube_ur_coord | cube_ur_coord | cube_ur_coord +----------------+-----------------+-----------------+----------------+----------------+---------------- + -6378168.00000 | -12756336.00000 | -12756336.00000 | 19134504.00000 | 12756336.00000 | 12756336.00000 +(1 row) + +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),3)::numeric(20,5); + cube_ll_coord | cube_ll_coord | cube_ll_coord | cube_ur_coord | cube_ur_coord | cube_ur_coord +----------------+-----------------+-----------------+----------------+----------------+---------------- + -6378168.00000 | -12756336.00000 | -12756336.00000 | 19134504.00000 | 12756336.00000 | 12756336.00000 +(1 row) + +-- +-- Test for points that should be in bounding boxes. +-- +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*1.00001) @> + ll_to_earth(0,1); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*1.00001) @> + ll_to_earth(0,0.1); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.01))*1.00001) @> + ll_to_earth(0,0.01); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.001))*1.00001) @> + ll_to_earth(0,0.001); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.0001))*1.00001) @> + ll_to_earth(0,0.0001); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0.0001,0.0001))*1.00001) @> + ll_to_earth(0.0001,0.0001); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(45,45), + earth_distance(ll_to_earth(45,45),ll_to_earth(45.0001,45.0001))*1.00001) @> + ll_to_earth(45.0001,45.0001); + ?column? +---------- + t +(1 row) + +SELECT earth_box(ll_to_earth(90,180), + earth_distance(ll_to_earth(90,180),ll_to_earth(90.0001,180.0001))*1.00001) @> + ll_to_earth(90.0001,180.0001); + ?column? +---------- + t +(1 row) + +-- +-- Test for points that shouldn't be in bounding boxes. Note that we need +-- to make points way outside, since some points close may be in the box +-- but further away than the distance we are testing. +-- +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*.57735) @> + ll_to_earth(0,1); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*.57735) @> + ll_to_earth(0,0.1); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.01))*.57735) @> + ll_to_earth(0,0.01); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.001))*.57735) @> + ll_to_earth(0,0.001); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.0001))*.57735) @> + ll_to_earth(0,0.0001); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0.0001,0.0001))*.57735) @> + ll_to_earth(0.0001,0.0001); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(45,45), + earth_distance(ll_to_earth(45,45),ll_to_earth(45.0001,45.0001))*.57735) @> + ll_to_earth(45.0001,45.0001); + ?column? +---------- + f +(1 row) + +SELECT earth_box(ll_to_earth(90,180), + earth_distance(ll_to_earth(90,180),ll_to_earth(90.0001,180.0001))*.57735) @> + ll_to_earth(90.0001,180.0001); + ?column? +---------- + f +(1 row) + +-- +-- Test the recommended constraints. +-- +SELECT cube_is_point(ll_to_earth(0,0)); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_dim(ll_to_earth(0,0)) <= 3; + ?column? +---------- + t +(1 row) + +SELECT abs(cube_distance(ll_to_earth(0,0), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; + ?column? +---------- + t +(1 row) + +SELECT cube_is_point(ll_to_earth(30,60)); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_dim(ll_to_earth(30,60)) <= 3; + ?column? +---------- + t +(1 row) + +SELECT abs(cube_distance(ll_to_earth(30,60), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; + ?column? +---------- + t +(1 row) + +SELECT cube_is_point(ll_to_earth(60,90)); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_dim(ll_to_earth(60,90)) <= 3; + ?column? +---------- + t +(1 row) + +SELECT abs(cube_distance(ll_to_earth(60,90), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; + ?column? +---------- + t +(1 row) + +SELECT cube_is_point(ll_to_earth(-30,-90)); + cube_is_point +--------------- + t +(1 row) + +SELECT cube_dim(ll_to_earth(-30,-90)) <= 3; + ?column? +---------- + t +(1 row) + +SELECT abs(cube_distance(ll_to_earth(-30,-90), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; + ?column? +---------- + t +(1 row) + +-- +-- Now we are going to test extension create/drop scenarios. +-- +-- list what's installed +\dT + List of data types + Schema | Name | Description +--------+-------+--------------------------------------------------------------------------------------------- + public | cube | multi-dimensional cube '(FLOAT-1, FLOAT-2, ..., FLOAT-N), (FLOAT-1, FLOAT-2, ..., FLOAT-N)' + public | earth | +(2 rows) + +drop extension cube; -- fail, earthdistance requires it +ERROR: cannot drop extension cube because other objects depend on it +DETAIL: extension earthdistance depends on extension cube +HINT: Use DROP ... CASCADE to drop the dependent objects too. +drop extension earthdistance; +drop type cube; -- fail, extension cube requires it +ERROR: cannot drop type cube because extension cube requires it +HINT: You can drop extension cube instead. +-- list what's installed +\dT + List of data types + Schema | Name | Description +--------+------+--------------------------------------------------------------------------------------------- + public | cube | multi-dimensional cube '(FLOAT-1, FLOAT-2, ..., FLOAT-N), (FLOAT-1, FLOAT-2, ..., FLOAT-N)' +(1 row) + +create table foo (f1 cube, f2 int); +drop extension cube; -- fail, foo.f1 requires it +ERROR: cannot drop extension cube because other objects depend on it +DETAIL: column f1 of table foo depends on type cube +HINT: Use DROP ... CASCADE to drop the dependent objects too. +drop table foo; +drop extension cube; +-- list what's installed +\dT + List of data types + Schema | Name | Description +--------+------+------------- +(0 rows) + +\df + List of functions + Schema | Name | Result data type | Argument data types | Type +--------+------+------------------+---------------------+------ +(0 rows) + +\do + List of operators + Schema | Name | Left arg type | Right arg type | Result type | Description +--------+------+---------------+----------------+-------------+------------- +(0 rows) + +create schema c; +create extension cube with schema c; +-- list what's installed +\dT public.* + List of data types + Schema | Name | Description +--------+------+------------- +(0 rows) + +\df public.* + List of functions + Schema | Name | Result data type | Argument data types | Type +--------+------+------------------+---------------------+------ +(0 rows) + +\do public.* + List of operators + Schema | Name | Left arg type | Right arg type | Result type | Description +--------+------+---------------+----------------+-------------+------------- +(0 rows) + +\dT c.* + List of data types + Schema | Name | Description +--------+--------+--------------------------------------------------------------------------------------------- + c | c.cube | multi-dimensional cube '(FLOAT-1, FLOAT-2, ..., FLOAT-N), (FLOAT-1, FLOAT-2, ..., FLOAT-N)' +(1 row) + +create table foo (f1 c.cube, f2 int); +drop extension cube; -- fail, foo.f1 requires it +ERROR: cannot drop extension cube because other objects depend on it +DETAIL: column f1 of table foo depends on type c.cube +HINT: Use DROP ... CASCADE to drop the dependent objects too. +drop schema c; -- fail, cube requires it +ERROR: cannot drop schema c because other objects depend on it +DETAIL: extension cube depends on schema c +column f1 of table foo depends on type c.cube +HINT: Use DROP ... CASCADE to drop the dependent objects too. +drop extension cube cascade; +NOTICE: drop cascades to column f1 of table foo +\d foo + Table "public.foo" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + f2 | integer | | | + +-- list what's installed +\dT public.* + List of data types + Schema | Name | Description +--------+------+------------- +(0 rows) + +\df public.* + List of functions + Schema | Name | Result data type | Argument data types | Type +--------+------+------------------+---------------------+------ +(0 rows) + +\do public.* + List of operators + Schema | Name | Left arg type | Right arg type | Result type | Description +--------+------+---------------+----------------+-------------+------------- +(0 rows) + +\dT c.* + List of data types + Schema | Name | Description +--------+------+------------- +(0 rows) + +\df c.* + List of functions + Schema | Name | Result data type | Argument data types | Type +--------+------+------------------+---------------------+------ +(0 rows) + +\do c.* + List of operators + Schema | Name | Left arg type | Right arg type | Result type | Description +--------+------+---------------+----------------+-------------+------------- +(0 rows) + +drop schema c; diff --git a/contrib/earthdistance/meson.build b/contrib/earthdistance/meson.build new file mode 100644 index 0000000..05794b3 --- /dev/null +++ b/contrib/earthdistance/meson.build @@ -0,0 +1,35 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +earthdistance_sources = files( + 'earthdistance.c', +) + +if host_system == 'windows' + earthdistance_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'earthdistance', + '--FILEDESC', 'earthdistance - calculate distances on the surface of the Earth',]) +endif + +earthdistance = shared_module('earthdistance', + earthdistance_sources, + kwargs: contrib_mod_args, +) +contrib_targets += earthdistance + +install_data( + 'earthdistance.control', + 'earthdistance--1.0--1.1.sql', + 'earthdistance--1.1.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'earthdistance', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'earthdistance', + ], + }, +} diff --git a/contrib/earthdistance/sql/earthdistance.sql b/contrib/earthdistance/sql/earthdistance.sql new file mode 100644 index 0000000..4145561 --- /dev/null +++ b/contrib/earthdistance/sql/earthdistance.sql @@ -0,0 +1,359 @@ +-- +-- Test earthdistance extension +-- +-- In this file we also do some testing of extension create/drop scenarios. +-- That's really exercising the core database's dependency logic, so ideally +-- we'd do it in the core regression tests, but we can't for lack of suitable +-- guaranteed-available extensions. earthdistance is a good test case because +-- it has a dependency on the cube extension. +-- + +CREATE EXTENSION earthdistance; -- fail, must install cube first +CREATE EXTENSION cube; +CREATE EXTENSION earthdistance; + +-- +-- The radius of the Earth we are using. +-- + +SELECT earth()::numeric(20,5); + +-- +-- Convert straight line distances to great circle distances. +-- +SELECT (pi()*earth())::numeric(20,5); +SELECT sec_to_gc(0)::numeric(20,5); +SELECT sec_to_gc(2*earth())::numeric(20,5); +SELECT sec_to_gc(10*earth())::numeric(20,5); +SELECT sec_to_gc(-earth())::numeric(20,5); +SELECT sec_to_gc(1000)::numeric(20,5); +SELECT sec_to_gc(10000)::numeric(20,5); +SELECT sec_to_gc(100000)::numeric(20,5); +SELECT sec_to_gc(1000000)::numeric(20,5); + +-- +-- Convert great circle distances to straight line distances. +-- + +SELECT gc_to_sec(0)::numeric(20,5); +SELECT gc_to_sec(sec_to_gc(2*earth()))::numeric(20,5); +SELECT gc_to_sec(10*earth())::numeric(20,5); +SELECT gc_to_sec(pi()*earth())::numeric(20,5); +SELECT gc_to_sec(-1000)::numeric(20,5); +SELECT gc_to_sec(1000)::numeric(20,5); +SELECT gc_to_sec(10000)::numeric(20,5); +SELECT gc_to_sec(100000)::numeric(20,5); +SELECT gc_to_sec(1000000)::numeric(20,5); + +-- +-- Set coordinates using latitude and longitude. +-- Extract each coordinate separately so we can round them. +-- + +SELECT cube_ll_coord(ll_to_earth(0,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,0),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(360,360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(360,360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(360,360),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(180,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,180),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(180,360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(180,360),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(-180,-360),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-180,-360),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-180,-360),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(0,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,180),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(0,-180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,-180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(0,-180),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(90,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,0),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(90,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(90,180),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(-90,0),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,0),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,0),3)::numeric(20,5); +SELECT cube_ll_coord(ll_to_earth(-90,180),1)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,180),2)::numeric(20,5), + cube_ll_coord(ll_to_earth(-90,180),3)::numeric(20,5); + +-- +-- Test getting the latitude of a location. +-- + +SELECT latitude(ll_to_earth(0,0))::numeric(20,10); +SELECT latitude(ll_to_earth(45,0))::numeric(20,10); +SELECT latitude(ll_to_earth(90,0))::numeric(20,10); +SELECT latitude(ll_to_earth(-45,0))::numeric(20,10); +SELECT latitude(ll_to_earth(-90,0))::numeric(20,10); +SELECT latitude(ll_to_earth(0,90))::numeric(20,10); +SELECT latitude(ll_to_earth(45,90))::numeric(20,10); +SELECT latitude(ll_to_earth(90,90))::numeric(20,10); +SELECT latitude(ll_to_earth(-45,90))::numeric(20,10); +SELECT latitude(ll_to_earth(-90,90))::numeric(20,10); +SELECT latitude(ll_to_earth(0,180))::numeric(20,10); +SELECT latitude(ll_to_earth(45,180))::numeric(20,10); +SELECT latitude(ll_to_earth(90,180))::numeric(20,10); +SELECT latitude(ll_to_earth(-45,180))::numeric(20,10); +SELECT latitude(ll_to_earth(-90,180))::numeric(20,10); +SELECT latitude(ll_to_earth(0,-90))::numeric(20,10); +SELECT latitude(ll_to_earth(45,-90))::numeric(20,10); +SELECT latitude(ll_to_earth(90,-90))::numeric(20,10); +SELECT latitude(ll_to_earth(-45,-90))::numeric(20,10); +SELECT latitude(ll_to_earth(-90,-90))::numeric(20,10); + +-- +-- Test getting the longitude of a location. +-- + +SELECT longitude(ll_to_earth(0,0))::numeric(20,10); +SELECT longitude(ll_to_earth(45,0))::numeric(20,10); +SELECT longitude(ll_to_earth(90,0))::numeric(20,10); +SELECT longitude(ll_to_earth(-45,0))::numeric(20,10); +SELECT longitude(ll_to_earth(-90,0))::numeric(20,10); +SELECT longitude(ll_to_earth(0,90))::numeric(20,10); +SELECT longitude(ll_to_earth(45,90))::numeric(20,10); +SELECT longitude(ll_to_earth(90,90))::numeric(20,10); +SELECT longitude(ll_to_earth(-45,90))::numeric(20,10); +SELECT longitude(ll_to_earth(-90,90))::numeric(20,10); +SELECT longitude(ll_to_earth(0,180))::numeric(20,10); +SELECT longitude(ll_to_earth(45,180))::numeric(20,10); +SELECT longitude(ll_to_earth(90,180))::numeric(20,10); +SELECT longitude(ll_to_earth(-45,180))::numeric(20,10); +SELECT longitude(ll_to_earth(-90,180))::numeric(20,10); +SELECT longitude(ll_to_earth(0,-90))::numeric(20,10); +SELECT longitude(ll_to_earth(45,-90))::numeric(20,10); +SELECT longitude(ll_to_earth(90,-90))::numeric(20,10); +SELECT longitude(ll_to_earth(-45,-90))::numeric(20,10); +SELECT longitude(ll_to_earth(-90,-90))::numeric(20,10); + +-- +-- For the distance tests the following is some real life data. +-- +-- Chicago has a latitude of 41.8 and a longitude of 87.6. +-- Albuquerque has a latitude of 35.1 and a longitude of 106.7. +-- (Note that latitude and longitude are specified differently +-- in the cube based functions than for the point based functions.) +-- + +-- +-- Test getting the distance between two points using earth_distance. +-- + +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,0))::numeric(20,5); +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,180))::numeric(20,5); +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(90,0))::numeric(20,5); +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,90))::numeric(20,5); +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))::numeric(20,5); +SELECT earth_distance(ll_to_earth(0,0),ll_to_earth(1,0))::numeric(20,5); +SELECT earth_distance(ll_to_earth(30,0),ll_to_earth(30,1))::numeric(20,5); +SELECT earth_distance(ll_to_earth(30,0),ll_to_earth(31,0))::numeric(20,5); +SELECT earth_distance(ll_to_earth(60,0),ll_to_earth(60,1))::numeric(20,5); +SELECT earth_distance(ll_to_earth(60,0),ll_to_earth(61,0))::numeric(20,5); +SELECT earth_distance(ll_to_earth(41.8,87.6),ll_to_earth(35.1,106.7))::numeric(20,5); +SELECT (earth_distance(ll_to_earth(41.8,87.6),ll_to_earth(35.1,106.7))* + 100./2.54/12./5280.)::numeric(20,5); + +-- +-- Test getting the distance between two points using geo_distance. +-- + +SELECT geo_distance('(0,0)'::point,'(0,0)'::point)::numeric(20,5); +SELECT geo_distance('(0,0)'::point,'(180,0)'::point)::numeric(20,5); +SELECT geo_distance('(0,0)'::point,'(0,90)'::point)::numeric(20,5); +SELECT geo_distance('(0,0)'::point,'(90,0)'::point)::numeric(20,5); +SELECT geo_distance('(0,0)'::point,'(1,0)'::point)::numeric(20,5); +SELECT geo_distance('(0,0)'::point,'(0,1)'::point)::numeric(20,5); +SELECT geo_distance('(0,30)'::point,'(1,30)'::point)::numeric(20,5); +SELECT geo_distance('(0,30)'::point,'(0,31)'::point)::numeric(20,5); +SELECT geo_distance('(0,60)'::point,'(1,60)'::point)::numeric(20,5); +SELECT geo_distance('(0,60)'::point,'(0,61)'::point)::numeric(20,5); +SELECT geo_distance('(87.6,41.8)'::point,'(106.7,35.1)'::point)::numeric(20,5); +SELECT (geo_distance('(87.6,41.8)'::point,'(106.7,35.1)'::point)*5280.*12.*2.54/100.)::numeric(20,5); + +-- +-- Test getting the distance between two points using the <@> operator. +-- + +SELECT ('(0,0)'::point <@> '(0,0)'::point)::numeric(20,5); +SELECT ('(0,0)'::point <@> '(180,0)'::point)::numeric(20,5); +SELECT ('(0,0)'::point <@> '(0,90)'::point)::numeric(20,5); +SELECT ('(0,0)'::point <@> '(90,0)'::point)::numeric(20,5); +SELECT ('(0,0)'::point <@> '(1,0)'::point)::numeric(20,5); +SELECT ('(0,0)'::point <@> '(0,1)'::point)::numeric(20,5); +SELECT ('(0,30)'::point <@> '(1,30)'::point)::numeric(20,5); +SELECT ('(0,30)'::point <@> '(0,31)'::point)::numeric(20,5); +SELECT ('(0,60)'::point <@> '(1,60)'::point)::numeric(20,5); +SELECT ('(0,60)'::point <@> '(0,61)'::point)::numeric(20,5); +SELECT ('(87.6,41.8)'::point <@> '(106.7,35.1)'::point)::numeric(20,5); +SELECT (('(87.6,41.8)'::point <@> '(106.7,35.1)'::point)*5280.*12.*2.54/100.)::numeric(20,5); + +-- +-- Test getting a bounding box around points. +-- + +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),112000),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),112000),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),112000),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),112000),3)::numeric(20,5); +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),pi()*earth()),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),pi()*earth()),3)::numeric(20,5); +SELECT cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),1)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),2)::numeric(20,5), + cube_ll_coord(earth_box(ll_to_earth(0,0),10*earth()),3)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),1)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),2)::numeric(20,5), + cube_ur_coord(earth_box(ll_to_earth(0,0),10*earth()),3)::numeric(20,5); + +-- +-- Test for points that should be in bounding boxes. +-- + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*1.00001) @> + ll_to_earth(0,1); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*1.00001) @> + ll_to_earth(0,0.1); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.01))*1.00001) @> + ll_to_earth(0,0.01); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.001))*1.00001) @> + ll_to_earth(0,0.001); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.0001))*1.00001) @> + ll_to_earth(0,0.0001); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0.0001,0.0001))*1.00001) @> + ll_to_earth(0.0001,0.0001); +SELECT earth_box(ll_to_earth(45,45), + earth_distance(ll_to_earth(45,45),ll_to_earth(45.0001,45.0001))*1.00001) @> + ll_to_earth(45.0001,45.0001); +SELECT earth_box(ll_to_earth(90,180), + earth_distance(ll_to_earth(90,180),ll_to_earth(90.0001,180.0001))*1.00001) @> + ll_to_earth(90.0001,180.0001); + +-- +-- Test for points that shouldn't be in bounding boxes. Note that we need +-- to make points way outside, since some points close may be in the box +-- but further away than the distance we are testing. +-- + +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,1))*.57735) @> + ll_to_earth(0,1); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.1))*.57735) @> + ll_to_earth(0,0.1); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.01))*.57735) @> + ll_to_earth(0,0.01); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.001))*.57735) @> + ll_to_earth(0,0.001); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0,0.0001))*.57735) @> + ll_to_earth(0,0.0001); +SELECT earth_box(ll_to_earth(0,0), + earth_distance(ll_to_earth(0,0),ll_to_earth(0.0001,0.0001))*.57735) @> + ll_to_earth(0.0001,0.0001); +SELECT earth_box(ll_to_earth(45,45), + earth_distance(ll_to_earth(45,45),ll_to_earth(45.0001,45.0001))*.57735) @> + ll_to_earth(45.0001,45.0001); +SELECT earth_box(ll_to_earth(90,180), + earth_distance(ll_to_earth(90,180),ll_to_earth(90.0001,180.0001))*.57735) @> + ll_to_earth(90.0001,180.0001); + +-- +-- Test the recommended constraints. +-- + +SELECT cube_is_point(ll_to_earth(0,0)); +SELECT cube_dim(ll_to_earth(0,0)) <= 3; +SELECT abs(cube_distance(ll_to_earth(0,0), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; +SELECT cube_is_point(ll_to_earth(30,60)); +SELECT cube_dim(ll_to_earth(30,60)) <= 3; +SELECT abs(cube_distance(ll_to_earth(30,60), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; +SELECT cube_is_point(ll_to_earth(60,90)); +SELECT cube_dim(ll_to_earth(60,90)) <= 3; +SELECT abs(cube_distance(ll_to_earth(60,90), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; +SELECT cube_is_point(ll_to_earth(-30,-90)); +SELECT cube_dim(ll_to_earth(-30,-90)) <= 3; +SELECT abs(cube_distance(ll_to_earth(-30,-90), '(0)'::cube) / earth() - 1) < + '10e-12'::float8; + +-- +-- Now we are going to test extension create/drop scenarios. +-- + +-- list what's installed +\dT + +drop extension cube; -- fail, earthdistance requires it + +drop extension earthdistance; + +drop type cube; -- fail, extension cube requires it + +-- list what's installed +\dT + +create table foo (f1 cube, f2 int); + +drop extension cube; -- fail, foo.f1 requires it + +drop table foo; + +drop extension cube; + +-- list what's installed +\dT +\df +\do + +create schema c; + +create extension cube with schema c; + +-- list what's installed +\dT public.* +\df public.* +\do public.* +\dT c.* + +create table foo (f1 c.cube, f2 int); + +drop extension cube; -- fail, foo.f1 requires it + +drop schema c; -- fail, cube requires it + +drop extension cube cascade; + +\d foo + +-- list what's installed +\dT public.* +\df public.* +\do public.* +\dT c.* +\df c.* +\do c.* + +drop schema c; diff --git a/contrib/file_fdw/.gitignore b/contrib/file_fdw/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/file_fdw/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/file_fdw/Makefile b/contrib/file_fdw/Makefile new file mode 100644 index 0000000..885459d --- /dev/null +++ b/contrib/file_fdw/Makefile @@ -0,0 +1,20 @@ +# contrib/file_fdw/Makefile + +MODULES = file_fdw + +EXTENSION = file_fdw +DATA = file_fdw--1.0.sql +PGFILEDESC = "file_fdw - foreign data wrapper for files" + +REGRESS = file_fdw + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/file_fdw +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/file_fdw/data/agg.bad b/contrib/file_fdw/data/agg.bad new file mode 100644 index 0000000..3415b15 --- /dev/null +++ b/contrib/file_fdw/data/agg.bad @@ -0,0 +1,4 @@ +56;@7.8@ +100;@99.097@ +0;@aaa@ +42;@324.78@ diff --git a/contrib/file_fdw/data/agg.csv b/contrib/file_fdw/data/agg.csv new file mode 100644 index 0000000..3ee6bf2 --- /dev/null +++ b/contrib/file_fdw/data/agg.csv @@ -0,0 +1,4 @@ +56;@7.8@ +100;@99.097@ +0;@0.09561@ +42;@324.78@ diff --git a/contrib/file_fdw/data/agg.data b/contrib/file_fdw/data/agg.data new file mode 100644 index 0000000..d92c7df --- /dev/null +++ b/contrib/file_fdw/data/agg.data @@ -0,0 +1,4 @@ +56 7.8 +100 99.097 +0 0.09561 +42 324.78 diff --git a/contrib/file_fdw/data/copy_default.csv b/contrib/file_fdw/data/copy_default.csv new file mode 100644 index 0000000..5e83a15 --- /dev/null +++ b/contrib/file_fdw/data/copy_default.csv @@ -0,0 +1,3 @@ +1,value,2022-07-04 +2,\D,2022-07-03 +3,\D,\D diff --git a/contrib/file_fdw/data/list1.csv b/contrib/file_fdw/data/list1.csv new file mode 100644 index 0000000..203f3b2 --- /dev/null +++ b/contrib/file_fdw/data/list1.csv @@ -0,0 +1,2 @@ +1,foo +1,bar diff --git a/contrib/file_fdw/data/list2.bad b/contrib/file_fdw/data/list2.bad new file mode 100644 index 0000000..00af47f --- /dev/null +++ b/contrib/file_fdw/data/list2.bad @@ -0,0 +1,2 @@ +2,baz +1,qux diff --git a/contrib/file_fdw/data/list2.csv b/contrib/file_fdw/data/list2.csv new file mode 100644 index 0000000..2fb133d --- /dev/null +++ b/contrib/file_fdw/data/list2.csv @@ -0,0 +1,2 @@ +2,baz +2,qux diff --git a/contrib/file_fdw/data/text.csv b/contrib/file_fdw/data/text.csv new file mode 100644 index 0000000..f55d9cf --- /dev/null +++ b/contrib/file_fdw/data/text.csv @@ -0,0 +1,5 @@ +AAA,aaa,123,"" +XYZ,xyz,"",321 +NULL,NULL,NULL,NULL +NULL,NULL,"NULL",NULL +ABC,abc,"","" diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out new file mode 100644 index 0000000..72304e0 --- /dev/null +++ b/contrib/file_fdw/expected/file_fdw.out @@ -0,0 +1,510 @@ +-- +-- Test foreign-data wrapper file_fdw. +-- +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR +-- Clean up in case a prior regression run failed +SET client_min_messages TO 'warning'; +DROP ROLE IF EXISTS regress_file_fdw_superuser, regress_file_fdw_user, regress_no_priv_user; +RESET client_min_messages; +CREATE ROLE regress_file_fdw_superuser LOGIN SUPERUSER; -- is a superuser +CREATE ROLE regress_file_fdw_user LOGIN; -- has priv and user mapping +CREATE ROLE regress_no_priv_user LOGIN; -- has priv but no user mapping +-- Install file_fdw +CREATE EXTENSION file_fdw; +-- create function to filter unstable results of EXPLAIN +CREATE FUNCTION explain_filter(text) RETURNS setof text +LANGUAGE plpgsql AS +$$ +declare + ln text; +begin + for ln in execute $1 + loop + -- Remove the path portion of foreign file names + ln := regexp_replace(ln, 'Foreign File: .*/([a-z.]+)$', 'Foreign File: .../\1'); + return next ln; + end loop; +end; +$$; +-- regress_file_fdw_superuser owns fdw-related objects +SET ROLE regress_file_fdw_superuser; +CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw; +-- privilege tests +SET ROLE regress_file_fdw_user; +CREATE FOREIGN DATA WRAPPER file_fdw2 HANDLER file_fdw_handler VALIDATOR file_fdw_validator; -- ERROR +ERROR: permission denied to create foreign-data wrapper "file_fdw2" +HINT: Must be superuser to create a foreign-data wrapper. +CREATE SERVER file_server2 FOREIGN DATA WRAPPER file_fdw; -- ERROR +ERROR: permission denied for foreign-data wrapper file_fdw +CREATE USER MAPPING FOR regress_file_fdw_user SERVER file_server; -- ERROR +ERROR: permission denied for foreign server file_server +SET ROLE regress_file_fdw_superuser; +GRANT USAGE ON FOREIGN SERVER file_server TO regress_file_fdw_user; +SET ROLE regress_file_fdw_user; +CREATE USER MAPPING FOR regress_file_fdw_user SERVER file_server; +-- create user mappings and grant privilege to test users +SET ROLE regress_file_fdw_superuser; +CREATE USER MAPPING FOR regress_file_fdw_superuser SERVER file_server; +CREATE USER MAPPING FOR regress_no_priv_user SERVER file_server; +-- validator tests +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'xml'); -- ERROR +ERROR: COPY format "xml" not recognized +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', quote ':'); -- ERROR +ERROR: COPY quote available only in CSV mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape ':'); -- ERROR +ERROR: COPY escape available only in CSV mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', header 'true'); -- ERROR +ERROR: cannot specify HEADER in BINARY mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', quote ':'); -- ERROR +ERROR: COPY quote available only in CSV mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', escape ':'); -- ERROR +ERROR: COPY escape available only in CSV mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter 'a'); -- ERROR +ERROR: COPY delimiter cannot be "a" +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape '-'); -- ERROR +ERROR: COPY escape available only in CSV mode +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '-', null '=-='); -- ERROR +ERROR: CSV quote character must not appear in the NULL specification +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '-', null '=-='); -- ERROR +ERROR: COPY delimiter must not appear in the NULL specification +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '-', quote '-'); -- ERROR +ERROR: COPY delimiter and quote must be different +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '---'); -- ERROR +ERROR: COPY delimiter must be a single one-byte character +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '---'); -- ERROR +ERROR: COPY quote must be a single one-byte character +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', escape '---'); -- ERROR +ERROR: COPY escape must be a single one-byte character +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\'); -- ERROR +ERROR: COPY delimiter cannot be "\" +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '.'); -- ERROR +ERROR: COPY delimiter cannot be "." +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '1'); -- ERROR +ERROR: COPY delimiter cannot be "1" +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter 'a'); -- ERROR +ERROR: COPY delimiter cannot be "a" +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter ' +'); -- ERROR +ERROR: COPY delimiter cannot be newline or carriage return +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', null ' +'); -- ERROR +ERROR: COPY null representation cannot use newline or carriage return +CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR +ERROR: either filename or program is required for file_fdw foreign tables +\set filename :abs_srcdir '/data/agg.data' +CREATE FOREIGN TABLE agg_text ( + a int2 CHECK (a >= 0), + b float4 +) SERVER file_server +OPTIONS (format 'text', filename :'filename', delimiter ' ', null '\N'); +GRANT SELECT ON agg_text TO regress_file_fdw_user; +\set filename :abs_srcdir '/data/agg.csv' +CREATE FOREIGN TABLE agg_csv ( + a int2, + b float4 +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', header 'true', delimiter ';', quote '@', escape '"', null ''); +ALTER FOREIGN TABLE agg_csv ADD CHECK (a >= 0); +\set filename :abs_srcdir '/data/agg.bad' +CREATE FOREIGN TABLE agg_bad ( + a int2, + b float4 +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', header 'true', delimiter ';', quote '@', escape '"', null ''); +ALTER FOREIGN TABLE agg_bad ADD CHECK (a >= 0); +-- test header matching +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE header_match ("1" int, foo text) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ',', header 'match'); +SELECT * FROM header_match; + 1 | foo +---+----- + 1 | bar +(1 row) + +CREATE FOREIGN TABLE header_doesnt_match (a int, foo text) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ',', header 'match'); +SELECT * FROM header_doesnt_match; -- ERROR +ERROR: column name mismatch in header line field 1: got "1", expected "a" +CONTEXT: COPY header_doesnt_match, line 1: "1,foo" +-- per-column options tests +\set filename :abs_srcdir '/data/text.csv' +CREATE FOREIGN TABLE text_csv ( + word1 text OPTIONS (force_not_null 'true'), + word2 text OPTIONS (force_not_null 'off'), + word3 text OPTIONS (force_null 'true'), + word4 text OPTIONS (force_null 'off') +) SERVER file_server +OPTIONS (format 'text', filename :'filename', null 'NULL'); +SELECT * FROM text_csv; -- ERROR +ERROR: COPY force not null available only in CSV mode +ALTER FOREIGN TABLE text_csv OPTIONS (SET format 'csv'); +\pset null _null_ +SELECT * FROM text_csv; + word1 | word2 | word3 | word4 +-------+--------+--------+-------- + AAA | aaa | 123 | + XYZ | xyz | | 321 + NULL | _null_ | _null_ | _null_ + NULL | _null_ | _null_ | _null_ + ABC | abc | | +(5 rows) + +-- force_not_null and force_null can be used together on the same column +ALTER FOREIGN TABLE text_csv ALTER COLUMN word1 OPTIONS (force_null 'true'); +ALTER FOREIGN TABLE text_csv ALTER COLUMN word3 OPTIONS (force_not_null 'true'); +-- force_not_null is not allowed to be specified at any foreign object level: +ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_not_null '*'); -- ERROR +ERROR: invalid option "force_not_null" +HINT: There are no valid options in this context. +ALTER SERVER file_server OPTIONS (ADD force_not_null '*'); -- ERROR +ERROR: invalid option "force_not_null" +HINT: There are no valid options in this context. +CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_not_null '*'); -- ERROR +ERROR: invalid option "force_not_null" +HINT: There are no valid options in this context. +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_not_null '*'); -- ERROR +ERROR: invalid option "force_not_null" +-- force_null is not allowed to be specified at any foreign object level: +ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_null '*'); -- ERROR +ERROR: invalid option "force_null" +HINT: There are no valid options in this context. +ALTER SERVER file_server OPTIONS (ADD force_null '*'); -- ERROR +ERROR: invalid option "force_null" +HINT: There are no valid options in this context. +CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_null '*'); -- ERROR +ERROR: invalid option "force_null" +HINT: There are no valid options in this context. +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_null '*'); -- ERROR +ERROR: invalid option "force_null" +-- basic query tests +SELECT * FROM agg_text WHERE b > 10.0 ORDER BY a; + a | b +-----+-------- + 42 | 324.78 + 100 | 99.097 +(2 rows) + +SELECT * FROM agg_csv ORDER BY a; + a | b +-----+--------- + 0 | 0.09561 + 42 | 324.78 + 100 | 99.097 +(3 rows) + +SELECT * FROM agg_csv c JOIN agg_text t ON (t.a = c.a) ORDER BY c.a; + a | b | a | b +-----+---------+-----+--------- + 0 | 0.09561 | 0 | 0.09561 + 42 | 324.78 | 42 | 324.78 + 100 | 99.097 | 100 | 99.097 +(3 rows) + +-- error context report tests +SELECT * FROM agg_bad; -- ERROR +ERROR: invalid input syntax for type real: "aaa" +CONTEXT: COPY agg_bad, line 3, column b: "aaa" +-- misc query tests +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv'); + Foreign Scan on public.agg_csv + Output: a, b + Foreign File: .../agg.csv + +\t off +PREPARE st(int) AS SELECT * FROM agg_csv WHERE a = $1; +EXECUTE st(100); + a | b +-----+-------- + 100 | 99.097 +(1 row) + +EXECUTE st(100); + a | b +-----+-------- + 100 | 99.097 +(1 row) + +DEALLOCATE st; +-- tableoid +SELECT tableoid::regclass, b FROM agg_csv; + tableoid | b +----------+--------- + agg_csv | 99.097 + agg_csv | 0.09561 + agg_csv | 324.78 +(3 rows) + +-- updates aren't supported +INSERT INTO agg_csv VALUES(1,2.0); +ERROR: cannot insert into foreign table "agg_csv" +UPDATE agg_csv SET a = 1; +ERROR: cannot update foreign table "agg_csv" +DELETE FROM agg_csv WHERE a = 100; +ERROR: cannot delete from foreign table "agg_csv" +TRUNCATE agg_csv; +ERROR: cannot truncate foreign table "agg_csv" +-- but this should be allowed +SELECT * FROM agg_csv FOR UPDATE; + a | b +-----+--------- + 100 | 99.097 + 0 | 0.09561 + 42 | 324.78 +(3 rows) + +-- copy from isn't supported either +COPY agg_csv FROM STDIN; +ERROR: cannot insert into foreign table "agg_csv" +-- constraint exclusion tests +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0'); + Foreign Scan on public.agg_csv + Output: a, b + Filter: (agg_csv.a < 0) + Foreign File: .../agg.csv + +\t off +SELECT * FROM agg_csv WHERE a < 0; + a | b +---+--- +(0 rows) + +SET constraint_exclusion = 'on'; +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0'); + Result + Output: a, b + One-Time Filter: false + +\t off +SELECT * FROM agg_csv WHERE a < 0; + a | b +---+--- +(0 rows) + +RESET constraint_exclusion; +-- table inheritance tests +CREATE TABLE agg (a int2, b float4); +ALTER FOREIGN TABLE agg_csv INHERIT agg; +SELECT tableoid::regclass, * FROM agg; + tableoid | a | b +----------+-----+--------- + agg_csv | 100 | 99.097 + agg_csv | 0 | 0.09561 + agg_csv | 42 | 324.78 +(3 rows) + +SELECT tableoid::regclass, * FROM agg_csv; + tableoid | a | b +----------+-----+--------- + agg_csv | 100 | 99.097 + agg_csv | 0 | 0.09561 + agg_csv | 42 | 324.78 +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY agg; + tableoid | a | b +----------+---+--- +(0 rows) + +-- updates aren't supported +UPDATE agg SET a = 1; +ERROR: cannot update foreign table "agg_csv" +DELETE FROM agg WHERE a = 100; +ERROR: cannot delete from foreign table "agg_csv" +-- but this should be allowed +SELECT tableoid::regclass, * FROM agg FOR UPDATE; + tableoid | a | b +----------+-----+--------- + agg_csv | 100 | 99.097 + agg_csv | 0 | 0.09561 + agg_csv | 42 | 324.78 +(3 rows) + +ALTER FOREIGN TABLE agg_csv NO INHERIT agg; +DROP TABLE agg; +-- declarative partitioning tests +SET ROLE regress_file_fdw_superuser; +CREATE TABLE pt (a int, b text) partition by list (a); +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE p1 partition of pt for values in (1) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ','); +CREATE TABLE p2 partition of pt for values in (2); +SELECT tableoid::regclass, * FROM pt; + tableoid | a | b +----------+---+----- + p1 | 1 | foo + p1 | 1 | bar +(2 rows) + +SELECT tableoid::regclass, * FROM p1; + tableoid | a | b +----------+---+----- + p1 | 1 | foo + p1 | 1 | bar +(2 rows) + +SELECT tableoid::regclass, * FROM p2; + tableoid | a | b +----------+---+--- +(0 rows) + +\set filename :abs_srcdir '/data/list2.bad' +COPY pt FROM :'filename' with (format 'csv', delimiter ','); -- ERROR +ERROR: cannot insert into foreign table "p1" +CONTEXT: COPY pt, line 2: "1,qux" +\set filename :abs_srcdir '/data/list2.csv' +COPY pt FROM :'filename' with (format 'csv', delimiter ','); +SELECT tableoid::regclass, * FROM pt; + tableoid | a | b +----------+---+----- + p1 | 1 | foo + p1 | 1 | bar + p2 | 2 | baz + p2 | 2 | qux +(4 rows) + +SELECT tableoid::regclass, * FROM p1; + tableoid | a | b +----------+---+----- + p1 | 1 | foo + p1 | 1 | bar +(2 rows) + +SELECT tableoid::regclass, * FROM p2; + tableoid | a | b +----------+---+----- + p2 | 2 | baz + p2 | 2 | qux +(2 rows) + +INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR +ERROR: cannot insert into foreign table "p1" +INSERT INTO pt VALUES (2, 'xyzzy'); +UPDATE pt set a = 1 where a = 2; -- ERROR +ERROR: cannot insert into foreign table "p1" +SELECT tableoid::regclass, * FROM pt; + tableoid | a | b +----------+---+------- + p1 | 1 | foo + p1 | 1 | bar + p2 | 2 | baz + p2 | 2 | qux + p2 | 2 | xyzzy +(5 rows) + +SELECT tableoid::regclass, * FROM p1; + tableoid | a | b +----------+---+----- + p1 | 1 | foo + p1 | 1 | bar +(2 rows) + +SELECT tableoid::regclass, * FROM p2; + tableoid | a | b +----------+---+------- + p2 | 2 | baz + p2 | 2 | qux + p2 | 2 | xyzzy +(3 rows) + +DROP TABLE pt; +-- generated column tests +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE gft1 (a int, b text, c text GENERATED ALWAYS AS ('foo') STORED) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ','); +SELECT a, c FROM gft1; + a | c +---+-------- + 1 | _null_ + 1 | _null_ +(2 rows) + +DROP FOREIGN TABLE gft1; +-- copy default tests +\set filename :abs_srcdir '/data/copy_default.csv' +CREATE FOREIGN TABLE copy_default ( + id integer, + text_value text not null default 'test', + ts_value timestamp without time zone not null default '2022-07-05' +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', default '\D'); +SELECT id, text_value, ts_value FROM copy_default; + id | text_value | ts_value +----+------------+-------------------------- + 1 | value | Mon Jul 04 00:00:00 2022 + 2 | test | Sun Jul 03 00:00:00 2022 + 3 | test | Tue Jul 05 00:00:00 2022 +(3 rows) + +DROP FOREIGN TABLE copy_default; +-- privilege tests +SET ROLE regress_file_fdw_superuser; +SELECT * FROM agg_text ORDER BY a; + a | b +-----+--------- + 0 | 0.09561 + 42 | 324.78 + 56 | 7.8 + 100 | 99.097 +(4 rows) + +SET ROLE regress_file_fdw_user; +SELECT * FROM agg_text ORDER BY a; + a | b +-----+--------- + 0 | 0.09561 + 42 | 324.78 + 56 | 7.8 + 100 | 99.097 +(4 rows) + +SET ROLE regress_no_priv_user; +SELECT * FROM agg_text ORDER BY a; -- ERROR +ERROR: permission denied for foreign table agg_text +SET ROLE regress_file_fdw_user; +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_text WHERE a > 0'); + Foreign Scan on public.agg_text + Output: a, b + Filter: (agg_text.a > 0) + Foreign File: .../agg.data + +\t off +-- file FDW allows foreign tables to be accessed without user mapping +DROP USER MAPPING FOR regress_file_fdw_user SERVER file_server; +SELECT * FROM agg_text ORDER BY a; + a | b +-----+--------- + 0 | 0.09561 + 42 | 324.78 + 56 | 7.8 + 100 | 99.097 +(4 rows) + +-- privilege tests for object +SET ROLE regress_file_fdw_superuser; +ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user; +ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text'); +SET ROLE regress_file_fdw_user; +ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text'); +ERROR: permission denied to set the "filename" option of a file_fdw foreign table +DETAIL: Only roles with privileges of the "pg_read_server_files" role may set this option. +SET ROLE regress_file_fdw_superuser; +-- cleanup +RESET ROLE; +DROP EXTENSION file_fdw CASCADE; +NOTICE: drop cascades to 9 other objects +DETAIL: drop cascades to server file_server +drop cascades to user mapping for regress_file_fdw_superuser on server file_server +drop cascades to user mapping for regress_no_priv_user on server file_server +drop cascades to foreign table agg_text +drop cascades to foreign table agg_csv +drop cascades to foreign table agg_bad +drop cascades to foreign table header_match +drop cascades to foreign table header_doesnt_match +drop cascades to foreign table text_csv +DROP ROLE regress_file_fdw_superuser, regress_file_fdw_user, regress_no_priv_user; diff --git a/contrib/file_fdw/file_fdw--1.0.sql b/contrib/file_fdw/file_fdw--1.0.sql new file mode 100644 index 0000000..856e6bf --- /dev/null +++ b/contrib/file_fdw/file_fdw--1.0.sql @@ -0,0 +1,18 @@ +/* contrib/file_fdw/file_fdw--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION file_fdw" to load this file. \quit + +CREATE FUNCTION file_fdw_handler() +RETURNS fdw_handler +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FUNCTION file_fdw_validator(text[], oid) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FOREIGN DATA WRAPPER file_fdw + HANDLER file_fdw_handler + VALIDATOR file_fdw_validator; diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c new file mode 100644 index 0000000..9e330b9 --- /dev/null +++ b/contrib/file_fdw/file_fdw.c @@ -0,0 +1,1252 @@ +/*------------------------------------------------------------------------- + * + * file_fdw.c + * foreign-data wrapper for server-side flat files (or programs). + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/file_fdw/file_fdw.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include +#include + +#include "access/htup_details.h" +#include "access/reloptions.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_foreign_table.h" +#include "commands/copy.h" +#include "commands/defrem.h" +#include "commands/explain.h" +#include "commands/vacuum.h" +#include "foreign/fdwapi.h" +#include "foreign/foreign.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/planmain.h" +#include "optimizer/restrictinfo.h" +#include "utils/acl.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/sampling.h" +#include "utils/varlena.h" + +PG_MODULE_MAGIC; + +/* + * Describes the valid options for objects that use this wrapper. + */ +struct FileFdwOption +{ + const char *optname; + Oid optcontext; /* Oid of catalog in which option may appear */ +}; + +/* + * Valid options for file_fdw. + * These options are based on the options for the COPY FROM command. + * But note that force_not_null and force_null are handled as boolean options + * attached to a column, not as table options. + * + * Note: If you are adding new option for user mapping, you need to modify + * fileGetOptions(), which currently doesn't bother to look at user mappings. + */ +static const struct FileFdwOption valid_options[] = { + /* Data source options */ + {"filename", ForeignTableRelationId}, + {"program", ForeignTableRelationId}, + + /* Format options */ + /* oids option is not supported */ + {"format", ForeignTableRelationId}, + {"header", ForeignTableRelationId}, + {"delimiter", ForeignTableRelationId}, + {"quote", ForeignTableRelationId}, + {"escape", ForeignTableRelationId}, + {"null", ForeignTableRelationId}, + {"default", ForeignTableRelationId}, + {"encoding", ForeignTableRelationId}, + {"force_not_null", AttributeRelationId}, + {"force_null", AttributeRelationId}, + + /* + * force_quote is not supported by file_fdw because it's for COPY TO. + */ + + /* Sentinel */ + {NULL, InvalidOid} +}; + +/* + * FDW-specific information for RelOptInfo.fdw_private. + */ +typedef struct FileFdwPlanState +{ + char *filename; /* file or program to read from */ + bool is_program; /* true if filename represents an OS command */ + List *options; /* merged COPY options, excluding filename and + * is_program */ + BlockNumber pages; /* estimate of file's physical size */ + double ntuples; /* estimate of number of data rows */ +} FileFdwPlanState; + +/* + * FDW-specific information for ForeignScanState.fdw_state. + */ +typedef struct FileFdwExecutionState +{ + char *filename; /* file or program to read from */ + bool is_program; /* true if filename represents an OS command */ + List *options; /* merged COPY options, excluding filename and + * is_program */ + CopyFromState cstate; /* COPY execution state */ +} FileFdwExecutionState; + +/* + * SQL functions + */ +PG_FUNCTION_INFO_V1(file_fdw_handler); +PG_FUNCTION_INFO_V1(file_fdw_validator); + +/* + * FDW callback routines + */ +static void fileGetForeignRelSize(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid); +static void fileGetForeignPaths(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid); +static ForeignScan *fileGetForeignPlan(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid, + ForeignPath *best_path, + List *tlist, + List *scan_clauses, + Plan *outer_plan); +static void fileExplainForeignScan(ForeignScanState *node, ExplainState *es); +static void fileBeginForeignScan(ForeignScanState *node, int eflags); +static TupleTableSlot *fileIterateForeignScan(ForeignScanState *node); +static void fileReScanForeignScan(ForeignScanState *node); +static void fileEndForeignScan(ForeignScanState *node); +static bool fileAnalyzeForeignTable(Relation relation, + AcquireSampleRowsFunc *func, + BlockNumber *totalpages); +static bool fileIsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte); + +/* + * Helper functions + */ +static bool is_valid_option(const char *option, Oid context); +static void fileGetOptions(Oid foreigntableid, + char **filename, + bool *is_program, + List **other_options); +static List *get_file_fdw_attribute_options(Oid relid); +static bool check_selective_binary_conversion(RelOptInfo *baserel, + Oid foreigntableid, + List **columns); +static void estimate_size(PlannerInfo *root, RelOptInfo *baserel, + FileFdwPlanState *fdw_private); +static void estimate_costs(PlannerInfo *root, RelOptInfo *baserel, + FileFdwPlanState *fdw_private, + Cost *startup_cost, Cost *total_cost); +static int file_acquire_sample_rows(Relation onerel, int elevel, + HeapTuple *rows, int targrows, + double *totalrows, double *totaldeadrows); + + +/* + * Foreign-data wrapper handler function: return a struct with pointers + * to my callback routines. + */ +Datum +file_fdw_handler(PG_FUNCTION_ARGS) +{ + FdwRoutine *fdwroutine = makeNode(FdwRoutine); + + fdwroutine->GetForeignRelSize = fileGetForeignRelSize; + fdwroutine->GetForeignPaths = fileGetForeignPaths; + fdwroutine->GetForeignPlan = fileGetForeignPlan; + fdwroutine->ExplainForeignScan = fileExplainForeignScan; + fdwroutine->BeginForeignScan = fileBeginForeignScan; + fdwroutine->IterateForeignScan = fileIterateForeignScan; + fdwroutine->ReScanForeignScan = fileReScanForeignScan; + fdwroutine->EndForeignScan = fileEndForeignScan; + fdwroutine->AnalyzeForeignTable = fileAnalyzeForeignTable; + fdwroutine->IsForeignScanParallelSafe = fileIsForeignScanParallelSafe; + + PG_RETURN_POINTER(fdwroutine); +} + +/* + * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER, + * USER MAPPING or FOREIGN TABLE that uses file_fdw. + * + * Raise an ERROR if the option or its value is considered invalid. + */ +Datum +file_fdw_validator(PG_FUNCTION_ARGS) +{ + List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + Oid catalog = PG_GETARG_OID(1); + char *filename = NULL; + DefElem *force_not_null = NULL; + DefElem *force_null = NULL; + List *other_options = NIL; + ListCell *cell; + + /* + * Check that only options supported by file_fdw, and allowed for the + * current object type, are given. + */ + foreach(cell, options_list) + { + DefElem *def = (DefElem *) lfirst(cell); + + if (!is_valid_option(def->defname, catalog)) + { + const struct FileFdwOption *opt; + const char *closest_match; + ClosestMatchState match_state; + bool has_valid_options = false; + + /* + * Unknown option specified, complain about it. Provide a hint + * with a valid option that looks similar, if there is one. + */ + initClosestMatch(&match_state, def->defname, 4); + for (opt = valid_options; opt->optname; opt++) + { + if (catalog == opt->optcontext) + { + has_valid_options = true; + updateClosestMatch(&match_state, opt->optname); + } + } + + closest_match = getClosestMatch(&match_state); + ereport(ERROR, + (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), + errmsg("invalid option \"%s\"", def->defname), + has_valid_options ? closest_match ? + errhint("Perhaps you meant the option \"%s\".", + closest_match) : 0 : + errhint("There are no valid options in this context."))); + } + + /* + * Separate out filename, program, and column-specific options, since + * ProcessCopyOptions won't accept them. + */ + if (strcmp(def->defname, "filename") == 0 || + strcmp(def->defname, "program") == 0) + { + if (filename) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + + /* + * Check permissions for changing which file or program is used by + * the file_fdw. + * + * Only members of the role 'pg_read_server_files' are allowed to + * set the 'filename' option of a file_fdw foreign table, while + * only members of the role 'pg_execute_server_program' are + * allowed to set the 'program' option. This is because we don't + * want regular users to be able to control which file gets read + * or which program gets executed. + * + * Putting this sort of permissions check in a validator is a bit + * of a crock, but there doesn't seem to be any other place that + * can enforce the check more cleanly. + * + * Note that the valid_options[] array disallows setting filename + * and program at any options level other than foreign table --- + * otherwise there'd still be a security hole. + */ + if (strcmp(def->defname, "filename") == 0 && + !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set the \"%s\" option of a file_fdw foreign table", + "filename"), + errdetail("Only roles with privileges of the \"%s\" role may set this option.", + "pg_read_server_files"))); + + if (strcmp(def->defname, "program") == 0 && + !has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set the \"%s\" option of a file_fdw foreign table", + "program"), + errdetail("Only roles with privileges of the \"%s\" role may set this option.", + "pg_execute_server_program"))); + + filename = defGetString(def); + } + + /* + * force_not_null is a boolean option; after validation we can discard + * it - it will be retrieved later in get_file_fdw_attribute_options() + */ + else if (strcmp(def->defname, "force_not_null") == 0) + { + if (force_not_null) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"), + errhint("Option \"force_not_null\" supplied more than once for a column."))); + force_not_null = def; + /* Don't care what the value is, as long as it's a legal boolean */ + (void) defGetBoolean(def); + } + /* See comments for force_not_null above */ + else if (strcmp(def->defname, "force_null") == 0) + { + if (force_null) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"), + errhint("Option \"force_null\" supplied more than once for a column."))); + force_null = def; + (void) defGetBoolean(def); + } + else + other_options = lappend(other_options, def); + } + + /* + * Now apply the core COPY code's validation logic for more checks. + */ + ProcessCopyOptions(NULL, NULL, true, other_options); + + /* + * Either filename or program option is required for file_fdw foreign + * tables. + */ + if (catalog == ForeignTableRelationId && filename == NULL) + ereport(ERROR, + (errcode(ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED), + errmsg("either filename or program is required for file_fdw foreign tables"))); + + PG_RETURN_VOID(); +} + +/* + * Check if the provided option is one of the valid options. + * context is the Oid of the catalog holding the object the option is for. + */ +static bool +is_valid_option(const char *option, Oid context) +{ + const struct FileFdwOption *opt; + + for (opt = valid_options; opt->optname; opt++) + { + if (context == opt->optcontext && strcmp(opt->optname, option) == 0) + return true; + } + return false; +} + +/* + * Fetch the options for a file_fdw foreign table. + * + * We have to separate out filename/program from the other options because + * those must not appear in the options list passed to the core COPY code. + */ +static void +fileGetOptions(Oid foreigntableid, + char **filename, bool *is_program, List **other_options) +{ + ForeignTable *table; + ForeignServer *server; + ForeignDataWrapper *wrapper; + List *options; + ListCell *lc; + + /* + * Extract options from FDW objects. We ignore user mappings because + * file_fdw doesn't have any options that can be specified there. + * + * (XXX Actually, given the current contents of valid_options[], there's + * no point in examining anything except the foreign table's own options. + * Simplify?) + */ + table = GetForeignTable(foreigntableid); + server = GetForeignServer(table->serverid); + wrapper = GetForeignDataWrapper(server->fdwid); + + options = NIL; + options = list_concat(options, wrapper->options); + options = list_concat(options, server->options); + options = list_concat(options, table->options); + options = list_concat(options, get_file_fdw_attribute_options(foreigntableid)); + + /* + * Separate out the filename or program option (we assume there is only + * one). + */ + *filename = NULL; + *is_program = false; + foreach(lc, options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "filename") == 0) + { + *filename = defGetString(def); + options = foreach_delete_current(options, lc); + break; + } + else if (strcmp(def->defname, "program") == 0) + { + *filename = defGetString(def); + *is_program = true; + options = foreach_delete_current(options, lc); + break; + } + } + + /* + * The validator should have checked that filename or program was included + * in the options, but check again, just in case. + */ + if (*filename == NULL) + elog(ERROR, "either filename or program is required for file_fdw foreign tables"); + + *other_options = options; +} + +/* + * Retrieve per-column generic options from pg_attribute and construct a list + * of DefElems representing them. + * + * At the moment we only have "force_not_null", and "force_null", + * which should each be combined into a single DefElem listing all such + * columns, since that's what COPY expects. + */ +static List * +get_file_fdw_attribute_options(Oid relid) +{ + Relation rel; + TupleDesc tupleDesc; + AttrNumber natts; + AttrNumber attnum; + List *fnncolumns = NIL; + List *fncolumns = NIL; + + List *options = NIL; + + rel = table_open(relid, AccessShareLock); + tupleDesc = RelationGetDescr(rel); + natts = tupleDesc->natts; + + /* Retrieve FDW options for all user-defined attributes. */ + for (attnum = 1; attnum <= natts; attnum++) + { + Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum - 1); + List *column_options; + ListCell *lc; + + /* Skip dropped attributes. */ + if (attr->attisdropped) + continue; + + column_options = GetForeignColumnOptions(relid, attnum); + foreach(lc, column_options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "force_not_null") == 0) + { + if (defGetBoolean(def)) + { + char *attname = pstrdup(NameStr(attr->attname)); + + fnncolumns = lappend(fnncolumns, makeString(attname)); + } + } + else if (strcmp(def->defname, "force_null") == 0) + { + if (defGetBoolean(def)) + { + char *attname = pstrdup(NameStr(attr->attname)); + + fncolumns = lappend(fncolumns, makeString(attname)); + } + } + /* maybe in future handle other column options here */ + } + } + + table_close(rel, AccessShareLock); + + /* + * Return DefElem only when some column(s) have force_not_null / + * force_null options set + */ + if (fnncolumns != NIL) + options = lappend(options, makeDefElem("force_not_null", (Node *) fnncolumns, -1)); + + if (fncolumns != NIL) + options = lappend(options, makeDefElem("force_null", (Node *) fncolumns, -1)); + + return options; +} + +/* + * fileGetForeignRelSize + * Obtain relation size estimates for a foreign table + */ +static void +fileGetForeignRelSize(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid) +{ + FileFdwPlanState *fdw_private; + + /* + * Fetch options. We only need filename (or program) at this point, but + * we might as well get everything and not need to re-fetch it later in + * planning. + */ + fdw_private = (FileFdwPlanState *) palloc(sizeof(FileFdwPlanState)); + fileGetOptions(foreigntableid, + &fdw_private->filename, + &fdw_private->is_program, + &fdw_private->options); + baserel->fdw_private = (void *) fdw_private; + + /* Estimate relation size */ + estimate_size(root, baserel, fdw_private); +} + +/* + * fileGetForeignPaths + * Create possible access paths for a scan on the foreign table + * + * Currently we don't support any push-down feature, so there is only one + * possible access path, which simply returns all records in the order in + * the data file. + */ +static void +fileGetForeignPaths(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid) +{ + FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private; + Cost startup_cost; + Cost total_cost; + List *columns; + List *coptions = NIL; + + /* Decide whether to selectively perform binary conversion */ + if (check_selective_binary_conversion(baserel, + foreigntableid, + &columns)) + coptions = list_make1(makeDefElem("convert_selectively", + (Node *) columns, -1)); + + /* Estimate costs */ + estimate_costs(root, baserel, fdw_private, + &startup_cost, &total_cost); + + /* + * Create a ForeignPath node and add it as only possible path. We use the + * fdw_private list of the path to carry the convert_selectively option; + * it will be propagated into the fdw_private list of the Plan node. + * + * We don't support pushing join clauses into the quals of this path, but + * it could still have required parameterization due to LATERAL refs in + * its tlist. + */ + add_path(baserel, (Path *) + create_foreignscan_path(root, baserel, + NULL, /* default pathtarget */ + baserel->rows, + startup_cost, + total_cost, + NIL, /* no pathkeys */ + baserel->lateral_relids, + NULL, /* no extra plan */ + coptions)); + + /* + * If data file was sorted, and we knew it somehow, we could insert + * appropriate pathkeys into the ForeignPath node to tell the planner + * that. + */ +} + +/* + * fileGetForeignPlan + * Create a ForeignScan plan node for scanning the foreign table + */ +static ForeignScan * +fileGetForeignPlan(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid, + ForeignPath *best_path, + List *tlist, + List *scan_clauses, + Plan *outer_plan) +{ + Index scan_relid = baserel->relid; + + /* + * We have no native ability to evaluate restriction clauses, so we just + * put all the scan_clauses into the plan node's qual list for the + * executor to check. So all we have to do here is strip RestrictInfo + * nodes from the clauses and ignore pseudoconstants (which will be + * handled elsewhere). + */ + scan_clauses = extract_actual_clauses(scan_clauses, false); + + /* Create the ForeignScan node */ + return make_foreignscan(tlist, + scan_clauses, + scan_relid, + NIL, /* no expressions to evaluate */ + best_path->fdw_private, + NIL, /* no custom tlist */ + NIL, /* no remote quals */ + outer_plan); +} + +/* + * fileExplainForeignScan + * Produce extra output for EXPLAIN + */ +static void +fileExplainForeignScan(ForeignScanState *node, ExplainState *es) +{ + char *filename; + bool is_program; + List *options; + + /* Fetch options --- we only need filename and is_program at this point */ + fileGetOptions(RelationGetRelid(node->ss.ss_currentRelation), + &filename, &is_program, &options); + + if (is_program) + ExplainPropertyText("Foreign Program", filename, es); + else + ExplainPropertyText("Foreign File", filename, es); + + /* Suppress file size if we're not showing cost details */ + if (es->costs) + { + struct stat stat_buf; + + if (!is_program && + stat(filename, &stat_buf) == 0) + ExplainPropertyInteger("Foreign File Size", "b", + (int64) stat_buf.st_size, es); + } +} + +/* + * fileBeginForeignScan + * Initiate access to the file by creating CopyState + */ +static void +fileBeginForeignScan(ForeignScanState *node, int eflags) +{ + ForeignScan *plan = (ForeignScan *) node->ss.ps.plan; + char *filename; + bool is_program; + List *options; + CopyFromState cstate; + FileFdwExecutionState *festate; + + /* + * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. + */ + if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + return; + + /* Fetch options of foreign table */ + fileGetOptions(RelationGetRelid(node->ss.ss_currentRelation), + &filename, &is_program, &options); + + /* Add any options from the plan (currently only convert_selectively) */ + options = list_concat(options, plan->fdw_private); + + /* + * Create CopyState from FDW options. We always acquire all columns, so + * as to match the expected ScanTupleSlot signature. + */ + cstate = BeginCopyFrom(NULL, + node->ss.ss_currentRelation, + NULL, + filename, + is_program, + NULL, + NIL, + options); + + /* + * Save state in node->fdw_state. We must save enough information to call + * BeginCopyFrom() again. + */ + festate = (FileFdwExecutionState *) palloc(sizeof(FileFdwExecutionState)); + festate->filename = filename; + festate->is_program = is_program; + festate->options = options; + festate->cstate = cstate; + + node->fdw_state = (void *) festate; +} + +/* + * fileIterateForeignScan + * Read next record from the data file and store it into the + * ScanTupleSlot as a virtual tuple + */ +static TupleTableSlot * +fileIterateForeignScan(ForeignScanState *node) +{ + FileFdwExecutionState *festate = (FileFdwExecutionState *) node->fdw_state; + EState *estate = CreateExecutorState(); + ExprContext *econtext; + MemoryContext oldcontext; + TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; + bool found; + ErrorContextCallback errcallback; + + /* Set up callback to identify error line number. */ + errcallback.callback = CopyFromErrorCallback; + errcallback.arg = (void *) festate->cstate; + errcallback.previous = error_context_stack; + error_context_stack = &errcallback; + + /* + * The protocol for loading a virtual tuple into a slot is first + * ExecClearTuple, then fill the values/isnull arrays, then + * ExecStoreVirtualTuple. If we don't find another row in the file, we + * just skip the last step, leaving the slot empty as required. + * + * We pass ExprContext because there might be a use of the DEFAULT option + * in COPY FROM, so we may need to evaluate default expressions. + */ + ExecClearTuple(slot); + econtext = GetPerTupleExprContext(estate); + + /* + * DEFAULT expressions need to be evaluated in a per-tuple context, so + * switch in case we are doing that. + */ + oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); + found = NextCopyFrom(festate->cstate, econtext, + slot->tts_values, slot->tts_isnull); + if (found) + ExecStoreVirtualTuple(slot); + + /* Switch back to original memory context */ + MemoryContextSwitchTo(oldcontext); + + /* Remove error callback. */ + error_context_stack = errcallback.previous; + + return slot; +} + +/* + * fileReScanForeignScan + * Rescan table, possibly with new parameters + */ +static void +fileReScanForeignScan(ForeignScanState *node) +{ + FileFdwExecutionState *festate = (FileFdwExecutionState *) node->fdw_state; + + EndCopyFrom(festate->cstate); + + festate->cstate = BeginCopyFrom(NULL, + node->ss.ss_currentRelation, + NULL, + festate->filename, + festate->is_program, + NULL, + NIL, + festate->options); +} + +/* + * fileEndForeignScan + * Finish scanning foreign table and dispose objects used for this scan + */ +static void +fileEndForeignScan(ForeignScanState *node) +{ + FileFdwExecutionState *festate = (FileFdwExecutionState *) node->fdw_state; + + /* if festate is NULL, we are in EXPLAIN; nothing to do */ + if (festate) + EndCopyFrom(festate->cstate); +} + +/* + * fileAnalyzeForeignTable + * Test whether analyzing this foreign table is supported + */ +static bool +fileAnalyzeForeignTable(Relation relation, + AcquireSampleRowsFunc *func, + BlockNumber *totalpages) +{ + char *filename; + bool is_program; + List *options; + struct stat stat_buf; + + /* Fetch options of foreign table */ + fileGetOptions(RelationGetRelid(relation), &filename, &is_program, &options); + + /* + * If this is a program instead of a file, just return false to skip + * analyzing the table. We could run the program and collect stats on + * whatever it currently returns, but it seems likely that in such cases + * the output would be too volatile for the stats to be useful. Maybe + * there should be an option to enable doing this? + */ + if (is_program) + return false; + + /* + * Get size of the file. (XXX if we fail here, would it be better to just + * return false to skip analyzing the table?) + */ + if (stat(filename, &stat_buf) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", + filename))); + + /* + * Convert size to pages. Must return at least 1 so that we can tell + * later on that pg_class.relpages is not default. + */ + *totalpages = (stat_buf.st_size + (BLCKSZ - 1)) / BLCKSZ; + if (*totalpages < 1) + *totalpages = 1; + + *func = file_acquire_sample_rows; + + return true; +} + +/* + * fileIsForeignScanParallelSafe + * Reading a file, or external program, in a parallel worker should work + * just the same as reading it in the leader, so mark scans safe. + */ +static bool +fileIsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte) +{ + return true; +} + +/* + * check_selective_binary_conversion + * + * Check to see if it's useful to convert only a subset of the file's columns + * to binary. If so, construct a list of the column names to be converted, + * return that at *columns, and return true. (Note that it's possible to + * determine that no columns need be converted, for instance with a COUNT(*) + * query. So we can't use returning a NIL list to indicate failure.) + */ +static bool +check_selective_binary_conversion(RelOptInfo *baserel, + Oid foreigntableid, + List **columns) +{ + ForeignTable *table; + ListCell *lc; + Relation rel; + TupleDesc tupleDesc; + int attidx; + Bitmapset *attrs_used = NULL; + bool has_wholerow = false; + int numattrs; + int i; + + *columns = NIL; /* default result */ + + /* + * Check format of the file. If binary format, this is irrelevant. + */ + table = GetForeignTable(foreigntableid); + foreach(lc, table->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "format") == 0) + { + char *format = defGetString(def); + + if (strcmp(format, "binary") == 0) + return false; + break; + } + } + + /* Collect all the attributes needed for joins or final output. */ + pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, + &attrs_used); + + /* Add all the attributes used by restriction clauses. */ + foreach(lc, baserel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + pull_varattnos((Node *) rinfo->clause, baserel->relid, + &attrs_used); + } + + /* Convert attribute numbers to column names. */ + rel = table_open(foreigntableid, AccessShareLock); + tupleDesc = RelationGetDescr(rel); + + attidx = -1; + while ((attidx = bms_next_member(attrs_used, attidx)) >= 0) + { + /* attidx is zero-based, attnum is the normal attribute number */ + AttrNumber attnum = attidx + FirstLowInvalidHeapAttributeNumber; + + if (attnum == 0) + { + has_wholerow = true; + break; + } + + /* Ignore system attributes. */ + if (attnum < 0) + continue; + + /* Get user attributes. */ + if (attnum > 0) + { + Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum - 1); + char *attname = NameStr(attr->attname); + + /* Skip dropped attributes (probably shouldn't see any here). */ + if (attr->attisdropped) + continue; + + /* + * Skip generated columns (COPY won't accept them in the column + * list) + */ + if (attr->attgenerated) + continue; + *columns = lappend(*columns, makeString(pstrdup(attname))); + } + } + + /* Count non-dropped user attributes while we have the tupdesc. */ + numattrs = 0; + for (i = 0; i < tupleDesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupleDesc, i); + + if (attr->attisdropped) + continue; + numattrs++; + } + + table_close(rel, AccessShareLock); + + /* If there's a whole-row reference, fail: we need all the columns. */ + if (has_wholerow) + { + *columns = NIL; + return false; + } + + /* If all the user attributes are needed, fail. */ + if (numattrs == list_length(*columns)) + { + *columns = NIL; + return false; + } + + return true; +} + +/* + * Estimate size of a foreign table. + * + * The main result is returned in baserel->rows. We also set + * fdw_private->pages and fdw_private->ntuples for later use in the cost + * calculation. + */ +static void +estimate_size(PlannerInfo *root, RelOptInfo *baserel, + FileFdwPlanState *fdw_private) +{ + struct stat stat_buf; + BlockNumber pages; + double ntuples; + double nrows; + + /* + * Get size of the file. It might not be there at plan time, though, in + * which case we have to use a default estimate. We also have to fall + * back to the default if using a program as the input. + */ + if (fdw_private->is_program || stat(fdw_private->filename, &stat_buf) < 0) + stat_buf.st_size = 10 * BLCKSZ; + + /* + * Convert size to pages for use in I/O cost estimate later. + */ + pages = (stat_buf.st_size + (BLCKSZ - 1)) / BLCKSZ; + if (pages < 1) + pages = 1; + fdw_private->pages = pages; + + /* + * Estimate the number of tuples in the file. + */ + if (baserel->tuples >= 0 && baserel->pages > 0) + { + /* + * We have # of pages and # of tuples from pg_class (that is, from a + * previous ANALYZE), so compute a tuples-per-page estimate and scale + * that by the current file size. + */ + double density; + + density = baserel->tuples / (double) baserel->pages; + ntuples = clamp_row_est(density * (double) pages); + } + else + { + /* + * Otherwise we have to fake it. We back into this estimate using the + * planner's idea of the relation width; which is bogus if not all + * columns are being read, not to mention that the text representation + * of a row probably isn't the same size as its internal + * representation. Possibly we could do something better, but the + * real answer to anyone who complains is "ANALYZE" ... + */ + int tuple_width; + + tuple_width = MAXALIGN(baserel->reltarget->width) + + MAXALIGN(SizeofHeapTupleHeader); + ntuples = clamp_row_est((double) stat_buf.st_size / + (double) tuple_width); + } + fdw_private->ntuples = ntuples; + + /* + * Now estimate the number of rows returned by the scan after applying the + * baserestrictinfo quals. + */ + nrows = ntuples * + clauselist_selectivity(root, + baserel->baserestrictinfo, + 0, + JOIN_INNER, + NULL); + + nrows = clamp_row_est(nrows); + + /* Save the output-rows estimate for the planner */ + baserel->rows = nrows; +} + +/* + * Estimate costs of scanning a foreign table. + * + * Results are returned in *startup_cost and *total_cost. + */ +static void +estimate_costs(PlannerInfo *root, RelOptInfo *baserel, + FileFdwPlanState *fdw_private, + Cost *startup_cost, Cost *total_cost) +{ + BlockNumber pages = fdw_private->pages; + double ntuples = fdw_private->ntuples; + Cost run_cost = 0; + Cost cpu_per_tuple; + + /* + * We estimate costs almost the same way as cost_seqscan(), thus assuming + * that I/O costs are equivalent to a regular table file of the same size. + * However, we take per-tuple CPU costs as 10x of a seqscan, to account + * for the cost of parsing records. + * + * In the case of a program source, this calculation is even more divorced + * from reality, but we have no good alternative; and it's not clear that + * the numbers we produce here matter much anyway, since there's only one + * access path for the rel. + */ + run_cost += seq_page_cost * pages; + + *startup_cost = baserel->baserestrictcost.startup; + cpu_per_tuple = cpu_tuple_cost * 10 + baserel->baserestrictcost.per_tuple; + run_cost += cpu_per_tuple * ntuples; + *total_cost = *startup_cost + run_cost; +} + +/* + * file_acquire_sample_rows -- acquire a random sample of rows from the table + * + * Selected rows are returned in the caller-allocated array rows[], + * which must have at least targrows entries. + * The actual number of rows selected is returned as the function result. + * We also count the total number of rows in the file and return it into + * *totalrows. Note that *totaldeadrows is always set to 0. + * + * Note that the returned list of rows is not always in order by physical + * position in the file. Therefore, correlation estimates derived later + * may be meaningless, but it's OK because we don't use the estimates + * currently (the planner only pays attention to correlation for indexscans). + */ +static int +file_acquire_sample_rows(Relation onerel, int elevel, + HeapTuple *rows, int targrows, + double *totalrows, double *totaldeadrows) +{ + int numrows = 0; + double rowstoskip = -1; /* -1 means not set yet */ + ReservoirStateData rstate; + TupleDesc tupDesc; + Datum *values; + bool *nulls; + bool found; + char *filename; + bool is_program; + List *options; + CopyFromState cstate; + ErrorContextCallback errcallback; + MemoryContext oldcontext = CurrentMemoryContext; + MemoryContext tupcontext; + + Assert(onerel); + Assert(targrows > 0); + + tupDesc = RelationGetDescr(onerel); + values = (Datum *) palloc(tupDesc->natts * sizeof(Datum)); + nulls = (bool *) palloc(tupDesc->natts * sizeof(bool)); + + /* Fetch options of foreign table */ + fileGetOptions(RelationGetRelid(onerel), &filename, &is_program, &options); + + /* + * Create CopyState from FDW options. + */ + cstate = BeginCopyFrom(NULL, onerel, NULL, filename, is_program, NULL, NIL, + options); + + /* + * Use per-tuple memory context to prevent leak of memory used to read + * rows from the file with Copy routines. + */ + tupcontext = AllocSetContextCreate(CurrentMemoryContext, + "file_fdw temporary context", + ALLOCSET_DEFAULT_SIZES); + + /* Prepare for sampling rows */ + reservoir_init_selection_state(&rstate, targrows); + + /* Set up callback to identify error line number. */ + errcallback.callback = CopyFromErrorCallback; + errcallback.arg = (void *) cstate; + errcallback.previous = error_context_stack; + error_context_stack = &errcallback; + + *totalrows = 0; + *totaldeadrows = 0; + for (;;) + { + /* Check for user-requested abort or sleep */ + vacuum_delay_point(); + + /* Fetch next row */ + MemoryContextReset(tupcontext); + MemoryContextSwitchTo(tupcontext); + + found = NextCopyFrom(cstate, NULL, values, nulls); + + MemoryContextSwitchTo(oldcontext); + + if (!found) + break; + + /* + * The first targrows sample rows are simply copied into the + * reservoir. Then we start replacing tuples in the sample until we + * reach the end of the relation. This algorithm is from Jeff Vitter's + * paper (see more info in commands/analyze.c). + */ + if (numrows < targrows) + { + rows[numrows++] = heap_form_tuple(tupDesc, values, nulls); + } + else + { + /* + * t in Vitter's paper is the number of records already processed. + * If we need to compute a new S value, we must use the + * not-yet-incremented value of totalrows as t. + */ + if (rowstoskip < 0) + rowstoskip = reservoir_get_next_S(&rstate, *totalrows, targrows); + + if (rowstoskip <= 0) + { + /* + * Found a suitable tuple, so save it, replacing one old tuple + * at random + */ + int k = (int) (targrows * sampler_random_fract(&rstate.randstate)); + + Assert(k >= 0 && k < targrows); + heap_freetuple(rows[k]); + rows[k] = heap_form_tuple(tupDesc, values, nulls); + } + + rowstoskip -= 1; + } + + *totalrows += 1; + } + + /* Remove error callback. */ + error_context_stack = errcallback.previous; + + /* Clean up. */ + MemoryContextDelete(tupcontext); + + EndCopyFrom(cstate); + + pfree(values); + pfree(nulls); + + /* + * Emit some interesting relation info + */ + ereport(elevel, + (errmsg("\"%s\": file contains %.0f rows; " + "%d rows in sample", + RelationGetRelationName(onerel), + *totalrows, numrows))); + + return numrows; +} diff --git a/contrib/file_fdw/file_fdw.control b/contrib/file_fdw/file_fdw.control new file mode 100644 index 0000000..f0fccab --- /dev/null +++ b/contrib/file_fdw/file_fdw.control @@ -0,0 +1,5 @@ +# file_fdw extension +comment = 'foreign-data wrapper for flat file access' +default_version = '1.0' +module_pathname = '$libdir/file_fdw' +relocatable = true diff --git a/contrib/file_fdw/meson.build b/contrib/file_fdw/meson.build new file mode 100644 index 0000000..7ea6ddf --- /dev/null +++ b/contrib/file_fdw/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +file_fdw_sources = files( + 'file_fdw.c', +) + +if host_system == 'windows' + file_fdw_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'file_fdw', + '--FILEDESC', 'file_fdw - foreign data wrapper for files',]) +endif + +file_fdw = shared_module('file_fdw', + file_fdw_sources, + kwargs: contrib_mod_args, +) +contrib_targets += file_fdw + +install_data( + 'file_fdw.control', + 'file_fdw--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'file_fdw', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'file_fdw', + ], + }, +} diff --git a/contrib/file_fdw/sql/file_fdw.sql b/contrib/file_fdw/sql/file_fdw.sql new file mode 100644 index 0000000..f0548e1 --- /dev/null +++ b/contrib/file_fdw/sql/file_fdw.sql @@ -0,0 +1,273 @@ +-- +-- Test foreign-data wrapper file_fdw. +-- + +-- directory paths are passed to us in environment variables +\getenv abs_srcdir PG_ABS_SRCDIR + +-- Clean up in case a prior regression run failed +SET client_min_messages TO 'warning'; +DROP ROLE IF EXISTS regress_file_fdw_superuser, regress_file_fdw_user, regress_no_priv_user; +RESET client_min_messages; + +CREATE ROLE regress_file_fdw_superuser LOGIN SUPERUSER; -- is a superuser +CREATE ROLE regress_file_fdw_user LOGIN; -- has priv and user mapping +CREATE ROLE regress_no_priv_user LOGIN; -- has priv but no user mapping + +-- Install file_fdw +CREATE EXTENSION file_fdw; + +-- create function to filter unstable results of EXPLAIN +CREATE FUNCTION explain_filter(text) RETURNS setof text +LANGUAGE plpgsql AS +$$ +declare + ln text; +begin + for ln in execute $1 + loop + -- Remove the path portion of foreign file names + ln := regexp_replace(ln, 'Foreign File: .*/([a-z.]+)$', 'Foreign File: .../\1'); + return next ln; + end loop; +end; +$$; + +-- regress_file_fdw_superuser owns fdw-related objects +SET ROLE regress_file_fdw_superuser; +CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw; + +-- privilege tests +SET ROLE regress_file_fdw_user; +CREATE FOREIGN DATA WRAPPER file_fdw2 HANDLER file_fdw_handler VALIDATOR file_fdw_validator; -- ERROR +CREATE SERVER file_server2 FOREIGN DATA WRAPPER file_fdw; -- ERROR +CREATE USER MAPPING FOR regress_file_fdw_user SERVER file_server; -- ERROR + +SET ROLE regress_file_fdw_superuser; +GRANT USAGE ON FOREIGN SERVER file_server TO regress_file_fdw_user; + +SET ROLE regress_file_fdw_user; +CREATE USER MAPPING FOR regress_file_fdw_user SERVER file_server; + +-- create user mappings and grant privilege to test users +SET ROLE regress_file_fdw_superuser; +CREATE USER MAPPING FOR regress_file_fdw_superuser SERVER file_server; +CREATE USER MAPPING FOR regress_no_priv_user SERVER file_server; + +-- validator tests +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'xml'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', quote ':'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape ':'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', header 'true'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', quote ':'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'binary', escape ':'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter 'a'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', escape '-'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '-', null '=-='); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '-', null '=-='); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '-', quote '-'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '---'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '---'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', escape '---'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '.'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '1'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter 'a'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter ' +'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', null ' +'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server; -- ERROR + +\set filename :abs_srcdir '/data/agg.data' +CREATE FOREIGN TABLE agg_text ( + a int2 CHECK (a >= 0), + b float4 +) SERVER file_server +OPTIONS (format 'text', filename :'filename', delimiter ' ', null '\N'); +GRANT SELECT ON agg_text TO regress_file_fdw_user; + +\set filename :abs_srcdir '/data/agg.csv' +CREATE FOREIGN TABLE agg_csv ( + a int2, + b float4 +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', header 'true', delimiter ';', quote '@', escape '"', null ''); +ALTER FOREIGN TABLE agg_csv ADD CHECK (a >= 0); + +\set filename :abs_srcdir '/data/agg.bad' +CREATE FOREIGN TABLE agg_bad ( + a int2, + b float4 +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', header 'true', delimiter ';', quote '@', escape '"', null ''); +ALTER FOREIGN TABLE agg_bad ADD CHECK (a >= 0); + +-- test header matching +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE header_match ("1" int, foo text) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ',', header 'match'); +SELECT * FROM header_match; +CREATE FOREIGN TABLE header_doesnt_match (a int, foo text) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ',', header 'match'); +SELECT * FROM header_doesnt_match; -- ERROR + +-- per-column options tests +\set filename :abs_srcdir '/data/text.csv' +CREATE FOREIGN TABLE text_csv ( + word1 text OPTIONS (force_not_null 'true'), + word2 text OPTIONS (force_not_null 'off'), + word3 text OPTIONS (force_null 'true'), + word4 text OPTIONS (force_null 'off') +) SERVER file_server +OPTIONS (format 'text', filename :'filename', null 'NULL'); +SELECT * FROM text_csv; -- ERROR +ALTER FOREIGN TABLE text_csv OPTIONS (SET format 'csv'); +\pset null _null_ +SELECT * FROM text_csv; + +-- force_not_null and force_null can be used together on the same column +ALTER FOREIGN TABLE text_csv ALTER COLUMN word1 OPTIONS (force_null 'true'); +ALTER FOREIGN TABLE text_csv ALTER COLUMN word3 OPTIONS (force_not_null 'true'); + +-- force_not_null is not allowed to be specified at any foreign object level: +ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_not_null '*'); -- ERROR +ALTER SERVER file_server OPTIONS (ADD force_not_null '*'); -- ERROR +CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_not_null '*'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_not_null '*'); -- ERROR + +-- force_null is not allowed to be specified at any foreign object level: +ALTER FOREIGN DATA WRAPPER file_fdw OPTIONS (ADD force_null '*'); -- ERROR +ALTER SERVER file_server OPTIONS (ADD force_null '*'); -- ERROR +CREATE USER MAPPING FOR public SERVER file_server OPTIONS (force_null '*'); -- ERROR +CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (force_null '*'); -- ERROR + +-- basic query tests +SELECT * FROM agg_text WHERE b > 10.0 ORDER BY a; +SELECT * FROM agg_csv ORDER BY a; +SELECT * FROM agg_csv c JOIN agg_text t ON (t.a = c.a) ORDER BY c.a; + +-- error context report tests +SELECT * FROM agg_bad; -- ERROR + +-- misc query tests +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv'); +\t off +PREPARE st(int) AS SELECT * FROM agg_csv WHERE a = $1; +EXECUTE st(100); +EXECUTE st(100); +DEALLOCATE st; + +-- tableoid +SELECT tableoid::regclass, b FROM agg_csv; + +-- updates aren't supported +INSERT INTO agg_csv VALUES(1,2.0); +UPDATE agg_csv SET a = 1; +DELETE FROM agg_csv WHERE a = 100; +TRUNCATE agg_csv; +-- but this should be allowed +SELECT * FROM agg_csv FOR UPDATE; + +-- copy from isn't supported either +COPY agg_csv FROM STDIN; +12 3.4 +\. + +-- constraint exclusion tests +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0'); +\t off +SELECT * FROM agg_csv WHERE a < 0; +SET constraint_exclusion = 'on'; +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_csv WHERE a < 0'); +\t off +SELECT * FROM agg_csv WHERE a < 0; +RESET constraint_exclusion; + +-- table inheritance tests +CREATE TABLE agg (a int2, b float4); +ALTER FOREIGN TABLE agg_csv INHERIT agg; +SELECT tableoid::regclass, * FROM agg; +SELECT tableoid::regclass, * FROM agg_csv; +SELECT tableoid::regclass, * FROM ONLY agg; +-- updates aren't supported +UPDATE agg SET a = 1; +DELETE FROM agg WHERE a = 100; +-- but this should be allowed +SELECT tableoid::regclass, * FROM agg FOR UPDATE; +ALTER FOREIGN TABLE agg_csv NO INHERIT agg; +DROP TABLE agg; + +-- declarative partitioning tests +SET ROLE regress_file_fdw_superuser; +CREATE TABLE pt (a int, b text) partition by list (a); +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE p1 partition of pt for values in (1) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ','); +CREATE TABLE p2 partition of pt for values in (2); +SELECT tableoid::regclass, * FROM pt; +SELECT tableoid::regclass, * FROM p1; +SELECT tableoid::regclass, * FROM p2; +\set filename :abs_srcdir '/data/list2.bad' +COPY pt FROM :'filename' with (format 'csv', delimiter ','); -- ERROR +\set filename :abs_srcdir '/data/list2.csv' +COPY pt FROM :'filename' with (format 'csv', delimiter ','); +SELECT tableoid::regclass, * FROM pt; +SELECT tableoid::regclass, * FROM p1; +SELECT tableoid::regclass, * FROM p2; +INSERT INTO pt VALUES (1, 'xyzzy'); -- ERROR +INSERT INTO pt VALUES (2, 'xyzzy'); +UPDATE pt set a = 1 where a = 2; -- ERROR +SELECT tableoid::regclass, * FROM pt; +SELECT tableoid::regclass, * FROM p1; +SELECT tableoid::regclass, * FROM p2; +DROP TABLE pt; + +-- generated column tests +\set filename :abs_srcdir '/data/list1.csv' +CREATE FOREIGN TABLE gft1 (a int, b text, c text GENERATED ALWAYS AS ('foo') STORED) SERVER file_server +OPTIONS (format 'csv', filename :'filename', delimiter ','); +SELECT a, c FROM gft1; +DROP FOREIGN TABLE gft1; + +-- copy default tests +\set filename :abs_srcdir '/data/copy_default.csv' +CREATE FOREIGN TABLE copy_default ( + id integer, + text_value text not null default 'test', + ts_value timestamp without time zone not null default '2022-07-05' +) SERVER file_server +OPTIONS (format 'csv', filename :'filename', default '\D'); +SELECT id, text_value, ts_value FROM copy_default; +DROP FOREIGN TABLE copy_default; + +-- privilege tests +SET ROLE regress_file_fdw_superuser; +SELECT * FROM agg_text ORDER BY a; +SET ROLE regress_file_fdw_user; +SELECT * FROM agg_text ORDER BY a; +SET ROLE regress_no_priv_user; +SELECT * FROM agg_text ORDER BY a; -- ERROR +SET ROLE regress_file_fdw_user; +\t on +SELECT explain_filter('EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM agg_text WHERE a > 0'); +\t off +-- file FDW allows foreign tables to be accessed without user mapping +DROP USER MAPPING FOR regress_file_fdw_user SERVER file_server; +SELECT * FROM agg_text ORDER BY a; + +-- privilege tests for object +SET ROLE regress_file_fdw_superuser; +ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user; +ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text'); +SET ROLE regress_file_fdw_user; +ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text'); +SET ROLE regress_file_fdw_superuser; + +-- cleanup +RESET ROLE; +DROP EXTENSION file_fdw CASCADE; +DROP ROLE regress_file_fdw_superuser, regress_file_fdw_user, regress_no_priv_user; diff --git a/contrib/fuzzystrmatch/.gitignore b/contrib/fuzzystrmatch/.gitignore new file mode 100644 index 0000000..b474444 --- /dev/null +++ b/contrib/fuzzystrmatch/.gitignore @@ -0,0 +1,6 @@ +# Generated files +/daitch_mokotoff.h +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/fuzzystrmatch/Makefile b/contrib/fuzzystrmatch/Makefile new file mode 100644 index 0000000..e68bc0e --- /dev/null +++ b/contrib/fuzzystrmatch/Makefile @@ -0,0 +1,40 @@ +# contrib/fuzzystrmatch/Makefile + +MODULE_big = fuzzystrmatch +OBJS = \ + $(WIN32RES) \ + daitch_mokotoff.o \ + dmetaphone.o \ + fuzzystrmatch.o + +EXTENSION = fuzzystrmatch +DATA = fuzzystrmatch--1.1.sql fuzzystrmatch--1.1--1.2.sql \ + fuzzystrmatch--1.0--1.1.sql + +PGFILEDESC = "fuzzystrmatch - similarities and distance between strings" + +REGRESS = fuzzystrmatch fuzzystrmatch_utf8 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/fuzzystrmatch +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +# Force this dependency to be known even without dependency info built: +daitch_mokotoff.o: daitch_mokotoff.h + +daitch_mokotoff.h: daitch_mokotoff_header.pl + $(PERL) $< $@ + +# daitch_mokotoff.h is included in tarballs, so it has to be made by +# "distprep" and not cleaned except by "maintainer-clean". +distprep: daitch_mokotoff.h + +maintainer-clean: + rm -f daitch_mokotoff.h diff --git a/contrib/fuzzystrmatch/daitch_mokotoff.c b/contrib/fuzzystrmatch/daitch_mokotoff.c new file mode 100644 index 0000000..162e32c --- /dev/null +++ b/contrib/fuzzystrmatch/daitch_mokotoff.c @@ -0,0 +1,571 @@ +/* + * Daitch-Mokotoff Soundex + * + * Copyright (c) 2023, PostgreSQL Global Development Group + * + * This module was originally sponsored by Finance Norway / + * Trafikkforsikringsforeningen, and implemented by Dag Lem + * + * The implementation of the Daitch-Mokotoff Soundex System aims at correctness + * and high performance, and can be summarized as follows: + * + * - The processing of each phoneme is initiated by an O(1) table lookup. + * - For phonemes containing more than one character, a coding tree is traversed + * to process the complete phoneme. + * - The (alternate) soundex codes are produced digit by digit in-place in + * another tree structure. + * + * References: + * + * https://www.avotaynu.com/soundex.htm + * https://www.jewishgen.org/InfoFiles/Soundex.html + * https://familypedia.fandom.com/wiki/Daitch-Mokotoff_Soundex + * https://stevemorse.org/census/soundex.html (dmlat.php, dmsoundex.php) + * https://github.com/apache/commons-codec/ (dmrules.txt, DaitchMokotoffSoundex.java) + * https://metacpan.org/pod/Text::Phonetic (DaitchMokotoff.pm) + * + * A few notes on other implementations: + * + * - All other known implementations have the same unofficial rules for "UE", + * these are also adapted by this implementation (0, 1, NC). + * - The only other known implementation which is capable of generating all + * correct soundex codes in all cases is the JOS Soundex Calculator at + * https://www.jewishgen.org/jos/jossound.htm + * - "J" is considered (only) a vowel in dmlat.php + * - The official rules for "RS" are commented out in dmlat.php + * - Identical code digits for adjacent letters are not collapsed correctly in + * dmsoundex.php when double digit codes are involved. E.g. "BESST" yields + * 744300 instead of 743000 as for "BEST". + * - "J" is considered (only) a consonant in DaitchMokotoffSoundex.java + * - "Y" is not considered a vowel in DaitchMokotoffSoundex.java +*/ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "mb/pg_wchar.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/memutils.h" + + +/* + * The soundex coding chart table is adapted from + * https://www.jewishgen.org/InfoFiles/Soundex.html + * See daitch_mokotoff_header.pl for details. +*/ + +/* Generated coding chart table */ +#include "daitch_mokotoff.h" + +#define DM_CODE_DIGITS 6 + +/* Node in soundex code tree */ +typedef struct dm_node +{ + int soundex_length; /* Length of generated soundex code */ + char soundex[DM_CODE_DIGITS]; /* Soundex code */ + int is_leaf; /* Candidate for complete soundex code */ + int last_update; /* Letter number for last update of node */ + char code_digit; /* Last code digit, 0 - 9 */ + + /* + * One or two alternate code digits leading to this node. If there are two + * digits, one of them is always an 'X'. Repeated code digits and 'X' lead + * back to the same node. + */ + char prev_code_digits[2]; + /* One or two alternate code digits moving forward. */ + char next_code_digits[2]; + /* ORed together code index(es) used to reach current node. */ + int prev_code_index; + int next_code_index; + /* Possible nodes branching out from this node - digits 0-9. */ + struct dm_node *children[10]; + /* Next node in linked list. Alternating index for each iteration. */ + struct dm_node *next[2]; +} dm_node; + +/* Template for new node in soundex code tree. */ +static const dm_node start_node = { + .soundex_length = 0, + .soundex = "000000", /* Six digits */ + .is_leaf = 0, + .last_update = 0, + .code_digit = '\0', + .prev_code_digits = {'\0', '\0'}, + .next_code_digits = {'\0', '\0'}, + .prev_code_index = 0, + .next_code_index = 0, + .children = {NULL}, + .next = {NULL} +}; + +/* Dummy soundex codes at end of input. */ +static const dm_codes end_codes[2] = +{ + { + "X", "X", "X" + } +}; + +/* Mapping from ISO8859-1 to upper-case ASCII, covering the range 0x60..0xFF. */ +static const char iso8859_1_to_ascii_upper[] = +"`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~ ! ?AAAAAAECEEEEIIIIDNOOOOO*OUUUUYDSAAAAAAECEEEEIIIIDNOOOOO/OUUUUYDY"; + +/* Internal C implementation */ +static bool daitch_mokotoff_coding(const char *word, ArrayBuildState *soundex); + + +PG_FUNCTION_INFO_V1(daitch_mokotoff); + +Datum +daitch_mokotoff(PG_FUNCTION_ARGS) +{ + text *arg = PG_GETARG_TEXT_PP(0); + Datum retval; + char *string; + ArrayBuildState *soundex; + MemoryContext old_ctx, + tmp_ctx; + + /* Work in a temporary context to simplify cleanup. */ + tmp_ctx = AllocSetContextCreate(CurrentMemoryContext, + "daitch_mokotoff temporary context", + ALLOCSET_DEFAULT_SIZES); + old_ctx = MemoryContextSwitchTo(tmp_ctx); + + /* We must convert the string to UTF-8 if it isn't already. */ + string = pg_server_to_any(text_to_cstring(arg), VARSIZE_ANY_EXHDR(arg), + PG_UTF8); + + /* The result is built in this ArrayBuildState. */ + soundex = initArrayResult(TEXTOID, tmp_ctx, false); + + if (!daitch_mokotoff_coding(string, soundex)) + { + /* No encodable characters in input */ + MemoryContextSwitchTo(old_ctx); + MemoryContextDelete(tmp_ctx); + PG_RETURN_NULL(); + } + + retval = makeArrayResult(soundex, old_ctx); + + MemoryContextSwitchTo(old_ctx); + MemoryContextDelete(tmp_ctx); + + PG_RETURN_DATUM(retval); +} + + +/* Initialize soundex code tree node for next code digit. */ +static void +initialize_node(dm_node *node, int last_update) +{ + if (node->last_update < last_update) + { + node->prev_code_digits[0] = node->next_code_digits[0]; + node->prev_code_digits[1] = node->next_code_digits[1]; + node->next_code_digits[0] = '\0'; + node->next_code_digits[1] = '\0'; + node->prev_code_index = node->next_code_index; + node->next_code_index = 0; + node->is_leaf = 0; + node->last_update = last_update; + } +} + + +/* Update soundex code tree node with next code digit. */ +static void +add_next_code_digit(dm_node *node, int code_index, char code_digit) +{ + /* OR in index 1 or 2. */ + node->next_code_index |= code_index; + + if (!node->next_code_digits[0]) + node->next_code_digits[0] = code_digit; + else if (node->next_code_digits[0] != code_digit) + node->next_code_digits[1] = code_digit; +} + + +/* Mark soundex code tree node as leaf. */ +static void +set_leaf(dm_node *first_node[2], dm_node *last_node[2], + dm_node *node, int ix_node) +{ + if (!node->is_leaf) + { + node->is_leaf = 1; + + if (first_node[ix_node] == NULL) + first_node[ix_node] = node; + else + last_node[ix_node]->next[ix_node] = node; + + last_node[ix_node] = node; + node->next[ix_node] = NULL; + } +} + + +/* Find next node corresponding to code digit, or create a new node. */ +static dm_node * +find_or_create_child_node(dm_node *parent, char code_digit, + ArrayBuildState *soundex) +{ + int i = code_digit - '0'; + dm_node **nodes = parent->children; + dm_node *node = nodes[i]; + + if (node) + { + /* Found existing child node. Skip completed nodes. */ + return node->soundex_length < DM_CODE_DIGITS ? node : NULL; + } + + /* Create new child node. */ + node = palloc_object(dm_node); + nodes[i] = node; + + *node = start_node; + memcpy(node->soundex, parent->soundex, sizeof(parent->soundex)); + node->soundex_length = parent->soundex_length; + node->soundex[node->soundex_length++] = code_digit; + node->code_digit = code_digit; + node->next_code_index = node->prev_code_index; + + if (node->soundex_length < DM_CODE_DIGITS) + { + return node; + } + else + { + /* Append completed soundex code to output array. */ + text *out = cstring_to_text_with_len(node->soundex, + DM_CODE_DIGITS); + + accumArrayResult(soundex, + PointerGetDatum(out), + false, + TEXTOID, + CurrentMemoryContext); + return NULL; + } +} + + +/* Update node for next code digit(s). */ +static void +update_node(dm_node *first_node[2], dm_node *last_node[2], + dm_node *node, int ix_node, + int letter_no, int prev_code_index, int next_code_index, + const char *next_code_digits, int digit_no, + ArrayBuildState *soundex) +{ + int i; + char next_code_digit = next_code_digits[digit_no]; + int num_dirty_nodes = 0; + dm_node *dirty_nodes[2]; + + initialize_node(node, letter_no); + + if (node->prev_code_index && !(node->prev_code_index & prev_code_index)) + { + /* + * If the sound (vowel / consonant) of this letter encoding doesn't + * correspond to the coding index of the previous letter, we skip this + * letter encoding. Note that currently, only "J" can be either a + * vowel or a consonant. + */ + return; + } + + if (next_code_digit == 'X' || + (digit_no == 0 && + (node->prev_code_digits[0] == next_code_digit || + node->prev_code_digits[1] == next_code_digit))) + { + /* The code digit is the same as one of the previous (i.e. not added). */ + dirty_nodes[num_dirty_nodes++] = node; + } + + if (next_code_digit != 'X' && + (digit_no > 0 || + node->prev_code_digits[0] != next_code_digit || + node->prev_code_digits[1])) + { + /* The code digit is different from one of the previous (i.e. added). */ + node = find_or_create_child_node(node, next_code_digit, soundex); + if (node) + { + initialize_node(node, letter_no); + dirty_nodes[num_dirty_nodes++] = node; + } + } + + for (i = 0; i < num_dirty_nodes; i++) + { + /* Add code digit leading to the current node. */ + add_next_code_digit(dirty_nodes[i], next_code_index, next_code_digit); + + if (next_code_digits[++digit_no]) + { + update_node(first_node, last_node, dirty_nodes[i], ix_node, + letter_no, prev_code_index, next_code_index, + next_code_digits, digit_no, + soundex); + } + else + { + /* Add incomplete leaf node to linked list. */ + set_leaf(first_node, last_node, dirty_nodes[i], ix_node); + } + } +} + + +/* Update soundex tree leaf nodes. */ +static void +update_leaves(dm_node *first_node[2], int *ix_node, int letter_no, + const dm_codes *codes, const dm_codes *next_codes, + ArrayBuildState *soundex) +{ + int i, + j, + code_index; + dm_node *node, + *last_node[2]; + const dm_code *code, + *next_code; + int ix_node_next = (*ix_node + 1) & 1; /* Alternating index: 0, 1 */ + + /* Initialize for new linked list of leaves. */ + first_node[ix_node_next] = NULL; + last_node[ix_node_next] = NULL; + + /* Process all nodes. */ + for (node = first_node[*ix_node]; node; node = node->next[*ix_node]) + { + /* One or two alternate code sequences. */ + for (i = 0; i < 2 && (code = codes[i]) && code[0][0]; i++) + { + /* Coding for previous letter - before vowel: 1, all other: 2 */ + int prev_code_index = (code[0][0] > '1') + 1; + + /* One or two alternate next code sequences. */ + for (j = 0; j < 2 && (next_code = next_codes[j]) && next_code[0][0]; j++) + { + /* Determine which code to use. */ + if (letter_no == 0) + { + /* This is the first letter. */ + code_index = 0; + } + else if (next_code[0][0] <= '1') + { + /* The next letter is a vowel. */ + code_index = 1; + } + else + { + /* All other cases. */ + code_index = 2; + } + + /* One or two sequential code digits. */ + update_node(first_node, last_node, node, ix_node_next, + letter_no, prev_code_index, code_index, + code[code_index], 0, + soundex); + } + } + } + + *ix_node = ix_node_next; +} + + +/* + * Return next character, converted from UTF-8 to uppercase ASCII. + * *ix is the current string index and is incremented by the character length. + */ +static char +read_char(const unsigned char *str, int *ix) +{ + /* Substitute character for skipped code points. */ + const char na = '\x1a'; + pg_wchar c; + + /* Decode UTF-8 character to ISO 10646 code point. */ + str += *ix; + c = utf8_to_unicode(str); + + /* Advance *ix, but (for safety) not if we've reached end of string. */ + if (c) + *ix += pg_utf_mblen(str); + + /* Convert. */ + if (c >= (unsigned char) '[' && c <= (unsigned char) ']') + { + /* ASCII characters [, \, and ] are reserved for conversions below. */ + return na; + } + else if (c < 0x60) + { + /* Other non-lowercase ASCII characters can be used as-is. */ + return (char) c; + } + else if (c < 0x100) + { + /* ISO-8859-1 code point; convert to upper-case ASCII via table. */ + return iso8859_1_to_ascii_upper[c - 0x60]; + } + else + { + /* Conversion of non-ASCII characters in the coding chart. */ + switch (c) + { + case 0x0104: /* LATIN CAPITAL LETTER A WITH OGONEK */ + case 0x0105: /* LATIN SMALL LETTER A WITH OGONEK */ + return '['; + case 0x0118: /* LATIN CAPITAL LETTER E WITH OGONEK */ + case 0x0119: /* LATIN SMALL LETTER E WITH OGONEK */ + return '\\'; + case 0x0162: /* LATIN CAPITAL LETTER T WITH CEDILLA */ + case 0x0163: /* LATIN SMALL LETTER T WITH CEDILLA */ + case 0x021A: /* LATIN CAPITAL LETTER T WITH COMMA BELOW */ + case 0x021B: /* LATIN SMALL LETTER T WITH COMMA BELOW */ + return ']'; + default: + return na; + } + } +} + + +/* Read next ASCII character, skipping any characters not in [A-\]]. */ +static char +read_valid_char(const char *str, int *ix) +{ + char c; + + while ((c = read_char((const unsigned char *) str, ix)) != '\0') + { + if (c >= 'A' && c <= ']') + break; + } + + return c; +} + + +/* Return sound coding for "letter" (letter sequence) */ +static const dm_codes * +read_letter(const char *str, int *ix) +{ + char c, + cmp; + int i, + j; + const dm_letter *letters; + const dm_codes *codes; + + /* First letter in sequence. */ + if ((c = read_valid_char(str, ix)) == '\0') + return NULL; + + letters = &letter_[c - 'A']; + codes = letters->codes; + i = *ix; + + /* Any subsequent letters in sequence. */ + while ((letters = letters->letters) && (c = read_valid_char(str, &i))) + { + for (j = 0; (cmp = letters[j].letter); j++) + { + if (cmp == c) + { + /* Letter found. */ + letters = &letters[j]; + if (letters->codes) + { + /* Coding for letter sequence found. */ + codes = letters->codes; + *ix = i; + } + break; + } + } + if (!cmp) + { + /* The sequence of letters has no coding. */ + break; + } + } + + return codes; +} + + +/* + * Generate all Daitch-Mokotoff soundex codes for word, + * adding them to the "soundex" ArrayBuildState. + * Returns false if string has no encodable characters, else true. + */ +static bool +daitch_mokotoff_coding(const char *word, ArrayBuildState *soundex) +{ + int i = 0; + int letter_no = 0; + int ix_node = 0; + const dm_codes *codes, + *next_codes; + dm_node *first_node[2], + *node; + + /* First letter. */ + if (!(codes = read_letter(word, &i))) + { + /* No encodable character in input. */ + return false; + } + + /* Starting point. */ + first_node[ix_node] = palloc_object(dm_node); + *first_node[ix_node] = start_node; + + /* + * Loop until either the word input is exhausted, or all generated soundex + * codes are completed to six digits. + */ + while (codes && first_node[ix_node]) + { + next_codes = read_letter(word, &i); + + /* Update leaf nodes. */ + update_leaves(first_node, &ix_node, letter_no, + codes, next_codes ? next_codes : end_codes, + soundex); + + codes = next_codes; + letter_no++; + } + + /* Append all remaining (incomplete) soundex codes to output array. */ + for (node = first_node[ix_node]; node; node = node->next[ix_node]) + { + text *out = cstring_to_text_with_len(node->soundex, + DM_CODE_DIGITS); + + accumArrayResult(soundex, + PointerGetDatum(out), + false, + TEXTOID, + CurrentMemoryContext); + } + + return true; +} diff --git a/contrib/fuzzystrmatch/daitch_mokotoff.h b/contrib/fuzzystrmatch/daitch_mokotoff.h new file mode 100644 index 0000000..8327415 --- /dev/null +++ b/contrib/fuzzystrmatch/daitch_mokotoff.h @@ -0,0 +1,949 @@ +/* + * Constants and lookup tables for Daitch-Mokotoff Soundex + * + * Copyright (c) 2023, PostgreSQL Global Development Group + * + * This file is generated by daitch_mokotoff_header.pl + */ + +/* Coding chart table: Soundex codes */ +typedef char dm_code[2 + 1]; /* One or two sequential code digits + NUL */ +typedef dm_code dm_codes[3]; /* Start of name, before a vowel, any other */ + +/* Coding chart table: Letter in input sequence */ +struct dm_letter +{ + char letter; /* Present letter in sequence */ + const struct dm_letter *letters; /* List of possible successive letters */ + const dm_codes *codes; /* Code sequence(s) for complete sequence */ +}; + +typedef struct dm_letter dm_letter; + +/* Codes for letter sequence at start of name, before a vowel, and any other. */ +static const dm_codes codes_0_1_X[2] = +{ + { + "0", "1", "X" + } +}; +static const dm_codes codes_0_7_X[2] = +{ + { + "0", "7", "X" + } +}; +static const dm_codes codes_0_X_X[2] = +{ + { + "0", "X", "X" + } +}; +static const dm_codes codes_1_1_X[2] = +{ + { + "1", "1", "X" + } +}; +static const dm_codes codes_1_X_X[2] = +{ + { + "1", "X", "X" + } +}; +static const dm_codes codes_1_X_X_or_4_4_4[2] = +{ + { + "1", "X", "X" + }, + { + "4", "4", "4" + } +}; +static const dm_codes codes_2_43_43[2] = +{ + { + "2", "43", "43" + } +}; +static const dm_codes codes_2_4_4[2] = +{ + { + "2", "4", "4" + } +}; +static const dm_codes codes_3_3_3[2] = +{ + { + "3", "3", "3" + } +}; +static const dm_codes codes_3_3_3_or_4_4_4[2] = +{ + { + "3", "3", "3" + }, + { + "4", "4", "4" + } +}; +static const dm_codes codes_4_4_4[2] = +{ + { + "4", "4", "4" + } +}; +static const dm_codes codes_5_54_54[2] = +{ + { + "5", "54", "54" + } +}; +static const dm_codes codes_5_5_5[2] = +{ + { + "5", "5", "5" + } +}; +static const dm_codes codes_5_5_5_or_45_45_45[2] = +{ + { + "5", "5", "5" + }, + { + "45", "45", "45" + } +}; +static const dm_codes codes_5_5_5_or_4_4_4[2] = +{ + { + "5", "5", "5" + }, + { + "4", "4", "4" + } +}; +static const dm_codes codes_5_5_X[2] = +{ + { + "5", "5", "X" + } +}; +static const dm_codes codes_66_66_66[2] = +{ + { + "66", "66", "66" + } +}; +static const dm_codes codes_6_6_6[2] = +{ + { + "6", "6", "6" + } +}; +static const dm_codes codes_7_7_7[2] = +{ + { + "7", "7", "7" + } +}; +static const dm_codes codes_8_8_8[2] = +{ + { + "8", "8", "8" + } +}; +static const dm_codes codes_94_94_94_or_4_4_4[2] = +{ + { + "94", "94", "94" + }, + { + "4", "4", "4" + } +}; +static const dm_codes codes_9_9_9[2] = +{ + { + "9", "9", "9" + } +}; +static const dm_codes codes_X_X_6_or_X_X_X[2] = +{ + { + "X", "X", "6" + }, + { + "X", "X", "X" + } +}; + +/* Coding for alternative following letters in sequence. */ +static const dm_letter letter_A[] = +{ + { + 'I', NULL, codes_0_1_X + }, + { + 'J', NULL, codes_0_1_X + }, + { + 'U', NULL, codes_0_7_X + }, + { + 'Y', NULL, codes_0_1_X + }, + { + '\0' + } +}; +static const dm_letter letter_CH[] = +{ + { + 'S', NULL, codes_5_54_54 + }, + { + '\0' + } +}; +static const dm_letter letter_CS[] = +{ + { + 'Z', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_CZ[] = +{ + { + 'S', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_C[] = +{ + { + 'H', letter_CH, codes_5_5_5_or_4_4_4 + }, + { + 'K', NULL, codes_5_5_5_or_45_45_45 + }, + { + 'S', letter_CS, codes_4_4_4 + }, + { + 'Z', letter_CZ, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_DR[] = +{ + { + 'S', NULL, codes_4_4_4 + }, + { + 'Z', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_DS[] = +{ + { + 'H', NULL, codes_4_4_4 + }, + { + 'Z', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_DZ[] = +{ + { + 'H', NULL, codes_4_4_4 + }, + { + 'S', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_D[] = +{ + { + 'R', letter_DR, NULL + }, + { + 'S', letter_DS, codes_4_4_4 + }, + { + 'T', NULL, codes_3_3_3 + }, + { + 'Z', letter_DZ, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_E[] = +{ + { + 'I', NULL, codes_0_1_X + }, + { + 'J', NULL, codes_0_1_X + }, + { + 'U', NULL, codes_1_1_X + }, + { + 'Y', NULL, codes_0_1_X + }, + { + '\0' + } +}; +static const dm_letter letter_F[] = +{ + { + 'B', NULL, codes_7_7_7 + }, + { + '\0' + } +}; +static const dm_letter letter_I[] = +{ + { + 'A', NULL, codes_1_X_X + }, + { + 'E', NULL, codes_1_X_X + }, + { + 'O', NULL, codes_1_X_X + }, + { + 'U', NULL, codes_1_X_X + }, + { + '\0' + } +}; +static const dm_letter letter_K[] = +{ + { + 'H', NULL, codes_5_5_5 + }, + { + 'S', NULL, codes_5_54_54 + }, + { + '\0' + } +}; +static const dm_letter letter_M[] = +{ + { + 'N', NULL, codes_66_66_66 + }, + { + '\0' + } +}; +static const dm_letter letter_N[] = +{ + { + 'M', NULL, codes_66_66_66 + }, + { + '\0' + } +}; +static const dm_letter letter_O[] = +{ + { + 'I', NULL, codes_0_1_X + }, + { + 'J', NULL, codes_0_1_X + }, + { + 'Y', NULL, codes_0_1_X + }, + { + '\0' + } +}; +static const dm_letter letter_P[] = +{ + { + 'F', NULL, codes_7_7_7 + }, + { + 'H', NULL, codes_7_7_7 + }, + { + '\0' + } +}; +static const dm_letter letter_R[] = +{ + { + 'S', NULL, codes_94_94_94_or_4_4_4 + }, + { + 'Z', NULL, codes_94_94_94_or_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_SCHTC[] = +{ + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_SCHTSC[] = +{ + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_SCHTS[] = +{ + { + 'C', letter_SCHTSC, NULL + }, + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_SCHT[] = +{ + { + 'C', letter_SCHTC, NULL + }, + { + 'S', letter_SCHTS, NULL + }, + { + '\0' + } +}; +static const dm_letter letter_SCH[] = +{ + { + 'D', NULL, codes_2_43_43 + }, + { + 'T', letter_SCHT, codes_2_43_43 + }, + { + '\0' + } +}; +static const dm_letter letter_SC[] = +{ + { + 'H', letter_SCH, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_SHC[] = +{ + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_SHTC[] = +{ + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_SHTS[] = +{ + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_SHT[] = +{ + { + 'C', letter_SHTC, NULL + }, + { + 'S', letter_SHTS, NULL + }, + { + '\0' + } +}; +static const dm_letter letter_SH[] = +{ + { + 'C', letter_SHC, NULL + }, + { + 'D', NULL, codes_2_43_43 + }, + { + 'T', letter_SHT, codes_2_43_43 + }, + { + '\0' + } +}; +static const dm_letter letter_STC[] = +{ + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_STR[] = +{ + { + 'S', NULL, codes_2_4_4 + }, + { + 'Z', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_STSC[] = +{ + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_STS[] = +{ + { + 'C', letter_STSC, NULL + }, + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_ST[] = +{ + { + 'C', letter_STC, NULL + }, + { + 'R', letter_STR, NULL + }, + { + 'S', letter_STS, NULL + }, + { + '\0' + } +}; +static const dm_letter letter_SZC[] = +{ + { + 'S', NULL, codes_2_4_4 + }, + { + 'Z', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_SZ[] = +{ + { + 'C', letter_SZC, NULL + }, + { + 'D', NULL, codes_2_43_43 + }, + { + 'T', NULL, codes_2_43_43 + }, + { + '\0' + } +}; +static const dm_letter letter_S[] = +{ + { + 'C', letter_SC, codes_2_4_4 + }, + { + 'D', NULL, codes_2_43_43 + }, + { + 'H', letter_SH, codes_4_4_4 + }, + { + 'T', letter_ST, codes_2_43_43 + }, + { + 'Z', letter_SZ, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_TC[] = +{ + { + 'H', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_TR[] = +{ + { + 'S', NULL, codes_4_4_4 + }, + { + 'Z', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_TSC[] = +{ + { + 'H', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_TS[] = +{ + { + 'C', letter_TSC, NULL + }, + { + 'H', NULL, codes_4_4_4 + }, + { + 'Z', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_TTC[] = +{ + { + 'H', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_TTSC[] = +{ + { + 'H', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_TTS[] = +{ + { + 'C', letter_TTSC, NULL + }, + { + 'Z', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_TT[] = +{ + { + 'C', letter_TTC, NULL + }, + { + 'S', letter_TTS, codes_4_4_4 + }, + { + 'Z', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_TZ[] = +{ + { + 'S', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_T[] = +{ + { + 'C', letter_TC, codes_4_4_4 + }, + { + 'H', NULL, codes_3_3_3 + }, + { + 'R', letter_TR, NULL + }, + { + 'S', letter_TS, codes_4_4_4 + }, + { + 'T', letter_TT, NULL + }, + { + 'Z', letter_TZ, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_U[] = +{ + { + 'E', NULL, codes_0_1_X + }, + { + 'I', NULL, codes_0_1_X + }, + { + 'J', NULL, codes_0_1_X + }, + { + 'Y', NULL, codes_0_1_X + }, + { + '\0' + } +}; +static const dm_letter letter_ZDZ[] = +{ + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_ZD[] = +{ + { + 'Z', letter_ZDZ, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_ZHDZ[] = +{ + { + 'H', NULL, codes_2_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_ZHD[] = +{ + { + 'Z', letter_ZHDZ, NULL + }, + { + '\0' + } +}; +static const dm_letter letter_ZH[] = +{ + { + 'D', letter_ZHD, codes_2_43_43 + }, + { + '\0' + } +}; +static const dm_letter letter_ZSC[] = +{ + { + 'H', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_ZS[] = +{ + { + 'C', letter_ZSC, NULL + }, + { + 'H', NULL, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_Z[] = +{ + { + 'D', letter_ZD, codes_2_43_43 + }, + { + 'H', letter_ZH, codes_4_4_4 + }, + { + 'S', letter_ZS, codes_4_4_4 + }, + { + '\0' + } +}; +static const dm_letter letter_[] = +{ + { + 'A', letter_A, codes_0_X_X + }, + { + 'B', NULL, codes_7_7_7 + }, + { + 'C', letter_C, codes_5_5_5_or_4_4_4 + }, + { + 'D', letter_D, codes_3_3_3 + }, + { + 'E', letter_E, codes_0_X_X + }, + { + 'F', letter_F, codes_7_7_7 + }, + { + 'G', NULL, codes_5_5_5 + }, + { + 'H', NULL, codes_5_5_X + }, + { + 'I', letter_I, codes_0_X_X + }, + { + 'J', NULL, codes_1_X_X_or_4_4_4 + }, + { + 'K', letter_K, codes_5_5_5 + }, + { + 'L', NULL, codes_8_8_8 + }, + { + 'M', letter_M, codes_6_6_6 + }, + { + 'N', letter_N, codes_6_6_6 + }, + { + 'O', letter_O, codes_0_X_X + }, + { + 'P', letter_P, codes_7_7_7 + }, + { + 'Q', NULL, codes_5_5_5 + }, + { + 'R', letter_R, codes_9_9_9 + }, + { + 'S', letter_S, codes_4_4_4 + }, + { + 'T', letter_T, codes_3_3_3 + }, + { + 'U', letter_U, codes_0_X_X + }, + { + 'V', NULL, codes_7_7_7 + }, + { + 'W', NULL, codes_7_7_7 + }, + { + 'X', NULL, codes_5_54_54 + }, + { + 'Y', NULL, codes_1_X_X + }, + { + 'Z', letter_Z, codes_4_4_4 + }, + { + 'a', NULL, codes_X_X_6_or_X_X_X + }, + { + 'e', NULL, codes_X_X_6_or_X_X_X + }, + { + 't', NULL, codes_3_3_3_or_4_4_4 + }, + { + '\0' + } +}; diff --git a/contrib/fuzzystrmatch/daitch_mokotoff_header.pl b/contrib/fuzzystrmatch/daitch_mokotoff_header.pl new file mode 100755 index 0000000..51a40e7 --- /dev/null +++ b/contrib/fuzzystrmatch/daitch_mokotoff_header.pl @@ -0,0 +1,219 @@ +#!/usr/bin/perl +# +# Generation of types and lookup tables for Daitch-Mokotoff soundex. +# +# Copyright (c) 2023, PostgreSQL Global Development Group +# +# This module was originally sponsored by Finance Norway / +# Trafikkforsikringsforeningen, and implemented by Dag Lem +# + +use strict; +use warnings; + +die "Usage: $0 OUTPUT_FILE\n" if @ARGV != 1; +my $output_file = $ARGV[0]; + +# Open the output file +open my $OUTPUT, '>', $output_file + or die "Could not open output file $output_file: $!\n"; + +# Parse code table and generate tree for letter transitions. +my %codes; +my $table = [ {}, [ [ "", "", "" ] ] ]; +while () +{ + chomp; + my ($letters, $codes) = split(/\s+/); + my @codes = map { [ split(/,/) ] } split(/\|/, $codes); + + my $key = "codes_" . join("_or_", map { join("_", @$_) } @codes); + my $val = join( + ",\n", + map { + "\t{\n\t\t" + . join(", ", map { "\"$_\"" } @$_) . "\n\t}" + } @codes); + $codes{$key} = $val; + + for my $letter (split(/,/, $letters)) + { + my $ref = $table->[0]; + # Link each character to the next in the letter combination. + my @c = split(//, $letter); + my $last_c = pop(@c); + for my $c (@c) + { + $ref->{$c} //= [ {}, undef ]; + $ref->{$c}[0] //= {}; + $ref = $ref->{$c}[0]; + } + # The sound code for the letter combination is stored at the last character. + $ref->{$last_c}[1] = $key; + } +} +close(DATA); + +print $OUTPUT <[0]; + for my $key (sort keys %$h) + { + $ref = $h->{$key}; + my $children = "NULL"; + if (defined $ref->[0]) + { + $children = "letter_$letter$key"; + hash2code($ref, "$letter$key"); + } + my $codes = $ref->[1] // "NULL"; + push(@letters, "\t{\n\t\t'$key', $children, $codes\n\t}"); + } + + print $OUTPUT "static const dm_letter letter_$letter\[\] =\n{\n"; + for (@letters) + { + print $OUTPUT "$_,\n"; + } + print $OUTPUT "\t{\n\t\t'\\0'\n\t}\n"; + print $OUTPUT "};\n"; +} + +hash2code($table, ''); + +close $OUTPUT; + +# Table adapted from https://www.jewishgen.org/InfoFiles/Soundex.html +# +# The conversion from the coding chart to the table should be self +# explanatory, but note the differences stated below. +# +# X = NC (not coded) +# +# The non-ASCII letters in the coding chart are coded with substitute +# lowercase ASCII letters, which sort after the uppercase ASCII letters: +# +# Ą => a (use '[' for table lookup) +# Ę => e (use '\\' for table lookup) +# Ţ => t (use ']' for table lookup) +# +# The rule for "UE" does not correspond to the coding chart, however +# it is used by all other known implementations, including the one at +# https://www.jewishgen.org/jos/jossound.htm (try e.g. "bouey"). +# +# Note that the implementation assumes that vowels are assigned code +# 0 or 1. "J" can be either a vowel or a consonant. +# + +__DATA__ +AI,AJ,AY 0,1,X +AU 0,7,X +a X,X,6|X,X,X +A 0,X,X +B 7,7,7 +CHS 5,54,54 +CH 5,5,5|4,4,4 +CK 5,5,5|45,45,45 +CZ,CS,CSZ,CZS 4,4,4 +C 5,5,5|4,4,4 +DRZ,DRS 4,4,4 +DS,DSH,DSZ 4,4,4 +DZ,DZH,DZS 4,4,4 +D,DT 3,3,3 +EI,EJ,EY 0,1,X +EU 1,1,X +e X,X,6|X,X,X +E 0,X,X +FB 7,7,7 +F 7,7,7 +G 5,5,5 +H 5,5,X +IA,IE,IO,IU 1,X,X +I 0,X,X +J 1,X,X|4,4,4 +KS 5,54,54 +KH 5,5,5 +K 5,5,5 +L 8,8,8 +MN 66,66,66 +M 6,6,6 +NM 66,66,66 +N 6,6,6 +OI,OJ,OY 0,1,X +O 0,X,X +P,PF,PH 7,7,7 +Q 5,5,5 +RZ,RS 94,94,94|4,4,4 +R 9,9,9 +SCHTSCH,SCHTSH,SCHTCH 2,4,4 +SCH 4,4,4 +SHTCH,SHCH,SHTSH 2,4,4 +SHT,SCHT,SCHD 2,43,43 +SH 4,4,4 +STCH,STSCH,SC 2,4,4 +STRZ,STRS,STSH 2,4,4 +ST 2,43,43 +SZCZ,SZCS 2,4,4 +SZT,SHD,SZD,SD 2,43,43 +SZ 4,4,4 +S 4,4,4 +TCH,TTCH,TTSCH 4,4,4 +TH 3,3,3 +TRZ,TRS 4,4,4 +TSCH,TSH 4,4,4 +TS,TTS,TTSZ,TC 4,4,4 +TZ,TTZ,TZS,TSZ 4,4,4 +t 3,3,3|4,4,4 +T 3,3,3 +UI,UJ,UY,UE 0,1,X +U 0,X,X +V 7,7,7 +W 7,7,7 +X 5,54,54 +Y 1,X,X +ZDZ,ZDZH,ZHDZH 2,4,4 +ZD,ZHD 2,43,43 +ZH,ZS,ZSCH,ZSH 4,4,4 +Z 4,4,4 diff --git a/contrib/fuzzystrmatch/dmetaphone.c b/contrib/fuzzystrmatch/dmetaphone.c new file mode 100644 index 0000000..f8f2c2b --- /dev/null +++ b/contrib/fuzzystrmatch/dmetaphone.c @@ -0,0 +1,1438 @@ +/* + * This is a port of the Double Metaphone algorithm for use in PostgreSQL. + * + * contrib/fuzzystrmatch/dmetaphone.c + * + * Double Metaphone computes 2 "sounds like" strings - a primary and an + * alternate. In most cases they are the same, but for foreign names + * especially they can be a bit different, depending on pronunciation. + * + * Information on using Double Metaphone can be found at + * http://www.codeproject.com/string/dmetaphone1.asp + * and the original article describing it can be found at + * http://drdobbs.com/184401251 + * + * For PostgreSQL we provide 2 functions - one for the primary and one for + * the alternate. That way the functions are pure text->text mappings that + * are useful in functional indexes. These are 'dmetaphone' for the + * primary and 'dmetaphone_alt' for the alternate. + * + * Assuming that dmetaphone.so is in $libdir, the SQL to set up the + * functions looks like this: + * + * CREATE FUNCTION dmetaphone (text) RETURNS text + * LANGUAGE C IMMUTABLE STRICT + * AS '$libdir/dmetaphone', 'dmetaphone'; + * + * CREATE FUNCTION dmetaphone_alt (text) RETURNS text + * LANGUAGE C IMMUTABLE STRICT + * AS '$libdir/dmetaphone', 'dmetaphone_alt'; + * + * Note that you have to declare the functions IMMUTABLE if you want to + * use them in functional indexes, and you have to declare them as STRICT + * as they do not check for NULL input, and will segfault if given NULL input. + * (See below for alternative ) Declaring them as STRICT means PostgreSQL + * will never call them with NULL, but instead assume the result is NULL, + * which is what we (I) want. + * + * Alternatively, compile with -DDMETAPHONE_NOSTRICT and the functions + * will detect NULL input and return NULL. The you don't have to declare them + * as STRICT. + * + * There is a small inefficiency here - each function call actually computes + * both the primary and the alternate and then throws away the one it doesn't + * need. That's the way the perl module was written, because perl can handle + * a list return more easily than we can in PostgreSQL. The result has been + * fast enough for my needs, but it could maybe be optimized a bit to remove + * that behaviour. + * + */ + + +/***************************** COPYRIGHT NOTICES *********************** + +Most of this code is directly from the Text::DoubleMetaphone perl module +version 0.05 available from https://www.cpan.org/. +It bears this copyright notice: + + + Copyright 2000, Maurice Aubrey . + All rights reserved. + + This code is based heavily on the C++ implementation by + Lawrence Philips and incorporates several bug fixes courtesy + of Kevin Atkinson . + + This module is free software; you may redistribute it and/or + modify it under the same terms as Perl itself. + +The remaining code is authored by Andrew Dunstan and + and is covered this copyright: + + Copyright 2003, North Carolina State Highway Patrol. + All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose, without fee, and without a written agreement + is hereby granted, provided that the above copyright notice and this + paragraph and the following two paragraphs appear in all copies. + + IN NO EVENT SHALL THE NORTH CAROLINA STATE HIGHWAY PATROL BE LIABLE TO ANY + PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, + INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + DOCUMENTATION, EVEN IF THE NORTH CAROLINA STATE HIGHWAY PATROL HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + THE NORTH CAROLINA STATE HIGHWAY PATROL SPECIFICALLY DISCLAIMS ANY + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED + HEREUNDER IS ON AN "AS IS" BASIS, AND THE NORTH CAROLINA STATE HIGHWAY PATROL + HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR + MODIFICATIONS. + +***********************************************************************/ + + +/* include these first, according to the docs */ +#ifndef DMETAPHONE_MAIN + +#include "postgres.h" + +#include "utils/builtins.h" + +/* turn off assertions for embedded function */ +#define NDEBUG + +#else /* DMETAPHONE_MAIN */ + +/* we need these if we didn't get them from postgres.h */ +#include +#include +#include +#include + +#endif /* DMETAPHONE_MAIN */ + +#include +#include + +/* prototype for the main function we got from the perl module */ +static void DoubleMetaphone(char *str, char **codes); + +#ifndef DMETAPHONE_MAIN + +/* + * The PostgreSQL visible dmetaphone function. + */ + +PG_FUNCTION_INFO_V1(dmetaphone); + +Datum +dmetaphone(PG_FUNCTION_ARGS) +{ + text *arg; + char *aptr, + *codes[2], + *code; + +#ifdef DMETAPHONE_NOSTRICT + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); +#endif + arg = PG_GETARG_TEXT_PP(0); + aptr = text_to_cstring(arg); + + DoubleMetaphone(aptr, codes); + code = codes[0]; + if (!code) + code = ""; + + PG_RETURN_TEXT_P(cstring_to_text(code)); +} + +/* + * The PostgreSQL visible dmetaphone_alt function. + */ + +PG_FUNCTION_INFO_V1(dmetaphone_alt); + +Datum +dmetaphone_alt(PG_FUNCTION_ARGS) +{ + text *arg; + char *aptr, + *codes[2], + *code; + +#ifdef DMETAPHONE_NOSTRICT + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); +#endif + arg = PG_GETARG_TEXT_PP(0); + aptr = text_to_cstring(arg); + + DoubleMetaphone(aptr, codes); + code = codes[1]; + if (!code) + code = ""; + + PG_RETURN_TEXT_P(cstring_to_text(code)); +} + + +/* here is where we start the code imported from the perl module */ + +/* all memory handling is done with these macros */ + +#define META_MALLOC(v,n,t) \ + (v = (t*)palloc(((n)*sizeof(t)))) + +#define META_REALLOC(v,n,t) \ + (v = (t*)repalloc((v),((n)*sizeof(t)))) + +/* + * Don't do pfree - it seems to cause a SIGSEGV sometimes - which might have just + * been caused by reloading the module in development. + * So we rely on context cleanup - Tom Lane says pfree shouldn't be necessary + * in a case like this. + */ + +#define META_FREE(x) ((void)true) /* pfree((x)) */ +#else /* not defined DMETAPHONE_MAIN */ + +/* use the standard malloc library when not running in PostgreSQL */ + +#define META_MALLOC(v,n,t) \ + (v = (t*)malloc(((n)*sizeof(t)))) + +#define META_REALLOC(v,n,t) \ + (v = (t*)realloc((v),((n)*sizeof(t)))) + +#define META_FREE(x) free((x)) +#endif /* defined DMETAPHONE_MAIN */ + + + +/* this typedef was originally in the perl module's .h file */ + +typedef struct +{ + char *str; + int length; + int bufsize; + int free_string_on_destroy; +} + +metastring; + +/* + * remaining perl module funcs unchanged except for declaring them static + * and reformatting to PostgreSQL indentation and to fit in 80 cols. + * + */ + +static metastring * +NewMetaString(const char *init_str) +{ + metastring *s; + char empty_string[] = ""; + + META_MALLOC(s, 1, metastring); + assert(s != NULL); + + if (init_str == NULL) + init_str = empty_string; + s->length = strlen(init_str); + /* preallocate a bit more for potential growth */ + s->bufsize = s->length + 7; + + META_MALLOC(s->str, s->bufsize, char); + assert(s->str != NULL); + + memcpy(s->str, init_str, s->length + 1); + s->free_string_on_destroy = 1; + + return s; +} + + +static void +DestroyMetaString(metastring *s) +{ + if (s == NULL) + return; + + if (s->free_string_on_destroy && (s->str != NULL)) + META_FREE(s->str); + + META_FREE(s); +} + + +static void +IncreaseBuffer(metastring *s, int chars_needed) +{ + META_REALLOC(s->str, (s->bufsize + chars_needed + 10), char); + assert(s->str != NULL); + s->bufsize = s->bufsize + chars_needed + 10; +} + + +static void +MakeUpper(metastring *s) +{ + char *i; + + for (i = s->str; *i; i++) + *i = toupper((unsigned char) *i); +} + + +static int +IsVowel(metastring *s, int pos) +{ + char c; + + if ((pos < 0) || (pos >= s->length)) + return 0; + + c = *(s->str + pos); + if ((c == 'A') || (c == 'E') || (c == 'I') || (c == 'O') || + (c == 'U') || (c == 'Y')) + return 1; + + return 0; +} + + +static int +SlavoGermanic(metastring *s) +{ + if ((char *) strstr(s->str, "W")) + return 1; + else if ((char *) strstr(s->str, "K")) + return 1; + else if ((char *) strstr(s->str, "CZ")) + return 1; + else if ((char *) strstr(s->str, "WITZ")) + return 1; + else + return 0; +} + + +static char +GetAt(metastring *s, int pos) +{ + if ((pos < 0) || (pos >= s->length)) + return '\0'; + + return ((char) *(s->str + pos)); +} + + +static void +SetAt(metastring *s, int pos, char c) +{ + if ((pos < 0) || (pos >= s->length)) + return; + + *(s->str + pos) = c; +} + + +/* + Caveats: the START value is 0 based +*/ +static int +StringAt(metastring *s, int start, int length,...) +{ + char *test; + char *pos; + va_list ap; + + if ((start < 0) || (start >= s->length)) + return 0; + + pos = (s->str + start); + va_start(ap, length); + + do + { + test = va_arg(ap, char *); + if (*test && (strncmp(pos, test, length) == 0)) + { + va_end(ap); + return 1; + } + } + while (strcmp(test, "") != 0); + + va_end(ap); + + return 0; +} + + +static void +MetaphAdd(metastring *s, const char *new_str) +{ + int add_length; + + if (new_str == NULL) + return; + + add_length = strlen(new_str); + if ((s->length + add_length) > (s->bufsize - 1)) + IncreaseBuffer(s, add_length); + + strcat(s->str, new_str); + s->length += add_length; +} + + +static void +DoubleMetaphone(char *str, char **codes) +{ + int length; + metastring *original; + metastring *primary; + metastring *secondary; + int current; + int last; + + current = 0; + /* we need the real length and last prior to padding */ + length = strlen(str); + last = length - 1; + original = NewMetaString(str); + /* Pad original so we can index beyond end */ + MetaphAdd(original, " "); + + primary = NewMetaString(""); + secondary = NewMetaString(""); + primary->free_string_on_destroy = 0; + secondary->free_string_on_destroy = 0; + + MakeUpper(original); + + /* skip these when at start of word */ + if (StringAt(original, 0, 2, "GN", "KN", "PN", "WR", "PS", "")) + current += 1; + + /* Initial 'X' is pronounced 'Z' e.g. 'Xavier' */ + if (GetAt(original, 0) == 'X') + { + MetaphAdd(primary, "S"); /* 'Z' maps to 'S' */ + MetaphAdd(secondary, "S"); + current += 1; + } + + /* main loop */ + while ((primary->length < 4) || (secondary->length < 4)) + { + if (current >= length) + break; + + switch (GetAt(original, current)) + { + case 'A': + case 'E': + case 'I': + case 'O': + case 'U': + case 'Y': + if (current == 0) + { + /* all init vowels now map to 'A' */ + MetaphAdd(primary, "A"); + MetaphAdd(secondary, "A"); + } + current += 1; + break; + + case 'B': + + /* "-mb", e.g", "dumb", already skipped over... */ + MetaphAdd(primary, "P"); + MetaphAdd(secondary, "P"); + + if (GetAt(original, current + 1) == 'B') + current += 2; + else + current += 1; + break; + + case '\xc7': /* C with cedilla */ + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "S"); + current += 1; + break; + + case 'C': + /* various germanic */ + if ((current > 1) + && !IsVowel(original, current - 2) + && StringAt(original, (current - 1), 3, "ACH", "") + && ((GetAt(original, current + 2) != 'I') + && ((GetAt(original, current + 2) != 'E') + || StringAt(original, (current - 2), 6, "BACHER", + "MACHER", "")))) + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + current += 2; + break; + } + + /* special case 'caesar' */ + if ((current == 0) + && StringAt(original, current, 6, "CAESAR", "")) + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "S"); + current += 2; + break; + } + + /* italian 'chianti' */ + if (StringAt(original, current, 4, "CHIA", "")) + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + current += 2; + break; + } + + if (StringAt(original, current, 2, "CH", "")) + { + /* find 'michael' */ + if ((current > 0) + && StringAt(original, current, 4, "CHAE", "")) + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "X"); + current += 2; + break; + } + + /* greek roots e.g. 'chemistry', 'chorus' */ + if ((current == 0) + && (StringAt(original, (current + 1), 5, + "HARAC", "HARIS", "") + || StringAt(original, (current + 1), 3, "HOR", + "HYM", "HIA", "HEM", "")) + && !StringAt(original, 0, 5, "CHORE", "")) + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + current += 2; + break; + } + + /* germanic, greek, or otherwise 'ch' for 'kh' sound */ + if ((StringAt(original, 0, 4, "VAN ", "VON ", "") + || StringAt(original, 0, 3, "SCH", "")) + /* 'architect but not 'arch', 'orchestra', 'orchid' */ + || StringAt(original, (current - 2), 6, "ORCHES", + "ARCHIT", "ORCHID", "") + || StringAt(original, (current + 2), 1, "T", "S", + "") + || ((StringAt(original, (current - 1), 1, + "A", "O", "U", "E", "") + || (current == 0)) + + /* + * e.g., 'wachtler', 'wechsler', but not 'tichner' + */ + && StringAt(original, (current + 2), 1, "L", "R", + "N", "M", "B", "H", "F", "V", "W", + " ", ""))) + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + } + else + { + if (current > 0) + { + if (StringAt(original, 0, 2, "MC", "")) + { + /* e.g., "McHugh" */ + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + } + else + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "K"); + } + } + else + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "X"); + } + } + current += 2; + break; + } + /* e.g, 'czerny' */ + if (StringAt(original, current, 2, "CZ", "") + && !StringAt(original, (current - 2), 4, "WICZ", "")) + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "X"); + current += 2; + break; + } + + /* e.g., 'focaccia' */ + if (StringAt(original, (current + 1), 3, "CIA", "")) + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "X"); + current += 3; + break; + } + + /* double 'C', but not if e.g. 'McClellan' */ + if (StringAt(original, current, 2, "CC", "") + && !((current == 1) && (GetAt(original, 0) == 'M'))) + { + /* 'bellocchio' but not 'bacchus' */ + if (StringAt(original, (current + 2), 1, "I", "E", "H", "") + && !StringAt(original, (current + 2), 2, "HU", "")) + { + /* 'accident', 'accede' 'succeed' */ + if (((current == 1) + && (GetAt(original, current - 1) == 'A')) + || StringAt(original, (current - 1), 5, "UCCEE", + "UCCES", "")) + { + MetaphAdd(primary, "KS"); + MetaphAdd(secondary, "KS"); + /* 'bacci', 'bertucci', other italian */ + } + else + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "X"); + } + current += 3; + break; + } + else + { /* Pierce's rule */ + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + current += 2; + break; + } + } + + if (StringAt(original, current, 2, "CK", "CG", "CQ", "")) + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + current += 2; + break; + } + + if (StringAt(original, current, 2, "CI", "CE", "CY", "")) + { + /* italian vs. english */ + if (StringAt + (original, current, 3, "CIO", "CIE", "CIA", "")) + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "X"); + } + else + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "S"); + } + current += 2; + break; + } + + /* else */ + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + + /* name sent in 'mac caffrey', 'mac gregor */ + if (StringAt(original, (current + 1), 2, " C", " Q", " G", "")) + current += 3; + else if (StringAt(original, (current + 1), 1, "C", "K", "Q", "") + && !StringAt(original, (current + 1), 2, + "CE", "CI", "")) + current += 2; + else + current += 1; + break; + + case 'D': + if (StringAt(original, current, 2, "DG", "")) + { + if (StringAt(original, (current + 2), 1, + "I", "E", "Y", "")) + { + /* e.g. 'edge' */ + MetaphAdd(primary, "J"); + MetaphAdd(secondary, "J"); + current += 3; + break; + } + else + { + /* e.g. 'edgar' */ + MetaphAdd(primary, "TK"); + MetaphAdd(secondary, "TK"); + current += 2; + break; + } + } + + if (StringAt(original, current, 2, "DT", "DD", "")) + { + MetaphAdd(primary, "T"); + MetaphAdd(secondary, "T"); + current += 2; + break; + } + + /* else */ + MetaphAdd(primary, "T"); + MetaphAdd(secondary, "T"); + current += 1; + break; + + case 'F': + if (GetAt(original, current + 1) == 'F') + current += 2; + else + current += 1; + MetaphAdd(primary, "F"); + MetaphAdd(secondary, "F"); + break; + + case 'G': + if (GetAt(original, current + 1) == 'H') + { + if ((current > 0) && !IsVowel(original, current - 1)) + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + current += 2; + break; + } + + if (current < 3) + { + /* 'ghislane', ghiradelli */ + if (current == 0) + { + if (GetAt(original, current + 2) == 'I') + { + MetaphAdd(primary, "J"); + MetaphAdd(secondary, "J"); + } + else + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + } + current += 2; + break; + } + } + + /* + * Parker's rule (with some further refinements) - e.g., + * 'hugh' + */ + if (((current > 1) + && StringAt(original, (current - 2), 1, + "B", "H", "D", "")) + /* e.g., 'bough' */ + || ((current > 2) + && StringAt(original, (current - 3), 1, + "B", "H", "D", "")) + /* e.g., 'broughton' */ + || ((current > 3) + && StringAt(original, (current - 4), 1, + "B", "H", ""))) + { + current += 2; + break; + } + else + { + /* + * e.g., 'laugh', 'McLaughlin', 'cough', 'gough', + * 'rough', 'tough' + */ + if ((current > 2) + && (GetAt(original, current - 1) == 'U') + && StringAt(original, (current - 3), 1, "C", + "G", "L", "R", "T", "")) + { + MetaphAdd(primary, "F"); + MetaphAdd(secondary, "F"); + } + else if ((current > 0) + && GetAt(original, current - 1) != 'I') + { + + + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + } + + current += 2; + break; + } + } + + if (GetAt(original, current + 1) == 'N') + { + if ((current == 1) && IsVowel(original, 0) + && !SlavoGermanic(original)) + { + MetaphAdd(primary, "KN"); + MetaphAdd(secondary, "N"); + } + else + /* not e.g. 'cagney' */ + if (!StringAt(original, (current + 2), 2, "EY", "") + && (GetAt(original, current + 1) != 'Y') + && !SlavoGermanic(original)) + { + MetaphAdd(primary, "N"); + MetaphAdd(secondary, "KN"); + } + else + { + MetaphAdd(primary, "KN"); + MetaphAdd(secondary, "KN"); + } + current += 2; + break; + } + + /* 'tagliaro' */ + if (StringAt(original, (current + 1), 2, "LI", "") + && !SlavoGermanic(original)) + { + MetaphAdd(primary, "KL"); + MetaphAdd(secondary, "L"); + current += 2; + break; + } + + /* -ges-,-gep-,-gel-, -gie- at beginning */ + if ((current == 0) + && ((GetAt(original, current + 1) == 'Y') + || StringAt(original, (current + 1), 2, "ES", "EP", + "EB", "EL", "EY", "IB", "IL", "IN", "IE", + "EI", "ER", ""))) + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "J"); + current += 2; + break; + } + + /* -ger-, -gy- */ + if ((StringAt(original, (current + 1), 2, "ER", "") + || (GetAt(original, current + 1) == 'Y')) + && !StringAt(original, 0, 6, + "DANGER", "RANGER", "MANGER", "") + && !StringAt(original, (current - 1), 1, "E", "I", "") + && !StringAt(original, (current - 1), 3, "RGY", "OGY", "")) + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "J"); + current += 2; + break; + } + + /* italian e.g, 'biaggi' */ + if (StringAt(original, (current + 1), 1, "E", "I", "Y", "") + || StringAt(original, (current - 1), 4, + "AGGI", "OGGI", "")) + { + /* obvious germanic */ + if ((StringAt(original, 0, 4, "VAN ", "VON ", "") + || StringAt(original, 0, 3, "SCH", "")) + || StringAt(original, (current + 1), 2, "ET", "")) + { + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + } + else + { + /* always soft if french ending */ + if (StringAt + (original, (current + 1), 4, "IER ", "")) + { + MetaphAdd(primary, "J"); + MetaphAdd(secondary, "J"); + } + else + { + MetaphAdd(primary, "J"); + MetaphAdd(secondary, "K"); + } + } + current += 2; + break; + } + + if (GetAt(original, current + 1) == 'G') + current += 2; + else + current += 1; + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + break; + + case 'H': + /* only keep if first & before vowel or btw. 2 vowels */ + if (((current == 0) || IsVowel(original, current - 1)) + && IsVowel(original, current + 1)) + { + MetaphAdd(primary, "H"); + MetaphAdd(secondary, "H"); + current += 2; + } + else + /* also takes care of 'HH' */ + current += 1; + break; + + case 'J': + /* obvious spanish, 'jose', 'san jacinto' */ + if (StringAt(original, current, 4, "JOSE", "") + || StringAt(original, 0, 4, "SAN ", "")) + { + if (((current == 0) + && (GetAt(original, current + 4) == ' ')) + || StringAt(original, 0, 4, "SAN ", "")) + { + MetaphAdd(primary, "H"); + MetaphAdd(secondary, "H"); + } + else + { + MetaphAdd(primary, "J"); + MetaphAdd(secondary, "H"); + } + current += 1; + break; + } + + if ((current == 0) + && !StringAt(original, current, 4, "JOSE", "")) + { + MetaphAdd(primary, "J"); /* Yankelovich/Jankelowicz */ + MetaphAdd(secondary, "A"); + } + else + { + /* spanish pron. of e.g. 'bajador' */ + if (IsVowel(original, current - 1) + && !SlavoGermanic(original) + && ((GetAt(original, current + 1) == 'A') + || (GetAt(original, current + 1) == 'O'))) + { + MetaphAdd(primary, "J"); + MetaphAdd(secondary, "H"); + } + else + { + if (current == last) + { + MetaphAdd(primary, "J"); + MetaphAdd(secondary, ""); + } + else + { + if (!StringAt(original, (current + 1), 1, "L", "T", + "K", "S", "N", "M", "B", "Z", "") + && !StringAt(original, (current - 1), 1, + "S", "K", "L", "")) + { + MetaphAdd(primary, "J"); + MetaphAdd(secondary, "J"); + } + } + } + } + + if (GetAt(original, current + 1) == 'J') /* it could happen! */ + current += 2; + else + current += 1; + break; + + case 'K': + if (GetAt(original, current + 1) == 'K') + current += 2; + else + current += 1; + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + break; + + case 'L': + if (GetAt(original, current + 1) == 'L') + { + /* spanish e.g. 'cabrillo', 'gallegos' */ + if (((current == (length - 3)) + && StringAt(original, (current - 1), 4, "ILLO", + "ILLA", "ALLE", "")) + || ((StringAt(original, (last - 1), 2, "AS", "OS", "") + || StringAt(original, last, 1, "A", "O", "")) + && StringAt(original, (current - 1), 4, + "ALLE", ""))) + { + MetaphAdd(primary, "L"); + MetaphAdd(secondary, ""); + current += 2; + break; + } + current += 2; + } + else + current += 1; + MetaphAdd(primary, "L"); + MetaphAdd(secondary, "L"); + break; + + case 'M': + if ((StringAt(original, (current - 1), 3, "UMB", "") + && (((current + 1) == last) + || StringAt(original, (current + 2), 2, "ER", ""))) + /* 'dumb','thumb' */ + || (GetAt(original, current + 1) == 'M')) + current += 2; + else + current += 1; + MetaphAdd(primary, "M"); + MetaphAdd(secondary, "M"); + break; + + case 'N': + if (GetAt(original, current + 1) == 'N') + current += 2; + else + current += 1; + MetaphAdd(primary, "N"); + MetaphAdd(secondary, "N"); + break; + + case '\xd1': /* N with tilde */ + current += 1; + MetaphAdd(primary, "N"); + MetaphAdd(secondary, "N"); + break; + + case 'P': + if (GetAt(original, current + 1) == 'H') + { + MetaphAdd(primary, "F"); + MetaphAdd(secondary, "F"); + current += 2; + break; + } + + /* also account for "campbell", "raspberry" */ + if (StringAt(original, (current + 1), 1, "P", "B", "")) + current += 2; + else + current += 1; + MetaphAdd(primary, "P"); + MetaphAdd(secondary, "P"); + break; + + case 'Q': + if (GetAt(original, current + 1) == 'Q') + current += 2; + else + current += 1; + MetaphAdd(primary, "K"); + MetaphAdd(secondary, "K"); + break; + + case 'R': + /* french e.g. 'rogier', but exclude 'hochmeier' */ + if ((current == last) + && !SlavoGermanic(original) + && StringAt(original, (current - 2), 2, "IE", "") + && !StringAt(original, (current - 4), 2, "ME", "MA", "")) + { + MetaphAdd(primary, ""); + MetaphAdd(secondary, "R"); + } + else + { + MetaphAdd(primary, "R"); + MetaphAdd(secondary, "R"); + } + + if (GetAt(original, current + 1) == 'R') + current += 2; + else + current += 1; + break; + + case 'S': + /* special cases 'island', 'isle', 'carlisle', 'carlysle' */ + if (StringAt(original, (current - 1), 3, "ISL", "YSL", "")) + { + current += 1; + break; + } + + /* special case 'sugar-' */ + if ((current == 0) + && StringAt(original, current, 5, "SUGAR", "")) + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "S"); + current += 1; + break; + } + + if (StringAt(original, current, 2, "SH", "")) + { + /* germanic */ + if (StringAt + (original, (current + 1), 4, "HEIM", "HOEK", "HOLM", + "HOLZ", "")) + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "S"); + } + else + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "X"); + } + current += 2; + break; + } + + /* italian & armenian */ + if (StringAt(original, current, 3, "SIO", "SIA", "") + || StringAt(original, current, 4, "SIAN", "")) + { + if (!SlavoGermanic(original)) + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "X"); + } + else + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "S"); + } + current += 3; + break; + } + + /* + * german & anglicisations, e.g. 'smith' match 'schmidt', + * 'snider' match 'schneider' also, -sz- in slavic language + * although in hungarian it is pronounced 's' + */ + if (((current == 0) + && StringAt(original, (current + 1), 1, + "M", "N", "L", "W", "")) + || StringAt(original, (current + 1), 1, "Z", "")) + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "X"); + if (StringAt(original, (current + 1), 1, "Z", "")) + current += 2; + else + current += 1; + break; + } + + if (StringAt(original, current, 2, "SC", "")) + { + /* Schlesinger's rule */ + if (GetAt(original, current + 2) == 'H') + { + /* dutch origin, e.g. 'school', 'schooner' */ + if (StringAt(original, (current + 3), 2, + "OO", "ER", "EN", + "UY", "ED", "EM", "")) + { + /* 'schermerhorn', 'schenker' */ + if (StringAt(original, (current + 3), 2, + "ER", "EN", "")) + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "SK"); + } + else + { + MetaphAdd(primary, "SK"); + MetaphAdd(secondary, "SK"); + } + current += 3; + break; + } + else + { + if ((current == 0) && !IsVowel(original, 3) + && (GetAt(original, 3) != 'W')) + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "S"); + } + else + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "X"); + } + current += 3; + break; + } + } + + if (StringAt(original, (current + 2), 1, + "I", "E", "Y", "")) + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "S"); + current += 3; + break; + } + /* else */ + MetaphAdd(primary, "SK"); + MetaphAdd(secondary, "SK"); + current += 3; + break; + } + + /* french e.g. 'resnais', 'artois' */ + if ((current == last) + && StringAt(original, (current - 2), 2, "AI", "OI", "")) + { + MetaphAdd(primary, ""); + MetaphAdd(secondary, "S"); + } + else + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "S"); + } + + if (StringAt(original, (current + 1), 1, "S", "Z", "")) + current += 2; + else + current += 1; + break; + + case 'T': + if (StringAt(original, current, 4, "TION", "")) + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "X"); + current += 3; + break; + } + + if (StringAt(original, current, 3, "TIA", "TCH", "")) + { + MetaphAdd(primary, "X"); + MetaphAdd(secondary, "X"); + current += 3; + break; + } + + if (StringAt(original, current, 2, "TH", "") + || StringAt(original, current, 3, "TTH", "")) + { + /* special case 'thomas', 'thames' or germanic */ + if (StringAt(original, (current + 2), 2, "OM", "AM", "") + || StringAt(original, 0, 4, "VAN ", "VON ", "") + || StringAt(original, 0, 3, "SCH", "")) + { + MetaphAdd(primary, "T"); + MetaphAdd(secondary, "T"); + } + else + { + MetaphAdd(primary, "0"); + MetaphAdd(secondary, "T"); + } + current += 2; + break; + } + + if (StringAt(original, (current + 1), 1, "T", "D", "")) + current += 2; + else + current += 1; + MetaphAdd(primary, "T"); + MetaphAdd(secondary, "T"); + break; + + case 'V': + if (GetAt(original, current + 1) == 'V') + current += 2; + else + current += 1; + MetaphAdd(primary, "F"); + MetaphAdd(secondary, "F"); + break; + + case 'W': + /* can also be in middle of word */ + if (StringAt(original, current, 2, "WR", "")) + { + MetaphAdd(primary, "R"); + MetaphAdd(secondary, "R"); + current += 2; + break; + } + + if ((current == 0) + && (IsVowel(original, current + 1) + || StringAt(original, current, 2, "WH", ""))) + { + /* Wasserman should match Vasserman */ + if (IsVowel(original, current + 1)) + { + MetaphAdd(primary, "A"); + MetaphAdd(secondary, "F"); + } + else + { + /* need Uomo to match Womo */ + MetaphAdd(primary, "A"); + MetaphAdd(secondary, "A"); + } + } + + /* Arnow should match Arnoff */ + if (((current == last) && IsVowel(original, current - 1)) + || StringAt(original, (current - 1), 5, "EWSKI", "EWSKY", + "OWSKI", "OWSKY", "") + || StringAt(original, 0, 3, "SCH", "")) + { + MetaphAdd(primary, ""); + MetaphAdd(secondary, "F"); + current += 1; + break; + } + + /* polish e.g. 'filipowicz' */ + if (StringAt(original, current, 4, "WICZ", "WITZ", "")) + { + MetaphAdd(primary, "TS"); + MetaphAdd(secondary, "FX"); + current += 4; + break; + } + + /* else skip it */ + current += 1; + break; + + case 'X': + /* french e.g. breaux */ + if (!((current == last) + && (StringAt(original, (current - 3), 3, + "IAU", "EAU", "") + || StringAt(original, (current - 2), 2, + "AU", "OU", "")))) + { + MetaphAdd(primary, "KS"); + MetaphAdd(secondary, "KS"); + } + + + if (StringAt(original, (current + 1), 1, "C", "X", "")) + current += 2; + else + current += 1; + break; + + case 'Z': + /* chinese pinyin e.g. 'zhao' */ + if (GetAt(original, current + 1) == 'H') + { + MetaphAdd(primary, "J"); + MetaphAdd(secondary, "J"); + current += 2; + break; + } + else if (StringAt(original, (current + 1), 2, + "ZO", "ZI", "ZA", "") + || (SlavoGermanic(original) + && ((current > 0) + && GetAt(original, current - 1) != 'T'))) + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "TS"); + } + else + { + MetaphAdd(primary, "S"); + MetaphAdd(secondary, "S"); + } + + if (GetAt(original, current + 1) == 'Z') + current += 2; + else + current += 1; + break; + + default: + current += 1; + } + + /* + * printf("PRIMARY: %s\n", primary->str); printf("SECONDARY: %s\n", + * secondary->str); + */ + } + + + if (primary->length > 4) + SetAt(primary, 4, '\0'); + + if (secondary->length > 4) + SetAt(secondary, 4, '\0'); + + *codes = primary->str; + *++codes = secondary->str; + + DestroyMetaString(original); + DestroyMetaString(primary); + DestroyMetaString(secondary); +} + +#ifdef DMETAPHONE_MAIN + +/* just for testing - not part of the perl code */ + +main(int argc, char **argv) +{ + char *codes[2]; + + if (argc > 1) + { + DoubleMetaphone(argv[1], codes); + printf("%s|%s\n", codes[0], codes[1]); + } +} + +#endif diff --git a/contrib/fuzzystrmatch/expected/fuzzystrmatch.out b/contrib/fuzzystrmatch/expected/fuzzystrmatch.out new file mode 100644 index 0000000..3195e1e --- /dev/null +++ b/contrib/fuzzystrmatch/expected/fuzzystrmatch.out @@ -0,0 +1,244 @@ +CREATE EXTENSION fuzzystrmatch; +SELECT soundex('hello world!'); + soundex +--------- + H464 +(1 row) + +SELECT soundex('Anne'), soundex('Ann'), difference('Anne', 'Ann'); + soundex | soundex | difference +---------+---------+------------ + A500 | A500 | 4 +(1 row) + +SELECT soundex('Anne'), soundex('Andrew'), difference('Anne', 'Andrew'); + soundex | soundex | difference +---------+---------+------------ + A500 | A536 | 2 +(1 row) + +SELECT soundex('Anne'), soundex('Margaret'), difference('Anne', 'Margaret'); + soundex | soundex | difference +---------+---------+------------ + A500 | M626 | 0 +(1 row) + +SELECT soundex(''), difference('', ''); + soundex | difference +---------+------------ + | 4 +(1 row) + +SELECT levenshtein('GUMBO', 'GAMBOL'); + levenshtein +------------- + 2 +(1 row) + +SELECT levenshtein('GUMBO', 'GAMBOL', 2, 1, 1); + levenshtein +------------- + 3 +(1 row) + +SELECT levenshtein_less_equal('extensive', 'exhaustive', 2); + levenshtein_less_equal +------------------------ + 3 +(1 row) + +SELECT levenshtein_less_equal('extensive', 'exhaustive', 4); + levenshtein_less_equal +------------------------ + 4 +(1 row) + +SELECT metaphone('GUMBO', 4); + metaphone +----------- + KM +(1 row) + +SELECT dmetaphone('gumbo'); + dmetaphone +------------ + KMP +(1 row) + +SELECT dmetaphone_alt('gumbo'); + dmetaphone_alt +---------------- + KMP +(1 row) + +-- Wovels +SELECT daitch_mokotoff('Augsburg'); + daitch_mokotoff +----------------- + {054795} +(1 row) + +SELECT daitch_mokotoff('Breuer'); + daitch_mokotoff +----------------- + {791900} +(1 row) + +SELECT daitch_mokotoff('Freud'); + daitch_mokotoff +----------------- + {793000} +(1 row) + +-- The letter "H" +SELECT daitch_mokotoff('Halberstadt'); + daitch_mokotoff +----------------- + {587943,587433} +(1 row) + +SELECT daitch_mokotoff('Mannheim'); + daitch_mokotoff +----------------- + {665600} +(1 row) + +-- Adjacent sounds +SELECT daitch_mokotoff('Chernowitz'); + daitch_mokotoff +----------------- + {596740,496740} +(1 row) + +-- Adjacent letters with identical adjacent code digits +SELECT daitch_mokotoff('Cherkassy'); + daitch_mokotoff +----------------- + {595400,495400} +(1 row) + +SELECT daitch_mokotoff('Kleinman'); + daitch_mokotoff +----------------- + {586660} +(1 row) + +-- More than one word +SELECT daitch_mokotoff('Nowy Targ'); + daitch_mokotoff +----------------- + {673950} +(1 row) + +-- Padded with "0" +SELECT daitch_mokotoff('Berlin'); + daitch_mokotoff +----------------- + {798600} +(1 row) + +-- Other examples from https://www.avotaynu.com/soundex.htm +SELECT daitch_mokotoff('Ceniow'); + daitch_mokotoff +----------------- + {567000,467000} +(1 row) + +SELECT daitch_mokotoff('Tsenyuv'); + daitch_mokotoff +----------------- + {467000} +(1 row) + +SELECT daitch_mokotoff('Holubica'); + daitch_mokotoff +----------------- + {587500,587400} +(1 row) + +SELECT daitch_mokotoff('Golubitsa'); + daitch_mokotoff +----------------- + {587400} +(1 row) + +SELECT daitch_mokotoff('Przemysl'); + daitch_mokotoff +----------------- + {794648,746480} +(1 row) + +SELECT daitch_mokotoff('Pshemeshil'); + daitch_mokotoff +----------------- + {746480} +(1 row) + +SELECT daitch_mokotoff('Rosochowaciec'); + daitch_mokotoff +----------------------------------------------------------- + {945755,945754,945745,945744,944755,944754,944745,944744} +(1 row) + +SELECT daitch_mokotoff('Rosokhovatsets'); + daitch_mokotoff +----------------- + {945744} +(1 row) + +-- Ignored characters +SELECT daitch_mokotoff('''OBrien'); + daitch_mokotoff +----------------- + {079600} +(1 row) + +SELECT daitch_mokotoff('O''Brien'); + daitch_mokotoff +----------------- + {079600} +(1 row) + +-- "Difficult" cases, likely to cause trouble for other implementations. +SELECT daitch_mokotoff('CJC'); + daitch_mokotoff +--------------------------------------------- + {550000,540000,545000,450000,400000,440000} +(1 row) + +SELECT daitch_mokotoff('BESST'); + daitch_mokotoff +----------------- + {743000} +(1 row) + +SELECT daitch_mokotoff('BOUEY'); + daitch_mokotoff +----------------- + {710000} +(1 row) + +SELECT daitch_mokotoff('HANNMANN'); + daitch_mokotoff +----------------- + {566600} +(1 row) + +SELECT daitch_mokotoff('MCCOYJR'); + daitch_mokotoff +----------------------------------------------------------- + {651900,654900,654190,654490,645190,645490,641900,644900} +(1 row) + +SELECT daitch_mokotoff('ACCURSO'); + daitch_mokotoff +----------------------------------------------------------- + {059400,054000,054940,054400,045940,045400,049400,044000} +(1 row) + +SELECT daitch_mokotoff('BIERSCHBACH'); + daitch_mokotoff +----------------------------------------------------------- + {794575,794574,794750,794740,745750,745740,747500,747400} +(1 row) + diff --git a/contrib/fuzzystrmatch/expected/fuzzystrmatch_utf8.out b/contrib/fuzzystrmatch/expected/fuzzystrmatch_utf8.out new file mode 100644 index 0000000..b0dd488 --- /dev/null +++ b/contrib/fuzzystrmatch/expected/fuzzystrmatch_utf8.out @@ -0,0 +1,61 @@ +/* + * 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; +-- CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; +-- Accents +SELECT daitch_mokotoff('Müller'); + daitch_mokotoff +----------------- + {689000} +(1 row) + +SELECT daitch_mokotoff('Schäfer'); + daitch_mokotoff +----------------- + {479000} +(1 row) + +SELECT daitch_mokotoff('Straßburg'); + daitch_mokotoff +----------------- + {294795} +(1 row) + +SELECT daitch_mokotoff('Éregon'); + daitch_mokotoff +----------------- + {095600} +(1 row) + +-- Special characters added at https://www.jewishgen.org/InfoFiles/Soundex.html +SELECT daitch_mokotoff('gąszczu'); + daitch_mokotoff +----------------- + {564000,540000} +(1 row) + +SELECT daitch_mokotoff('brzęczy'); + daitch_mokotoff +------------------------------- + {794640,794400,746400,744000} +(1 row) + +SELECT daitch_mokotoff('ţamas'); + daitch_mokotoff +----------------- + {364000,464000} +(1 row) + +SELECT daitch_mokotoff('țamas'); + daitch_mokotoff +----------------- + {364000,464000} +(1 row) + diff --git a/contrib/fuzzystrmatch/expected/fuzzystrmatch_utf8_1.out b/contrib/fuzzystrmatch/expected/fuzzystrmatch_utf8_1.out new file mode 100644 index 0000000..37aead8 --- /dev/null +++ b/contrib/fuzzystrmatch/expected/fuzzystrmatch_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/contrib/fuzzystrmatch/fuzzystrmatch--1.0--1.1.sql b/contrib/fuzzystrmatch/fuzzystrmatch--1.0--1.1.sql new file mode 100644 index 0000000..f2b1555 --- /dev/null +++ b/contrib/fuzzystrmatch/fuzzystrmatch--1.0--1.1.sql @@ -0,0 +1,15 @@ +/* contrib/fuzzystrmatch/fuzzystrmatch--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION fuzzystrmatch UPDATE TO '1.1'" to load this file. \quit + +ALTER FUNCTION levenshtein(text, text) PARALLEL SAFE; +ALTER FUNCTION levenshtein(text, text, int, int, int) PARALLEL SAFE; +ALTER FUNCTION levenshtein_less_equal(text, text, int) PARALLEL SAFE; +ALTER FUNCTION levenshtein_less_equal(text, text, int, int, int, int) PARALLEL SAFE; +ALTER FUNCTION metaphone(text, int) PARALLEL SAFE; +ALTER FUNCTION soundex(text) PARALLEL SAFE; +ALTER FUNCTION text_soundex(text) PARALLEL SAFE; +ALTER FUNCTION difference(text, text) PARALLEL SAFE; +ALTER FUNCTION dmetaphone(text) PARALLEL SAFE; +ALTER FUNCTION dmetaphone_alt(text) PARALLEL SAFE; diff --git a/contrib/fuzzystrmatch/fuzzystrmatch--1.1--1.2.sql b/contrib/fuzzystrmatch/fuzzystrmatch--1.1--1.2.sql new file mode 100644 index 0000000..d8542a7 --- /dev/null +++ b/contrib/fuzzystrmatch/fuzzystrmatch--1.1--1.2.sql @@ -0,0 +1,8 @@ +/* contrib/fuzzystrmatch/fuzzystrmatch--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION fuzzystrmatch UPDATE TO '1.2'" to load this file. \quit + +CREATE FUNCTION daitch_mokotoff(text) RETURNS text[] +AS 'MODULE_PATHNAME', 'daitch_mokotoff' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; diff --git a/contrib/fuzzystrmatch/fuzzystrmatch--1.1.sql b/contrib/fuzzystrmatch/fuzzystrmatch--1.1.sql new file mode 100644 index 0000000..41de9d9 --- /dev/null +++ b/contrib/fuzzystrmatch/fuzzystrmatch--1.1.sql @@ -0,0 +1,44 @@ +/* contrib/fuzzystrmatch/fuzzystrmatch--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION fuzzystrmatch" to load this file. \quit + +CREATE FUNCTION levenshtein (text,text) RETURNS int +AS 'MODULE_PATHNAME','levenshtein' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION levenshtein (text,text,int,int,int) RETURNS int +AS 'MODULE_PATHNAME','levenshtein_with_costs' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION levenshtein_less_equal (text,text,int) RETURNS int +AS 'MODULE_PATHNAME','levenshtein_less_equal' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION levenshtein_less_equal (text,text,int,int,int,int) RETURNS int +AS 'MODULE_PATHNAME','levenshtein_less_equal_with_costs' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION metaphone (text,int) RETURNS text +AS 'MODULE_PATHNAME','metaphone' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION soundex(text) RETURNS text +AS 'MODULE_PATHNAME', 'soundex' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION text_soundex(text) RETURNS text +AS 'MODULE_PATHNAME', 'soundex' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION difference(text,text) RETURNS int +AS 'MODULE_PATHNAME', 'difference' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION dmetaphone (text) RETURNS text +AS 'MODULE_PATHNAME', 'dmetaphone' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION dmetaphone_alt (text) RETURNS text +AS 'MODULE_PATHNAME', 'dmetaphone_alt' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; diff --git a/contrib/fuzzystrmatch/fuzzystrmatch.c b/contrib/fuzzystrmatch/fuzzystrmatch.c new file mode 100644 index 0000000..5686497 --- /dev/null +++ b/contrib/fuzzystrmatch/fuzzystrmatch.c @@ -0,0 +1,794 @@ +/* + * fuzzystrmatch.c + * + * Functions for "fuzzy" comparison of strings + * + * Joe Conway + * + * contrib/fuzzystrmatch/fuzzystrmatch.c + * Copyright (c) 2001-2023, PostgreSQL Global Development Group + * ALL RIGHTS RESERVED; + * + * metaphone() + * ----------- + * Modified for PostgreSQL by Joe Conway. + * Based on CPAN's "Text-Metaphone-1.96" by Michael G Schwern + * Code slightly modified for use as PostgreSQL function (palloc, elog, etc). + * Metaphone was originally created by Lawrence Philips and presented in article + * in "Computer Language" December 1990 issue. + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + */ + +#include "postgres.h" + +#include + +#include "mb/pg_wchar.h" +#include "utils/builtins.h" +#include "utils/varlena.h" +#include "varatt.h" + +PG_MODULE_MAGIC; + +/* + * Soundex + */ +static void _soundex(const char *instr, char *outstr); + +#define SOUNDEX_LEN 4 + +/* ABCDEFGHIJKLMNOPQRSTUVWXYZ */ +static const char *soundex_table = "01230120022455012623010202"; + +static char +soundex_code(char letter) +{ + letter = toupper((unsigned char) letter); + /* Defend against non-ASCII letters */ + if (letter >= 'A' && letter <= 'Z') + return soundex_table[letter - 'A']; + return letter; +} + +/* + * Metaphone + */ +#define MAX_METAPHONE_STRLEN 255 + +/* + * Original code by Michael G Schwern starts here. + * Code slightly modified for use as PostgreSQL function. + */ + + +/************************************************************************** + metaphone -- Breaks english phrases down into their phonemes. + + Input + word -- An english word to be phonized + max_phonemes -- How many phonemes to calculate. If 0, then it + will phonize the entire phrase. + phoned_word -- The final phonized word. (We'll allocate the + memory.) + Output + error -- A simple error flag, returns true or false + + NOTES: ALL non-alpha characters are ignored, this includes whitespace, + although non-alpha characters will break up phonemes. +****************************************************************************/ + + +/* I add modifications to the traditional metaphone algorithm that you + might find in books. Define this if you want metaphone to behave + traditionally */ +#undef USE_TRADITIONAL_METAPHONE + +/* Special encodings */ +#define SH 'X' +#define TH '0' + +static char Lookahead(char *word, int how_far); +static void _metaphone(char *word, int max_phonemes, char **phoned_word); + +/* Metachar.h ... little bits about characters for metaphone */ + + +/*-- Character encoding array & accessing macros --*/ +/* Stolen directly out of the book... */ +static const char _codes[26] = { + 1, 16, 4, 16, 9, 2, 4, 16, 9, 2, 0, 2, 2, 2, 1, 4, 0, 2, 4, 4, 1, 0, 0, 0, 8, 0 +/* a b c d e f g h i j k l m n o p q r s t u v w x y z */ +}; + +static int +getcode(char c) +{ + if (isalpha((unsigned char) c)) + { + c = toupper((unsigned char) c); + /* Defend against non-ASCII letters */ + if (c >= 'A' && c <= 'Z') + return _codes[c - 'A']; + } + return 0; +} + +#define isvowel(c) (getcode(c) & 1) /* AEIOU */ + +/* These letters are passed through unchanged */ +#define NOCHANGE(c) (getcode(c) & 2) /* FJMNR */ + +/* These form diphthongs when preceding H */ +#define AFFECTH(c) (getcode(c) & 4) /* CGPST */ + +/* These make C and G soft */ +#define MAKESOFT(c) (getcode(c) & 8) /* EIY */ + +/* These prevent GH from becoming F */ +#define NOGHTOF(c) (getcode(c) & 16) /* BDH */ + +PG_FUNCTION_INFO_V1(levenshtein_with_costs); +Datum +levenshtein_with_costs(PG_FUNCTION_ARGS) +{ + text *src = PG_GETARG_TEXT_PP(0); + text *dst = PG_GETARG_TEXT_PP(1); + int ins_c = PG_GETARG_INT32(2); + int del_c = PG_GETARG_INT32(3); + int sub_c = PG_GETARG_INT32(4); + const char *s_data; + const char *t_data; + int s_bytes, + t_bytes; + + /* Extract a pointer to the actual character data */ + s_data = VARDATA_ANY(src); + t_data = VARDATA_ANY(dst); + /* Determine length of each string in bytes */ + s_bytes = VARSIZE_ANY_EXHDR(src); + t_bytes = VARSIZE_ANY_EXHDR(dst); + + PG_RETURN_INT32(varstr_levenshtein(s_data, s_bytes, t_data, t_bytes, + ins_c, del_c, sub_c, false)); +} + + +PG_FUNCTION_INFO_V1(levenshtein); +Datum +levenshtein(PG_FUNCTION_ARGS) +{ + text *src = PG_GETARG_TEXT_PP(0); + text *dst = PG_GETARG_TEXT_PP(1); + const char *s_data; + const char *t_data; + int s_bytes, + t_bytes; + + /* Extract a pointer to the actual character data */ + s_data = VARDATA_ANY(src); + t_data = VARDATA_ANY(dst); + /* Determine length of each string in bytes */ + s_bytes = VARSIZE_ANY_EXHDR(src); + t_bytes = VARSIZE_ANY_EXHDR(dst); + + PG_RETURN_INT32(varstr_levenshtein(s_data, s_bytes, t_data, t_bytes, + 1, 1, 1, false)); +} + + +PG_FUNCTION_INFO_V1(levenshtein_less_equal_with_costs); +Datum +levenshtein_less_equal_with_costs(PG_FUNCTION_ARGS) +{ + text *src = PG_GETARG_TEXT_PP(0); + text *dst = PG_GETARG_TEXT_PP(1); + int ins_c = PG_GETARG_INT32(2); + int del_c = PG_GETARG_INT32(3); + int sub_c = PG_GETARG_INT32(4); + int max_d = PG_GETARG_INT32(5); + const char *s_data; + const char *t_data; + int s_bytes, + t_bytes; + + /* Extract a pointer to the actual character data */ + s_data = VARDATA_ANY(src); + t_data = VARDATA_ANY(dst); + /* Determine length of each string in bytes */ + s_bytes = VARSIZE_ANY_EXHDR(src); + t_bytes = VARSIZE_ANY_EXHDR(dst); + + PG_RETURN_INT32(varstr_levenshtein_less_equal(s_data, s_bytes, + t_data, t_bytes, + ins_c, del_c, sub_c, + max_d, false)); +} + + +PG_FUNCTION_INFO_V1(levenshtein_less_equal); +Datum +levenshtein_less_equal(PG_FUNCTION_ARGS) +{ + text *src = PG_GETARG_TEXT_PP(0); + text *dst = PG_GETARG_TEXT_PP(1); + int max_d = PG_GETARG_INT32(2); + const char *s_data; + const char *t_data; + int s_bytes, + t_bytes; + + /* Extract a pointer to the actual character data */ + s_data = VARDATA_ANY(src); + t_data = VARDATA_ANY(dst); + /* Determine length of each string in bytes */ + s_bytes = VARSIZE_ANY_EXHDR(src); + t_bytes = VARSIZE_ANY_EXHDR(dst); + + PG_RETURN_INT32(varstr_levenshtein_less_equal(s_data, s_bytes, + t_data, t_bytes, + 1, 1, 1, + max_d, false)); +} + + +/* + * Calculates the metaphone of an input string. + * Returns number of characters requested + * (suggested value is 4) + */ +PG_FUNCTION_INFO_V1(metaphone); +Datum +metaphone(PG_FUNCTION_ARGS) +{ + char *str_i = TextDatumGetCString(PG_GETARG_DATUM(0)); + size_t str_i_len = strlen(str_i); + int reqlen; + char *metaph; + + /* return an empty string if we receive one */ + if (!(str_i_len > 0)) + PG_RETURN_TEXT_P(cstring_to_text("")); + + if (str_i_len > MAX_METAPHONE_STRLEN) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument exceeds the maximum length of %d bytes", + MAX_METAPHONE_STRLEN))); + + reqlen = PG_GETARG_INT32(1); + if (reqlen > MAX_METAPHONE_STRLEN) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("output exceeds the maximum length of %d bytes", + MAX_METAPHONE_STRLEN))); + + if (!(reqlen > 0)) + ereport(ERROR, + (errcode(ERRCODE_ZERO_LENGTH_CHARACTER_STRING), + errmsg("output cannot be empty string"))); + + _metaphone(str_i, reqlen, &metaph); + PG_RETURN_TEXT_P(cstring_to_text(metaph)); +} + + +/* + * Original code by Michael G Schwern starts here. + * Code slightly modified for use as PostgreSQL + * function (palloc, etc). + */ + +/* I suppose I could have been using a character pointer instead of + * accessing the array directly... */ + +/* Look at the next letter in the word */ +#define Next_Letter (toupper((unsigned char) word[w_idx+1])) +/* Look at the current letter in the word */ +#define Curr_Letter (toupper((unsigned char) word[w_idx])) +/* Go N letters back. */ +#define Look_Back_Letter(n) \ + (w_idx >= (n) ? toupper((unsigned char) word[w_idx-(n)]) : '\0') +/* Previous letter. I dunno, should this return null on failure? */ +#define Prev_Letter (Look_Back_Letter(1)) +/* Look two letters down. It makes sure you don't walk off the string. */ +#define After_Next_Letter \ + (Next_Letter != '\0' ? toupper((unsigned char) word[w_idx+2]) : '\0') +#define Look_Ahead_Letter(n) toupper((unsigned char) Lookahead(word+w_idx, n)) + + +/* Allows us to safely look ahead an arbitrary # of letters */ +/* I probably could have just used strlen... */ +static char +Lookahead(char *word, int how_far) +{ + char letter_ahead = '\0'; /* null by default */ + int idx; + + for (idx = 0; word[idx] != '\0' && idx < how_far; idx++); + /* Edge forward in the string... */ + + letter_ahead = word[idx]; /* idx will be either == to how_far or at the + * end of the string */ + return letter_ahead; +} + + +/* phonize one letter */ +#define Phonize(c) do {(*phoned_word)[p_idx++] = c;} while (0) +/* Slap a null character on the end of the phoned word */ +#define End_Phoned_Word do {(*phoned_word)[p_idx] = '\0';} while (0) +/* How long is the phoned word? */ +#define Phone_Len (p_idx) + +/* Note is a letter is a 'break' in the word */ +#define Isbreak(c) (!isalpha((unsigned char) (c))) + + +static void +_metaphone(char *word, /* IN */ + int max_phonemes, + char **phoned_word) /* OUT */ +{ + int w_idx = 0; /* point in the phonization we're at. */ + int p_idx = 0; /* end of the phoned phrase */ + + /*-- Parameter checks --*/ + + /* + * Shouldn't be necessary, but left these here anyway jec Aug 3, 2001 + */ + + /* Negative phoneme length is meaningless */ + if (!(max_phonemes > 0)) + /* internal error */ + elog(ERROR, "metaphone: Requested output length must be > 0"); + + /* Empty/null string is meaningless */ + if ((word == NULL) || !(strlen(word) > 0)) + /* internal error */ + elog(ERROR, "metaphone: Input string length must be > 0"); + + /*-- Allocate memory for our phoned_phrase --*/ + if (max_phonemes == 0) + { /* Assume largest possible */ + *phoned_word = palloc(sizeof(char) * strlen(word) + 1); + } + else + { + *phoned_word = palloc(sizeof(char) * max_phonemes + 1); + } + + /*-- The first phoneme has to be processed specially. --*/ + /* Find our first letter */ + for (; !isalpha((unsigned char) (Curr_Letter)); w_idx++) + { + /* On the off chance we were given nothing but crap... */ + if (Curr_Letter == '\0') + { + End_Phoned_Word; + return; + } + } + + switch (Curr_Letter) + { + /* AE becomes E */ + case 'A': + if (Next_Letter == 'E') + { + Phonize('E'); + w_idx += 2; + } + /* Remember, preserve vowels at the beginning */ + else + { + Phonize('A'); + w_idx++; + } + break; + /* [GKP]N becomes N */ + case 'G': + case 'K': + case 'P': + if (Next_Letter == 'N') + { + Phonize('N'); + w_idx += 2; + } + break; + + /* + * WH becomes H, WR becomes R W if followed by a vowel + */ + case 'W': + if (Next_Letter == 'H' || + Next_Letter == 'R') + { + Phonize(Next_Letter); + w_idx += 2; + } + else if (isvowel(Next_Letter)) + { + Phonize('W'); + w_idx += 2; + } + /* else ignore */ + break; + /* X becomes S */ + case 'X': + Phonize('S'); + w_idx++; + break; + /* Vowels are kept */ + + /* + * We did A already case 'A': case 'a': + */ + case 'E': + case 'I': + case 'O': + case 'U': + Phonize(Curr_Letter); + w_idx++; + break; + default: + /* do nothing */ + break; + } + + + + /* On to the metaphoning */ + for (; Curr_Letter != '\0' && + (max_phonemes == 0 || Phone_Len < max_phonemes); + w_idx++) + { + /* + * How many letters to skip because an earlier encoding handled + * multiple letters + */ + unsigned short int skip_letter = 0; + + + /* + * THOUGHT: It would be nice if, rather than having things like... + * well, SCI. For SCI you encode the S, then have to remember to skip + * the C. So the phonome SCI invades both S and C. It would be + * better, IMHO, to skip the C from the S part of the encoding. Hell, + * I'm trying it. + */ + + /* Ignore non-alphas */ + if (!isalpha((unsigned char) (Curr_Letter))) + continue; + + /* Drop duplicates, except CC */ + if (Curr_Letter == Prev_Letter && + Curr_Letter != 'C') + continue; + + switch (Curr_Letter) + { + /* B -> B unless in MB */ + case 'B': + if (Prev_Letter != 'M') + Phonize('B'); + break; + + /* + * 'sh' if -CIA- or -CH, but not SCH, except SCHW. (SCHW is + * handled in S) S if -CI-, -CE- or -CY- dropped if -SCI-, + * SCE-, -SCY- (handed in S) else K + */ + case 'C': + if (MAKESOFT(Next_Letter)) + { /* C[IEY] */ + if (After_Next_Letter == 'A' && + Next_Letter == 'I') + { /* CIA */ + Phonize(SH); + } + /* SC[IEY] */ + else if (Prev_Letter == 'S') + { + /* Dropped */ + } + else + Phonize('S'); + } + else if (Next_Letter == 'H') + { +#ifndef USE_TRADITIONAL_METAPHONE + if (After_Next_Letter == 'R' || + Prev_Letter == 'S') + { /* Christ, School */ + Phonize('K'); + } + else + Phonize(SH); +#else + Phonize(SH); +#endif + skip_letter++; + } + else + Phonize('K'); + break; + + /* + * J if in -DGE-, -DGI- or -DGY- else T + */ + case 'D': + if (Next_Letter == 'G' && + MAKESOFT(After_Next_Letter)) + { + Phonize('J'); + skip_letter++; + } + else + Phonize('T'); + break; + + /* + * F if in -GH and not B--GH, D--GH, -H--GH, -H---GH else + * dropped if -GNED, -GN, else dropped if -DGE-, -DGI- or + * -DGY- (handled in D) else J if in -GE-, -GI, -GY and not GG + * else K + */ + case 'G': + if (Next_Letter == 'H') + { + if (!(NOGHTOF(Look_Back_Letter(3)) || + Look_Back_Letter(4) == 'H')) + { + Phonize('F'); + skip_letter++; + } + else + { + /* silent */ + } + } + else if (Next_Letter == 'N') + { + if (Isbreak(After_Next_Letter) || + (After_Next_Letter == 'E' && + Look_Ahead_Letter(3) == 'D')) + { + /* dropped */ + } + else + Phonize('K'); + } + else if (MAKESOFT(Next_Letter) && + Prev_Letter != 'G') + Phonize('J'); + else + Phonize('K'); + break; + /* H if before a vowel and not after C,G,P,S,T */ + case 'H': + if (isvowel(Next_Letter) && + !AFFECTH(Prev_Letter)) + Phonize('H'); + break; + + /* + * dropped if after C else K + */ + case 'K': + if (Prev_Letter != 'C') + Phonize('K'); + break; + + /* + * F if before H else P + */ + case 'P': + if (Next_Letter == 'H') + Phonize('F'); + else + Phonize('P'); + break; + + /* + * K + */ + case 'Q': + Phonize('K'); + break; + + /* + * 'sh' in -SH-, -SIO- or -SIA- or -SCHW- else S + */ + case 'S': + if (Next_Letter == 'I' && + (After_Next_Letter == 'O' || + After_Next_Letter == 'A')) + Phonize(SH); + else if (Next_Letter == 'H') + { + Phonize(SH); + skip_letter++; + } +#ifndef USE_TRADITIONAL_METAPHONE + else if (Next_Letter == 'C' && + Look_Ahead_Letter(2) == 'H' && + Look_Ahead_Letter(3) == 'W') + { + Phonize(SH); + skip_letter += 2; + } +#endif + else + Phonize('S'); + break; + + /* + * 'sh' in -TIA- or -TIO- else 'th' before H else T + */ + case 'T': + if (Next_Letter == 'I' && + (After_Next_Letter == 'O' || + After_Next_Letter == 'A')) + Phonize(SH); + else if (Next_Letter == 'H') + { + Phonize(TH); + skip_letter++; + } + else + Phonize('T'); + break; + /* F */ + case 'V': + Phonize('F'); + break; + /* W before a vowel, else dropped */ + case 'W': + if (isvowel(Next_Letter)) + Phonize('W'); + break; + /* KS */ + case 'X': + Phonize('K'); + if (max_phonemes == 0 || Phone_Len < max_phonemes) + Phonize('S'); + break; + /* Y if followed by a vowel */ + case 'Y': + if (isvowel(Next_Letter)) + Phonize('Y'); + break; + /* S */ + case 'Z': + Phonize('S'); + break; + /* No transformation */ + case 'F': + case 'J': + case 'L': + case 'M': + case 'N': + case 'R': + Phonize(Curr_Letter); + break; + default: + /* nothing */ + break; + } /* END SWITCH */ + + w_idx += skip_letter; + } /* END FOR */ + + End_Phoned_Word; +} /* END metaphone */ + + +/* + * SQL function: soundex(text) returns text + */ +PG_FUNCTION_INFO_V1(soundex); + +Datum +soundex(PG_FUNCTION_ARGS) +{ + char outstr[SOUNDEX_LEN + 1]; + char *arg; + + arg = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + _soundex(arg, outstr); + + PG_RETURN_TEXT_P(cstring_to_text(outstr)); +} + +static void +_soundex(const char *instr, char *outstr) +{ + int count; + + Assert(instr); + Assert(outstr); + + /* Skip leading non-alphabetic characters */ + while (*instr && !isalpha((unsigned char) *instr)) + ++instr; + + /* If no string left, return all-zeroes buffer */ + if (!*instr) + { + memset(outstr, '\0', SOUNDEX_LEN + 1); + return; + } + + /* Take the first letter as is */ + *outstr++ = (char) toupper((unsigned char) *instr++); + + count = 1; + while (*instr && count < SOUNDEX_LEN) + { + if (isalpha((unsigned char) *instr) && + soundex_code(*instr) != soundex_code(*(instr - 1))) + { + *outstr = soundex_code(*instr); + if (*outstr != '0') + { + ++outstr; + ++count; + } + } + ++instr; + } + + /* Fill with 0's */ + while (count < SOUNDEX_LEN) + { + *outstr = '0'; + ++outstr; + ++count; + } + + /* And null-terminate */ + *outstr = '\0'; +} + +PG_FUNCTION_INFO_V1(difference); + +Datum +difference(PG_FUNCTION_ARGS) +{ + char sndx1[SOUNDEX_LEN + 1], + sndx2[SOUNDEX_LEN + 1]; + int i, + result; + + _soundex(text_to_cstring(PG_GETARG_TEXT_PP(0)), sndx1); + _soundex(text_to_cstring(PG_GETARG_TEXT_PP(1)), sndx2); + + result = 0; + for (i = 0; i < SOUNDEX_LEN; i++) + { + if (sndx1[i] == sndx2[i]) + result++; + } + + PG_RETURN_INT32(result); +} diff --git a/contrib/fuzzystrmatch/fuzzystrmatch.control b/contrib/fuzzystrmatch/fuzzystrmatch.control new file mode 100644 index 0000000..8b6e9fd --- /dev/null +++ b/contrib/fuzzystrmatch/fuzzystrmatch.control @@ -0,0 +1,6 @@ +# fuzzystrmatch extension +comment = 'determine similarities and distance between strings' +default_version = '1.2' +module_pathname = '$libdir/fuzzystrmatch' +relocatable = true +trusted = true diff --git a/contrib/fuzzystrmatch/meson.build b/contrib/fuzzystrmatch/meson.build new file mode 100644 index 0000000..3ff84eb --- /dev/null +++ b/contrib/fuzzystrmatch/meson.build @@ -0,0 +1,48 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +fuzzystrmatch_sources = files( + 'daitch_mokotoff.c', + 'dmetaphone.c', + 'fuzzystrmatch.c', +) + +daitch_mokotoff_h = custom_target('daitch_mokotoff', + input: 'daitch_mokotoff_header.pl', + output: 'daitch_mokotoff.h', + command: [perl, '@INPUT@', '@OUTPUT@'], +) +generated_sources += daitch_mokotoff_h +fuzzystrmatch_sources += daitch_mokotoff_h + +if host_system == 'windows' + fuzzystrmatch_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'fuzzystrmatch', + '--FILEDESC', 'fuzzystrmatch - similarities and distance between strings',]) +endif + +fuzzystrmatch = shared_module('fuzzystrmatch', + fuzzystrmatch_sources, + include_directories: include_directories('.'), + kwargs: contrib_mod_args, +) +contrib_targets += fuzzystrmatch + +install_data( + 'fuzzystrmatch.control', + 'fuzzystrmatch--1.0--1.1.sql', + 'fuzzystrmatch--1.1.sql', + 'fuzzystrmatch--1.1--1.2.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'fuzzystrmatch', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'fuzzystrmatch', + 'fuzzystrmatch_utf8', + ], + }, +} diff --git a/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql b/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql new file mode 100644 index 0000000..0b4bb9b --- /dev/null +++ b/contrib/fuzzystrmatch/sql/fuzzystrmatch.sql @@ -0,0 +1,67 @@ +CREATE EXTENSION fuzzystrmatch; + + +SELECT soundex('hello world!'); + +SELECT soundex('Anne'), soundex('Ann'), difference('Anne', 'Ann'); +SELECT soundex('Anne'), soundex('Andrew'), difference('Anne', 'Andrew'); +SELECT soundex('Anne'), soundex('Margaret'), difference('Anne', 'Margaret'); +SELECT soundex(''), difference('', ''); + + +SELECT levenshtein('GUMBO', 'GAMBOL'); +SELECT levenshtein('GUMBO', 'GAMBOL', 2, 1, 1); +SELECT levenshtein_less_equal('extensive', 'exhaustive', 2); +SELECT levenshtein_less_equal('extensive', 'exhaustive', 4); + + +SELECT metaphone('GUMBO', 4); + + +SELECT dmetaphone('gumbo'); +SELECT dmetaphone_alt('gumbo'); + +-- Wovels +SELECT daitch_mokotoff('Augsburg'); +SELECT daitch_mokotoff('Breuer'); +SELECT daitch_mokotoff('Freud'); + +-- The letter "H" +SELECT daitch_mokotoff('Halberstadt'); +SELECT daitch_mokotoff('Mannheim'); + +-- Adjacent sounds +SELECT daitch_mokotoff('Chernowitz'); + +-- Adjacent letters with identical adjacent code digits +SELECT daitch_mokotoff('Cherkassy'); +SELECT daitch_mokotoff('Kleinman'); + +-- More than one word +SELECT daitch_mokotoff('Nowy Targ'); + +-- Padded with "0" +SELECT daitch_mokotoff('Berlin'); + +-- Other examples from https://www.avotaynu.com/soundex.htm +SELECT daitch_mokotoff('Ceniow'); +SELECT daitch_mokotoff('Tsenyuv'); +SELECT daitch_mokotoff('Holubica'); +SELECT daitch_mokotoff('Golubitsa'); +SELECT daitch_mokotoff('Przemysl'); +SELECT daitch_mokotoff('Pshemeshil'); +SELECT daitch_mokotoff('Rosochowaciec'); +SELECT daitch_mokotoff('Rosokhovatsets'); + +-- Ignored characters +SELECT daitch_mokotoff('''OBrien'); +SELECT daitch_mokotoff('O''Brien'); + +-- "Difficult" cases, likely to cause trouble for other implementations. +SELECT daitch_mokotoff('CJC'); +SELECT daitch_mokotoff('BESST'); +SELECT daitch_mokotoff('BOUEY'); +SELECT daitch_mokotoff('HANNMANN'); +SELECT daitch_mokotoff('MCCOYJR'); +SELECT daitch_mokotoff('ACCURSO'); +SELECT daitch_mokotoff('BIERSCHBACH'); diff --git a/contrib/fuzzystrmatch/sql/fuzzystrmatch_utf8.sql b/contrib/fuzzystrmatch/sql/fuzzystrmatch_utf8.sql new file mode 100644 index 0000000..f42c01a --- /dev/null +++ b/contrib/fuzzystrmatch/sql/fuzzystrmatch_utf8.sql @@ -0,0 +1,26 @@ +/* + * 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; + +-- CREATE EXTENSION IF NOT EXISTS fuzzystrmatch; + +-- Accents +SELECT daitch_mokotoff('Müller'); +SELECT daitch_mokotoff('Schäfer'); +SELECT daitch_mokotoff('Straßburg'); +SELECT daitch_mokotoff('Éregon'); + +-- Special characters added at https://www.jewishgen.org/InfoFiles/Soundex.html +SELECT daitch_mokotoff('gąszczu'); +SELECT daitch_mokotoff('brzęczy'); +SELECT daitch_mokotoff('ţamas'); +SELECT daitch_mokotoff('țamas'); diff --git a/contrib/hstore/.gitignore b/contrib/hstore/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/hstore/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/hstore/Makefile b/contrib/hstore/Makefile new file mode 100644 index 0000000..48ee98f --- /dev/null +++ b/contrib/hstore/Makefile @@ -0,0 +1,36 @@ +# contrib/hstore/Makefile + +MODULE_big = hstore +OBJS = \ + $(WIN32RES) \ + hstore_compat.o \ + hstore_gin.o \ + hstore_gist.o \ + hstore_io.o \ + hstore_op.o \ + hstore_subs.o + +EXTENSION = hstore +DATA = hstore--1.4.sql \ + hstore--1.7--1.8.sql \ + hstore--1.6--1.7.sql \ + hstore--1.5--1.6.sql \ + hstore--1.4--1.5.sql \ + hstore--1.3--1.4.sql hstore--1.2--1.3.sql \ + hstore--1.1--1.2.sql +PGFILEDESC = "hstore - key/value pair data type" + +HEADERS = hstore.h + +REGRESS = hstore hstore_utf8 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/hstore +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/hstore/data/hstore.data b/contrib/hstore/data/hstore.data new file mode 100644 index 0000000..b7391da --- /dev/null +++ b/contrib/hstore/data/hstore.data @@ -0,0 +1,1001 @@ +line=>1, date=>CB, node=>AA +cleaned=>f, status=>59, line=>2, disabled=>f, node=>CBB +indexed=>t, status=>35, line=>3, disabled=>f, wait=>CAA, subtitle=>BA, user=>CCA +line=>4, disabled=>t, space=>BB +cleaned=>f, line=>5, wait=>BB, query=>CAC, coauthors=>ACA, node=>CBA +world=>CB, query=>CBC, indexed=>f, 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=>t, query=>AA, indexed=>t, line=>9, pos=>56 +title=>AAC, bad=>t, user=>AAB, query=>AC, line=>10, node=>AB +world=>CAC, user=>AB, query=>ACA, indexed=>t, line=>11, space=>CB +line=>12, pos=>72, abstract=>BBA, space=>AAC + +world=>CC, query=>AA, line=>14, disabled=>f, date=>CAC, coauthors=>AB +org=>68, title=>BBB, query=>BAC, line=>15, public=>f +org=>73, user=>AA, indexed=>t, line=>16, date=>CCC, public=>t, coauthors=>AB +indexed=>f, line=>17 +state=>23, auth=>BCC, org=>38, status=>28, line=>18, disabled=>f, abstract=>CB +state=>99, auth=>CA, indexed=>t, line=>19, date=>BA +wait=>CBA, user=>BBA, indexed=>t, line=>20, disabled=>f, abstract=>BA, date=>ABA +org=>10, query=>AC, indexed=>f, line=>21, disabled=>t, abstract=>CA, pos=>44 +state=>65, title=>AC, user=>AAC, cleaned=>t, status=>93, line=>22, abstract=>ABC, node=>CCC +subtitle=>AC, user=>CCC, line=>23 +state=>67, world=>ACB, bad=>t, user=>CB, line=>24, disabled=>t + +state=>65, title=>CBC, wait=>AAC, bad=>t, query=>ACA, line=>26, disabled=>f, space=>CA +auth=>BAA, state=>68, indexed=>t, line=>27, space=>BA +indexed=>f, line=>28, disabled=>t, space=>CC, node=>BB +auth=>BAB, org=>80, title=>BBA, query=>BBC, status=>3, line=>29 +title=>AC, status=>16, cleaned=>t, line=>30 +state=>39, world=>AAB, user=>BB, line=>31, disabled=>t +wait=>BC, bad=>f, query=>AA, line=>32, coauthors=>CAC +line=>33, pos=>97 +title=>AA, world=>CCA, wait=>CC, bad=>t, status=>86, line=>34, disabled=>t, node=>ACA + +world=>BCC, title=>ACB, org=>61, status=>99, cleaned=>t, line=>36, pos=>76, space=>ACC, coauthors=>AA, node=>CB +title=>CAA, cleaned=>f, line=>37, abstract=>ACA, node=>BC +auth=>BC, title=>BA, world=>ACA, indexed=>t, line=>38, abstract=>AAA, public=>t +org=>90, line=>39, public=>f +state=>16, indexed=>t, line=>40, pos=>53 +auth=>AAB, wait=>CAC, status=>44, line=>41 +subtitle=>ACA, bad=>t, line=>42 +org=>19, world=>BC, user=>ABA, indexed=>f, line=>43, disabled=>t, pos=>48, abstract=>CAB, space=>CCB +indexed=>f, line=>44 +indexed=>t, line=>45 +status=>84, line=>46, date=>CCC +state=>94, title=>BAB, bad=>t, user=>BBB, indexed=>t, line=>47, public=>f +org=>90, subtitle=>BAC, query=>CAC, cleaned=>f, line=>48, disabled=>t, abstract=>CC, pos=>17, space=>BCA +world=>CBC, line=>49 +org=>24, line=>50, date=>CA, public=>f +world=>BC, indexed=>t, status=>44, line=>51, pos=>59, date=>BA, public=>t +org=>98, line=>52 +title=>CA, world=>ABC, subtitle=>CBB, line=>53, abstract=>BBA, date=>ACB, node=>CA +user=>BAB, cleaned=>t, line=>54 +subtitle=>CAA, line=>55, disabled=>f, pos=>55, abstract=>AB, public=>f, coauthors=>AA +wait=>CC, user=>CC, cleaned=>t, line=>56, pos=>73, node=>ABC +title=>BCC, wait=>ABC, indexed=>t, line=>57, disabled=>f +org=>42, title=>BB, line=>58, disabled=>t, public=>t, coauthors=>BCC +wait=>CAB, title=>CCB, query=>BAC, status=>66, line=>59, disabled=>t +user=>CAC, line=>60 +user=>BBB, line=>61, disabled=>f, 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=>f, user=>BBB, line=>65, abstract=>ACA, public=>t +line=>66, coauthors=>AC +state=>20, wait=>CCB, bad=>t, line=>67, abstract=>CB +state=>79, wait=>BAC, bad=>f, status=>11, line=>68, abstract=>BC, public=>t, coauthors=>CBA +state=>39, title=>CCA, bad=>f, query=>BBA, line=>69, pos=>42, public=>f +title=>BC, subtitle=>CA, query=>BC, line=>70 + +bad=>t, query=>BBB, line=>72 +state=>35, world=>CC, bad=>f, line=>73, space=>BB, public=>f +title=>ACC, wait=>CAB, subtitle=>CB, status=>19, line=>74, disabled=>f, space=>BAA, coauthors=>CBC, node=>AC +subtitle=>BCB, indexed=>f, status=>83, line=>75, public=>t +state=>32, line=>76, disabled=>f, pos=>66, space=>CC +state=>43, cleaned=>t, line=>77 + +state=>97, wait=>CBA, indexed=>f, cleaned=>f, line=>79, abstract=>CB, date=>ACC, public=>f +user=>AAB, line=>80, pos=>85, date=>AC +world=>AC, wait=>CC, subtitle=>AAB, bad=>f, cleaned=>f, line=>81, pos=>91, node=>CCC + +org=>87, bad=>f, user=>AAC, query=>CCC, line=>83, disabled=>f, abstract=>AC, date=>CCA, public=>f +state=>50, line=>84 +wait=>AA, subtitle=>AA, query=>BB, status=>97, line=>85, disabled=>t, abstract=>CB + +subtitle=>CA, query=>BC, line=>87 + +title=>CC, line=>89, disabled=>f, pos=>49, date=>CCB, space=>CB, node=>BB +auth=>CC, wait=>AA, title=>BC, bad=>t, line=>90 +state=>37, org=>85, indexed=>f, line=>91, space=>CAA, public=>t, coauthors=>BA +wait=>BBB, title=>BBC, org=>95, subtitle=>AC, line=>92, pos=>23, date=>AC, public=>t, space=>BBC +org=>48, user=>AC, line=>93, space=>CCC + +state=>77, wait=>ABA, subtitle=>AC, user=>BA, status=>43, line=>95, public=>f +title=>CA, indexed=>t, status=>26, line=>96 +auth=>BCA, subtitle=>ACC, user=>CA, line=>97, disabled=>f, node=>ACB +query=>BB, line=>98, coauthors=>AB + +auth=>AA, title=>ACB, org=>58, subtitle=>AC, bad=>f, cleaned=>f, line=>100, space=>ACC, public=>t +subtitle=>AAB, bad=>f, line=>101, public=>t +subtitle=>AAA, indexed=>f, cleaned=>f, line=>102, disabled=>t, pos=>35 + +world=>CAC, org=>10, query=>AAA, cleaned=>t, status=>79, indexed=>t, line=>104, pos=>65, public=>f, node=>BAB +bad=>f, 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=>f, line=>107, abstract=>CBC, date=>BCC, public=>f +title=>CCC, indexed=>t, line=>108, abstract=>ACB, public=>f, coauthors=>ABB +auth=>BB, query=>ACC, status=>68, line=>109 +user=>CC, cleaned=>f, indexed=>t, line=>110, date=>BAA, space=>BCB +auth=>CC, org=>4, wait=>BAC, bad=>f, indexed=>f, line=>111, pos=>55, node=>BBC +line=>112, disabled=>t +org=>66, cleaned=>t, indexed=>f, line=>113, pos=>96 +world=>CA, title=>ACA, org=>83, query=>BAC, user=>BBC, indexed=>f, line=>114 +subtitle=>BCC, line=>115, space=>AA, public=>t, node=>CBA +state=>77, status=>23, line=>116 +bad=>f, status=>4, line=>117, node=>CC +state=>99, title=>BCC, query=>AC, status=>98, line=>118, date=>BA +status=>55, line=>119, public=>f, coauthors=>BBA, node=>BCA +query=>CAA, status=>40, indexed=>f, line=>120, disabled=>f, 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=>f, cleaned=>f, line=>124, pos=>41, date=>BBA, coauthors=>ABB +wait=>ACC, line=>125 +org=>30, wait=>CBB, subtitle=>CCA, cleaned=>t, line=>126, date=>AC, node=>ABC + +auth=>BBA, org=>66, subtitle=>CCB, bad=>t, cleaned=>f, line=>128, abstract=>BB, public=>t, coauthors=>BA +subtitle=>AC, bad=>f, user=>BAA, line=>129, date=>BCB, node=>BAC +wait=>CC, subtitle=>CA, line=>130, disabled=>f, pos=>49, node=>BA +indexed=>f, line=>131, pos=>79, date=>AAA, node=>CAC +wait=>AC, world=>CB, title=>AAA, user=>ABC, indexed=>f, status=>15, line=>132, coauthors=>BA +state=>96, bad=>t, line=>133, disabled=>f, space=>BAC, coauthors=>ABA +world=>BAC, line=>134 +title=>CCC, line=>135, coauthors=>CC +cleaned=>t, line=>136 +bad=>t, query=>CCA, user=>CA, cleaned=>f, line=>137, disabled=>t + +world=>CC, subtitle=>BBB, line=>139 +wait=>CA, status=>2, line=>140 +world=>BC, bad=>f, user=>BBC, query=>ACB, line=>141, pos=>33, space=>ACA +state=>92, title=>CA, bad=>t, query=>AB, line=>142, abstract=>BA, date=>ABB, space=>BC, coauthors=>CAA +state=>79, query=>AB, user=>CCA, indexed=>t, cleaned=>t, line=>143, public=>t +org=>37, query=>CA, cleaned=>t, line=>144, disabled=>t, date=>CC +wait=>AC, title=>CBA, user=>AAA, status=>24, line=>145, date=>CBC, public=>f, coauthors=>BAC, node=>ACC +user=>CA, indexed=>t, line=>146, disabled=>f, coauthors=>BA +wait=>BC, org=>35, bad=>f, query=>CBB, line=>147, date=>AAA, public=>f, space=>BBB +org=>56, user=>AB, indexed=>t, line=>148 + +title=>CBB, org=>78, subtitle=>CBA, bad=>t, user=>AAB, line=>150, disabled=>t, abstract=>BAC +world=>CCA, query=>BC, cleaned=>t, indexed=>f, 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=>t, node=>BBC +world=>BBB, bad=>f, line=>156, abstract=>CBC +line=>157, pos=>60, node=>ACC +line=>158, node=>CC +line=>159, public=>t + +query=>BA, status=>53, cleaned=>f, line=>161, public=>t +line=>162, date=>CC + +title=>BC, bad=>f, query=>CC, line=>164, abstract=>CCB, date=>BA +status=>36, line=>165 +title=>AB, bad=>f, status=>64, line=>166, abstract=>AB, coauthors=>AA, node=>AA +wait=>AA, line=>167 +subtitle=>CBC, user=>AC, cleaned=>f, line=>168, disabled=>t, coauthors=>BAB, node=>CC +state=>34, status=>73, cleaned=>t, line=>169, abstract=>BC, public=>f, space=>BBC, node=>BAA +state=>10, auth=>BBB, bad=>t, indexed=>f, status=>34, line=>170, abstract=>BC +subtitle=>AAA, bad=>f, user=>ACA, status=>53, line=>171, disabled=>f, date=>AAA +subtitle=>CB, query=>CC, indexed=>t, line=>172, node=>BBC +state=>5, world=>ABC, bad=>f, line=>173, public=>f +subtitle=>AC, line=>174 +auth=>AC, org=>72, query=>CA, indexed=>f, cleaned=>t, line=>175, disabled=>t, pos=>54 +title=>BCB, bad=>f, line=>176, pos=>35, coauthors=>AAC, node=>ABB +title=>BB, cleaned=>t, 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=>f, indexed=>t, line=>179, space=>CBC +org=>68, line=>180 +wait=>ABB, subtitle=>CCC, cleaned=>t, line=>181, abstract=>BC, coauthors=>BA +title=>ACA, subtitle=>AAB, line=>182, node=>BAC + + +subtitle=>BA, query=>BBB, indexed=>t, cleaned=>t, 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=>f, 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=>t, line=>191, space=>BC, coauthors=>AA +wait=>CAA, bad=>f, user=>BC, status=>23, line=>192, disabled=>t, date=>CA, coauthors=>BBB +status=>26, line=>193, disabled=>t +world=>CA, subtitle=>CCC, query=>ABB, status=>86, line=>194, pos=>97, space=>CAC +cleaned=>t, line=>195 +state=>53, org=>84, wait=>BC, query=>BCC, line=>196, disabled=>t, abstract=>AAC, node=>CAC +state=>25, status=>70, cleaned=>f, line=>197, disabled=>t, space=>AA, public=>f +org=>82, subtitle=>AAC, line=>198 +org=>87, bad=>t, status=>69, line=>199, public=>f +wait=>CC, org=>60, subtitle=>BCA, bad=>t, cleaned=>f, indexed=>t, line=>200, date=>BA +state=>9, world=>CAA, org=>78, user=>ACB, cleaned=>t, line=>201, disabled=>t, abstract=>ACC, public=>f +state=>50, world=>AAA, title=>CAA, user=>AB, status=>37, line=>202, disabled=>f +org=>36, subtitle=>CB, query=>BAA, status=>35, line=>203, abstract=>CC +auth=>CCC, bad=>t, query=>CB, status=>84, line=>204, disabled=>f, date=>BB +auth=>AC, query=>BA, indexed=>f, line=>205, date=>AAB, space=>ABB +state=>30, world=>CCA, query=>CC, user=>BAA, line=>206 +title=>CAB, wait=>BAB, bad=>t, query=>BCB, indexed=>t, status=>48, cleaned=>t, line=>207, node=>ACB +state=>97, subtitle=>BC, status=>99, line=>208, abstract=>CB +title=>CA, world=>BBA, bad=>t, indexed=>f, cleaned=>f, status=>82, line=>209, disabled=>f, pos=>44, space=>ACA +line=>210, public=>t +line=>211, space=>BBC, node=>AAA +wait=>BAA, org=>50, line=>212, abstract=>BB, public=>t, 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=>f +state=>14, wait=>ACB, org=>79, title=>BB, subtitle=>BA, line=>216 +wait=>BC, line=>217, date=>BB +wait=>AC, user=>BB, indexed=>f, status=>83, line=>218 +auth=>BC, org=>9, user=>BA, status=>31, line=>219, disabled=>f +state=>80, world=>BA, wait=>CA, line=>220, pos=>65, node=>CAC +wait=>AC, subtitle=>ABB, status=>79, indexed=>t, line=>221, abstract=>AC, pos=>33, space=>BA +state=>69, org=>83, world=>CBC, subtitle=>CAC, cleaned=>f, line=>222, space=>BC, node=>CCA +line=>223, abstract=>BC + +world=>BB, title=>BC, bad=>f, query=>BBC, cleaned=>f, line=>225, disabled=>f, public=>t +line=>226, date=>AC +auth=>CB, subtitle=>AB, indexed=>f, 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=>t, status=>5, line=>230, pos=>2, date=>CCA +line=>231, coauthors=>CB +state=>1, user=>CAA, cleaned=>f, line=>232, date=>BA, public=>t, coauthors=>AAA, node=>BCC +auth=>AB, world=>CAC, query=>BC, cleaned=>t, line=>233, pos=>47, space=>AB, node=>AB +title=>CAA, line=>234, pos=>9, public=>t, node=>AB +auth=>CCA, title=>AA, org=>6, subtitle=>CA, cleaned=>t, status=>12, indexed=>f, line=>235, space=>ABB +auth=>CA, bad=>f, query=>BC, status=>61, cleaned=>f, line=>236, disabled=>t, public=>t +user=>BCB, line=>237, pos=>70, node=>CBA +query=>CCB, line=>238, disabled=>t, coauthors=>BAB, node=>BC +auth=>AC, org=>73, title=>CA, bad=>f, status=>94, line=>239, abstract=>CC +subtitle=>BC, indexed=>f, line=>240, disabled=>t +auth=>AAC, org=>73, title=>CB, bad=>t, query=>CA, cleaned=>t, line=>241, disabled=>f, public=>f +line=>242, public=>f +auth=>AC, title=>BC, status=>61, line=>243, disabled=>f +auth=>ABB, bad=>f, indexed=>f, line=>244, abstract=>BAB, date=>ABC, coauthors=>BC +query=>BA, line=>245, disabled=>f, space=>BAB +world=>BCC, bad=>f, indexed=>f, line=>246, disabled=>t, pos=>80, public=>f, coauthors=>BC +indexed=>t, line=>247 +wait=>CCA, subtitle=>CBB, bad=>f, line=>248, pos=>83, public=>f, space=>BA + +auth=>ABA, org=>13, title=>BA, bad=>f, indexed=>t, line=>250, disabled=>f, abstract=>BBA, date=>AB +state=>37, title=>AAA, bad=>f, line=>251, disabled=>f, coauthors=>CBC +auth=>ACB, world=>AC, title=>CAA, subtitle=>BCA, bad=>f, status=>32, line=>252, pos=>84 +query=>BA, indexed=>f, 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=>t, coauthors=>BCC +auth=>AAA, state=>31, line=>256 + + +wait=>AC, query=>AAA, cleaned=>t, indexed=>f, line=>259, pos=>89, coauthors=>BCA, node=>BC +world=>CC, query=>BB, line=>260 + +org=>99, bad=>f, user=>ABA, line=>262, abstract=>BA, coauthors=>BCC +auth=>CAC, world=>CBC, subtitle=>CA, bad=>f, status=>22, line=>263, pos=>4, public=>t, node=>BB +wait=>BB, subtitle=>BCC, indexed=>t, 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=>f, query=>CCB, line=>268, public=>t, 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=>t, coauthors=>CB, node=>AB +cleaned=>t, line=>271 +world=>ACC, cleaned=>t, status=>11, line=>272, disabled=>f, abstract=>AA, space=>BCA, node=>BA +cleaned=>t, line=>273, pos=>50, public=>t +status=>95, line=>274, abstract=>BB, coauthors=>AC +auth=>BCC, state=>80, cleaned=>t, line=>275, abstract=>AC +wait=>BA, line=>276 +org=>62, subtitle=>CAA, query=>BA, user=>BCC, indexed=>f, line=>277, disabled=>f, abstract=>ACA, date=>AB +org=>63, bad=>t, line=>278, pos=>26, coauthors=>BA +auth=>CBB, indexed=>f, line=>279, pos=>40, space=>CA, coauthors=>CC +auth=>BA, line=>280, abstract=>AAA, public=>t, coauthors=>CAC +org=>10, status=>16, line=>281, date=>CCC, space=>AC +org=>76, user=>BBC, indexed=>f, line=>282, pos=>56, node=>CBA +auth=>CA, subtitle=>AB, query=>AA, indexed=>t, line=>283, disabled=>f, coauthors=>ABC, node=>CAA +title=>BA, status=>91, line=>284, pos=>7, coauthors=>BB +wait=>CCA, line=>285, public=>t +world=>AC, line=>286, disabled=>t +line=>287, abstract=>AAA +user=>CCB, status=>50, line=>288, public=>f +state=>41, world=>CCC, query=>AA, line=>289, disabled=>t, pos=>49, public=>f +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=>t, line=>292, disabled=>t, pos=>67, date=>AA +org=>56, query=>BCA, line=>293, pos=>81, coauthors=>AAA, node=>CAB +world=>CB, subtitle=>CBC, bad=>t, query=>ACB, indexed=>f, 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=>f, line=>299, space=>ABA, public=>f, coauthors=>BC +line=>300, space=>ABA +state=>36, org=>16, world=>BBC, status=>13, line=>301, public=>f +subtitle=>CB, user=>BC, line=>302, date=>AA, coauthors=>CAC +wait=>CBC, indexed=>t, cleaned=>t, line=>303, date=>ACC, public=>t +user=>CAC, status=>81, line=>304, node=>CAB +title=>CBB, org=>89, subtitle=>CAA, user=>CCA, indexed=>t, line=>305 +state=>10, title=>CBA, org=>66, cleaned=>t, 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=>f, status=>11, line=>309, disabled=>t, date=>AA +title=>BC, subtitle=>CB, indexed=>f, line=>310, disabled=>t, abstract=>BA, space=>ACA +wait=>ABB, cleaned=>t, indexed=>f, 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=>t, line=>319 +wait=>BB, org=>42, world=>AC, subtitle=>ACC, indexed=>t, line=>320, disabled=>t + +auth=>CAC, line=>322 + +line=>324, pos=>38, space=>CC, node=>BBC +title=>CC, line=>325, public=>t, coauthors=>BAC, node=>ACC +world=>CC, subtitle=>BBC, bad=>f, user=>BA, line=>326, date=>AAA, space=>AA +state=>81, title=>BC, wait=>BA, indexed=>f, 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=>t, date=>AAA, node=>BC +auth=>ABA, title=>CC, user=>CBA, line=>332, disabled=>t, space=>ACC +org=>98, subtitle=>ACB, line=>333, abstract=>BC, public=>f, coauthors=>BC, node=>ABA + +world=>BC, subtitle=>BAC, user=>AB, query=>BAA, cleaned=>t, line=>335, space=>AC, node=>BAA +state=>76, indexed=>t, cleaned=>f, 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=>t, line=>340, public=>t, space=>CB, coauthors=>CAB +wait=>AA, status=>82, line=>341 +world=>CC, line=>342 +auth=>BAB, title=>CAC, query=>BCC, indexed=>t, 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=>t, line=>346, node=>CC +state=>3, title=>CBC, user=>BAA, status=>98, line=>347, disabled=>t, pos=>96, date=>BBA +auth=>BAA, world=>ABB, line=>348, disabled=>f, abstract=>ACA, pos=>66, space=>CCC, coauthors=>CBB, node=>BC + +status=>54, line=>350 +wait=>CC, query=>ABA, user=>AB, status=>76, cleaned=>f, line=>351, abstract=>CBA +line=>352, disabled=>t, public=>f +state=>93, org=>92, status=>88, line=>353, space=>AB, coauthors=>CB +org=>34, wait=>ABC, world=>CBA, bad=>f, query=>BB, indexed=>f, line=>354, date=>CB, public=>t +wait=>CBA, title=>CAC, cleaned=>t, indexed=>t, line=>355, pos=>9, date=>CAA +user=>BC, indexed=>f, cleaned=>t, status=>73, line=>356, disabled=>t, space=>CB +state=>20, cleaned=>f, line=>357, pos=>28, abstract=>CCB, space=>BC +state=>17, wait=>ABC, query=>CB, cleaned=>f, status=>4, line=>358, disabled=>f + +state=>83, world=>CC, org=>53, cleaned=>f, status=>64, line=>360, abstract=>CBC, coauthors=>BC +title=>BB, indexed=>f, line=>361 +state=>49, wait=>BCA, line=>362 +world=>CCC, title=>CA, query=>CCC, cleaned=>t, line=>363, space=>AA, coauthors=>AAC +state=>8, wait=>BBB, line=>364, pos=>70, public=>f, space=>BAA, coauthors=>AB +state=>20, indexed=>f, status=>87, cleaned=>t, line=>365, public=>t + +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=>t +indexed=>t, line=>371, pos=>98, node=>CBA +world=>BA, status=>40, line=>372, coauthors=>AA + +query=>BA, indexed=>f, cleaned=>t, line=>374, date=>BCC +query=>CA, indexed=>t, line=>375, public=>f +auth=>CCA, wait=>BBC, bad=>f, status=>91, line=>376, abstract=>BBC, date=>ABA +user=>BA, query=>CB, status=>86, indexed=>f, line=>377, pos=>83, abstract=>BCC, space=>CBC, public=>t +title=>ACA, org=>15, wait=>CBC, status=>85, line=>378 +state=>57, bad=>t, line=>379, abstract=>BC, date=>CAC +world=>CC, cleaned=>t, line=>380 +title=>CB, subtitle=>AC, line=>381, public=>f + + +status=>12, line=>384, coauthors=>CC +auth=>BAC, bad=>f, line=>385, abstract=>CBB, public=>f, space=>BBC + + +world=>BBC, bad=>t, status=>71, cleaned=>f, line=>388, node=>BB +cleaned=>f, line=>389 +state=>73, line=>390 +wait=>BB, org=>5, subtitle=>BAA, bad=>f, indexed=>f, line=>391, public=>f, node=>BAA +auth=>CCC, org=>51, bad=>f, cleaned=>t, line=>392, space=>AC, node=>CC + +line=>394, abstract=>ACC, public=>t +org=>44, subtitle=>BAC, query=>BAC, line=>395 +wait=>BC, line=>396 +state=>68, world=>AB, title=>ABB, user=>CBC, cleaned=>f, indexed=>t, line=>397, abstract=>BA, pos=>11 +world=>CA, title=>AB, subtitle=>BC, user=>BCB, line=>398 +bad=>t, query=>BCC, line=>399 +wait=>BB, user=>BB, cleaned=>t, indexed=>f, line=>400, date=>BC, public=>f + +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=>f, line=>406, disabled=>t, date=>AC + +state=>11, bad=>t, indexed=>t, line=>408, pos=>79, abstract=>BA, date=>CB, space=>BBA +auth=>AC, status=>59, line=>409 +org=>15, line=>410, disabled=>t, date=>BAC, space=>CCA + + +state=>65, world=>AB, status=>69, line=>413, space=>BA + +title=>CCB, line=>415 +title=>BAB, subtitle=>CA, indexed=>f, line=>416, public=>t +wait=>CAB, user=>CAB, cleaned=>t, line=>417, date=>BC, coauthors=>BBA +subtitle=>ABA, user=>BB, query=>AA, indexed=>t, line=>418, pos=>8, space=>BB, coauthors=>CBA +state=>11, indexed=>t, line=>419, node=>AA +state=>86, cleaned=>f, line=>420, pos=>2, node=>CBC +org=>73, line=>421, disabled=>f +query=>BAC, user=>CB, status=>69, line=>422 +status=>22, line=>423 +auth=>CB, wait=>CCA, world=>AAB, line=>424, disabled=>f, space=>BA, public=>f +state=>81, world=>AC, subtitle=>CBA, bad=>t, cleaned=>f, indexed=>t, line=>425, date=>AAB, coauthors=>BC, node=>BAC +wait=>CB, query=>BCC, status=>97, line=>426 +org=>47, query=>CB, cleaned=>t, line=>427, date=>CC +org=>33, query=>AC, status=>48, indexed=>f, line=>428, disabled=>t, abstract=>BC, space=>ACC +org=>10, query=>AB, line=>429, pos=>77, date=>BC +line=>430, pos=>7, abstract=>CCA, space=>AA +bad=>f, user=>CA, query=>CAB, line=>431, node=>AC +auth=>CA, bad=>f, line=>432 + +auth=>BAA, org=>98, title=>CCC, world=>BAC, line=>434, public=>t +state=>54, wait=>AA, user=>BBA, indexed=>f, line=>435, disabled=>t, pos=>12, space=>AB +world=>AC, title=>CA, query=>AAA, line=>436, space=>AB, coauthors=>AA +auth=>CB, wait=>CCC, bad=>f, 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=>f +title=>AB, line=>440, disabled=>f +org=>3, bad=>t, user=>BCB, query=>AB, indexed=>f, cleaned=>t, line=>441, disabled=>f, space=>BA, node=>BB +state=>62, user=>BCC, status=>12, line=>442, pos=>58, date=>CC, node=>CB +world=>BCB, bad=>t, line=>443, space=>AAB +state=>56, bad=>f, cleaned=>f, line=>444, disabled=>f, date=>CA, space=>BBB, public=>t + +org=>31, world=>ABC, cleaned=>t, line=>446, disabled=>t, public=>t, coauthors=>CB +state=>54, indexed=>t, line=>447 +state=>98, title=>AC, wait=>AAC, world=>BC, bad=>f, line=>448, disabled=>t, public=>t, node=>ABB +world=>AAC, indexed=>t, line=>449, disabled=>t, pos=>61 +org=>56, title=>CA, line=>450 +auth=>BBB, line=>451, pos=>58, date=>BB, space=>ABA +auth=>AB, world=>CA, cleaned=>t, line=>452 +bad=>t, line=>453, disabled=>t, abstract=>AC, pos=>20, date=>ABB, node=>CAB + +state=>91, wait=>AC, org=>96, world=>AA, subtitle=>BBC, query=>AA, cleaned=>t, line=>455, public=>f +status=>99, line=>456, disabled=>t +org=>86, line=>457, public=>t, coauthors=>AC +status=>14, cleaned=>t, line=>458, disabled=>t +world=>AB, user=>CB, query=>AAB, line=>459, pos=>66, public=>f, 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=>f, query=>AA, status=>49, line=>463, disabled=>f +bad=>t, cleaned=>f, 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=>f, abstract=>AC +org=>76, title=>CA, query=>AB, line=>468, public=>f +state=>95, world=>AC, bad=>f, status=>65, cleaned=>f, line=>469, disabled=>f +wait=>AB, subtitle=>AA, bad=>f, user=>CC, query=>BBC, status=>6, line=>470, date=>CCC +state=>82, bad=>t, indexed=>t, line=>471, date=>BB, coauthors=>AAA + +state=>12, auth=>ACB, world=>CBC, bad=>f, indexed=>t, line=>473, date=>CA, space=>ABB, coauthors=>CC +subtitle=>AA, bad=>f, user=>ACC, line=>474, pos=>86, abstract=>CAC, space=>BBA +cleaned=>t, line=>475 +title=>CC, wait=>BB, status=>6, line=>476, abstract=>ACC, date=>CB, space=>BA, public=>t +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=>f, line=>479, date=>ACB, public=>t, node=>CB +state=>0, query=>ABC, status=>65, line=>480, disabled=>t, space=>CBA, node=>BA +auth=>BAC, org=>24, subtitle=>BBC, bad=>f, user=>CAC, line=>481, date=>BBB, public=>t, coauthors=>CBA +org=>18, bad=>t, cleaned=>f, status=>3, indexed=>t, line=>482, date=>BB, coauthors=>ACC +wait=>CB, user=>AC, line=>483, disabled=>f +world=>AC, subtitle=>AA, query=>AAB, line=>484, disabled=>t, space=>CAA +line=>485, pos=>2, space=>CA +org=>42, indexed=>f, line=>486, date=>CB +org=>3, wait=>CAA, subtitle=>CA, cleaned=>t, line=>487, disabled=>t +org=>68, subtitle=>CCB, query=>CAA, cleaned=>f, status=>46, line=>488, pos=>87, public=>f, node=>BC + +status=>60, cleaned=>f, 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=>f, status=>24, line=>492, disabled=>t, node=>CBC +auth=>CC, wait=>BBB, line=>493, disabled=>f, public=>f, coauthors=>AB + +wait=>BB, title=>BBC, subtitle=>BA, status=>3, cleaned=>f, line=>495, disabled=>f, coauthors=>AB, node=>BAC + +query=>CC, indexed=>f, line=>497, coauthors=>CAC, node=>BC +auth=>BBA, state=>68, line=>498 +state=>21, title=>CCB, wait=>AAA, subtitle=>CCC, user=>BAA, indexed=>t, line=>499, coauthors=>BB +auth=>AAA, subtitle=>CC, bad=>t, user=>CC, indexed=>t, line=>500, disabled=>t, date=>AB, node=>AC +auth=>BB, title=>CCA, user=>BA, cleaned=>t, line=>501, pos=>37, space=>BA, public=>f +auth=>BCA, line=>502, date=>BA +world=>ABA, bad=>t, indexed=>f, line=>503, disabled=>t, abstract=>AC, pos=>1, public=>f +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=>f, indexed=>f, line=>506, date=>CAB, public=>f, node=>ABC +state=>87, wait=>CCC, query=>CAC, user=>CBB, line=>507, abstract=>BBC, date=>AA, coauthors=>CA +auth=>AC, subtitle=>BC, bad=>f, query=>ABA, user=>CBB, indexed=>t, cleaned=>f, 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=>t, line=>511 +wait=>ACB, indexed=>f, line=>512 +auth=>CBA, world=>BA, bad=>t, user=>CBA, query=>CC, line=>513, public=>f, coauthors=>CC + +state=>97, wait=>BB, line=>515, date=>CBC, space=>CA +auth=>CBC, line=>516, disabled=>t +state=>91, user=>CCA, line=>517, coauthors=>BA, node=>CBA +bad=>f, cleaned=>t, line=>518, space=>AAB + +title=>CA, cleaned=>f, status=>38, line=>520 +auth=>BCA, world=>AC, org=>71, user=>CA, line=>521, abstract=>AAB +bad=>t, line=>522, pos=>28, abstract=>BAA +line=>523, coauthors=>CBC, node=>AAB +status=>51, cleaned=>f, line=>524 +query=>AAB, line=>525, disabled=>t, date=>AA, public=>t, coauthors=>CA +org=>15, user=>AC, cleaned=>f, line=>526, coauthors=>CAC, node=>BAB +world=>ABA, line=>527, disabled=>t, public=>t +auth=>BBC, state=>48, bad=>f, line=>528, abstract=>BB, date=>BAC, space=>BA, public=>t +auth=>BA, wait=>CAC, subtitle=>ABC, query=>CB, indexed=>f, cleaned=>f, line=>529, disabled=>t, date=>CA +wait=>AC, world=>ABA, org=>55, bad=>t, indexed=>t, line=>530, pos=>32, space=>BCA, public=>t +title=>CBC, wait=>BAA, line=>531 +world=>AA, line=>532, pos=>35, space=>AAB, public=>t +line=>533, space=>AB, coauthors=>BA +auth=>CBC, world=>BB, line=>534, space=>ACA, coauthors=>CBB +wait=>ACA, status=>47, line=>535, public=>t, 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=>f, status=>18, line=>539, pos=>62, space=>BC, coauthors=>BAC +org=>30, world=>AA, query=>BC, user=>BAC, status=>12, cleaned=>t, line=>540, space=>AB +org=>30, user=>CCB, query=>BB, cleaned=>f, line=>541, disabled=>t, public=>t, node=>CBA + +subtitle=>ABB, bad=>t, line=>543 +subtitle=>BBB, bad=>t, 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=>t, date=>CB, space=>AA +org=>39, cleaned=>t, line=>550, public=>f +state=>41, auth=>CC, world=>CB, line=>551, space=>AAB + +state=>26, bad=>f, query=>BAA, status=>84, indexed=>t, line=>553, disabled=>f, coauthors=>CC, node=>CBB +world=>ABA, user=>CCC, query=>ABB, line=>554, space=>ABC, node=>AAA +state=>18, wait=>CCB, bad=>t, user=>BA, line=>555, space=>CC, coauthors=>BB, node=>BBB +auth=>AA, state=>71, subtitle=>AA, query=>ACC, indexed=>t, line=>556, space=>BAB, public=>f +indexed=>t, cleaned=>t, line=>557, disabled=>f, abstract=>AB +auth=>BCC, title=>ACB, world=>BCA, user=>BAB, cleaned=>f, line=>558, space=>BB, coauthors=>CBC + +auth=>ACC, org=>18, wait=>AB, status=>1, indexed=>t, line=>560 +status=>8, line=>561, abstract=>BA, public=>f +state=>27, title=>ABA, bad=>t, query=>AAB, indexed=>f, line=>562, pos=>86, public=>t, coauthors=>BA + +title=>BAC, wait=>CCC, user=>BA, line=>564, disabled=>f, date=>BB, public=>t, 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=>t, line=>571, abstract=>CC, coauthors=>BA, node=>ABB +state=>41, line=>572 +subtitle=>CBC, cleaned=>t, line=>573, node=>BCB +title=>ABA, line=>574, pos=>27, space=>CC +status=>29, indexed=>f, cleaned=>f, line=>575, pos=>52, public=>f, coauthors=>ACC +title=>BBB, org=>86, wait=>AAA, user=>CC, query=>CA, line=>576, disabled=>f, date=>AB, node=>BC +line=>577, abstract=>CAA, date=>BB +auth=>CCC, subtitle=>BBB, query=>ABA, line=>578, pos=>99, space=>CCB, public=>t, coauthors=>ACA, node=>ACB +wait=>BCC, line=>579 +state=>99, world=>BAC, user=>CA, line=>580 +state=>55, world=>AAA, title=>AAA, cleaned=>f, line=>581, date=>AC, public=>t, node=>AA +query=>ACC, cleaned=>t, line=>582, disabled=>f +auth=>AAB, query=>BAC, line=>583 +auth=>AA, user=>BAC, line=>584 + +org=>96, wait=>BC, bad=>f, cleaned=>f, status=>96, line=>586, pos=>95 +auth=>BC, subtitle=>BCB, bad=>t, 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=>f, user=>AAB, cleaned=>f, indexed=>f, line=>590 +title=>CB, wait=>BC, subtitle=>BAC, cleaned=>t, line=>591, disabled=>f, abstract=>CBB, public=>f, node=>ACC +user=>BC, line=>592, public=>f + +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=>f, line=>601, space=>AC, coauthors=>ABB +title=>CAB, bad=>f, line=>602, coauthors=>ABB + +world=>CCC, org=>79, line=>604 +org=>56, query=>AB, cleaned=>t, indexed=>t, status=>20, line=>605, public=>t, coauthors=>ACA +auth=>BBC, org=>13, subtitle=>CC, bad=>t, user=>ABC, line=>606, date=>CA, public=>f +query=>BA, line=>607 +bad=>t, line=>608, pos=>12, coauthors=>CB +bad=>f, status=>42, line=>609 + +bad=>t, line=>611 +auth=>CCA, subtitle=>BC, bad=>t, query=>CAA, cleaned=>f, line=>612, public=>f, node=>CBA +org=>65, query=>BC, line=>613 + +wait=>BAC, title=>AAB, user=>CAC, line=>615, pos=>69, space=>CC, node=>AAC +bad=>f, line=>616, abstract=>AB, pos=>65, coauthors=>BBB + +org=>38, world=>BA, line=>618, coauthors=>AA, node=>BC +cleaned=>f, line=>619, disabled=>f +auth=>BC, line=>620, pos=>79, date=>AB, coauthors=>BAA, node=>CB +auth=>CAA, title=>CB, user=>BAC, cleaned=>f, line=>621, public=>f, space=>CBA + +bad=>f, status=>12, line=>623 +auth=>BBB, wait=>BAC, org=>36, title=>AB, indexed=>f, cleaned=>f, line=>624, date=>AB, coauthors=>CB +wait=>AA, subtitle=>AB, query=>CCB, line=>625, node=>CBB +wait=>BC, subtitle=>BA, bad=>t, user=>AA, line=>626, pos=>3, date=>BB +org=>28, user=>BC, query=>AC, status=>63, line=>627, pos=>45, public=>t, node=>BC +query=>BC, status=>47, line=>628, disabled=>f, date=>CA, public=>f + +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=>f, pos=>69 +wait=>CB, line=>635, pos=>34 +state=>9, wait=>CC, status=>23, line=>636, disabled=>t, date=>BB, space=>AC +user=>CCB, indexed=>f, cleaned=>t, line=>637, pos=>65, date=>AA, public=>t +auth=>BC, wait=>AB, title=>BB, bad=>t, line=>638, abstract=>ACC, date=>BC, public=>f +state=>44, auth=>BC, world=>CBC, line=>639, disabled=>f, date=>CAA +world=>CB, title=>ACB, user=>BA, query=>AA, line=>640, disabled=>t, space=>AC +state=>37, line=>641, disabled=>t, pos=>66 +world=>AAA, bad=>t, user=>AAA, query=>BA, line=>642, disabled=>t, coauthors=>CBC +world=>BA, title=>ABB, org=>96, bad=>f, query=>AAA, status=>75, cleaned=>f, line=>643, space=>BA +state=>36, org=>66, subtitle=>AA, query=>CA, cleaned=>t, status=>79, line=>644, date=>CB +wait=>BC, line=>645, date=>CBA, space=>BCB, public=>t, node=>ABA +auth=>BB, org=>37, query=>CAA, indexed=>t, line=>646, abstract=>CBA, coauthors=>CBA + + +state=>58, world=>BAB, org=>11, user=>CC, line=>649 +title=>CB, status=>19, line=>650, disabled=>f, public=>f, coauthors=>AA +user=>BBC, indexed=>t, line=>651, disabled=>t, pos=>8 +query=>CC, cleaned=>t, indexed=>f, line=>652, pos=>67, date=>AA +auth=>AAC, line=>653, disabled=>t, public=>f, coauthors=>AAA, node=>CBB +bad=>t, query=>AC, line=>654, disabled=>f +world=>CCA, org=>15, bad=>f, user=>CCC, line=>655, public=>t, space=>CC +line=>656, coauthors=>BBB +title=>BA, line=>657, date=>ACB +user=>BC, query=>CC, cleaned=>t, line=>658, pos=>51, abstract=>BA +subtitle=>CCA, user=>CCA, cleaned=>f, line=>659, abstract=>BA, pos=>95, date=>CA +auth=>CA, state=>23, org=>19, bad=>f, user=>BCB, indexed=>f, line=>660, date=>ABA +state=>64, org=>97, bad=>f, indexed=>f, line=>661, space=>BAB, coauthors=>BB, node=>BA +status=>11, line=>662 +title=>BCC, org=>44, subtitle=>ACB, cleaned=>f, line=>663, pos=>58 +auth=>ABB, bad=>t, line=>664, pos=>82, coauthors=>CC, node=>AB +bad=>f, cleaned=>t, status=>25, line=>665, disabled=>f, abstract=>BB, public=>t +wait=>AC, user=>CB, line=>666, pos=>71, abstract=>ACA, coauthors=>CBB +title=>AA, bad=>t, user=>BB, line=>667, date=>CA, space=>BC, node=>CC + +auth=>AAB, line=>669 +wait=>AAC, query=>ABA, status=>35, line=>670, disabled=>f, pos=>56 +org=>3, line=>671 +state=>46, bad=>f, cleaned=>t, line=>672 +state=>30, org=>9, status=>72, line=>673, abstract=>ACA, coauthors=>CB +auth=>BB, wait=>CA, title=>BBB, bad=>t, user=>AAA, status=>86, indexed=>f, line=>674, node=>BCC +indexed=>t, line=>675, pos=>63 +bad=>t, query=>CBB, status=>5, line=>676, abstract=>CCC, public=>f, space=>BB +title=>BBB, org=>60, bad=>t, cleaned=>f, line=>677, pos=>82, date=>BAA, space=>BB, coauthors=>CAA + + +state=>73, bad=>f, cleaned=>f, line=>680, abstract=>CA, date=>CCA, space=>CB +state=>92, query=>CC, line=>681, abstract=>AB, date=>BBB, public=>t, coauthors=>CBA +subtitle=>CCA, line=>682 +world=>BAC, subtitle=>AC, line=>683, disabled=>t, 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=>t, line=>686, disabled=>f +world=>CCA, wait=>AB, user=>CC, query=>BB, cleaned=>t, line=>687 +auth=>CAC, state=>94, wait=>ACC, title=>BBC, user=>BB, line=>688, disabled=>f, pos=>16, coauthors=>AAC + +org=>43, line=>690 + +state=>4, title=>CA, subtitle=>AA, query=>BC, line=>692, pos=>57, date=>BCA, public=>f, coauthors=>ABB +wait=>BBA, line=>693 +auth=>BCA, bad=>f, user=>BBA, line=>694, disabled=>f, date=>CC, public=>t, coauthors=>CB +state=>66, wait=>BB, user=>CC, indexed=>t, line=>695, pos=>99, space=>BCA +org=>1, line=>696, disabled=>f, space=>BCC, coauthors=>BC +auth=>BC, cleaned=>t, indexed=>f, line=>697, space=>CBB +wait=>AC, indexed=>f, line=>698, pos=>44 +wait=>AA, title=>BBB, org=>31, indexed=>t, line=>699, disabled=>f +auth=>BB, world=>ACC, bad=>t, indexed=>f, line=>700, abstract=>CB, pos=>5, space=>ACB, node=>CC +cleaned=>f, 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=>t, line=>708, pos=>94, date=>CBC +state=>86, org=>5, world=>BB, indexed=>f, line=>709, date=>BBB, space=>CA, public=>t +world=>ACA, query=>ABC, status=>40, line=>710, disabled=>t, public=>t, node=>CA +bad=>t, line=>711 +query=>AB, line=>712, coauthors=>BBC, node=>AA +user=>ABB, line=>713, public=>f, space=>AAA, node=>BBA +auth=>AC, wait=>BAC, bad=>t, line=>714, public=>f +line=>715, abstract=>CA, public=>f +user=>AC, indexed=>t, line=>716, coauthors=>CB +state=>4, title=>ABB, org=>26, indexed=>t, line=>717, public=>t, 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=>t, line=>719, public=>f, node=>CA + +line=>721, disabled=>t, abstract=>BAC +world=>BC, line=>722 +state=>27, auth=>AA, title=>BC, world=>CC, query=>BCC, line=>723, disabled=>t, pos=>9, public=>f, node=>BCC +org=>78, wait=>ABA, cleaned=>t, indexed=>t, line=>724, date=>ACB, space=>AA +state=>60, line=>725 + + +wait=>ABA, title=>CAC, user=>CCC, line=>728 +wait=>CC, indexed=>t, status=>39, line=>729, disabled=>t, public=>f +auth=>CB, subtitle=>BBA, line=>730, coauthors=>CAC +world=>CBB, line=>731, space=>BCB +cleaned=>t, line=>732 +org=>67, bad=>t, line=>733, pos=>9, node=>ACC +world=>BC, wait=>CAC, org=>58, subtitle=>ACC, bad=>t, query=>CAA, line=>734, abstract=>BCA, pos=>1, public=>t +state=>45, query=>AB, indexed=>f, line=>735, pos=>82, date=>BC, public=>f, coauthors=>BA +state=>68, title=>BC, cleaned=>t, status=>34, line=>736, disabled=>t, node=>BBB +auth=>AC, line=>737 +line=>738, date=>BA, space=>CCC, public=>f +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=>t, user=>BC, line=>742, pos=>52, coauthors=>BCA + +query=>BC, line=>744, abstract=>AB, public=>f, node=>BAC +world=>CAC, line=>745 +auth=>CBB, title=>AA, user=>AB, line=>746, pos=>35, public=>f, space=>AAB +state=>69, world=>AB, org=>78, subtitle=>BA, bad=>f, line=>747, node=>AAA +bad=>t, line=>748, public=>t +wait=>BC, org=>47, query=>BBB, line=>749 +title=>BBB, line=>750 +org=>33, query=>CB, line=>751, disabled=>t +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=>f, line=>756, pos=>72, date=>BA, coauthors=>BCA +state=>46, status=>88, line=>757, disabled=>f, public=>t +world=>AB, line=>758, disabled=>t, abstract=>BB, coauthors=>AAA +query=>AC, line=>759, abstract=>AAB +auth=>BC, indexed=>f, line=>760, abstract=>BA, node=>CAA +state=>10, auth=>BAC, title=>BC, query=>BCA, cleaned=>t, line=>761, disabled=>t, space=>ACC, coauthors=>ABA +line=>762, disabled=>t, pos=>43 +world=>CBA, user=>BBC, indexed=>t, line=>763 +wait=>ACB, query=>BA, status=>22, line=>764, pos=>70, abstract=>BAC, public=>f, space=>BC + +line=>766, disabled=>f, abstract=>CBC, date=>CA +title=>CC, bad=>t, user=>BCC, indexed=>f, 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=>f, 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=>t, line=>775, abstract=>AAA +subtitle=>BAA, indexed=>f, line=>776 +line=>777, pos=>33, date=>CCB, public=>t +world=>BCA, bad=>t, line=>778 +auth=>CA, line=>779, date=>AC, space=>CAC +title=>BB, bad=>f, cleaned=>t, line=>780, disabled=>f, date=>BAB, space=>ACB +auth=>CAC, title=>AAB, subtitle=>CA, bad=>f, line=>781, disabled=>f, space=>CB +state=>78, auth=>AC, bad=>t, status=>46, line=>782, abstract=>CCA, pos=>97, public=>t +user=>BBA, line=>783 + +state=>63, title=>CA, cleaned=>t, 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=>f, line=>791, disabled=>f, pos=>57, node=>CB +auth=>CAA, org=>84, wait=>AB, indexed=>t, status=>51, line=>792, abstract=>CC +org=>72, bad=>t, line=>793, space=>ACA + +auth=>BC, state=>76, wait=>CC, user=>ABB, cleaned=>f, line=>795, pos=>99, abstract=>CA +wait=>CCA, world=>CBC, line=>796, date=>CB, public=>f +state=>49, line=>797, coauthors=>CC +wait=>BBB, title=>ABB, org=>74, line=>798, disabled=>f, pos=>34, space=>BB +line=>799, abstract=>CB +state=>84, user=>ABB, cleaned=>f, status=>18, line=>800, disabled=>t, 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=>f, abstract=>ABA, public=>f +auth=>CBC, state=>99, cleaned=>t, line=>803, disabled=>t, space=>BC, node=>BBC +auth=>AC, wait=>CA, cleaned=>f, line=>804, pos=>54, date=>BAA, public=>t, space=>AB + +auth=>BCB, wait=>BCC, subtitle=>AAA, line=>806 +line=>807, disabled=>f, space=>ACA +org=>96, query=>CBA, line=>808, disabled=>f, pos=>74, space=>CA, public=>f + +state=>12, title=>AA, bad=>f, status=>20, line=>810, disabled=>t, coauthors=>CAC, node=>AB +auth=>ABC, line=>811, date=>CA +title=>AB, indexed=>f, line=>812, disabled=>f, 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=>t, line=>819, disabled=>t +auth=>ABB, wait=>AC, query=>CC, cleaned=>t, indexed=>f, line=>820, abstract=>AA, public=>f, node=>AB +org=>5, wait=>BA, indexed=>t, line=>821, node=>AB +title=>CC, wait=>CC, bad=>f, query=>BCC, indexed=>f, line=>822, pos=>27, date=>CB, node=>CBA +query=>BC, status=>28, line=>823, public=>f +status=>1, line=>824, abstract=>BB + +auth=>AA, title=>BC, query=>CA, status=>33, line=>826 +state=>9, title=>BB, subtitle=>ACC, bad=>t, query=>BA, status=>41, line=>827, abstract=>ACB, public=>f +auth=>AB, subtitle=>CAB, line=>828, public=>f, coauthors=>AB, node=>BAC +line=>829, disabled=>f, public=>t, node=>CBC +auth=>BAB, line=>830 +wait=>BBA, bad=>t, indexed=>f, line=>831, space=>BB +org=>70, wait=>BC, world=>AC, indexed=>t, status=>96, line=>832, disabled=>t, space=>AB +state=>8, world=>BAB, bad=>t, indexed=>t, status=>18, line=>833, date=>BA, space=>BA +query=>AB, line=>834 +bad=>t, status=>5, line=>835 +world=>BAC, subtitle=>BB, bad=>f, user=>AB, indexed=>f, cleaned=>t, line=>836 +line=>837, public=>f +line=>838, pos=>7 +auth=>CA, query=>ABB, indexed=>t, line=>839, public=>t +wait=>CC, bad=>f, line=>840, date=>AAB, public=>f, 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=>t +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=>f, cleaned=>f, line=>845 +title=>CB, cleaned=>f, indexed=>f, line=>846 +wait=>CAA, indexed=>f, line=>847, disabled=>f + +title=>CB, query=>CC, line=>849, abstract=>CB, pos=>10, public=>t +auth=>CB, subtitle=>BA, cleaned=>t, line=>850, disabled=>t, 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=>t, node=>AA +subtitle=>BC, user=>AB, cleaned=>t, line=>854, public=>t +state=>26, wait=>BA, world=>BBB, user=>BB, status=>53, line=>855, abstract=>ABA, pos=>72, space=>AC +line=>856, public=>f, coauthors=>CA +wait=>ABB, subtitle=>CBA, line=>857, pos=>44 +auth=>ABA, wait=>CC, org=>0, bad=>t, query=>BB, line=>858, disabled=>f, public=>t +bad=>f, user=>AA, line=>859, pos=>90 +world=>BCC, title=>BAA, bad=>f, user=>CAA, query=>BBC, line=>860, date=>CAA, space=>BCB, public=>t, coauthors=>CB +title=>BAB, world=>BC, subtitle=>AA, cleaned=>f, status=>9, line=>861, pos=>95 +title=>AC, line=>862 +wait=>CB, bad=>f, status=>89, line=>863, coauthors=>AB +subtitle=>ACC, indexed=>t, cleaned=>t, line=>864 +title=>AB, subtitle=>CBB, query=>ACA, indexed=>t, line=>865, disabled=>t +title=>BC, world=>BB, query=>AA, user=>ACB, status=>43, cleaned=>f, line=>866, coauthors=>CBC, node=>ACB + +org=>25, wait=>AC, indexed=>f, line=>868, disabled=>f, abstract=>CA, pos=>48 +bad=>t, status=>34, line=>869, pos=>32, date=>AC, public=>t, node=>AA +state=>33, wait=>AAC, indexed=>t, status=>20, line=>870, abstract=>BA +wait=>CCC, subtitle=>AC, line=>871, disabled=>f, space=>BA, public=>t, coauthors=>BCC +status=>49, line=>872 +state=>90, title=>ACC, world=>CBB, subtitle=>BAB, bad=>f, status=>94, line=>873, abstract=>CB +title=>BCB, line=>874 +cleaned=>f, line=>875 +wait=>BAA, subtitle=>BBC, line=>876 +auth=>AB, org=>35, bad=>f, indexed=>f, line=>877, coauthors=>BBA +line=>878, public=>f + +auth=>CB, wait=>CBC, indexed=>f, line=>880, public=>t +query=>CC, status=>4, line=>881, disabled=>t, node=>CA +title=>BB, line=>882 +state=>53, bad=>f, cleaned=>f, 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=>f, line=>885, space=>AC +world=>CA, user=>CA, line=>886, node=>CB +state=>32, org=>50, wait=>AA, line=>887, disabled=>f, space=>BBA, public=>f +cleaned=>t, 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=>f, 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=>t, coauthors=>AB +auth=>AB, indexed=>t, cleaned=>f, line=>897 +line=>898, coauthors=>CBB + +wait=>ACC, line=>900, abstract=>BBA +auth=>AA, wait=>BCB, cleaned=>f, line=>901, abstract=>AAC +state=>68, title=>AC, subtitle=>BB, line=>902 +state=>41, wait=>ABA, bad=>f, user=>BBA, status=>46, line=>903, node=>AAB + +cleaned=>f, line=>905, pos=>33 +bad=>f, query=>BA, line=>906, pos=>48, space=>CB, public=>t +query=>CB, indexed=>t, line=>907, pos=>41, abstract=>CBB, space=>BA, public=>f, node=>BC +title=>AB, line=>908 +auth=>BC, title=>CB, line=>909, disabled=>f, space=>CA, public=>t, coauthors=>BC +world=>AA, user=>ABA, indexed=>f, line=>910, abstract=>CC +auth=>CCA, indexed=>f, line=>911, date=>AC, public=>f +world=>AAB, bad=>t, line=>912 +subtitle=>CBB, line=>913, public=>t +wait=>CB, line=>914, disabled=>f, pos=>71, date=>BA, space=>CBA, public=>f, coauthors=>BB +org=>67, wait=>CA, bad=>f, line=>915, disabled=>f, public=>t + +line=>917, date=>CB +auth=>CBB, world=>AAA, status=>83, indexed=>f, line=>918, disabled=>t, date=>CBA, coauthors=>ACC +wait=>AB, title=>BA, status=>33, line=>919, disabled=>f +wait=>ACB, cleaned=>f, line=>920, abstract=>AA, coauthors=>BCB +wait=>ABB, org=>40, world=>BC, subtitle=>CA, user=>AAC, status=>14, indexed=>t, line=>921, pos=>66 +auth=>BA, org=>22, wait=>BAB, bad=>t, user=>ACC, status=>32, line=>922 +world=>BA, query=>CAB, status=>0, line=>923 +org=>84, bad=>t, line=>924, coauthors=>BAB +auth=>ACC, subtitle=>AAA, query=>CCA, cleaned=>f, line=>925, pos=>60, space=>BC +wait=>BC, subtitle=>CCC, bad=>f, cleaned=>f, indexed=>f, line=>926, public=>f, 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=>f +world=>CBC, user=>CBC, line=>929, date=>CAC +world=>BCB, bad=>f, user=>BB, line=>930 +auth=>CA, world=>AA, query=>ABA, user=>AA, indexed=>t, line=>931, coauthors=>BBC +auth=>BAA, bad=>t, line=>932, disabled=>f, pos=>93, abstract=>CCA, date=>BBA, coauthors=>AA +line=>933, space=>CAB, node=>AB + +status=>60, cleaned=>t, indexed=>t, line=>935, pos=>35, space=>CAB, node=>BBB +wait=>AAA, bad=>t, status=>26, line=>936, abstract=>ACB, space=>BBA, coauthors=>CCC +title=>BA, bad=>t, line=>937, date=>BBA, public=>t + +query=>BA, user=>BA, line=>939, disabled=>t +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=>f, 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=>f, status=>5, line=>948, abstract=>BB, public=>f, coauthors=>ABC +world=>CA, org=>61, bad=>f, query=>CC, cleaned=>t, 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=>t, node=>AAA +auth=>CAA, subtitle=>ABA, bad=>t, line=>953 +auth=>CA, wait=>BB, org=>12, user=>BCC, cleaned=>f, line=>954, public=>f, coauthors=>AA +org=>93, cleaned=>t, line=>955, disabled=>t, public=>t, node=>ACA +line=>956, pos=>10 +org=>74, world=>CCC, subtitle=>AB, user=>AAA, cleaned=>t, line=>957, pos=>70, public=>t, node=>CC +state=>51, line=>958 +world=>CCA, title=>BCB, user=>AB, indexed=>t, line=>959, disabled=>t, pos=>21, date=>CBC +org=>86, wait=>BC, query=>BB, user=>AA, indexed=>t, line=>960, pos=>58, date=>AB +line=>961, node=>CC +auth=>BCB, world=>ACC, subtitle=>CA, bad=>t, user=>BA, indexed=>f, line=>962, public=>f + +status=>37, line=>964 +state=>70, status=>76, indexed=>f, line=>965, disabled=>t, space=>BB + +state=>67, world=>CA, title=>AA, line=>967, abstract=>BA, space=>BAA +auth=>CA, world=>AA, bad=>t, query=>BC, status=>53, indexed=>f, line=>968, date=>AB, node=>BAA +query=>AC, cleaned=>t, line=>969, abstract=>BC, space=>CAB, coauthors=>BAA +wait=>BCA, world=>CB, title=>BC, indexed=>f, line=>970, disabled=>t, pos=>70, date=>AB + +subtitle=>BC, query=>AA, line=>972 +line=>973, public=>t +org=>75, world=>AAB, subtitle=>BB, user=>CC, line=>974, space=>CA +auth=>BCB, cleaned=>t, line=>975 +title=>BAC, user=>CB, line=>976, public=>f +subtitle=>BAC, indexed=>f, cleaned=>f, line=>977, disabled=>f, abstract=>ABC, space=>ABA +state=>63, bad=>f, line=>978, pos=>93, node=>AAC + +cleaned=>f, 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=>t, line=>983, date=>ACB, node=>CB +state=>32, title=>ABC, org=>58, status=>95, line=>984, disabled=>t, pos=>6, space=>CBB +title=>BCC, subtitle=>CCC, user=>BBC, line=>985, public=>f, coauthors=>CCB, node=>AA +subtitle=>ACA, query=>BCC, status=>43, cleaned=>t, indexed=>t, line=>986, abstract=>CAC + +world=>CAB, org=>21, indexed=>t, line=>988, abstract=>ABC +title=>CBC, status=>66, line=>989 + +line=>991, abstract=>BA, node=>BBB +line=>992, disabled=>t, pos=>29, public=>f +state=>53, wait=>CB, subtitle=>CCC, line=>993, date=>CAC, public=>f, coauthors=>BB +wait=>CBA, title=>CA, subtitle=>BB, user=>BAA, line=>994, disabled=>t, date=>BB, coauthors=>CCC, node=>CC +title=>BB, user=>AA, query=>CAA, status=>43, line=>995, pos=>6, abstract=>CC, public=>t +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=>t, status=>59, line=>999, disabled=>t +user=>BC, line=>1000 +wait=>NULL, line=>1000 diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out new file mode 100644 index 0000000..1836c9a --- /dev/null +++ b/contrib/hstore/expected/hstore.out @@ -0,0 +1,1635 @@ +CREATE EXTENSION hstore; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + amname | opcname +--------+--------- +(0 rows) + +set escape_string_warning=off; +--hstore; +select ''::hstore; + hstore +-------- + +(1 row) + +select 'a=>b'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select ' a=>b'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select 'a =>b'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select 'a=>b '::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select 'a=> b'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select '"a"=>"b"'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select ' "a"=>"b"'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select '"a" =>"b"'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select '"a"=>"b" '::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select '"a"=> "b"'::hstore; + hstore +---------- + "a"=>"b" +(1 row) + +select 'aa=>bb'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select ' aa=>bb'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select 'aa =>bb'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select 'aa=>bb '::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select 'aa=> bb'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select '"aa"=>"bb"'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select ' "aa"=>"bb"'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select '"aa" =>"bb"'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select '"aa"=>"bb" '::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select '"aa"=> "bb"'::hstore; + hstore +------------ + "aa"=>"bb" +(1 row) + +select 'aa=>bb, cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>bb , cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>bb ,cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>bb, "cc"=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>bb , "cc"=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>bb ,"cc"=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>"bb", cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>"bb" , cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>"bb" ,cc=>dd'::hstore; + hstore +------------------------ + "aa"=>"bb", "cc"=>"dd" +(1 row) + +select 'aa=>null'::hstore; + hstore +------------ + "aa"=>NULL +(1 row) + +select 'aa=>NuLl'::hstore; + hstore +------------ + "aa"=>NULL +(1 row) + +select 'aa=>"NuLl"'::hstore; + hstore +-------------- + "aa"=>"NuLl" +(1 row) + +select e'\\=a=>q=w'::hstore; + hstore +------------- + "=a"=>"q=w" +(1 row) + +select e'"=a"=>q\\=w'::hstore; + hstore +------------- + "=a"=>"q=w" +(1 row) + +select e'"\\"a"=>q>w'::hstore; + hstore +-------------- + "\"a"=>"q>w" +(1 row) + +select e'\\"a=>q"w'::hstore; + hstore +--------------- + "\"a"=>"q\"w" +(1 row) + +select ''::hstore; + hstore +-------- + +(1 row) + +select ' '::hstore; + hstore +-------- + +(1 row) + +-- invalid input +select ' =>null'::hstore; +ERROR: syntax error in hstore, near "=" at position 2 +LINE 1: select ' =>null'::hstore; + ^ +select 'aa=>"'::hstore; +ERROR: syntax error in hstore: unexpected end of string +LINE 1: select 'aa=>"'::hstore; + ^ +-- also try it with non-error-throwing API +select pg_input_is_valid('a=>b', 'hstore'); + pg_input_is_valid +------------------- + t +(1 row) + +select pg_input_is_valid('a=b', 'hstore'); + pg_input_is_valid +------------------- + f +(1 row) + +select * from pg_input_error_info('a=b', 'hstore'); + message | detail | hint | sql_error_code +------------------------------------------------+--------+------+---------------- + syntax error in hstore, near "b" at position 2 | | | 42601 +(1 row) + +select * from pg_input_error_info(' =>b', 'hstore'); + message | detail | hint | sql_error_code +------------------------------------------------+--------+------+---------------- + syntax error in hstore, near "=" at position 1 | | | 42601 +(1 row) + +-- -> operator +select 'aa=>b, c=>d , b=>16'::hstore->'c'; + ?column? +---------- + d +(1 row) + +select 'aa=>b, c=>d , b=>16'::hstore->'b'; + ?column? +---------- + 16 +(1 row) + +select 'aa=>b, c=>d , b=>16'::hstore->'aa'; + ?column? +---------- + b +(1 row) + +select ('aa=>b, c=>d , b=>16'::hstore->'gg') is null; + ?column? +---------- + t +(1 row) + +select ('aa=>NULL, c=>d , b=>16'::hstore->'aa') is null; + ?column? +---------- + t +(1 row) + +select ('aa=>"NULL", c=>d , b=>16'::hstore->'aa') is null; + ?column? +---------- + f +(1 row) + +-- -> array operator +select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['aa','c']; + ?column? +------------ + {"NULL",d} +(1 row) + +select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['c','aa']; + ?column? +------------ + {d,"NULL"} +(1 row) + +select 'aa=>NULL, c=>d , b=>16'::hstore -> ARRAY['aa','c',null]; + ?column? +--------------- + {NULL,d,NULL} +(1 row) + +select 'aa=>1, c=>3, b=>2, d=>4'::hstore -> ARRAY[['b','d'],['aa','c']]; + ?column? +--------------- + {{2,4},{1,3}} +(1 row) + +-- exists/defined +select exist('a=>NULL, b=>qq', 'a'); + exist +------- + t +(1 row) + +select exist('a=>NULL, b=>qq', 'b'); + exist +------- + t +(1 row) + +select exist('a=>NULL, b=>qq', 'c'); + exist +------- + f +(1 row) + +select exist('a=>"NULL", b=>qq', 'a'); + exist +------- + t +(1 row) + +select defined('a=>NULL, b=>qq', 'a'); + defined +--------- + f +(1 row) + +select defined('a=>NULL, b=>qq', 'b'); + defined +--------- + t +(1 row) + +select defined('a=>NULL, b=>qq', 'c'); + defined +--------- + f +(1 row) + +select defined('a=>"NULL", b=>qq', 'a'); + defined +--------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ? 'a'; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ? 'b'; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ? 'c'; + ?column? +---------- + f +(1 row) + +select hstore 'a=>"NULL", b=>qq' ? 'a'; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?| ARRAY['a','b']; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?| ARRAY['b','a']; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','a']; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','d']; + ?column? +---------- + f +(1 row) + +select hstore 'a=>NULL, b=>qq' ?| '{}'::text[]; + ?column? +---------- + f +(1 row) + +select hstore 'a=>NULL, b=>qq' ?& ARRAY['a','b']; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?& ARRAY['b','a']; + ?column? +---------- + t +(1 row) + +select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','a']; + ?column? +---------- + f +(1 row) + +select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','d']; + ?column? +---------- + f +(1 row) + +select hstore 'a=>NULL, b=>qq' ?& '{}'::text[]; + ?column? +---------- + t +(1 row) + +-- delete +select delete('a=>1 , b=>2, c=>3'::hstore, 'a'); + delete +-------------------- + "b"=>"2", "c"=>"3" +(1 row) + +select delete('a=>null , b=>2, c=>3'::hstore, 'a'); + delete +-------------------- + "b"=>"2", "c"=>"3" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, 'b'); + delete +-------------------- + "a"=>"1", "c"=>"3" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, 'c'); + delete +-------------------- + "a"=>"1", "b"=>"2" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, 'd'); + delete +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - 'a'::text; + ?column? +-------------------- + "b"=>"2", "c"=>"3" +(1 row) + +select 'a=>null , b=>2, c=>3'::hstore - 'a'::text; + ?column? +-------------------- + "b"=>"2", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - 'b'::text; + ?column? +-------------------- + "a"=>"1", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - 'c'::text; + ?column? +-------------------- + "a"=>"1", "b"=>"2" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - 'd'::text; + ?column? +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b'::text) + = pg_column_size('a=>1, b=>2'::hstore); + ?column? +---------- + t +(1 row) + +-- delete (array) +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','e']); + delete +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','b']); + delete +-------------------- + "a"=>"1", "c"=>"3" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['a','c']); + delete +---------- + "b"=>"2" +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY[['b'],['c'],['a']]); + delete +-------- + +(1 row) + +select delete('a=>1 , b=>2, c=>3'::hstore, '{}'::text[]); + delete +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','e']; + ?column? +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','b']; + ?column? +-------------------- + "a"=>"1", "c"=>"3" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c']; + ?column? +---------- + "b"=>"2" +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY[['b'],['c'],['a']]; + ?column? +---------- + +(1 row) + +select 'a=>1 , b=>2, c=>3'::hstore - '{}'::text[]; + ?column? +------------------------------ + "a"=>"1", "b"=>"2", "c"=>"3" +(1 row) + +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c']) + = pg_column_size('b=>2'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - '{}'::text[]) + = pg_column_size('a=>1, b=>2, c=>3'::hstore); + ?column? +---------- + t +(1 row) + +-- delete (hstore) +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>4, b=>2'::hstore); + delete +--------------------- + "c"=>"3", "aa"=>"1" +(1 row) + +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>NULL, c=>3'::hstore); + delete +--------------------- + "b"=>"2", "aa"=>"1" +(1 row) + +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>1, b=>2, c=>3'::hstore); + delete +-------- + +(1 row) + +select delete('aa=>1 , b=>2, c=>3'::hstore, 'b=>2'::hstore); + delete +--------------------- + "c"=>"3", "aa"=>"1" +(1 row) + +select delete('aa=>1 , b=>2, c=>3'::hstore, ''::hstore); + delete +------------------------------- + "b"=>"2", "c"=>"3", "aa"=>"1" +(1 row) + +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>4, b=>2'::hstore; + ?column? +--------------------- + "c"=>"3", "aa"=>"1" +(1 row) + +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>NULL, c=>3'::hstore; + ?column? +--------------------- + "b"=>"2", "aa"=>"1" +(1 row) + +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>1, b=>2, c=>3'::hstore; + ?column? +---------- + +(1 row) + +select 'aa=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore; + ?column? +--------------------- + "c"=>"3", "aa"=>"1" +(1 row) + +select 'aa=>1 , b=>2, c=>3'::hstore - ''::hstore; + ?column? +------------------------------- + "b"=>"2", "c"=>"3", "aa"=>"1" +(1 row) + +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore) + = pg_column_size('a=>1, c=>3'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ''::hstore) + = pg_column_size('a=>1, b=>2, c=>3'::hstore); + ?column? +---------- + t +(1 row) + +-- || +select 'aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'; + ?column? +------------------------------------------- + "b"=>"g", "aa"=>"1", "cq"=>"l", "fg"=>"f" +(1 row) + +select 'aa=>1 , b=>2, cq=>3'::hstore || 'aq=>l'; + ?column? +------------------------------------------- + "b"=>"2", "aa"=>"1", "aq"=>"l", "cq"=>"3" +(1 row) + +select 'aa=>1 , b=>2, cq=>3'::hstore || 'aa=>l'; + ?column? +-------------------------------- + "b"=>"2", "aa"=>"l", "cq"=>"3" +(1 row) + +select 'aa=>1 , b=>2, cq=>3'::hstore || ''; + ?column? +-------------------------------- + "b"=>"2", "aa"=>"1", "cq"=>"3" +(1 row) + +select ''::hstore || 'cq=>l, b=>g, fg=>f'; + ?column? +-------------------------------- + "b"=>"g", "cq"=>"l", "fg"=>"f" +(1 row) + +select pg_column_size(''::hstore || ''::hstore) = pg_column_size(''::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size('aa=>1'::hstore || 'b=>2'::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size('aa=>1, b=>2'::hstore || ''::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size(''::hstore || 'aa=>1, b=>2'::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); + ?column? +---------- + t +(1 row) + +-- hstore(text,text) +select 'a=>g, b=>c'::hstore || hstore('asd', 'gf'); + ?column? +--------------------------------- + "a"=>"g", "b"=>"c", "asd"=>"gf" +(1 row) + +select 'a=>g, b=>c'::hstore || hstore('b', 'gf'); + ?column? +--------------------- + "a"=>"g", "b"=>"gf" +(1 row) + +select 'a=>g, b=>c'::hstore || hstore('b', 'NULL'); + ?column? +----------------------- + "a"=>"g", "b"=>"NULL" +(1 row) + +select 'a=>g, b=>c'::hstore || hstore('b', NULL); + ?column? +--------------------- + "a"=>"g", "b"=>NULL +(1 row) + +select ('a=>g, b=>c'::hstore || hstore(NULL, 'b')) is null; + ?column? +---------- + t +(1 row) + +select pg_column_size(hstore('b', 'gf')) + = pg_column_size('b=>gf'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size('a=>g, b=>c'::hstore || hstore('b', 'gf')) + = pg_column_size('a=>g, b=>gf'::hstore); + ?column? +---------- + t +(1 row) + +-- slice() +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['g','h','i']); + slice +------- + +(1 row) + +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b']); + slice +-------------------- + "b"=>"2", "c"=>"3" +(1 row) + +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['aa','b']); + slice +--------------------- + "b"=>"2", "aa"=>"1" +(1 row) + +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']); + slice +------------------------------- + "b"=>"2", "c"=>"3", "aa"=>"1" +(1 row) + +select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b'])) + = pg_column_size('b=>2, c=>3'::hstore); + ?column? +---------- + t +(1 row) + +select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa'])) + = pg_column_size('aa=>1, b=>2, c=>3'::hstore); + ?column? +---------- + t +(1 row) + +-- array input +select '{}'::text[]::hstore; + hstore +-------- + +(1 row) + +select ARRAY['a','g','b','h','asd']::hstore; +ERROR: array must have even number of elements +select ARRAY['a','g','b','h','asd','i']::hstore; + array +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore; + array +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select ARRAY[['a','g','b'],['h','asd','i']]::hstore; +ERROR: array must have two columns +select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore; +ERROR: wrong number of array subscripts +select hstore('{}'::text[]); + hstore +-------- + +(1 row) + +select hstore(ARRAY['a','g','b','h','asd']); +ERROR: array must have even number of elements +select hstore(ARRAY['a','g','b','h','asd','i']); + hstore +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select hstore(ARRAY[['a','g'],['b','h'],['asd','i']]); + hstore +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select hstore(ARRAY[['a','g','b'],['h','asd','i']]); +ERROR: array must have two columns +select hstore(ARRAY[[['a','g'],['b','h'],['asd','i']]]); +ERROR: wrong number of array subscripts +select hstore('[0:5]={a,g,b,h,asd,i}'::text[]); + hstore +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select hstore('[0:2][1:2]={{a,g},{b,h},{asd,i}}'::text[]); + hstore +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +-- pairs of arrays +select hstore(ARRAY['a','b','asd'], ARRAY['g','h','i']); + hstore +-------------------------------- + "a"=>"g", "b"=>"h", "asd"=>"i" +(1 row) + +select hstore(ARRAY['a','b','asd'], ARRAY['g','h',NULL]); + hstore +--------------------------------- + "a"=>"g", "b"=>"h", "asd"=>NULL +(1 row) + +select hstore(ARRAY['z','y','x'], ARRAY['1','2','3']); + hstore +------------------------------ + "x"=>"3", "y"=>"2", "z"=>"1" +(1 row) + +select hstore(ARRAY['aaa','bb','c','d'], ARRAY[null::text,null,null,null]); + hstore +----------------------------------------------- + "c"=>NULL, "d"=>NULL, "bb"=>NULL, "aaa"=>NULL +(1 row) + +select hstore(ARRAY['aaa','bb','c','d'], null); + hstore +----------------------------------------------- + "c"=>NULL, "d"=>NULL, "bb"=>NULL, "aaa"=>NULL +(1 row) + +select quote_literal(hstore('{}'::text[], '{}'::text[])); + quote_literal +--------------- + '' +(1 row) + +select quote_literal(hstore('{}'::text[], null)); + quote_literal +--------------- + '' +(1 row) + +select hstore(ARRAY['a'], '{}'::text[]); -- error +ERROR: arrays must have same bounds +select hstore('{}'::text[], ARRAY['a']); -- error +ERROR: arrays must have same bounds +select pg_column_size(hstore(ARRAY['a','b','asd'], ARRAY['g','h','i'])) + = pg_column_size('a=>g, b=>h, asd=>i'::hstore); + ?column? +---------- + t +(1 row) + +-- records +select hstore(v) from (values (1, 'foo', 1.2, 3::float8)) v(a,b,c,d); + hstore +-------------------------------------------- + "a"=>"1", "b"=>"foo", "c"=>"1.2", "d"=>"3" +(1 row) + +create domain hstestdom1 as integer not null default 0; +create table testhstore0 (a integer, b text, c numeric, d float8); +create table testhstore1 (a integer, b text, c numeric, d float8, e hstestdom1); +insert into testhstore0 values (1, 'foo', 1.2, 3::float8); +insert into testhstore1 values (1, 'foo', 1.2, 3::float8); +select hstore(v) from testhstore1 v; + hstore +------------------------------------------------------ + "a"=>"1", "b"=>"foo", "c"=>"1.2", "d"=>"3", "e"=>"0" +(1 row) + +select hstore(null::testhstore0); + hstore +-------------------------------------------- + "a"=>NULL, "b"=>NULL, "c"=>NULL, "d"=>NULL +(1 row) + +select hstore(null::testhstore1); + hstore +------------------------------------------------------- + "a"=>NULL, "b"=>NULL, "c"=>NULL, "d"=>NULL, "e"=>NULL +(1 row) + +select pg_column_size(hstore(v)) + = pg_column_size('a=>1, b=>"foo", c=>"1.2", d=>"3", e=>"0"'::hstore) + from testhstore1 v; + ?column? +---------- + t +(1 row) + +select populate_record(v, hstore('c', '3.45')) from testhstore1 v; + populate_record +------------------ + (1,foo,3.45,3,0) +(1 row) + +select populate_record(v, hstore('d', '3.45')) from testhstore1 v; + populate_record +-------------------- + (1,foo,1.2,3.45,0) +(1 row) + +select populate_record(v, hstore('e', '123')) from testhstore1 v; + populate_record +------------------- + (1,foo,1.2,3,123) +(1 row) + +select populate_record(v, hstore('e', null)) from testhstore1 v; +ERROR: domain hstestdom1 does not allow null values +select populate_record(v, hstore('c', null)) from testhstore1 v; + populate_record +----------------- + (1,foo,,3,0) +(1 row) + +select populate_record(v, hstore('b', 'foo') || hstore('a', '123')) from testhstore1 v; + populate_record +------------------- + (123,foo,1.2,3,0) +(1 row) + +select populate_record(v, hstore('b', 'foo') || hstore('e', null)) from testhstore0 v; + populate_record +----------------- + (1,foo,1.2,3) +(1 row) + +select populate_record(v, hstore('b', 'foo') || hstore('e', null)) from testhstore1 v; +ERROR: domain hstestdom1 does not allow null values +select populate_record(v, '') from testhstore0 v; + populate_record +----------------- + (1,foo,1.2,3) +(1 row) + +select populate_record(v, '') from testhstore1 v; + populate_record +----------------- + (1,foo,1.2,3,0) +(1 row) + +select populate_record(null::testhstore1, hstore('c', '3.45') || hstore('a', '123')); +ERROR: domain hstestdom1 does not allow null values +select populate_record(null::testhstore1, hstore('c', '3.45') || hstore('e', '123')); + populate_record +----------------- + (,,3.45,,123) +(1 row) + +select populate_record(null::testhstore0, ''); + populate_record +----------------- + (,,,) +(1 row) + +select populate_record(null::testhstore1, ''); +ERROR: domain hstestdom1 does not allow null values +select v #= hstore('c', '3.45') from testhstore1 v; + ?column? +------------------ + (1,foo,3.45,3,0) +(1 row) + +select v #= hstore('d', '3.45') from testhstore1 v; + ?column? +-------------------- + (1,foo,1.2,3.45,0) +(1 row) + +select v #= hstore('e', '123') from testhstore1 v; + ?column? +------------------- + (1,foo,1.2,3,123) +(1 row) + +select v #= hstore('c', null) from testhstore1 v; + ?column? +-------------- + (1,foo,,3,0) +(1 row) + +select v #= hstore('e', null) from testhstore0 v; + ?column? +--------------- + (1,foo,1.2,3) +(1 row) + +select v #= hstore('e', null) from testhstore1 v; +ERROR: domain hstestdom1 does not allow null values +select v #= (hstore('b', 'foo') || hstore('a', '123')) from testhstore1 v; + ?column? +------------------- + (123,foo,1.2,3,0) +(1 row) + +select v #= (hstore('b', 'foo') || hstore('e', '123')) from testhstore1 v; + ?column? +------------------- + (1,foo,1.2,3,123) +(1 row) + +select v #= hstore '' from testhstore0 v; + ?column? +--------------- + (1,foo,1.2,3) +(1 row) + +select v #= hstore '' from testhstore1 v; + ?column? +----------------- + (1,foo,1.2,3,0) +(1 row) + +select null::testhstore1 #= (hstore('c', '3.45') || hstore('a', '123')); +ERROR: domain hstestdom1 does not allow null values +select null::testhstore1 #= (hstore('c', '3.45') || hstore('e', '123')); + ?column? +--------------- + (,,3.45,,123) +(1 row) + +select null::testhstore0 #= hstore ''; + ?column? +---------- + (,,,) +(1 row) + +select null::testhstore1 #= hstore ''; +ERROR: domain hstestdom1 does not allow null values +select v #= h from testhstore1 v, (values (hstore 'a=>123',1),('b=>foo,c=>3.21',2),('a=>null',3),('e=>123',4),('f=>blah',5)) x(h,i) order by i; + ?column? +------------------- + (123,foo,1.2,3,0) + (1,foo,3.21,3,0) + (,foo,1.2,3,0) + (1,foo,1.2,3,123) + (1,foo,1.2,3,0) +(5 rows) + +-- keys/values +select akeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); + akeys +-------------- + {b,aa,cq,fg} +(1 row) + +select akeys('""=>1'); + akeys +------- + {""} +(1 row) + +select akeys(''); + akeys +------- + {} +(1 row) + +select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); + avals +----------- + {g,1,l,f} +(1 row) + +select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL'); + avals +-------------- + {g,1,l,NULL} +(1 row) + +select avals('""=>1'); + avals +------- + {1} +(1 row) + +select avals(''); + avals +------- + {} +(1 row) + +select hstore_to_array('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore); + hstore_to_array +------------------------- + {b,g,aa,1,cq,l,fg,NULL} +(1 row) + +select %% 'aa=>1, cq=>l, b=>g, fg=>NULL'; + ?column? +------------------------- + {b,g,aa,1,cq,l,fg,NULL} +(1 row) + +select hstore_to_matrix('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore); + hstore_to_matrix +--------------------------------- + {{b,g},{aa,1},{cq,l},{fg,NULL}} +(1 row) + +select %# 'aa=>1, cq=>l, b=>g, fg=>NULL'; + ?column? +--------------------------------- + {{b,g},{aa,1},{cq,l},{fg,NULL}} +(1 row) + +select * from skeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); + skeys +------- + b + aa + cq + fg +(4 rows) + +select * from skeys('""=>1'); + skeys +------- + +(1 row) + +select * from skeys(''); + skeys +------- +(0 rows) + +select * from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); + svals +------- + g + 1 + l + f +(4 rows) + +select *, svals is null from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL'); + svals | ?column? +-------+---------- + g | f + 1 | f + l | f + | t +(4 rows) + +select * from svals('""=>1'); + svals +------- + 1 +(1 row) + +select * from svals(''); + svals +------- +(0 rows) + +select * from each('aaa=>bq, b=>NULL, ""=>1 '); + key | value +-----+------- + | 1 + b | + aaa | bq +(3 rows) + +-- @> +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b'; + ?column? +---------- + t +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>NULL'; + ?column? +---------- + t +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, g=>NULL'; + ?column? +---------- + f +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'g=>NULL'; + ?column? +---------- + f +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>c'; + ?column? +---------- + f +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b'; + ?column? +---------- + t +(1 row) + +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>q'; + ?column? +---------- + f +(1 row) + +CREATE TABLE testhstore (h hstore); +\copy testhstore from 'data/hstore.data' +select count(*) from testhstore where h @> 'wait=>NULL'; + count +------- + 1 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC'; + count +------- + 15 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; + count +------- + 2 +(1 row) + +select count(*) from testhstore where h ? 'public'; + count +------- + 194 +(1 row) + +select count(*) from testhstore where h ?| ARRAY['public','disabled']; + count +------- + 337 +(1 row) + +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +create index hidx on testhstore using gist(h); +set enable_seqscan=off; +select count(*) from testhstore where h @> 'wait=>NULL'; + count +------- + 1 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC'; + count +------- + 15 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; + count +------- + 2 +(1 row) + +select count(*) from testhstore where h ? 'public'; + count +------- + 194 +(1 row) + +select count(*) from testhstore where h ?| ARRAY['public','disabled']; + count +------- + 337 +(1 row) + +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +drop index hidx; +create index hidx on testhstore using gist(h gist_hstore_ops(siglen=0)); +ERROR: value 0 out of bounds for option "siglen" +DETAIL: Valid values are between "1" and "2024". +create index hidx on testhstore using gist(h gist_hstore_ops(siglen=2025)); +ERROR: value 2025 out of bounds for option "siglen" +DETAIL: Valid values are between "1" and "2024". +create index hidx on testhstore using gist(h gist_hstore_ops(siglen=2024)); +set enable_seqscan=off; +select count(*) from testhstore where h @> 'wait=>NULL'; + count +------- + 1 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC'; + count +------- + 15 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; + count +------- + 2 +(1 row) + +select count(*) from testhstore where h ? 'public'; + count +------- + 194 +(1 row) + +select count(*) from testhstore where h ?| ARRAY['public','disabled']; + count +------- + 337 +(1 row) + +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +drop index hidx; +create index hidx on testhstore using gin (h); +set enable_seqscan=off; +select count(*) from testhstore where h @> 'wait=>NULL'; + count +------- + 1 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC'; + count +------- + 15 +(1 row) + +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; + count +------- + 2 +(1 row) + +select count(*) from testhstore where h ? 'public'; + count +------- + 194 +(1 row) + +select count(*) from testhstore where h ?| ARRAY['public','disabled']; + count +------- + 337 +(1 row) + +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + count +------- + 42 +(1 row) + +select count(*) from (select (each(h)).key from testhstore) as wow ; + count +------- + 4781 +(1 row) + +select key, count(*) from (select (each(h)).key from testhstore) as wow group by key order by count desc, key; + key | count +-----------+------- + line | 884 + query | 207 + pos | 203 + node | 202 + space | 197 + status | 195 + public | 194 + title | 190 + wait | 190 + org | 189 + user | 189 + coauthors | 188 + disabled | 185 + indexed | 184 + cleaned | 180 + bad | 179 + date | 179 + world | 176 + state | 172 + subtitle | 169 + auth | 168 + abstract | 161 +(22 rows) + +-- sort/hash +select count(distinct h) from testhstore; + count +------- + 885 +(1 row) + +set enable_hashagg = false; +select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2; + count +------- + 885 +(1 row) + +set enable_hashagg = true; +set enable_sort = false; +select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2; + count +------- + 885 +(1 row) + +select distinct * from (values (hstore '' || ''),('')) v(h); + h +--- + +(1 row) + +set enable_sort = true; +-- btree +drop index hidx; +create index hidx on testhstore using btree (h); +set enable_seqscan=off; +select count(*) from testhstore where h #># 'p=>1'; + count +------- + 125 +(1 row) + +select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexed=>t'; + count +------- + 1 +(1 row) + +-- json and jsonb +select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); + hstore_to_json +------------------------------------------------------------------------------------------------- + {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"} +(1 row) + +select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json); + json +------------------------------------------------------------------------------------------------- + {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"} +(1 row) + +select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"'); + hstore_to_json_loose +------------------------------------------------------------------------------------------------------------- + {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "h": "2016-01-01", "a key": 1} +(1 row) + +select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); + hstore_to_jsonb +------------------------------------------------------------------------------------------------- + {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"} +(1 row) + +select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb); + jsonb +------------------------------------------------------------------------------------------------- + {"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"} +(1 row) + +select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"'); + hstore_to_jsonb_loose +---------------------------------------------------------------------------------------------------------- + {"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 23450, "h": "2016-01-01", "a key": 1} +(1 row) + +create table test_json_agg (f1 text, f2 hstore); +insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'), + ('rec2','"a key" =>2, b => f, c => "null", d=> -12345, e => 012345.6, f=> -1.234, g=> 0.345e-4'); +select json_agg(q) from test_json_agg q; + json_agg +---------------------------------------------------------------------------------------------------------------------------- + [{"f1":"rec1","f2":{"b": "t", "c": null, "d": "12345", "e": "012345", "f": "1.234", "g": "2.345e+4", "a key": "1"}}, + + {"f1":"rec2","f2":{"b": "f", "c": "null", "d": "-12345", "e": "012345.6", "f": "-1.234", "g": "0.345e-4", "a key": "2"}}] +(1 row) + +select json_agg(q) from (select f1, hstore_to_json_loose(f2) as f2 from test_json_agg) q; + json_agg +---------------------------------------------------------------------------------------------------------------------- + [{"f1":"rec1","f2":{"b": true, "c": null, "d": 12345, "e": "012345", "f": 1.234, "g": 2.345e+4, "a key": 1}}, + + {"f1":"rec2","f2":{"b": false, "c": "null", "d": -12345, "e": "012345.6", "f": -1.234, "g": 0.345e-4, "a key": 2}}] +(1 row) + +-- Test subscripting +insert into test_json_agg default values; +select f2['d'], f2['x'] is null as x_isnull from test_json_agg; + f2 | x_isnull +--------+---------- + 12345 | t + -12345 | t + | t +(3 rows) + +select f2['d']['e'] from test_json_agg; -- error +ERROR: hstore allows only one subscript +select f2['d':'e'] from test_json_agg; -- error +ERROR: hstore allows only one subscript +update test_json_agg set f2['d'] = f2['e'], f2['x'] = 'xyzzy'; +select f2 from test_json_agg; + f2 +--------------------------------------------------------------------------------------------------------------------- + "b"=>"t", "c"=>NULL, "d"=>"012345", "e"=>"012345", "f"=>"1.234", "g"=>"2.345e+4", "x"=>"xyzzy", "a key"=>"1" + "b"=>"f", "c"=>"null", "d"=>"012345.6", "e"=>"012345.6", "f"=>"-1.234", "g"=>"0.345e-4", "x"=>"xyzzy", "a key"=>"2" + "d"=>NULL, "x"=>"xyzzy" +(3 rows) + +-- Test subscripting in plpgsql +do $$ declare h hstore; +begin h['a'] := 'b'; raise notice 'h = %, h[a] = %', h, h['a']; end $$; +NOTICE: h = "a"=>"b", h[a] = b +-- Check the hstore_hash() and hstore_hash_extended() function explicitly. +SELECT v as value, hstore_hash(v)::bit(32) as standard, + hstore_hash_extended(v, 0)::bit(32) as extended0, + hstore_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::hstore), (''), ('"a key" =>1'), ('c => null'), + ('e => 012345'), ('g => 2.345e+4')) x(v) +WHERE hstore_hash(v)::bit(32) != hstore_hash_extended(v, 0)::bit(32) + OR hstore_hash(v)::bit(32) = hstore_hash_extended(v, 1)::bit(32); + value | standard | extended0 | extended1 +-------+----------+-----------+----------- +(0 rows) + diff --git a/contrib/hstore/expected/hstore_utf8.out b/contrib/hstore/expected/hstore_utf8.out new file mode 100644 index 0000000..4405824 --- /dev/null +++ b/contrib/hstore/expected/hstore_utf8.out @@ -0,0 +1,36 @@ +/* + * 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; +-- UTF-8 locale bug on macOS: isspace(0x85) returns true. \u0105 encodes +-- as 0xc4 0x85 in UTF-8; the 0x85 was interpreted here as a whitespace. +SELECT E'key\u0105=>value\u0105'::hstore; + hstore +------------------ + "keyą"=>"valueą" +(1 row) + +SELECT 'keyą=>valueą'::hstore; + hstore +------------------ + "keyą"=>"valueą" +(1 row) + +SELECT 'ą=>ą'::hstore; + hstore +---------- + "ą"=>"ą" +(1 row) + +SELECT 'keyąfoo=>valueą'::hstore; + hstore +--------------------- + "keyąfoo"=>"valueą" +(1 row) + diff --git a/contrib/hstore/expected/hstore_utf8_1.out b/contrib/hstore/expected/hstore_utf8_1.out new file mode 100644 index 0000000..37aead8 --- /dev/null +++ b/contrib/hstore/expected/hstore_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/contrib/hstore/hstore--1.1--1.2.sql b/contrib/hstore/hstore--1.1--1.2.sql new file mode 100644 index 0000000..cc69fc7 --- /dev/null +++ b/contrib/hstore/hstore--1.1--1.2.sql @@ -0,0 +1,53 @@ +/* contrib/hstore/hstore--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION hstore UPDATE TO '1.2'" to load this file. \quit + + +-- A version of 1.1 was shipped with these objects mistakenly in 9.3.0. +-- Therefore we only add them if we detect that they aren't already there and +-- dependent on the extension. + +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + + PERFORM 1 + FROM pg_proc p + JOIN pg_depend d + ON p.proname = 'hstore_to_json_loose' + AND d.classid = 'pg_proc'::regclass + AND d.objid = p.oid + AND d.refclassid = 'pg_extension'::regclass + JOIN pg_extension x + ON d.refobjid = x.oid + AND x.extname = 'hstore'; + + IF NOT FOUND + THEN + PERFORM pg_catalog.set_config('search_path', old_path, true); + + CREATE FUNCTION hstore_to_json(hstore) + RETURNS json + AS 'MODULE_PATHNAME', 'hstore_to_json' + LANGUAGE C IMMUTABLE STRICT; + + CREATE CAST (hstore AS json) + WITH FUNCTION hstore_to_json(hstore); + + CREATE FUNCTION hstore_to_json_loose(hstore) + RETURNS json + AS 'MODULE_PATHNAME', 'hstore_to_json_loose' + LANGUAGE C IMMUTABLE STRICT; + + END IF; + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END; + +$$; diff --git a/contrib/hstore/hstore--1.2--1.3.sql b/contrib/hstore/hstore--1.2--1.3.sql new file mode 100644 index 0000000..0a70560 --- /dev/null +++ b/contrib/hstore/hstore--1.2--1.3.sql @@ -0,0 +1,17 @@ +/* contrib/hstore/hstore--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION hstore UPDATE TO '1.3'" to load this file. \quit + +CREATE FUNCTION hstore_to_jsonb(hstore) +RETURNS jsonb +AS 'MODULE_PATHNAME', 'hstore_to_jsonb' +LANGUAGE C IMMUTABLE STRICT; + +CREATE CAST (hstore AS jsonb) + WITH FUNCTION hstore_to_jsonb(hstore); + +CREATE FUNCTION hstore_to_jsonb_loose(hstore) +RETURNS jsonb +AS 'MODULE_PATHNAME', 'hstore_to_jsonb_loose' +LANGUAGE C IMMUTABLE STRICT; diff --git a/contrib/hstore/hstore--1.3--1.4.sql b/contrib/hstore/hstore--1.3--1.4.sql new file mode 100644 index 0000000..53f26f9 --- /dev/null +++ b/contrib/hstore/hstore--1.3--1.4.sql @@ -0,0 +1,99 @@ +/* contrib/hstore/hstore--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION hstore UPDATE TO '1.4'" to load this file. \quit + +-- Update procedure signatures the hard way. +-- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, +-- wherein the signatures have been updated already. In that case to_regprocedure() will +-- return NULL and no updates will happen. +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) +FROM (VALUES +(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types +('ghstore_same(internal,internal,internal)', '{SCH.ghstore,SCH.ghstore,internal}'), +('ghstore_consistent(internal,internal,int4,oid,internal)', '{internal,SCH.hstore,int2,oid,internal}'), +('gin_extract_hstore(internal,internal)', '{SCH.hstore,internal}'), +('gin_extract_hstore_query(internal,internal,int2,internal,internal)', '{SCH.hstore,internal,int2,internal,internal}'), +('gin_consistent_hstore(internal,int2,internal,int4,internal,internal)', '{internal,int2,SCH.hstore,int4,internal,internal}') +) AS update_data (oldproc, newtypestext), +LATERAL ( + SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ +) ls +WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + +UPDATE pg_catalog.pg_proc SET + prorettype = (my_schema || '.ghstore')::pg_catalog.regtype +WHERE oid = pg_catalog.to_regprocedure((my_schema || '.ghstore_union(internal,internal)')); + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; + +ALTER FUNCTION hstore_in(cstring) PARALLEL SAFE; +ALTER FUNCTION hstore_out(hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_recv(internal) PARALLEL SAFE; +ALTER FUNCTION hstore_send(hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_version_diag(hstore) PARALLEL SAFE; +ALTER FUNCTION fetchval(hstore, text) PARALLEL SAFE; +ALTER FUNCTION slice_array(hstore, text[]) PARALLEL SAFE; +ALTER FUNCTION slice(hstore, text[]) PARALLEL SAFE; +ALTER FUNCTION isexists(hstore, text) PARALLEL SAFE; +ALTER FUNCTION exist(hstore, text) PARALLEL SAFE; +ALTER FUNCTION exists_any(hstore, text[]) PARALLEL SAFE; +ALTER FUNCTION exists_all(hstore, text[]) PARALLEL SAFE; +ALTER FUNCTION isdefined(hstore, text) PARALLEL SAFE; +ALTER FUNCTION defined(hstore, text) PARALLEL SAFE; +ALTER FUNCTION delete(hstore, text) PARALLEL SAFE; +ALTER FUNCTION delete(hstore, text[]) PARALLEL SAFE; +ALTER FUNCTION delete(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION hs_concat(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION hs_contains(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION hs_contained(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION tconvert(text, text) PARALLEL SAFE; +ALTER FUNCTION hstore(text, text) PARALLEL SAFE; +ALTER FUNCTION hstore(text[], text[]) PARALLEL SAFE; +ALTER FUNCTION hstore(text[]) PARALLEL SAFE; +ALTER FUNCTION hstore_to_json(hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_to_json_loose(hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_to_jsonb(hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_to_jsonb_loose(hstore) PARALLEL SAFE; +ALTER FUNCTION hstore(record) PARALLEL SAFE; +ALTER FUNCTION hstore_to_array(hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_to_matrix(hstore) PARALLEL SAFE; +ALTER FUNCTION akeys(hstore) PARALLEL SAFE; +ALTER FUNCTION avals(hstore) PARALLEL SAFE; +ALTER FUNCTION skeys(hstore) PARALLEL SAFE; +ALTER FUNCTION svals(hstore) PARALLEL SAFE; +ALTER FUNCTION each(hstore) PARALLEL SAFE; +ALTER FUNCTION populate_record(anyelement, hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_eq(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_ne(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_gt(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_ge(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_lt(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_le(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_cmp(hstore, hstore) PARALLEL SAFE; +ALTER FUNCTION hstore_hash(hstore) PARALLEL SAFE; +ALTER FUNCTION ghstore_in(cstring) PARALLEL SAFE; +ALTER FUNCTION ghstore_out(ghstore) PARALLEL SAFE; +ALTER FUNCTION ghstore_compress(internal) PARALLEL SAFE; +ALTER FUNCTION ghstore_decompress(internal) PARALLEL SAFE; +ALTER FUNCTION ghstore_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION ghstore_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION ghstore_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION ghstore_same(ghstore, ghstore, internal) PARALLEL SAFE; +ALTER FUNCTION ghstore_consistent(internal, hstore, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gin_extract_hstore(hstore, internal) PARALLEL SAFE; +ALTER FUNCTION gin_extract_hstore_query(hstore, internal, int2, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gin_consistent_hstore(internal, int2, hstore, int4, internal, internal) PARALLEL SAFE; diff --git a/contrib/hstore/hstore--1.4--1.5.sql b/contrib/hstore/hstore--1.4--1.5.sql new file mode 100644 index 0000000..92c1832 --- /dev/null +++ b/contrib/hstore/hstore--1.4--1.5.sql @@ -0,0 +1,14 @@ +/* contrib/hstore/hstore--1.4--1.5.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION hstore UPDATE TO '1.5'" to load this file. \quit + +ALTER OPERATOR #<=# (hstore, hstore) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +ALTER OPERATOR #>=# (hstore, hstore) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); diff --git a/contrib/hstore/hstore--1.4.sql b/contrib/hstore/hstore--1.4.sql new file mode 100644 index 0000000..4294d14 --- /dev/null +++ b/contrib/hstore/hstore--1.4.sql @@ -0,0 +1,550 @@ +/* contrib/hstore/hstore--1.4.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION hstore" to load this file. \quit + +CREATE TYPE hstore; + +CREATE FUNCTION hstore_in(cstring) +RETURNS hstore +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION hstore_out(hstore) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION hstore_recv(internal) +RETURNS hstore +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION hstore_send(hstore) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE TYPE hstore ( + INTERNALLENGTH = -1, + INPUT = hstore_in, + OUTPUT = hstore_out, + RECEIVE = hstore_recv, + SEND = hstore_send, + STORAGE = extended +); + +CREATE FUNCTION hstore_version_diag(hstore) +RETURNS integer +AS 'MODULE_PATHNAME','hstore_version_diag' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION fetchval(hstore,text) +RETURNS text +AS 'MODULE_PATHNAME','hstore_fetchval' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR -> ( + LEFTARG = hstore, + RIGHTARG = text, + PROCEDURE = fetchval +); + +CREATE FUNCTION slice_array(hstore,text[]) +RETURNS text[] +AS 'MODULE_PATHNAME','hstore_slice_to_array' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR -> ( + LEFTARG = hstore, + RIGHTARG = text[], + PROCEDURE = slice_array +); + +CREATE FUNCTION slice(hstore,text[]) +RETURNS hstore +AS 'MODULE_PATHNAME','hstore_slice_to_hstore' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION isexists(hstore,text) +RETURNS bool +AS 'MODULE_PATHNAME','hstore_exists' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION exist(hstore,text) +RETURNS bool +AS 'MODULE_PATHNAME','hstore_exists' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR ? ( + LEFTARG = hstore, + RIGHTARG = text, + PROCEDURE = exist, + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION exists_any(hstore,text[]) +RETURNS bool +AS 'MODULE_PATHNAME','hstore_exists_any' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR ?| ( + LEFTARG = hstore, + RIGHTARG = text[], + PROCEDURE = exists_any, + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION exists_all(hstore,text[]) +RETURNS bool +AS 'MODULE_PATHNAME','hstore_exists_all' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR ?& ( + LEFTARG = hstore, + RIGHTARG = text[], + PROCEDURE = exists_all, + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION isdefined(hstore,text) +RETURNS bool +AS 'MODULE_PATHNAME','hstore_defined' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION defined(hstore,text) +RETURNS bool +AS 'MODULE_PATHNAME','hstore_defined' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION delete(hstore,text) +RETURNS hstore +AS 'MODULE_PATHNAME','hstore_delete' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION delete(hstore,text[]) +RETURNS hstore +AS 'MODULE_PATHNAME','hstore_delete_array' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION delete(hstore,hstore) +RETURNS hstore +AS 'MODULE_PATHNAME','hstore_delete_hstore' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR - ( + LEFTARG = hstore, + RIGHTARG = text, + PROCEDURE = delete +); + +CREATE OPERATOR - ( + LEFTARG = hstore, + RIGHTARG = text[], + PROCEDURE = delete +); + +CREATE OPERATOR - ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = delete +); + +CREATE FUNCTION hs_concat(hstore,hstore) +RETURNS hstore +AS 'MODULE_PATHNAME','hstore_concat' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR || ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hs_concat +); + +CREATE FUNCTION hs_contains(hstore,hstore) +RETURNS bool +AS 'MODULE_PATHNAME','hstore_contains' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION hs_contained(hstore,hstore) +RETURNS bool +AS 'MODULE_PATHNAME','hstore_contained' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR @> ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hs_contains, + COMMUTATOR = '<@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR <@ ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hs_contained, + COMMUTATOR = '@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +-- obsolete: +CREATE OPERATOR @ ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hs_contains, + COMMUTATOR = '~', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ~ ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hs_contained, + COMMUTATOR = '@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION tconvert(text,text) +RETURNS hstore +AS 'MODULE_PATHNAME','hstore_from_text' +LANGUAGE C IMMUTABLE PARALLEL SAFE; -- not STRICT; needs to allow (key,NULL) + +CREATE FUNCTION hstore(text,text) +RETURNS hstore +AS 'MODULE_PATHNAME','hstore_from_text' +LANGUAGE C IMMUTABLE PARALLEL SAFE; -- not STRICT; needs to allow (key,NULL) + +CREATE FUNCTION hstore(text[],text[]) +RETURNS hstore +AS 'MODULE_PATHNAME', 'hstore_from_arrays' +LANGUAGE C IMMUTABLE PARALLEL SAFE; -- not STRICT; allows (keys,null) + +CREATE FUNCTION hstore(text[]) +RETURNS hstore +AS 'MODULE_PATHNAME', 'hstore_from_array' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE CAST (text[] AS hstore) + WITH FUNCTION hstore(text[]); + +CREATE FUNCTION hstore_to_json(hstore) +RETURNS json +AS 'MODULE_PATHNAME', 'hstore_to_json' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE CAST (hstore AS json) + WITH FUNCTION hstore_to_json(hstore); + +CREATE FUNCTION hstore_to_json_loose(hstore) +RETURNS json +AS 'MODULE_PATHNAME', 'hstore_to_json_loose' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION hstore_to_jsonb(hstore) +RETURNS jsonb +AS 'MODULE_PATHNAME', 'hstore_to_jsonb' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE CAST (hstore AS jsonb) + WITH FUNCTION hstore_to_jsonb(hstore); + +CREATE FUNCTION hstore_to_jsonb_loose(hstore) +RETURNS jsonb +AS 'MODULE_PATHNAME', 'hstore_to_jsonb_loose' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION hstore(record) +RETURNS hstore +AS 'MODULE_PATHNAME', 'hstore_from_record' +LANGUAGE C IMMUTABLE PARALLEL SAFE; -- not STRICT; allows (null::recordtype) + +CREATE FUNCTION hstore_to_array(hstore) +RETURNS text[] +AS 'MODULE_PATHNAME','hstore_to_array' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR %% ( + RIGHTARG = hstore, + PROCEDURE = hstore_to_array +); + +CREATE FUNCTION hstore_to_matrix(hstore) +RETURNS text[] +AS 'MODULE_PATHNAME','hstore_to_matrix' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR %# ( + RIGHTARG = hstore, + PROCEDURE = hstore_to_matrix +); + +CREATE FUNCTION akeys(hstore) +RETURNS text[] +AS 'MODULE_PATHNAME','hstore_akeys' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION avals(hstore) +RETURNS text[] +AS 'MODULE_PATHNAME','hstore_avals' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION skeys(hstore) +RETURNS setof text +AS 'MODULE_PATHNAME','hstore_skeys' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION svals(hstore) +RETURNS setof text +AS 'MODULE_PATHNAME','hstore_svals' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION each(IN hs hstore, + OUT key text, + OUT value text) +RETURNS SETOF record +AS 'MODULE_PATHNAME','hstore_each' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION populate_record(anyelement,hstore) +RETURNS anyelement +AS 'MODULE_PATHNAME', 'hstore_populate_record' +LANGUAGE C IMMUTABLE PARALLEL SAFE; -- not STRICT; allows (null::rectype,hstore) + +CREATE OPERATOR #= ( + LEFTARG = anyelement, + RIGHTARG = hstore, + PROCEDURE = populate_record +); + +-- btree support + +CREATE FUNCTION hstore_eq(hstore,hstore) +RETURNS boolean +AS 'MODULE_PATHNAME','hstore_eq' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION hstore_ne(hstore,hstore) +RETURNS boolean +AS 'MODULE_PATHNAME','hstore_ne' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION hstore_gt(hstore,hstore) +RETURNS boolean +AS 'MODULE_PATHNAME','hstore_gt' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION hstore_ge(hstore,hstore) +RETURNS boolean +AS 'MODULE_PATHNAME','hstore_ge' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION hstore_lt(hstore,hstore) +RETURNS boolean +AS 'MODULE_PATHNAME','hstore_lt' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION hstore_le(hstore,hstore) +RETURNS boolean +AS 'MODULE_PATHNAME','hstore_le' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION hstore_cmp(hstore,hstore) +RETURNS integer +AS 'MODULE_PATHNAME','hstore_cmp' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR = ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hstore_eq, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES +); +CREATE OPERATOR <> ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hstore_ne, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +-- the comparison operators have funky names (and are undocumented) +-- in an attempt to discourage anyone from actually using them. they +-- only exist to support the btree opclass + +CREATE OPERATOR #<# ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hstore_lt, + COMMUTATOR = #>#, + NEGATOR = #>=#, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); +CREATE OPERATOR #<=# ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hstore_le, + COMMUTATOR = #>=#, + NEGATOR = #>#, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); +CREATE OPERATOR #># ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hstore_gt, + COMMUTATOR = #<#, + NEGATOR = #<=#, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); +CREATE OPERATOR #>=# ( + LEFTARG = hstore, + RIGHTARG = hstore, + PROCEDURE = hstore_ge, + COMMUTATOR = #<=#, + NEGATOR = #<#, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR CLASS btree_hstore_ops +DEFAULT FOR TYPE hstore USING btree +AS + OPERATOR 1 #<# , + OPERATOR 2 #<=# , + OPERATOR 3 = , + OPERATOR 4 #>=# , + OPERATOR 5 #># , + FUNCTION 1 hstore_cmp(hstore,hstore); + +-- hash support + +CREATE FUNCTION hstore_hash(hstore) +RETURNS integer +AS 'MODULE_PATHNAME','hstore_hash' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR CLASS hash_hstore_ops +DEFAULT FOR TYPE hstore USING hash +AS + OPERATOR 1 = , + FUNCTION 1 hstore_hash(hstore); + +-- GiST support + +CREATE TYPE ghstore; + +CREATE FUNCTION ghstore_in(cstring) +RETURNS ghstore +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ghstore_out(ghstore) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE TYPE ghstore ( + INTERNALLENGTH = -1, + INPUT = ghstore_in, + OUTPUT = ghstore_out +); + +CREATE FUNCTION ghstore_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ghstore_decompress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ghstore_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ghstore_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ghstore_union(internal, internal) +RETURNS ghstore +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ghstore_same(ghstore, ghstore, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ghstore_consistent(internal,hstore,smallint,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR CLASS gist_hstore_ops +DEFAULT FOR TYPE hstore USING gist +AS + OPERATOR 7 @> , + OPERATOR 9 ?(hstore,text) , + OPERATOR 10 ?|(hstore,text[]) , + OPERATOR 11 ?&(hstore,text[]) , + --OPERATOR 8 <@ , + OPERATOR 13 @ , + --OPERATOR 14 ~ , + FUNCTION 1 ghstore_consistent (internal, hstore, smallint, oid, internal), + FUNCTION 2 ghstore_union (internal, internal), + FUNCTION 3 ghstore_compress (internal), + FUNCTION 4 ghstore_decompress (internal), + FUNCTION 5 ghstore_penalty (internal, internal, internal), + FUNCTION 6 ghstore_picksplit (internal, internal), + FUNCTION 7 ghstore_same (ghstore, ghstore, internal), + STORAGE ghstore; + +-- GIN support + +CREATE FUNCTION gin_extract_hstore(hstore, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gin_extract_hstore_query(hstore, internal, int2, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gin_consistent_hstore(internal, int2, hstore, int4, internal, internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR CLASS gin_hstore_ops +DEFAULT FOR TYPE hstore USING gin +AS + OPERATOR 7 @>, + OPERATOR 9 ?(hstore,text), + OPERATOR 10 ?|(hstore,text[]), + OPERATOR 11 ?&(hstore,text[]), + FUNCTION 1 bttextcmp(text,text), + FUNCTION 2 gin_extract_hstore(hstore, internal), + FUNCTION 3 gin_extract_hstore_query(hstore, internal, int2, internal, internal), + FUNCTION 4 gin_consistent_hstore(internal, int2, hstore, int4, internal, internal), + STORAGE text; diff --git a/contrib/hstore/hstore--1.5--1.6.sql b/contrib/hstore/hstore--1.5--1.6.sql new file mode 100644 index 0000000..c5a2bae --- /dev/null +++ b/contrib/hstore/hstore--1.5--1.6.sql @@ -0,0 +1,12 @@ +/* contrib/hstore/hstore--1.5--1.6.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION hstore UPDATE TO '1.6'" to load this file. \quit + +CREATE FUNCTION hstore_hash_extended(hstore, int8) +RETURNS int8 +AS 'MODULE_PATHNAME','hstore_hash_extended' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +ALTER OPERATOR FAMILY hash_hstore_ops USING hash ADD + FUNCTION 2 hstore_hash_extended(hstore, int8); diff --git a/contrib/hstore/hstore--1.6--1.7.sql b/contrib/hstore/hstore--1.6--1.7.sql new file mode 100644 index 0000000..3e5cb67 --- /dev/null +++ b/contrib/hstore/hstore--1.6--1.7.sql @@ -0,0 +1,27 @@ +/* contrib/hstore/hstore--1.6--1.7.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION hstore UPDATE TO '1.7'" to load this file. \quit + +CREATE FUNCTION ghstore_options(internal) +RETURNS void +AS 'MODULE_PATHNAME', 'ghstore_options' +LANGUAGE C IMMUTABLE PARALLEL SAFE; + +ALTER OPERATOR FAMILY gist_hstore_ops USING gist +ADD FUNCTION 10 (hstore) ghstore_options (internal); + +ALTER OPERATOR ? (hstore, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ?| (hstore, text[]) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ?& (hstore, text[]) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR @> (hstore, hstore) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR <@ (hstore, hstore) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR @ (hstore, hstore) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ~ (hstore, hstore) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); diff --git a/contrib/hstore/hstore--1.7--1.8.sql b/contrib/hstore/hstore--1.7--1.8.sql new file mode 100644 index 0000000..fb450a9 --- /dev/null +++ b/contrib/hstore/hstore--1.7--1.8.sql @@ -0,0 +1,17 @@ +/* contrib/hstore/hstore--1.7--1.8.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION hstore UPDATE TO '1.8'" to load this file. \quit + +CREATE FUNCTION hstore_subscript_handler(internal) +RETURNS internal +AS 'MODULE_PATHNAME', 'hstore_subscript_handler' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +ALTER TYPE hstore SET ( + SUBSCRIPT = hstore_subscript_handler +); + +-- Remove @ and ~ +DROP OPERATOR @ (hstore, hstore); +DROP OPERATOR ~ (hstore, hstore); diff --git a/contrib/hstore/hstore.control b/contrib/hstore/hstore.control new file mode 100644 index 0000000..89e3c74 --- /dev/null +++ b/contrib/hstore/hstore.control @@ -0,0 +1,6 @@ +# hstore extension +comment = 'data type for storing sets of (key, value) pairs' +default_version = '1.8' +module_pathname = '$libdir/hstore' +relocatable = true +trusted = true diff --git a/contrib/hstore/hstore.h b/contrib/hstore/hstore.h new file mode 100644 index 0000000..897af24 --- /dev/null +++ b/contrib/hstore/hstore.h @@ -0,0 +1,205 @@ +/* + * contrib/hstore/hstore.h + */ +#ifndef __HSTORE_H__ +#define __HSTORE_H__ + +#include "fmgr.h" +#include "utils/array.h" + + +/* + * HEntry: there is one of these for each key _and_ value in an hstore + * + * the position offset points to the _end_ so that we can get the length + * by subtraction from the previous entry. the ISFIRST flag lets us tell + * whether there is a previous entry. + */ +typedef struct +{ + uint32 entry; +} HEntry; + +#define HENTRY_ISFIRST 0x80000000 +#define HENTRY_ISNULL 0x40000000 +#define HENTRY_POSMASK 0x3FFFFFFF + +/* note possible multiple evaluations, also access to prior array element */ +#define HSE_ISFIRST(he_) (((he_).entry & HENTRY_ISFIRST) != 0) +#define HSE_ISNULL(he_) (((he_).entry & HENTRY_ISNULL) != 0) +#define HSE_ENDPOS(he_) ((he_).entry & HENTRY_POSMASK) +#define HSE_OFF(he_) (HSE_ISFIRST(he_) ? 0 : HSE_ENDPOS((&(he_))[-1])) +#define HSE_LEN(he_) (HSE_ISFIRST(he_) \ + ? HSE_ENDPOS(he_) \ + : HSE_ENDPOS(he_) - HSE_ENDPOS((&(he_))[-1])) + +/* + * determined by the size of "endpos" (ie HENTRY_POSMASK), though this is a + * bit academic since currently varlenas (and hence both the input and the + * whole hstore) have the same limit + */ +#define HSTORE_MAX_KEY_LEN 0x3FFFFFFF +#define HSTORE_MAX_VALUE_LEN 0x3FFFFFFF + +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + uint32 size_; /* flags and number of items in hstore */ + /* array of HEntry follows */ +} HStore; + +/* + * It's not possible to get more than 2^28 items into an hstore, so we reserve + * the top few bits of the size field. See hstore_compat.c for one reason + * why. Some bits are left for future use here. MaxAllocSize makes the + * practical count limit slightly more than 2^28 / 3, or INT_MAX / 24, the + * limit for an hstore full of 4-byte keys and null values. Therefore, we + * don't explicitly check the format-imposed limit. + */ +#define HS_FLAG_NEWVERSION 0x80000000 + +#define HS_COUNT(hsp_) ((hsp_)->size_ & 0x0FFFFFFF) +#define HS_SETCOUNT(hsp_,c_) ((hsp_)->size_ = (c_) | HS_FLAG_NEWVERSION) + + +/* + * "x" comes from an existing HS_COUNT() (as discussed, <= INT_MAX/24) or a + * Pairs array length (due to MaxAllocSize, <= INT_MAX/40). "lenstr" is no + * more than INT_MAX, that extreme case arising in hstore_from_arrays(). + * Therefore, this calculation is limited to about INT_MAX / 5 + INT_MAX. + */ +#define HSHRDSIZE (sizeof(HStore)) +#define CALCDATASIZE(x, lenstr) ( (x) * 2 * sizeof(HEntry) + HSHRDSIZE + (lenstr) ) + +/* note multiple evaluations of x */ +#define ARRPTR(x) ( (HEntry*) ( (HStore*)(x) + 1 ) ) +#define STRPTR(x) ( (char*)(ARRPTR(x) + HS_COUNT((HStore*)(x)) * 2) ) + +/* note multiple/non evaluations */ +#define HSTORE_KEY(arr_,str_,i_) ((str_) + HSE_OFF((arr_)[2*(i_)])) +#define HSTORE_VAL(arr_,str_,i_) ((str_) + HSE_OFF((arr_)[2*(i_)+1])) +#define HSTORE_KEYLEN(arr_,i_) (HSE_LEN((arr_)[2*(i_)])) +#define HSTORE_VALLEN(arr_,i_) (HSE_LEN((arr_)[2*(i_)+1])) +#define HSTORE_VALISNULL(arr_,i_) (HSE_ISNULL((arr_)[2*(i_)+1])) + +/* + * currently, these following macros are the _only_ places that rely + * on internal knowledge of HEntry. Everything else should be using + * the above macros. Exception: the in-place upgrade in hstore_compat.c + * messes with entries directly. + */ + +/* + * copy one key/value pair (which must be contiguous starting at + * sptr_) into an under-construction hstore; dent_ is an HEntry*, + * dbuf_ is the destination's string buffer, dptr_ is the current + * position in the destination. lots of modification and multiple + * evaluation here. + */ +#define HS_COPYITEM(dent_,dbuf_,dptr_,sptr_,klen_,vlen_,vnull_) \ + do { \ + memcpy((dptr_), (sptr_), (klen_)+(vlen_)); \ + (dptr_) += (klen_)+(vlen_); \ + (dent_)++->entry = ((dptr_) - (dbuf_) - (vlen_)) & HENTRY_POSMASK; \ + (dent_)++->entry = ((((dptr_) - (dbuf_)) & HENTRY_POSMASK) \ + | ((vnull_) ? HENTRY_ISNULL : 0)); \ + } while(0) + +/* + * add one key/item pair, from a Pairs structure, into an + * under-construction hstore + */ +#define HS_ADDITEM(dent_,dbuf_,dptr_,pair_) \ + do { \ + memcpy((dptr_), (pair_).key, (pair_).keylen); \ + (dptr_) += (pair_).keylen; \ + (dent_)++->entry = ((dptr_) - (dbuf_)) & HENTRY_POSMASK; \ + if ((pair_).isnull) \ + (dent_)++->entry = ((((dptr_) - (dbuf_)) & HENTRY_POSMASK) \ + | HENTRY_ISNULL); \ + else \ + { \ + memcpy((dptr_), (pair_).val, (pair_).vallen); \ + (dptr_) += (pair_).vallen; \ + (dent_)++->entry = ((dptr_) - (dbuf_)) & HENTRY_POSMASK; \ + } \ + } while (0) + +/* finalize a newly-constructed hstore */ +#define HS_FINALIZE(hsp_,count_,buf_,ptr_) \ + do { \ + int _buflen = (ptr_) - (buf_); \ + if ((count_)) \ + ARRPTR(hsp_)[0].entry |= HENTRY_ISFIRST; \ + if ((count_) != HS_COUNT((hsp_))) \ + { \ + HS_SETCOUNT((hsp_),(count_)); \ + memmove(STRPTR(hsp_), (buf_), _buflen); \ + } \ + SET_VARSIZE((hsp_), CALCDATASIZE((count_), _buflen)); \ + } while (0) + +/* ensure the varlena size of an existing hstore is correct */ +#define HS_FIXSIZE(hsp_,count_) \ + do { \ + int bl = (count_) ? HSE_ENDPOS(ARRPTR(hsp_)[2*(count_)-1]) : 0; \ + SET_VARSIZE((hsp_), CALCDATASIZE((count_),bl)); \ + } while (0) + +/* DatumGetHStoreP includes support for reading old-format hstore values */ +extern PGDLLEXPORT HStore *hstoreUpgrade(Datum orig); + +#define DatumGetHStoreP(d) hstoreUpgrade(d) + +#define PG_GETARG_HSTORE_P(x) DatumGetHStoreP(PG_GETARG_DATUM(x)) + + +/* + * Pairs is a "decompressed" representation of one key/value pair. + * The two strings are not necessarily null-terminated. + */ +typedef struct +{ + char *key; + char *val; + size_t keylen; + size_t vallen; + bool isnull; /* value is null? */ + bool needfree; /* need to pfree the value? */ +} Pairs; + +extern PGDLLEXPORT int hstoreUniquePairs(Pairs *a, int32 l, int32 *buflen); +extern PGDLLEXPORT HStore *hstorePairs(Pairs *pairs, int32 pcount, int32 buflen); + +extern PGDLLEXPORT size_t hstoreCheckKeyLen(size_t len); +extern PGDLLEXPORT size_t hstoreCheckValLen(size_t len); + +extern PGDLLEXPORT int hstoreFindKey(HStore *hs, int *lowbound, char *key, int keylen); +extern PGDLLEXPORT Pairs *hstoreArrayToPairs(ArrayType *a, int *npairs); + +#define HStoreContainsStrategyNumber 7 +#define HStoreExistsStrategyNumber 9 +#define HStoreExistsAnyStrategyNumber 10 +#define HStoreExistsAllStrategyNumber 11 +#define HStoreOldContainsStrategyNumber 13 /* backwards compatibility */ + +/* + * defining HSTORE_POLLUTE_NAMESPACE=0 will prevent use of old function names; + * for now, we default to on for the benefit of people restoring old dumps + */ +#ifndef HSTORE_POLLUTE_NAMESPACE +#define HSTORE_POLLUTE_NAMESPACE 1 +#endif + +#if HSTORE_POLLUTE_NAMESPACE +#define HSTORE_POLLUTE(newname_,oldname_) \ + PG_FUNCTION_INFO_V1(oldname_); \ + extern PGDLLEXPORT Datum newname_(PG_FUNCTION_ARGS); \ + Datum oldname_(PG_FUNCTION_ARGS) { return newname_(fcinfo); } \ + extern int no_such_variable +#else +#define HSTORE_POLLUTE(newname_,oldname_) \ + extern int no_such_variable +#endif + +#endif /* __HSTORE_H__ */ diff --git a/contrib/hstore/hstore_compat.c b/contrib/hstore/hstore_compat.c new file mode 100644 index 0000000..d75e9cb --- /dev/null +++ b/contrib/hstore/hstore_compat.c @@ -0,0 +1,359 @@ +/* + * contrib/hstore/hstore_compat.c + * + * Notes on old/new hstore format disambiguation. + * + * There are three formats to consider: + * 1) old contrib/hstore (referred to as hstore-old) + * 2) prerelease pgfoundry hstore + * 3) new contrib/hstore + * + * (2) and (3) are identical except for the HS_FLAG_NEWVERSION + * bit, which is set in (3) but not (2). + * + * Values that are already in format (3), or which are + * unambiguously in format (2), are handled by the first + * "return immediately" test in hstoreUpgrade(). + * + * To stress a point: we ONLY get here with possibly-ambiguous + * values if we're doing some sort of in-place migration from an + * old prerelease pgfoundry hstore-new; and we explicitly don't + * support that without fixing up any potentially padded values + * first. Most of the code here is serious overkill, but the + * performance penalty isn't serious (especially compared to the + * palloc() that we have to do anyway) and the belt-and-braces + * validity checks provide some reassurance. (If for some reason + * we get a value that would have worked on the old code, but + * which would be botched by the conversion code, the validity + * checks will fail it first so we get an error rather than bad + * data.) + * + * Note also that empty hstores are the same in (2) and (3), so + * there are some special-case paths for them. + * + * We tell the difference between formats (2) and (3) as follows (but + * note that there are some edge cases where we can't tell; see + * comments in hstoreUpgrade): + * + * First, since there must be at least one entry, we look at + * how the bits line up. The new format looks like: + * + * 10kkkkkkkkkkkkkkkkkkkkkkkkkkkkkk (k..k = keylen) + * 0nvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv (v..v = keylen+vallen) + * + * The old format looks like one of these, depending on endianness + * and bitfield layout: (k..k = keylen, v..v = vallen, p..p = pos, + * n = isnull) + * + * kkkkkkkkkkkkkkkkvvvvvvvvvvvvvvvv + * nppppppppppppppppppppppppppppppp + * + * kkkkkkkkkkkkkkkkvvvvvvvvvvvvvvvv + * pppppppppppppppppppppppppppppppn + * + * vvvvvvvvvvvvvvvvkkkkkkkkkkkkkkkk + * nppppppppppppppppppppppppppppppp + * + * vvvvvvvvvvvvvvvvkkkkkkkkkkkkkkkk + * pppppppppppppppppppppppppppppppn (usual i386 format) + * + * If the entry is in old format, for the first entry "pos" must be 0. + * We can obviously see that either keylen or vallen must be >32768 + * for there to be any ambiguity (which is why lengths less than that + * are fasttracked in hstore.h) Since "pos"==0, the "v" field in the + * new-format interpretation can only be 0 or 1, which constrains all + * but three bits of the old-format's k and v fields. But in addition + * to all of this, the data length implied by the keylen and vallen + * must fit in the varlena size. So the only ambiguous edge case for + * hstores with only one entry occurs between a new-format entry with + * an excess (~32k) of padding, and an old-format entry. But we know + * which format to use in that case based on how we were compiled, so + * no actual data corruption can occur. + * + * If there is more than one entry, the requirement that keys do not + * decrease in length, and that positions increase contiguously, and + * that the end of the data not be beyond the end of the varlena + * itself, disambiguates in almost all other cases. There is a small + * set of ambiguous cases which could occur if the old-format value + * has a large excess of padding and just the right pattern of key + * sizes, but these are also handled based on how we were compiled. + * + * The otherwise undocumented function hstore_version_diag is provided + * for testing purposes. + */ +#include "postgres.h" + + +#include "hstore.h" + +/* + * This is the structure used for entries in the old contrib/hstore + * implementation. Notice that this is the same size as the new entry + * (two 32-bit words per key/value pair) and that the header is the + * same, so the old and new versions of ARRPTR, STRPTR, CALCDATASIZE + * etc. are compatible. + * + * If the above statement isn't true on some bizarre platform, we're + * a bit hosed (see StaticAssertStmt in hstoreValidOldFormat). + */ +typedef struct +{ + uint16 keylen; + uint16 vallen; + uint32 + valisnull:1, + pos:31; +} HOldEntry; + +static int hstoreValidNewFormat(HStore *hs); +static int hstoreValidOldFormat(HStore *hs); + + +/* + * Validity test for a new-format hstore. + * 0 = not valid + * 1 = valid but with "slop" in the length + * 2 = exactly valid + */ +static int +hstoreValidNewFormat(HStore *hs) +{ + int count = HS_COUNT(hs); + HEntry *entries = ARRPTR(hs); + int buflen = (count) ? HSE_ENDPOS(entries[2 * (count) - 1]) : 0; + int vsize = CALCDATASIZE(count, buflen); + int i; + + if (hs->size_ & HS_FLAG_NEWVERSION) + return 2; + + if (count == 0) + return 2; + + if (!HSE_ISFIRST(entries[0])) + return 0; + + if (vsize > VARSIZE(hs)) + return 0; + + /* entry position must be nondecreasing */ + + for (i = 1; i < 2 * count; ++i) + { + if (HSE_ISFIRST(entries[i]) + || (HSE_ENDPOS(entries[i]) < HSE_ENDPOS(entries[i - 1]))) + return 0; + } + + /* key length must be nondecreasing and keys must not be null */ + + for (i = 1; i < count; ++i) + { + if (HSTORE_KEYLEN(entries, i) < HSTORE_KEYLEN(entries, i - 1)) + return 0; + if (HSE_ISNULL(entries[2 * i])) + return 0; + } + + if (vsize != VARSIZE(hs)) + return 1; + + return 2; +} + +/* + * Validity test for an old-format hstore. + * 0 = not valid + * 1 = valid but with "slop" in the length + * 2 = exactly valid + */ +static int +hstoreValidOldFormat(HStore *hs) +{ + int count = hs->size_; + HOldEntry *entries = (HOldEntry *) ARRPTR(hs); + int vsize; + int lastpos = 0; + int i; + + if (hs->size_ & HS_FLAG_NEWVERSION) + return 0; + + /* New format uses an HEntry for key and another for value */ + StaticAssertStmt(sizeof(HOldEntry) == 2 * sizeof(HEntry), + "old hstore format is not upward-compatible"); + + if (count == 0) + return 2; + + if (count > 0xFFFFFFF) + return 0; + + if (CALCDATASIZE(count, 0) > VARSIZE(hs)) + return 0; + + if (entries[0].pos != 0) + return 0; + + /* key length must be nondecreasing */ + + for (i = 1; i < count; ++i) + { + if (entries[i].keylen < entries[i - 1].keylen) + return 0; + } + + /* + * entry position must be strictly increasing, except for the first entry + * (which can be ""=>"" and thus zero-length); and all entries must be + * properly contiguous + */ + + for (i = 0; i < count; ++i) + { + if (entries[i].pos != lastpos) + return 0; + lastpos += (entries[i].keylen + + ((entries[i].valisnull) ? 0 : entries[i].vallen)); + } + + vsize = CALCDATASIZE(count, lastpos); + + if (vsize > VARSIZE(hs)) + return 0; + + if (vsize != VARSIZE(hs)) + return 1; + + return 2; +} + + +/* + * hstoreUpgrade: PG_DETOAST_DATUM plus support for conversion of old hstores + */ +HStore * +hstoreUpgrade(Datum orig) +{ + HStore *hs = (HStore *) PG_DETOAST_DATUM(orig); + int valid_new; + int valid_old; + + /* Return immediately if no conversion needed */ + if (hs->size_ & HS_FLAG_NEWVERSION) + return hs; + + /* Do we have a writable copy? If not, make one. */ + if ((void *) hs == (void *) DatumGetPointer(orig)) + hs = (HStore *) PG_DETOAST_DATUM_COPY(orig); + + if (hs->size_ == 0 || + (VARSIZE(hs) < 32768 && HSE_ISFIRST((ARRPTR(hs)[0])))) + { + HS_SETCOUNT(hs, HS_COUNT(hs)); + HS_FIXSIZE(hs, HS_COUNT(hs)); + return hs; + } + + valid_new = hstoreValidNewFormat(hs); + valid_old = hstoreValidOldFormat(hs); + + if (!valid_old || hs->size_ == 0) + { + if (valid_new) + { + /* + * force the "new version" flag and the correct varlena length. + */ + HS_SETCOUNT(hs, HS_COUNT(hs)); + HS_FIXSIZE(hs, HS_COUNT(hs)); + return hs; + } + else + { + elog(ERROR, "invalid hstore value found"); + } + } + + /* + * this is the tricky edge case. It is only possible in some quite extreme + * cases (the hstore must have had a lot of wasted padding space at the + * end). But the only way a "new" hstore value could get here is if we're + * upgrading in place from a pre-release version of hstore-new (NOT + * contrib/hstore), so we work off the following assumptions: 1. If you're + * moving from old contrib/hstore to hstore-new, you're required to fix up + * any potential conflicts first, e.g. by running ALTER TABLE ... USING + * col::text::hstore; on all hstore columns before upgrading. 2. If you're + * moving from old contrib/hstore to new contrib/hstore, then "new" values + * are impossible here 3. If you're moving from pre-release hstore-new to + * hstore-new, then "old" values are impossible here 4. If you're moving + * from pre-release hstore-new to new contrib/hstore, you're not doing so + * as an in-place upgrade, so there is no issue So the upshot of all this + * is that we can treat all the edge cases as "new" if we're being built + * as hstore-new, and "old" if we're being built as contrib/hstore. + * + * XXX the WARNING can probably be downgraded to DEBUG1 once this has been + * beta-tested. But for now, it would be very useful to know if anyone can + * actually reach this case in a non-contrived setting. + */ + + if (valid_new) + { +#ifdef HSTORE_IS_HSTORE_NEW + elog(WARNING, "ambiguous hstore value resolved as hstore-new"); + + /* + * force the "new version" flag and the correct varlena length. + */ + HS_SETCOUNT(hs, HS_COUNT(hs)); + HS_FIXSIZE(hs, HS_COUNT(hs)); + return hs; +#else + elog(WARNING, "ambiguous hstore value resolved as hstore-old"); +#endif + } + + /* + * must have an old-style value. Overwrite it in place as a new-style one. + */ + { + int count = hs->size_; + HEntry *new_entries = ARRPTR(hs); + HOldEntry *old_entries = (HOldEntry *) ARRPTR(hs); + int i; + + for (i = 0; i < count; ++i) + { + uint32 pos = old_entries[i].pos; + uint32 keylen = old_entries[i].keylen; + uint32 vallen = old_entries[i].vallen; + bool isnull = old_entries[i].valisnull; + + if (isnull) + vallen = 0; + + new_entries[2 * i].entry = (pos + keylen) & HENTRY_POSMASK; + new_entries[2 * i + 1].entry = (((pos + keylen + vallen) & HENTRY_POSMASK) + | ((isnull) ? HENTRY_ISNULL : 0)); + } + + if (count) + new_entries[0].entry |= HENTRY_ISFIRST; + HS_SETCOUNT(hs, count); + HS_FIXSIZE(hs, count); + } + + return hs; +} + + +PG_FUNCTION_INFO_V1(hstore_version_diag); +Datum +hstore_version_diag(PG_FUNCTION_ARGS) +{ + HStore *hs = (HStore *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); + int valid_new = hstoreValidNewFormat(hs); + int valid_old = hstoreValidOldFormat(hs); + + PG_RETURN_INT32(valid_old * 10 + valid_new); +} diff --git a/contrib/hstore/hstore_gin.c b/contrib/hstore/hstore_gin.c new file mode 100644 index 0000000..766c00b --- /dev/null +++ b/contrib/hstore/hstore_gin.c @@ -0,0 +1,210 @@ +/* + * contrib/hstore/hstore_gin.c + */ +#include "postgres.h" + +#include "access/gin.h" +#include "access/stratnum.h" +#include "catalog/pg_type.h" + +#include "hstore.h" + + +/* + * When using a GIN index for hstore, we choose to index both keys and values. + * The storage format is "text" values, with K, V, or N prepended to the string + * to indicate key, value, or null values. (As of 9.1 it might be better to + * store null values as nulls, but we'll keep it this way for on-disk + * compatibility.) + */ +#define KEYFLAG 'K' +#define VALFLAG 'V' +#define NULLFLAG 'N' + +PG_FUNCTION_INFO_V1(gin_extract_hstore); + +/* Build an indexable text value */ +static text * +makeitem(char *str, int len, char flag) +{ + text *item; + + item = (text *) palloc(VARHDRSZ + len + 1); + SET_VARSIZE(item, VARHDRSZ + len + 1); + + *VARDATA(item) = flag; + + if (str && len > 0) + memcpy(VARDATA(item) + 1, str, len); + + return item; +} + +Datum +gin_extract_hstore(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + Datum *entries = NULL; + HEntry *hsent = ARRPTR(hs); + char *ptr = STRPTR(hs); + int count = HS_COUNT(hs); + int i; + + *nentries = 2 * count; + if (count) + entries = (Datum *) palloc(sizeof(Datum) * 2 * count); + + for (i = 0; i < count; ++i) + { + text *item; + + item = makeitem(HSTORE_KEY(hsent, ptr, i), + HSTORE_KEYLEN(hsent, i), + KEYFLAG); + entries[2 * i] = PointerGetDatum(item); + + if (HSTORE_VALISNULL(hsent, i)) + item = makeitem(NULL, 0, NULLFLAG); + else + item = makeitem(HSTORE_VAL(hsent, ptr, i), + HSTORE_VALLEN(hsent, i), + VALFLAG); + entries[2 * i + 1] = PointerGetDatum(item); + } + + PG_RETURN_POINTER(entries); +} + +PG_FUNCTION_INFO_V1(gin_extract_hstore_query); + +Datum +gin_extract_hstore_query(PG_FUNCTION_ARGS) +{ + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + StrategyNumber strategy = PG_GETARG_UINT16(2); + int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); + Datum *entries; + + if (strategy == HStoreContainsStrategyNumber) + { + /* Query is an hstore, so just apply gin_extract_hstore... */ + entries = (Datum *) + DatumGetPointer(DirectFunctionCall2(gin_extract_hstore, + PG_GETARG_DATUM(0), + PointerGetDatum(nentries))); + /* ... except that "contains {}" requires a full index scan */ + if (entries == NULL) + *searchMode = GIN_SEARCH_MODE_ALL; + } + else if (strategy == HStoreExistsStrategyNumber) + { + text *query = PG_GETARG_TEXT_PP(0); + text *item; + + *nentries = 1; + entries = (Datum *) palloc(sizeof(Datum)); + item = makeitem(VARDATA_ANY(query), VARSIZE_ANY_EXHDR(query), KEYFLAG); + entries[0] = PointerGetDatum(item); + } + else if (strategy == HStoreExistsAnyStrategyNumber || + strategy == HStoreExistsAllStrategyNumber) + { + ArrayType *query = PG_GETARG_ARRAYTYPE_P(0); + Datum *key_datums; + bool *key_nulls; + int key_count; + int i, + j; + text *item; + + deconstruct_array_builtin(query, TEXTOID, &key_datums, &key_nulls, &key_count); + + entries = (Datum *) palloc(sizeof(Datum) * key_count); + + for (i = 0, j = 0; i < key_count; ++i) + { + /* Nulls in the array are ignored, cf hstoreArrayToPairs */ + if (key_nulls[i]) + continue; + item = makeitem(VARDATA(key_datums[i]), VARSIZE(key_datums[i]) - VARHDRSZ, KEYFLAG); + entries[j++] = PointerGetDatum(item); + } + + *nentries = j; + /* ExistsAll with no keys should match everything */ + if (j == 0 && strategy == HStoreExistsAllStrategyNumber) + *searchMode = GIN_SEARCH_MODE_ALL; + } + else + { + elog(ERROR, "unrecognized strategy number: %d", strategy); + entries = NULL; /* keep compiler quiet */ + } + + PG_RETURN_POINTER(entries); +} + +PG_FUNCTION_INFO_V1(gin_consistent_hstore); + +Datum +gin_consistent_hstore(PG_FUNCTION_ARGS) +{ + bool *check = (bool *) PG_GETARG_POINTER(0); + StrategyNumber strategy = PG_GETARG_UINT16(1); + + /* HStore *query = PG_GETARG_HSTORE_P(2); */ + int32 nkeys = PG_GETARG_INT32(3); + + /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ + bool *recheck = (bool *) PG_GETARG_POINTER(5); + bool res = true; + int32 i; + + if (strategy == HStoreContainsStrategyNumber) + { + /* + * Index doesn't have information about correspondence of keys and + * values, so we need recheck. However, if not all the keys are + * present, we can fail at once. + */ + *recheck = true; + for (i = 0; i < nkeys; i++) + { + if (!check[i]) + { + res = false; + break; + } + } + } + else if (strategy == HStoreExistsStrategyNumber) + { + /* Existence of key is guaranteed in default search mode */ + *recheck = false; + res = true; + } + else if (strategy == HStoreExistsAnyStrategyNumber) + { + /* Existence of key is guaranteed in default search mode */ + *recheck = false; + res = true; + } + else if (strategy == HStoreExistsAllStrategyNumber) + { + /* Testing for all the keys being present gives an exact result */ + *recheck = false; + for (i = 0; i < nkeys; i++) + { + if (!check[i]) + { + res = false; + break; + } + } + } + else + elog(ERROR, "unrecognized strategy number: %d", strategy); + + PG_RETURN_BOOL(res); +} diff --git a/contrib/hstore/hstore_gist.c b/contrib/hstore/hstore_gist.c new file mode 100644 index 0000000..3df0049 --- /dev/null +++ b/contrib/hstore/hstore_gist.c @@ -0,0 +1,623 @@ +/* + * contrib/hstore/hstore_gist.c + */ +#include "postgres.h" + +#include "access/gist.h" +#include "access/reloptions.h" +#include "access/stratnum.h" +#include "catalog/pg_type.h" +#include "hstore.h" +#include "utils/pg_crc.h" + +/* gist_hstore_ops opclass options */ +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int siglen; /* signature length in bytes */ +} GistHstoreOptions; + +/* bigint defines */ +#define BITBYTE 8 +#define SIGLEN_DEFAULT (sizeof(int32) * 4) +#define SIGLEN_MAX GISTMaxIndexKeySize +#define SIGLENBIT(siglen) ((siglen) * BITBYTE) +#define GET_SIGLEN() (PG_HAS_OPCLASS_OPTIONS() ? \ + ((GistHstoreOptions *) PG_GET_OPCLASS_OPTIONS())->siglen : \ + SIGLEN_DEFAULT) + + +typedef char *BITVECP; + +#define LOOPBYTE(siglen) \ + for (i = 0; i < (siglen); i++) + +#define LOOPBIT(siglen) \ + for (i = 0; i < SIGLENBIT(siglen); i++) + +/* beware of multiple evaluation of arguments to these macros! */ +#define GETBYTE(x,i) ( *( (BITVECP)(x) + (int)( (i) / BITBYTE ) ) ) +#define GETBITBYTE(x,i) ( (*((char*)(x)) >> (i)) & 0x01 ) +#define CLRBIT(x,i) GETBYTE(x,i) &= ~( 0x01 << ( (i) % BITBYTE ) ) +#define SETBIT(x,i) GETBYTE(x,i) |= ( 0x01 << ( (i) % BITBYTE ) ) +#define GETBIT(x,i) ( (GETBYTE(x,i) >> ( (i) % BITBYTE )) & 0x01 ) +#define HASHVAL(val, siglen) (((unsigned int)(val)) % SIGLENBIT(siglen)) +#define HASH(sign, val, siglen) SETBIT((sign), HASHVAL(val, siglen)) + +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int32 flag; + char data[FLEXIBLE_ARRAY_MEMBER]; +} GISTTYPE; + +#define ALLISTRUE 0x04 + +#define ISALLTRUE(x) ( ((GISTTYPE*)x)->flag & ALLISTRUE ) + +#define GTHDRSIZE (VARHDRSZ + sizeof(int32)) +#define CALCGTSIZE(flag, siglen) ( GTHDRSIZE+(((flag) & ALLISTRUE) ? 0 : (siglen)) ) + +#define GETSIGN(x) ( (BITVECP)( (char*)x+GTHDRSIZE ) ) + +#define SUMBIT(val) ( \ + GETBITBYTE((val),0) + \ + GETBITBYTE((val),1) + \ + GETBITBYTE((val),2) + \ + GETBITBYTE((val),3) + \ + GETBITBYTE((val),4) + \ + GETBITBYTE((val),5) + \ + GETBITBYTE((val),6) + \ + GETBITBYTE((val),7) \ +) + +#define GETENTRY(vec,pos) ((GISTTYPE *) DatumGetPointer((vec)->vector[(pos)].key)) + +#define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) ) + +/* shorthand for calculating CRC-32 of a single chunk of data. */ +static pg_crc32 +crc32_sz(char *buf, int size) +{ + pg_crc32 crc; + + INIT_TRADITIONAL_CRC32(crc); + COMP_TRADITIONAL_CRC32(crc, buf, size); + FIN_TRADITIONAL_CRC32(crc); + + return crc; +} + + +PG_FUNCTION_INFO_V1(ghstore_in); +PG_FUNCTION_INFO_V1(ghstore_out); + + +Datum +ghstore_in(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type %s", "ghstore"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +Datum +ghstore_out(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot display a value of type %s", "ghstore"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +static GISTTYPE * +ghstore_alloc(bool allistrue, int siglen, BITVECP sign) +{ + int flag = allistrue ? ALLISTRUE : 0; + int size = CALCGTSIZE(flag, siglen); + GISTTYPE *res = palloc(size); + + SET_VARSIZE(res, size); + res->flag = flag; + + if (!allistrue) + { + if (sign) + memcpy(GETSIGN(res), sign, siglen); + else + memset(GETSIGN(res), 0, siglen); + } + + return res; +} + +PG_FUNCTION_INFO_V1(ghstore_consistent); +PG_FUNCTION_INFO_V1(ghstore_compress); +PG_FUNCTION_INFO_V1(ghstore_decompress); +PG_FUNCTION_INFO_V1(ghstore_penalty); +PG_FUNCTION_INFO_V1(ghstore_picksplit); +PG_FUNCTION_INFO_V1(ghstore_union); +PG_FUNCTION_INFO_V1(ghstore_same); +PG_FUNCTION_INFO_V1(ghstore_options); + +Datum +ghstore_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int siglen = GET_SIGLEN(); + GISTENTRY *retval = entry; + + if (entry->leafkey) + { + GISTTYPE *res = ghstore_alloc(false, siglen, NULL); + HStore *val = DatumGetHStoreP(entry->key); + HEntry *hsent = ARRPTR(val); + char *ptr = STRPTR(val); + int count = HS_COUNT(val); + int i; + + for (i = 0; i < count; ++i) + { + int h; + + h = crc32_sz((char *) HSTORE_KEY(hsent, ptr, i), + HSTORE_KEYLEN(hsent, i)); + HASH(GETSIGN(res), h, siglen); + if (!HSTORE_VALISNULL(hsent, i)) + { + h = crc32_sz((char *) HSTORE_VAL(hsent, ptr, i), + HSTORE_VALLEN(hsent, i)); + HASH(GETSIGN(res), h, siglen); + } + } + + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(res), + entry->rel, entry->page, + entry->offset, + false); + } + else if (!ISALLTRUE(DatumGetPointer(entry->key))) + { + int32 i; + GISTTYPE *res; + BITVECP sign = GETSIGN(DatumGetPointer(entry->key)); + + LOOPBYTE(siglen) + { + if ((sign[i] & 0xff) != 0xff) + PG_RETURN_POINTER(retval); + } + + res = ghstore_alloc(true, siglen, NULL); + + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(res), + entry->rel, entry->page, + entry->offset, + false); + } + + PG_RETURN_POINTER(retval); +} + +/* + * Since type ghstore isn't toastable (and doesn't need to be), + * this function can be a no-op. + */ +Datum +ghstore_decompress(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(PG_GETARG_POINTER(0)); +} + +Datum +ghstore_same(PG_FUNCTION_ARGS) +{ + GISTTYPE *a = (GISTTYPE *) PG_GETARG_POINTER(0); + GISTTYPE *b = (GISTTYPE *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + int siglen = GET_SIGLEN(); + + + if (ISALLTRUE(a) && ISALLTRUE(b)) + *result = true; + else if (ISALLTRUE(a)) + *result = false; + else if (ISALLTRUE(b)) + *result = false; + else + { + int32 i; + BITVECP sa = GETSIGN(a), + sb = GETSIGN(b); + + *result = true; + LOOPBYTE(siglen) + { + if (sa[i] != sb[i]) + { + *result = false; + break; + } + } + } + PG_RETURN_POINTER(result); +} + +static int32 +sizebitvec(BITVECP sign, int siglen) +{ + int32 size = 0, + i; + + LOOPBYTE(siglen) + { + size += SUMBIT(sign); + sign = (BITVECP) (((char *) sign) + 1); + } + return size; +} + +static int +hemdistsign(BITVECP a, BITVECP b, int siglen) +{ + int i, + dist = 0; + + LOOPBIT(siglen) + { + if (GETBIT(a, i) != GETBIT(b, i)) + dist++; + } + return dist; +} + +static int +hemdist(GISTTYPE *a, GISTTYPE *b, int siglen) +{ + if (ISALLTRUE(a)) + { + if (ISALLTRUE(b)) + return 0; + else + return SIGLENBIT(siglen) - sizebitvec(GETSIGN(b), siglen); + } + else if (ISALLTRUE(b)) + return SIGLENBIT(siglen) - sizebitvec(GETSIGN(a), siglen); + + return hemdistsign(GETSIGN(a), GETSIGN(b), siglen); +} + +static int32 +unionkey(BITVECP sbase, GISTTYPE *add, int siglen) +{ + int32 i; + BITVECP sadd = GETSIGN(add); + + if (ISALLTRUE(add)) + return 1; + LOOPBYTE(siglen) + sbase[i] |= sadd[i]; + return 0; +} + +Datum +ghstore_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int32 len = entryvec->n; + + int *size = (int *) PG_GETARG_POINTER(1); + int siglen = GET_SIGLEN(); + int32 i; + GISTTYPE *result = ghstore_alloc(false, siglen, NULL); + BITVECP base = GETSIGN(result); + + for (i = 0; i < len; i++) + { + if (unionkey(base, GETENTRY(entryvec, i), siglen)) + { + result->flag |= ALLISTRUE; + SET_VARSIZE(result, CALCGTSIZE(ALLISTRUE, siglen)); + break; + } + } + + *size = VARSIZE(result); + + PG_RETURN_POINTER(result); +} + +Datum +ghstore_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); /* always ISSIGNKEY */ + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *penalty = (float *) PG_GETARG_POINTER(2); + int siglen = GET_SIGLEN(); + GISTTYPE *origval = (GISTTYPE *) DatumGetPointer(origentry->key); + GISTTYPE *newval = (GISTTYPE *) DatumGetPointer(newentry->key); + + *penalty = hemdist(origval, newval, siglen); + PG_RETURN_POINTER(penalty); +} + + +typedef struct +{ + OffsetNumber pos; + int32 cost; +} SPLITCOST; + +static int +comparecost(const void *a, const void *b) +{ + return ((const SPLITCOST *) a)->cost - ((const SPLITCOST *) b)->cost; +} + + +Datum +ghstore_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + OffsetNumber maxoff = entryvec->n - 2; + + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + int siglen = GET_SIGLEN(); + OffsetNumber k, + j; + GISTTYPE *datum_l, + *datum_r; + BITVECP union_l, + union_r; + int32 size_alpha, + size_beta; + int32 size_waste, + waste = -1; + int32 nbytes; + OffsetNumber seed_1 = 0, + seed_2 = 0; + OffsetNumber *left, + *right; + BITVECP ptr; + int i; + SPLITCOST *costvector; + GISTTYPE *_k, + *_j; + + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k)) + { + _k = GETENTRY(entryvec, k); + for (j = OffsetNumberNext(k); j <= maxoff; j = OffsetNumberNext(j)) + { + size_waste = hemdist(_k, GETENTRY(entryvec, j), siglen); + if (size_waste > waste) + { + waste = size_waste; + seed_1 = k; + seed_2 = j; + } + } + } + + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + + if (seed_1 == 0 || seed_2 == 0) + { + seed_1 = 1; + seed_2 = 2; + } + + /* form initial .. */ + datum_l = ghstore_alloc(ISALLTRUE(GETENTRY(entryvec, seed_1)), siglen, + GETSIGN(GETENTRY(entryvec, seed_1))); + datum_r = ghstore_alloc(ISALLTRUE(GETENTRY(entryvec, seed_2)), siglen, + GETSIGN(GETENTRY(entryvec, seed_2))); + + maxoff = OffsetNumberNext(maxoff); + /* sort before ... */ + costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff); + for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) + { + costvector[j - 1].pos = j; + _j = GETENTRY(entryvec, j); + size_alpha = hemdist(datum_l, _j, siglen); + size_beta = hemdist(datum_r, _j, siglen); + costvector[j - 1].cost = abs(size_alpha - size_beta); + } + qsort(costvector, maxoff, sizeof(SPLITCOST), comparecost); + + union_l = GETSIGN(datum_l); + union_r = GETSIGN(datum_r); + + for (k = 0; k < maxoff; k++) + { + j = costvector[k].pos; + if (j == seed_1) + { + *left++ = j; + v->spl_nleft++; + continue; + } + else if (j == seed_2) + { + *right++ = j; + v->spl_nright++; + continue; + } + _j = GETENTRY(entryvec, j); + size_alpha = hemdist(datum_l, _j, siglen); + size_beta = hemdist(datum_r, _j, siglen); + + if (size_alpha < size_beta + WISH_F(v->spl_nleft, v->spl_nright, 0.0001)) + { + if (ISALLTRUE(datum_l) || ISALLTRUE(_j)) + { + if (!ISALLTRUE(datum_l)) + memset(union_l, 0xff, siglen); + } + else + { + ptr = GETSIGN(_j); + LOOPBYTE(siglen) + union_l[i] |= ptr[i]; + } + *left++ = j; + v->spl_nleft++; + } + else + { + if (ISALLTRUE(datum_r) || ISALLTRUE(_j)) + { + if (!ISALLTRUE(datum_r)) + memset(union_r, 0xff, siglen); + } + else + { + ptr = GETSIGN(_j); + LOOPBYTE(siglen) + union_r[i] |= ptr[i]; + } + *right++ = j; + v->spl_nright++; + } + } + + *right = *left = FirstOffsetNumber; + + v->spl_ldatum = PointerGetDatum(datum_l); + v->spl_rdatum = PointerGetDatum(datum_r); + + PG_RETURN_POINTER(v); +} + + +Datum +ghstore_consistent(PG_FUNCTION_ARGS) +{ + GISTTYPE *entry = (GISTTYPE *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + int siglen = GET_SIGLEN(); + bool res = true; + BITVECP sign; + + /* All cases served by this function are inexact */ + *recheck = true; + + if (ISALLTRUE(entry)) + PG_RETURN_BOOL(true); + + sign = GETSIGN(entry); + + if (strategy == HStoreContainsStrategyNumber || + strategy == HStoreOldContainsStrategyNumber) + { + HStore *query = PG_GETARG_HSTORE_P(1); + HEntry *qe = ARRPTR(query); + char *qv = STRPTR(query); + int count = HS_COUNT(query); + int i; + + for (i = 0; res && i < count; ++i) + { + int crc = crc32_sz((char *) HSTORE_KEY(qe, qv, i), + HSTORE_KEYLEN(qe, i)); + + if (GETBIT(sign, HASHVAL(crc, siglen))) + { + if (!HSTORE_VALISNULL(qe, i)) + { + crc = crc32_sz((char *) HSTORE_VAL(qe, qv, i), + HSTORE_VALLEN(qe, i)); + if (!GETBIT(sign, HASHVAL(crc, siglen))) + res = false; + } + } + else + res = false; + } + } + else if (strategy == HStoreExistsStrategyNumber) + { + text *query = PG_GETARG_TEXT_PP(1); + int crc = crc32_sz(VARDATA_ANY(query), VARSIZE_ANY_EXHDR(query)); + + res = (GETBIT(sign, HASHVAL(crc, siglen))) ? true : false; + } + else if (strategy == HStoreExistsAllStrategyNumber) + { + ArrayType *query = PG_GETARG_ARRAYTYPE_P(1); + Datum *key_datums; + bool *key_nulls; + int key_count; + int i; + + deconstruct_array_builtin(query, TEXTOID, &key_datums, &key_nulls, &key_count); + + for (i = 0; res && i < key_count; ++i) + { + int crc; + + if (key_nulls[i]) + continue; + crc = crc32_sz(VARDATA(key_datums[i]), VARSIZE(key_datums[i]) - VARHDRSZ); + if (!(GETBIT(sign, HASHVAL(crc, siglen)))) + res = false; + } + } + else if (strategy == HStoreExistsAnyStrategyNumber) + { + ArrayType *query = PG_GETARG_ARRAYTYPE_P(1); + Datum *key_datums; + bool *key_nulls; + int key_count; + int i; + + deconstruct_array_builtin(query, TEXTOID, &key_datums, &key_nulls, &key_count); + + res = false; + + for (i = 0; !res && i < key_count; ++i) + { + int crc; + + if (key_nulls[i]) + continue; + crc = crc32_sz(VARDATA(key_datums[i]), VARSIZE(key_datums[i]) - VARHDRSZ); + if (GETBIT(sign, HASHVAL(crc, siglen))) + res = true; + } + } + else + elog(ERROR, "Unsupported strategy number: %d", strategy); + + PG_RETURN_BOOL(res); +} + +Datum +ghstore_options(PG_FUNCTION_ARGS) +{ + local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0); + + init_local_reloptions(relopts, sizeof(GistHstoreOptions)); + add_local_int_reloption(relopts, "siglen", + "signature length in bytes", + SIGLEN_DEFAULT, 1, SIGLEN_MAX, + offsetof(GistHstoreOptions, siglen)); + + PG_RETURN_VOID(); +} diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c new file mode 100644 index 0000000..999ddad --- /dev/null +++ b/contrib/hstore/hstore_io.c @@ -0,0 +1,1554 @@ +/* + * contrib/hstore/hstore_io.c + */ +#include "postgres.h" + +#include + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "common/jsonapi.h" +#include "funcapi.h" +#include "hstore.h" +#include "lib/stringinfo.h" +#include "libpq/pqformat.h" +#include "nodes/miscnodes.h" +#include "parser/scansup.h" +#include "utils/builtins.h" +#include "utils/json.h" +#include "utils/jsonb.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/typcache.h" + +PG_MODULE_MAGIC; + +/* old names for C functions */ +HSTORE_POLLUTE(hstore_from_text, tconvert); + + +typedef struct +{ + char *begin; + char *ptr; + char *cur; + char *word; + int wordlen; + Node *escontext; + + Pairs *pairs; + int pcur; + int plen; +} HSParser; + +static bool hstoreCheckKeyLength(size_t len, HSParser *state); +static bool hstoreCheckValLength(size_t len, HSParser *state); + + +#define RESIZEPRSBUF \ +do { \ + if ( state->cur - state->word + 1 >= state->wordlen ) \ + { \ + int32 clen = state->cur - state->word; \ + state->wordlen *= 2; \ + state->word = (char*)repalloc( (void*)state->word, state->wordlen ); \ + state->cur = state->word + clen; \ + } \ +} while (0) + +#define PRSSYNTAXERROR return prssyntaxerror(state) + +static bool +prssyntaxerror(HSParser *state) +{ + errsave(state->escontext, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error in hstore, near \"%.*s\" at position %d", + pg_mblen(state->ptr), state->ptr, + (int) (state->ptr - state->begin)))); + /* In soft error situation, return false as convenience for caller */ + return false; +} + +#define PRSEOF return prseof(state) + +static bool +prseof(HSParser *state) +{ + errsave(state->escontext, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error in hstore: unexpected end of string"))); + /* In soft error situation, return false as convenience for caller */ + return false; +} + + +#define GV_WAITVAL 0 +#define GV_INVAL 1 +#define GV_INESCVAL 2 +#define GV_WAITESCIN 3 +#define GV_WAITESCESCIN 4 + +static bool +get_val(HSParser *state, bool ignoreeq, bool *escaped) +{ + int st = GV_WAITVAL; + + state->wordlen = 32; + state->cur = state->word = palloc(state->wordlen); + *escaped = false; + + while (1) + { + if (st == GV_WAITVAL) + { + if (*(state->ptr) == '"') + { + *escaped = true; + st = GV_INESCVAL; + } + else if (*(state->ptr) == '\0') + { + return false; + } + else if (*(state->ptr) == '=' && !ignoreeq) + { + PRSSYNTAXERROR; + } + else if (*(state->ptr) == '\\') + { + st = GV_WAITESCIN; + } + else if (!scanner_isspace((unsigned char) *(state->ptr))) + { + *(state->cur) = *(state->ptr); + state->cur++; + st = GV_INVAL; + } + } + else if (st == GV_INVAL) + { + if (*(state->ptr) == '\\') + { + st = GV_WAITESCIN; + } + else if (*(state->ptr) == '=' && !ignoreeq) + { + state->ptr--; + return true; + } + else if (*(state->ptr) == ',' && ignoreeq) + { + state->ptr--; + return true; + } + else if (scanner_isspace((unsigned char) *(state->ptr))) + { + return true; + } + else if (*(state->ptr) == '\0') + { + state->ptr--; + return true; + } + else + { + RESIZEPRSBUF; + *(state->cur) = *(state->ptr); + state->cur++; + } + } + else if (st == GV_INESCVAL) + { + if (*(state->ptr) == '\\') + { + st = GV_WAITESCESCIN; + } + else if (*(state->ptr) == '"') + { + return true; + } + else if (*(state->ptr) == '\0') + { + PRSEOF; + } + else + { + RESIZEPRSBUF; + *(state->cur) = *(state->ptr); + state->cur++; + } + } + else if (st == GV_WAITESCIN) + { + if (*(state->ptr) == '\0') + PRSEOF; + RESIZEPRSBUF; + *(state->cur) = *(state->ptr); + state->cur++; + st = GV_INVAL; + } + else if (st == GV_WAITESCESCIN) + { + if (*(state->ptr) == '\0') + PRSEOF; + RESIZEPRSBUF; + *(state->cur) = *(state->ptr); + state->cur++; + st = GV_INESCVAL; + } + else + elog(ERROR, "unrecognized get_val state: %d", st); + + state->ptr++; + } +} + +#define WKEY 0 +#define WVAL 1 +#define WEQ 2 +#define WGT 3 +#define WDEL 4 + + +static bool +parse_hstore(HSParser *state) +{ + int st = WKEY; + bool escaped = false; + + state->plen = 16; + state->pairs = (Pairs *) palloc(sizeof(Pairs) * state->plen); + state->pcur = 0; + state->ptr = state->begin; + state->word = NULL; + + while (1) + { + if (st == WKEY) + { + if (!get_val(state, false, &escaped)) + { + if (SOFT_ERROR_OCCURRED(state->escontext)) + return false; + return true; /* EOF, all okay */ + } + if (state->pcur >= state->plen) + { + state->plen *= 2; + state->pairs = (Pairs *) repalloc(state->pairs, sizeof(Pairs) * state->plen); + } + if (!hstoreCheckKeyLength(state->cur - state->word, state)) + return false; + state->pairs[state->pcur].key = state->word; + state->pairs[state->pcur].keylen = state->cur - state->word; + state->pairs[state->pcur].val = NULL; + state->word = NULL; + st = WEQ; + } + else if (st == WEQ) + { + if (*(state->ptr) == '=') + { + st = WGT; + } + else if (*(state->ptr) == '\0') + { + PRSEOF; + } + else if (!scanner_isspace((unsigned char) *(state->ptr))) + { + PRSSYNTAXERROR; + } + } + else if (st == WGT) + { + if (*(state->ptr) == '>') + { + st = WVAL; + } + else if (*(state->ptr) == '\0') + { + PRSEOF; + } + else + { + PRSSYNTAXERROR; + } + } + else if (st == WVAL) + { + if (!get_val(state, true, &escaped)) + { + if (SOFT_ERROR_OCCURRED(state->escontext)) + return false; + PRSEOF; + } + if (!hstoreCheckValLength(state->cur - state->word, state)) + return false; + state->pairs[state->pcur].val = state->word; + state->pairs[state->pcur].vallen = state->cur - state->word; + state->pairs[state->pcur].isnull = false; + state->pairs[state->pcur].needfree = true; + if (state->cur - state->word == 4 && !escaped) + { + state->word[4] = '\0'; + if (pg_strcasecmp(state->word, "null") == 0) + state->pairs[state->pcur].isnull = true; + } + state->word = NULL; + state->pcur++; + st = WDEL; + } + else if (st == WDEL) + { + if (*(state->ptr) == ',') + { + st = WKEY; + } + else if (*(state->ptr) == '\0') + { + return true; + } + else if (!scanner_isspace((unsigned char) *(state->ptr))) + { + PRSSYNTAXERROR; + } + } + else + elog(ERROR, "unrecognized parse_hstore state: %d", st); + + state->ptr++; + } +} + +static int +comparePairs(const void *a, const void *b) +{ + const Pairs *pa = a; + const Pairs *pb = b; + + if (pa->keylen == pb->keylen) + { + int res = memcmp(pa->key, pb->key, pa->keylen); + + if (res) + return res; + + /* guarantee that needfree will be later */ + if (pb->needfree == pa->needfree) + return 0; + else if (pa->needfree) + return 1; + else + return -1; + } + return (pa->keylen > pb->keylen) ? 1 : -1; +} + +/* + * this code still respects pairs.needfree, even though in general + * it should never be called in a context where anything needs freeing. + * we keep it because (a) those calls are in a rare code path anyway, + * and (b) who knows whether they might be needed by some caller. + */ +int +hstoreUniquePairs(Pairs *a, int32 l, int32 *buflen) +{ + Pairs *ptr, + *res; + + *buflen = 0; + if (l < 2) + { + if (l == 1) + *buflen = a->keylen + ((a->isnull) ? 0 : a->vallen); + return l; + } + + qsort(a, l, sizeof(Pairs), comparePairs); + + /* + * We can't use qunique here because we have some clean-up code to run on + * removed elements. + */ + ptr = a + 1; + res = a; + while (ptr - a < l) + { + if (ptr->keylen == res->keylen && + memcmp(ptr->key, res->key, res->keylen) == 0) + { + if (ptr->needfree) + { + pfree(ptr->key); + pfree(ptr->val); + } + } + else + { + *buflen += res->keylen + ((res->isnull) ? 0 : res->vallen); + res++; + if (res != ptr) + memcpy(res, ptr, sizeof(Pairs)); + } + + ptr++; + } + + *buflen += res->keylen + ((res->isnull) ? 0 : res->vallen); + return res + 1 - a; +} + +size_t +hstoreCheckKeyLen(size_t len) +{ + if (len > HSTORE_MAX_KEY_LEN) + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("string too long for hstore key"))); + return len; +} + +static bool +hstoreCheckKeyLength(size_t len, HSParser *state) +{ + if (len > HSTORE_MAX_KEY_LEN) + ereturn(state->escontext, false, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("string too long for hstore key"))); + return true; +} + +size_t +hstoreCheckValLen(size_t len) +{ + if (len > HSTORE_MAX_VALUE_LEN) + ereport(ERROR, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("string too long for hstore value"))); + return len; +} + +static bool +hstoreCheckValLength(size_t len, HSParser *state) +{ + if (len > HSTORE_MAX_VALUE_LEN) + ereturn(state->escontext, false, + (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), + errmsg("string too long for hstore value"))); + return true; +} + + +HStore * +hstorePairs(Pairs *pairs, int32 pcount, int32 buflen) +{ + HStore *out; + HEntry *entry; + char *ptr; + char *buf; + int32 len; + int32 i; + + len = CALCDATASIZE(pcount, buflen); + out = palloc(len); + SET_VARSIZE(out, len); + HS_SETCOUNT(out, pcount); + + if (pcount == 0) + return out; + + entry = ARRPTR(out); + buf = ptr = STRPTR(out); + + for (i = 0; i < pcount; i++) + HS_ADDITEM(entry, buf, ptr, pairs[i]); + + HS_FINALIZE(out, pcount, buf, ptr); + + return out; +} + + +PG_FUNCTION_INFO_V1(hstore_in); +Datum +hstore_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + Node *escontext = fcinfo->context; + HSParser state; + int32 buflen; + HStore *out; + + state.begin = str; + state.escontext = escontext; + + if (!parse_hstore(&state)) + PG_RETURN_NULL(); + + state.pcur = hstoreUniquePairs(state.pairs, state.pcur, &buflen); + + out = hstorePairs(state.pairs, state.pcur, buflen); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_recv); +Datum +hstore_recv(PG_FUNCTION_ARGS) +{ + int32 buflen; + HStore *out; + Pairs *pairs; + int32 i; + int32 pcount; + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + + pcount = pq_getmsgint(buf, 4); + + if (pcount == 0) + { + out = hstorePairs(NULL, 0, 0); + PG_RETURN_POINTER(out); + } + + if (pcount < 0 || pcount > MaxAllocSize / sizeof(Pairs)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of pairs (%d) exceeds the maximum allowed (%d)", + pcount, (int) (MaxAllocSize / sizeof(Pairs))))); + pairs = palloc(pcount * sizeof(Pairs)); + + for (i = 0; i < pcount; ++i) + { + int rawlen = pq_getmsgint(buf, 4); + int len; + + if (rawlen < 0) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for hstore key"))); + + pairs[i].key = pq_getmsgtext(buf, rawlen, &len); + pairs[i].keylen = hstoreCheckKeyLen(len); + pairs[i].needfree = true; + + rawlen = pq_getmsgint(buf, 4); + if (rawlen < 0) + { + pairs[i].val = NULL; + pairs[i].vallen = 0; + pairs[i].isnull = true; + } + else + { + pairs[i].val = pq_getmsgtext(buf, rawlen, &len); + pairs[i].vallen = hstoreCheckValLen(len); + pairs[i].isnull = false; + } + } + + pcount = hstoreUniquePairs(pairs, pcount, &buflen); + + out = hstorePairs(pairs, pcount, buflen); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_from_text); +Datum +hstore_from_text(PG_FUNCTION_ARGS) +{ + text *key; + text *val = NULL; + Pairs p; + HStore *out; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + p.needfree = false; + key = PG_GETARG_TEXT_PP(0); + p.key = VARDATA_ANY(key); + p.keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key)); + + if (PG_ARGISNULL(1)) + { + p.vallen = 0; + p.isnull = true; + } + else + { + val = PG_GETARG_TEXT_PP(1); + p.val = VARDATA_ANY(val); + p.vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(val)); + p.isnull = false; + } + + out = hstorePairs(&p, 1, p.keylen + p.vallen); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_from_arrays); +Datum +hstore_from_arrays(PG_FUNCTION_ARGS) +{ + int32 buflen; + HStore *out; + Pairs *pairs; + Datum *key_datums; + bool *key_nulls; + int key_count; + Datum *value_datums; + bool *value_nulls; + int value_count; + ArrayType *key_array; + ArrayType *value_array; + int i; + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + key_array = PG_GETARG_ARRAYTYPE_P(0); + + Assert(ARR_ELEMTYPE(key_array) == TEXTOID); + + /* + * must check >1 rather than != 1 because empty arrays have 0 dimensions, + * not 1 + */ + + if (ARR_NDIM(key_array) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + deconstruct_array_builtin(key_array, TEXTOID, &key_datums, &key_nulls, &key_count); + + /* see discussion in hstoreArrayToPairs() */ + if (key_count > MaxAllocSize / sizeof(Pairs)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of pairs (%d) exceeds the maximum allowed (%d)", + key_count, (int) (MaxAllocSize / sizeof(Pairs))))); + + /* value_array might be NULL */ + + if (PG_ARGISNULL(1)) + { + value_array = NULL; + value_count = key_count; + value_datums = NULL; + value_nulls = NULL; + } + else + { + value_array = PG_GETARG_ARRAYTYPE_P(1); + + Assert(ARR_ELEMTYPE(value_array) == TEXTOID); + + if (ARR_NDIM(value_array) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + + if ((ARR_NDIM(key_array) > 0 || ARR_NDIM(value_array) > 0) && + (ARR_NDIM(key_array) != ARR_NDIM(value_array) || + ARR_DIMS(key_array)[0] != ARR_DIMS(value_array)[0] || + ARR_LBOUND(key_array)[0] != ARR_LBOUND(value_array)[0])) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("arrays must have same bounds"))); + + deconstruct_array_builtin(value_array, TEXTOID, &value_datums, &value_nulls, &value_count); + + Assert(key_count == value_count); + } + + pairs = palloc(key_count * sizeof(Pairs)); + + for (i = 0; i < key_count; ++i) + { + if (key_nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for hstore key"))); + + if (!value_nulls || value_nulls[i]) + { + pairs[i].key = VARDATA(key_datums[i]); + pairs[i].val = NULL; + pairs[i].keylen = + hstoreCheckKeyLen(VARSIZE(key_datums[i]) - VARHDRSZ); + pairs[i].vallen = 4; + pairs[i].isnull = true; + pairs[i].needfree = false; + } + else + { + pairs[i].key = VARDATA(key_datums[i]); + pairs[i].val = VARDATA(value_datums[i]); + pairs[i].keylen = + hstoreCheckKeyLen(VARSIZE(key_datums[i]) - VARHDRSZ); + pairs[i].vallen = + hstoreCheckValLen(VARSIZE(value_datums[i]) - VARHDRSZ); + pairs[i].isnull = false; + pairs[i].needfree = false; + } + } + + key_count = hstoreUniquePairs(pairs, key_count, &buflen); + + out = hstorePairs(pairs, key_count, buflen); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_from_array); +Datum +hstore_from_array(PG_FUNCTION_ARGS) +{ + ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); + int ndims = ARR_NDIM(in_array); + int count; + int32 buflen; + HStore *out; + Pairs *pairs; + Datum *in_datums; + bool *in_nulls; + int in_count; + int i; + + Assert(ARR_ELEMTYPE(in_array) == TEXTOID); + + switch (ndims) + { + case 0: + out = hstorePairs(NULL, 0, 0); + PG_RETURN_POINTER(out); + + case 1: + if ((ARR_DIMS(in_array)[0]) % 2) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must have even number of elements"))); + break; + + case 2: + if ((ARR_DIMS(in_array)[1]) != 2) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must have two columns"))); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + } + + deconstruct_array_builtin(in_array, TEXTOID, &in_datums, &in_nulls, &in_count); + + count = in_count / 2; + + /* see discussion in hstoreArrayToPairs() */ + if (count > MaxAllocSize / sizeof(Pairs)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of pairs (%d) exceeds the maximum allowed (%d)", + count, (int) (MaxAllocSize / sizeof(Pairs))))); + + pairs = palloc(count * sizeof(Pairs)); + + for (i = 0; i < count; ++i) + { + if (in_nulls[i * 2]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for hstore key"))); + + if (in_nulls[i * 2 + 1]) + { + pairs[i].key = VARDATA(in_datums[i * 2]); + pairs[i].val = NULL; + pairs[i].keylen = + hstoreCheckKeyLen(VARSIZE(in_datums[i * 2]) - VARHDRSZ); + pairs[i].vallen = 4; + pairs[i].isnull = true; + pairs[i].needfree = false; + } + else + { + pairs[i].key = VARDATA(in_datums[i * 2]); + pairs[i].val = VARDATA(in_datums[i * 2 + 1]); + pairs[i].keylen = + hstoreCheckKeyLen(VARSIZE(in_datums[i * 2]) - VARHDRSZ); + pairs[i].vallen = + hstoreCheckValLen(VARSIZE(in_datums[i * 2 + 1]) - VARHDRSZ); + pairs[i].isnull = false; + pairs[i].needfree = false; + } + } + + count = hstoreUniquePairs(pairs, count, &buflen); + + out = hstorePairs(pairs, count, buflen); + + PG_RETURN_POINTER(out); +} + +/* most of hstore_from_record is shamelessly swiped from record_out */ + +/* + * structure to cache metadata needed for record I/O + */ +typedef struct ColumnIOData +{ + Oid column_type; + Oid typiofunc; + Oid typioparam; + FmgrInfo proc; +} ColumnIOData; + +typedef struct RecordIOData +{ + Oid record_type; + int32 record_typmod; + /* this field is used only if target type is domain over composite: */ + void *domain_info; /* opaque cache for domain checks */ + int ncolumns; + ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER]; +} RecordIOData; + +PG_FUNCTION_INFO_V1(hstore_from_record); +Datum +hstore_from_record(PG_FUNCTION_ARGS) +{ + HeapTupleHeader rec; + int32 buflen; + HStore *out; + Pairs *pairs; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tuple; + RecordIOData *my_extra; + int ncolumns; + int i, + j; + Datum *values; + bool *nulls; + + if (PG_ARGISNULL(0)) + { + Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + + /* + * We have no tuple to look at, so the only source of type info is the + * argtype --- which might be domain over composite, but we don't care + * here, since we have no need to be concerned about domain + * constraints. The lookup_rowtype_tupdesc_domain call below will + * error out if we don't have a known composite type oid here. + */ + tupType = argtype; + tupTypmod = -1; + + rec = NULL; + } + else + { + rec = PG_GETARG_HEAPTUPLEHEADER(0); + + /* + * Extract type info from the tuple itself -- this will work even for + * anonymous record types. + */ + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); + } + + tupdesc = lookup_rowtype_tupdesc_domain(tupType, tupTypmod, false); + ncolumns = tupdesc->natts; + + /* + * We arrange to look up the needed I/O info just once per series of + * calls, assuming the record type doesn't change underneath us. + */ + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns != ncolumns) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + my_extra->record_type = InvalidOid; + my_extra->record_typmod = 0; + } + + if (my_extra->record_type != tupType || + my_extra->record_typmod != tupTypmod) + { + MemSet(my_extra, 0, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra->record_type = tupType; + my_extra->record_typmod = tupTypmod; + my_extra->ncolumns = ncolumns; + } + + Assert(ncolumns <= MaxTupleAttributeNumber); /* thus, no overflow */ + pairs = palloc(ncolumns * sizeof(Pairs)); + + if (rec) + { + /* 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)); + + /* Break down the tuple into fields */ + heap_deform_tuple(&tuple, tupdesc, values, nulls); + } + else + { + values = NULL; + nulls = NULL; + } + + for (i = 0, j = 0; i < ncolumns; ++i) + { + ColumnIOData *column_info = &my_extra->columns[i]; + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + Oid column_type = att->atttypid; + char *value; + + /* Ignore dropped columns in datatype */ + if (att->attisdropped) + continue; + + pairs[j].key = NameStr(att->attname); + pairs[j].keylen = hstoreCheckKeyLen(strlen(NameStr(att->attname))); + + if (!nulls || nulls[i]) + { + pairs[j].val = NULL; + pairs[j].vallen = 4; + pairs[j].isnull = true; + pairs[j].needfree = false; + ++j; + continue; + } + + /* + * Convert the column value to text + */ + if (column_info->column_type != column_type) + { + bool typIsVarlena; + + getTypeOutputInfo(column_type, + &column_info->typiofunc, + &typIsVarlena); + fmgr_info_cxt(column_info->typiofunc, &column_info->proc, + fcinfo->flinfo->fn_mcxt); + column_info->column_type = column_type; + } + + value = OutputFunctionCall(&column_info->proc, values[i]); + + pairs[j].val = value; + pairs[j].vallen = hstoreCheckValLen(strlen(value)); + pairs[j].isnull = false; + pairs[j].needfree = false; + ++j; + } + + ncolumns = hstoreUniquePairs(pairs, j, &buflen); + + out = hstorePairs(pairs, ncolumns, buflen); + + ReleaseTupleDesc(tupdesc); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_populate_record); +Datum +hstore_populate_record(PG_FUNCTION_ARGS) +{ + Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + HStore *hs; + HEntry *entries; + char *ptr; + HeapTupleHeader rec; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tuple; + HeapTuple rettuple; + RecordIOData *my_extra; + int ncolumns; + int i; + Datum *values; + bool *nulls; + + if (!type_is_rowtype(argtype)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("first argument must be a rowtype"))); + + if (PG_ARGISNULL(0)) + { + if (PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + rec = NULL; + + /* + * We have no tuple to look at, so the only source of type info is the + * argtype. The lookup_rowtype_tupdesc_domain call below will error + * out if we don't have a known composite type oid here. + */ + tupType = argtype; + tupTypmod = -1; + } + else + { + rec = PG_GETARG_HEAPTUPLEHEADER(0); + + if (PG_ARGISNULL(1)) + PG_RETURN_POINTER(rec); + + /* + * Extract type info from the tuple itself -- this will work even for + * anonymous record types. + */ + tupType = HeapTupleHeaderGetTypeId(rec); + tupTypmod = HeapTupleHeaderGetTypMod(rec); + } + + hs = PG_GETARG_HSTORE_P(1); + entries = ARRPTR(hs); + ptr = STRPTR(hs); + + /* + * if the input hstore is empty, we can only skip the rest if we were + * passed in a non-null record, since otherwise there may be issues with + * domain nulls. + */ + + if (HS_COUNT(hs) == 0 && rec) + PG_RETURN_POINTER(rec); + + /* + * Lookup the input record's tupdesc. For the moment, we don't worry + * about whether it is a domain over composite. + */ + tupdesc = lookup_rowtype_tupdesc_domain(tupType, tupTypmod, false); + ncolumns = tupdesc->natts; + + if (rec) + { + /* Build a temporary HeapTuple control structure */ + tuple.t_len = HeapTupleHeaderGetDatumLength(rec); + ItemPointerSetInvalid(&(tuple.t_self)); + tuple.t_tableOid = InvalidOid; + tuple.t_data = rec; + } + + /* + * We arrange to look up the needed I/O info just once per series of + * calls, assuming the record type doesn't change underneath us. + */ + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + if (my_extra == NULL || + my_extra->ncolumns != ncolumns) + { + fcinfo->flinfo->fn_extra = + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; + my_extra->record_type = InvalidOid; + my_extra->record_typmod = 0; + my_extra->domain_info = NULL; + } + + if (my_extra->record_type != tupType || + my_extra->record_typmod != tupTypmod) + { + MemSet(my_extra, 0, + offsetof(RecordIOData, columns) + + ncolumns * sizeof(ColumnIOData)); + my_extra->record_type = tupType; + my_extra->record_typmod = tupTypmod; + my_extra->ncolumns = ncolumns; + } + + values = (Datum *) palloc(ncolumns * sizeof(Datum)); + nulls = (bool *) palloc(ncolumns * sizeof(bool)); + + if (rec) + { + /* Break down the tuple into fields */ + heap_deform_tuple(&tuple, tupdesc, values, nulls); + } + else + { + for (i = 0; i < ncolumns; ++i) + { + values[i] = (Datum) 0; + nulls[i] = true; + } + } + + for (i = 0; i < ncolumns; ++i) + { + ColumnIOData *column_info = &my_extra->columns[i]; + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + Oid column_type = att->atttypid; + char *value; + int idx; + int vallen; + + /* Ignore dropped columns in datatype */ + if (att->attisdropped) + { + nulls[i] = true; + continue; + } + + idx = hstoreFindKey(hs, 0, + NameStr(att->attname), + strlen(NameStr(att->attname))); + + /* + * we can't just skip here if the key wasn't found since we might have + * a domain to deal with. If we were passed in a non-null record + * datum, we assume that the existing values are valid (if they're + * not, then it's not our fault), but if we were passed in a null, + * then every field which we don't populate needs to be run through + * the input function just in case it's a domain type. + */ + if (idx < 0 && rec) + continue; + + /* + * Prepare to convert the column value from text + */ + if (column_info->column_type != column_type) + { + getTypeInputInfo(column_type, + &column_info->typiofunc, + &column_info->typioparam); + fmgr_info_cxt(column_info->typiofunc, &column_info->proc, + fcinfo->flinfo->fn_mcxt); + column_info->column_type = column_type; + } + + if (idx < 0 || HSTORE_VALISNULL(entries, idx)) + { + /* + * need InputFunctionCall to happen even for nulls, so that domain + * checks are done + */ + values[i] = InputFunctionCall(&column_info->proc, NULL, + column_info->typioparam, + att->atttypmod); + nulls[i] = true; + } + else + { + vallen = HSTORE_VALLEN(entries, idx); + value = palloc(1 + vallen); + memcpy(value, HSTORE_VAL(entries, ptr, idx), vallen); + value[vallen] = 0; + + values[i] = InputFunctionCall(&column_info->proc, value, + column_info->typioparam, + att->atttypmod); + nulls[i] = false; + } + } + + rettuple = heap_form_tuple(tupdesc, values, nulls); + + /* + * If the target type is domain over composite, all we know at this point + * is that we've made a valid value of the base composite type. Must + * check domain constraints before deciding we're done. + */ + if (argtype != tupdesc->tdtypeid) + domain_check(HeapTupleGetDatum(rettuple), false, + argtype, + &my_extra->domain_info, + fcinfo->flinfo->fn_mcxt); + + ReleaseTupleDesc(tupdesc); + + PG_RETURN_DATUM(HeapTupleGetDatum(rettuple)); +} + + +static char * +cpw(char *dst, char *src, int len) +{ + char *ptr = src; + + while (ptr - src < len) + { + if (*ptr == '"' || *ptr == '\\') + *dst++ = '\\'; + *dst++ = *ptr++; + } + return dst; +} + +PG_FUNCTION_INFO_V1(hstore_out); +Datum +hstore_out(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int buflen, + i; + int count = HS_COUNT(in); + char *out, + *ptr; + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + + if (count == 0) + PG_RETURN_CSTRING(pstrdup("")); + + buflen = 0; + + /* + * this loop overestimates due to pessimistic assumptions about escaping, + * so very large hstore values can't be output. this could be fixed, but + * many other data types probably have the same issue. This replaced code + * that used the original varlena size for calculations, which was wrong + * in some subtle ways. + */ + + for (i = 0; i < count; i++) + { + /* include "" and => and comma-space */ + buflen += 6 + 2 * HSTORE_KEYLEN(entries, i); + /* include "" only if nonnull */ + buflen += 2 + (HSTORE_VALISNULL(entries, i) + ? 2 + : 2 * HSTORE_VALLEN(entries, i)); + } + + out = ptr = palloc(buflen); + + for (i = 0; i < count; i++) + { + *ptr++ = '"'; + ptr = cpw(ptr, HSTORE_KEY(entries, base, i), HSTORE_KEYLEN(entries, i)); + *ptr++ = '"'; + *ptr++ = '='; + *ptr++ = '>'; + if (HSTORE_VALISNULL(entries, i)) + { + *ptr++ = 'N'; + *ptr++ = 'U'; + *ptr++ = 'L'; + *ptr++ = 'L'; + } + else + { + *ptr++ = '"'; + ptr = cpw(ptr, HSTORE_VAL(entries, base, i), HSTORE_VALLEN(entries, i)); + *ptr++ = '"'; + } + + if (i + 1 != count) + { + *ptr++ = ','; + *ptr++ = ' '; + } + } + *ptr = '\0'; + + PG_RETURN_CSTRING(out); +} + + +PG_FUNCTION_INFO_V1(hstore_send); +Datum +hstore_send(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + StringInfoData buf; + + pq_begintypsend(&buf); + + pq_sendint32(&buf, count); + + for (i = 0; i < count; i++) + { + int32 keylen = HSTORE_KEYLEN(entries, i); + + pq_sendint32(&buf, keylen); + pq_sendtext(&buf, HSTORE_KEY(entries, base, i), keylen); + if (HSTORE_VALISNULL(entries, i)) + { + pq_sendint32(&buf, -1); + } + else + { + int32 vallen = HSTORE_VALLEN(entries, i); + + pq_sendint32(&buf, vallen); + pq_sendtext(&buf, HSTORE_VAL(entries, base, i), vallen); + } + } + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + + +/* + * hstore_to_json_loose + * + * This is a heuristic conversion to json which treats + * 't' and 'f' as booleans and strings that look like numbers as numbers, + * as long as they don't start with a leading zero followed by another digit + * (think zip codes or phone numbers starting with 0). + */ +PG_FUNCTION_INFO_V1(hstore_to_json_loose); +Datum +hstore_to_json_loose(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + StringInfoData tmp, + dst; + + if (count == 0) + PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); + + initStringInfo(&tmp); + initStringInfo(&dst); + + appendStringInfoChar(&dst, '{'); + + for (i = 0; i < count; i++) + { + resetStringInfo(&tmp); + appendBinaryStringInfo(&tmp, HSTORE_KEY(entries, base, i), + HSTORE_KEYLEN(entries, i)); + escape_json(&dst, tmp.data); + appendStringInfoString(&dst, ": "); + if (HSTORE_VALISNULL(entries, i)) + appendStringInfoString(&dst, "null"); + /* guess that values of 't' or 'f' are booleans */ + else if (HSTORE_VALLEN(entries, i) == 1 && + *(HSTORE_VAL(entries, base, i)) == 't') + appendStringInfoString(&dst, "true"); + else if (HSTORE_VALLEN(entries, i) == 1 && + *(HSTORE_VAL(entries, base, i)) == 'f') + appendStringInfoString(&dst, "false"); + else + { + resetStringInfo(&tmp); + appendBinaryStringInfo(&tmp, HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + if (IsValidJsonNumber(tmp.data, tmp.len)) + appendBinaryStringInfo(&dst, tmp.data, tmp.len); + else + escape_json(&dst, tmp.data); + } + + if (i + 1 != count) + appendStringInfoString(&dst, ", "); + } + appendStringInfoChar(&dst, '}'); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(dst.data, dst.len)); +} + +PG_FUNCTION_INFO_V1(hstore_to_json); +Datum +hstore_to_json(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + StringInfoData tmp, + dst; + + if (count == 0) + PG_RETURN_TEXT_P(cstring_to_text_with_len("{}", 2)); + + initStringInfo(&tmp); + initStringInfo(&dst); + + appendStringInfoChar(&dst, '{'); + + for (i = 0; i < count; i++) + { + resetStringInfo(&tmp); + appendBinaryStringInfo(&tmp, HSTORE_KEY(entries, base, i), + HSTORE_KEYLEN(entries, i)); + escape_json(&dst, tmp.data); + appendStringInfoString(&dst, ": "); + if (HSTORE_VALISNULL(entries, i)) + appendStringInfoString(&dst, "null"); + else + { + resetStringInfo(&tmp); + appendBinaryStringInfo(&tmp, HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + escape_json(&dst, tmp.data); + } + + if (i + 1 != count) + appendStringInfoString(&dst, ", "); + } + appendStringInfoChar(&dst, '}'); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(dst.data, dst.len)); +} + +PG_FUNCTION_INFO_V1(hstore_to_jsonb); +Datum +hstore_to_jsonb(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + JsonbParseState *state = NULL; + JsonbValue *res; + + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + for (i = 0; i < count; i++) + { + JsonbValue key, + val; + + key.type = jbvString; + key.val.string.len = HSTORE_KEYLEN(entries, i); + key.val.string.val = HSTORE_KEY(entries, base, i); + + (void) pushJsonbValue(&state, WJB_KEY, &key); + + if (HSTORE_VALISNULL(entries, i)) + { + val.type = jbvNull; + } + else + { + val.type = jbvString; + val.val.string.len = HSTORE_VALLEN(entries, i); + val.val.string.val = HSTORE_VAL(entries, base, i); + } + (void) pushJsonbValue(&state, WJB_VALUE, &val); + } + + res = pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(res)); +} + +PG_FUNCTION_INFO_V1(hstore_to_jsonb_loose); +Datum +hstore_to_jsonb_loose(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + JsonbParseState *state = NULL; + JsonbValue *res; + StringInfoData tmp; + + initStringInfo(&tmp); + + (void) pushJsonbValue(&state, WJB_BEGIN_OBJECT, NULL); + + for (i = 0; i < count; i++) + { + JsonbValue key, + val; + + key.type = jbvString; + key.val.string.len = HSTORE_KEYLEN(entries, i); + key.val.string.val = HSTORE_KEY(entries, base, i); + + (void) pushJsonbValue(&state, WJB_KEY, &key); + + if (HSTORE_VALISNULL(entries, i)) + { + val.type = jbvNull; + } + /* guess that values of 't' or 'f' are booleans */ + else if (HSTORE_VALLEN(entries, i) == 1 && + *(HSTORE_VAL(entries, base, i)) == 't') + { + val.type = jbvBool; + val.val.boolean = true; + } + else if (HSTORE_VALLEN(entries, i) == 1 && + *(HSTORE_VAL(entries, base, i)) == 'f') + { + val.type = jbvBool; + val.val.boolean = false; + } + else + { + resetStringInfo(&tmp); + appendBinaryStringInfo(&tmp, HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + if (IsValidJsonNumber(tmp.data, tmp.len)) + { + Datum numd; + + val.type = jbvNumeric; + numd = DirectFunctionCall3(numeric_in, + CStringGetDatum(tmp.data), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + val.val.numeric = DatumGetNumeric(numd); + } + else + { + val.type = jbvString; + val.val.string.len = HSTORE_VALLEN(entries, i); + val.val.string.val = HSTORE_VAL(entries, base, i); + } + } + (void) pushJsonbValue(&state, WJB_VALUE, &val); + } + + res = pushJsonbValue(&state, WJB_END_OBJECT, NULL); + + PG_RETURN_POINTER(JsonbValueToJsonb(res)); +} diff --git a/contrib/hstore/hstore_op.c b/contrib/hstore/hstore_op.c new file mode 100644 index 0000000..0d4ec16 --- /dev/null +++ b/contrib/hstore/hstore_op.c @@ -0,0 +1,1273 @@ +/* + * contrib/hstore/hstore_op.c + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "common/hashfn.h" +#include "funcapi.h" +#include "hstore.h" +#include "utils/builtins.h" +#include "utils/memutils.h" + +/* old names for C functions */ +HSTORE_POLLUTE(hstore_fetchval, fetchval); +HSTORE_POLLUTE(hstore_exists, exists); +HSTORE_POLLUTE(hstore_defined, defined); +HSTORE_POLLUTE(hstore_delete, delete); +HSTORE_POLLUTE(hstore_concat, hs_concat); +HSTORE_POLLUTE(hstore_contains, hs_contains); +HSTORE_POLLUTE(hstore_contained, hs_contained); +HSTORE_POLLUTE(hstore_akeys, akeys); +HSTORE_POLLUTE(hstore_avals, avals); +HSTORE_POLLUTE(hstore_skeys, skeys); +HSTORE_POLLUTE(hstore_svals, svals); +HSTORE_POLLUTE(hstore_each, each); + + +/* + * We're often finding a sequence of keys in ascending order. The + * "lowbound" parameter is used to cache lower bounds of searches + * between calls, based on this assumption. Pass NULL for it for + * one-off or unordered searches. + */ +int +hstoreFindKey(HStore *hs, int *lowbound, char *key, int keylen) +{ + HEntry *entries = ARRPTR(hs); + int stopLow = lowbound ? *lowbound : 0; + int stopHigh = HS_COUNT(hs); + int stopMiddle; + char *base = STRPTR(hs); + + while (stopLow < stopHigh) + { + int difference; + + stopMiddle = stopLow + (stopHigh - stopLow) / 2; + + if (HSTORE_KEYLEN(entries, stopMiddle) == keylen) + difference = memcmp(HSTORE_KEY(entries, base, stopMiddle), key, keylen); + else + difference = (HSTORE_KEYLEN(entries, stopMiddle) > keylen) ? 1 : -1; + + if (difference == 0) + { + if (lowbound) + *lowbound = stopMiddle + 1; + return stopMiddle; + } + else if (difference < 0) + stopLow = stopMiddle + 1; + else + stopHigh = stopMiddle; + } + + if (lowbound) + *lowbound = stopLow; + return -1; +} + +Pairs * +hstoreArrayToPairs(ArrayType *a, int *npairs) +{ + Datum *key_datums; + bool *key_nulls; + int key_count; + Pairs *key_pairs; + int bufsiz; + int i, + j; + + deconstruct_array_builtin(a, TEXTOID, &key_datums, &key_nulls, &key_count); + + if (key_count == 0) + { + *npairs = 0; + return NULL; + } + + /* + * A text array uses at least eight bytes per element, so any overflow in + * "key_count * sizeof(Pairs)" is small enough for palloc() to catch. + * However, credible improvements to the array format could invalidate + * that assumption. Therefore, use an explicit check rather than relying + * on palloc() to complain. + */ + if (key_count > MaxAllocSize / sizeof(Pairs)) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of pairs (%d) exceeds the maximum allowed (%d)", + key_count, (int) (MaxAllocSize / sizeof(Pairs))))); + + key_pairs = palloc(sizeof(Pairs) * key_count); + + for (i = 0, j = 0; i < key_count; i++) + { + if (!key_nulls[i]) + { + key_pairs[j].key = VARDATA(key_datums[i]); + key_pairs[j].keylen = VARSIZE(key_datums[i]) - VARHDRSZ; + key_pairs[j].val = NULL; + key_pairs[j].vallen = 0; + key_pairs[j].needfree = 0; + key_pairs[j].isnull = 1; + j++; + } + } + + *npairs = hstoreUniquePairs(key_pairs, j, &bufsiz); + + return key_pairs; +} + + +PG_FUNCTION_INFO_V1(hstore_fetchval); +Datum +hstore_fetchval(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + text *key = PG_GETARG_TEXT_PP(1); + HEntry *entries = ARRPTR(hs); + text *out; + int idx = hstoreFindKey(hs, NULL, + VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key)); + + if (idx < 0 || HSTORE_VALISNULL(entries, idx)) + PG_RETURN_NULL(); + + out = cstring_to_text_with_len(HSTORE_VAL(entries, STRPTR(hs), idx), + HSTORE_VALLEN(entries, idx)); + + PG_RETURN_TEXT_P(out); +} + + +PG_FUNCTION_INFO_V1(hstore_exists); +Datum +hstore_exists(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + text *key = PG_GETARG_TEXT_PP(1); + int idx = hstoreFindKey(hs, NULL, + VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key)); + + PG_RETURN_BOOL(idx >= 0); +} + + +PG_FUNCTION_INFO_V1(hstore_exists_any); +Datum +hstore_exists_any(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); + int nkeys; + Pairs *key_pairs = hstoreArrayToPairs(keys, &nkeys); + int i; + int lowbound = 0; + bool res = false; + + /* + * we exploit the fact that the pairs list is already sorted into strictly + * increasing order to narrow the hstoreFindKey search; each search can + * start one entry past the previous "found" entry, or at the lower bound + * of the last search. + */ + for (i = 0; i < nkeys; i++) + { + int idx = hstoreFindKey(hs, &lowbound, + key_pairs[i].key, key_pairs[i].keylen); + + if (idx >= 0) + { + res = true; + break; + } + } + + PG_RETURN_BOOL(res); +} + + +PG_FUNCTION_INFO_V1(hstore_exists_all); +Datum +hstore_exists_all(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); + int nkeys; + Pairs *key_pairs = hstoreArrayToPairs(keys, &nkeys); + int i; + int lowbound = 0; + bool res = true; + + /* + * we exploit the fact that the pairs list is already sorted into strictly + * increasing order to narrow the hstoreFindKey search; each search can + * start one entry past the previous "found" entry, or at the lower bound + * of the last search. + */ + for (i = 0; i < nkeys; i++) + { + int idx = hstoreFindKey(hs, &lowbound, + key_pairs[i].key, key_pairs[i].keylen); + + if (idx < 0) + { + res = false; + break; + } + } + + PG_RETURN_BOOL(res); +} + + +PG_FUNCTION_INFO_V1(hstore_defined); +Datum +hstore_defined(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + text *key = PG_GETARG_TEXT_PP(1); + HEntry *entries = ARRPTR(hs); + int idx = hstoreFindKey(hs, NULL, + VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key)); + bool res = (idx >= 0 && !HSTORE_VALISNULL(entries, idx)); + + PG_RETURN_BOOL(res); +} + + +PG_FUNCTION_INFO_V1(hstore_delete); +Datum +hstore_delete(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + text *key = PG_GETARG_TEXT_PP(1); + char *keyptr = VARDATA_ANY(key); + int keylen = VARSIZE_ANY_EXHDR(key); + HStore *out = palloc(VARSIZE(hs)); + char *bufs, + *bufd, + *ptrd; + HEntry *es, + *ed; + int i; + int count = HS_COUNT(hs); + int outcount = 0; + + SET_VARSIZE(out, VARSIZE(hs)); + HS_SETCOUNT(out, count); /* temporary! */ + + bufs = STRPTR(hs); + es = ARRPTR(hs); + bufd = ptrd = STRPTR(out); + ed = ARRPTR(out); + + for (i = 0; i < count; ++i) + { + int len = HSTORE_KEYLEN(es, i); + char *ptrs = HSTORE_KEY(es, bufs, i); + + if (!(len == keylen && memcmp(ptrs, keyptr, keylen) == 0)) + { + int vallen = HSTORE_VALLEN(es, i); + + HS_COPYITEM(ed, bufd, ptrd, ptrs, len, vallen, + HSTORE_VALISNULL(es, i)); + ++outcount; + } + } + + HS_FINALIZE(out, outcount, bufd, ptrd); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_delete_array); +Datum +hstore_delete_array(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + HStore *out = palloc(VARSIZE(hs)); + int hs_count = HS_COUNT(hs); + char *ps, + *bufd, + *pd; + HEntry *es, + *ed; + int i, + j; + int outcount = 0; + ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(1); + int nkeys; + Pairs *key_pairs = hstoreArrayToPairs(key_array, &nkeys); + + SET_VARSIZE(out, VARSIZE(hs)); + HS_SETCOUNT(out, hs_count); /* temporary! */ + + ps = STRPTR(hs); + es = ARRPTR(hs); + bufd = pd = STRPTR(out); + ed = ARRPTR(out); + + if (nkeys == 0) + { + /* return a copy of the input, unchanged */ + memcpy(out, hs, VARSIZE(hs)); + HS_FIXSIZE(out, hs_count); + HS_SETCOUNT(out, hs_count); + PG_RETURN_POINTER(out); + } + + /* + * this is in effect a merge between hs and key_pairs, both of which are + * already sorted by (keylen,key); we take keys from hs only + */ + + for (i = j = 0; i < hs_count;) + { + int difference; + + if (j >= nkeys) + difference = -1; + else + { + int skeylen = HSTORE_KEYLEN(es, i); + + if (skeylen == key_pairs[j].keylen) + difference = memcmp(HSTORE_KEY(es, ps, i), + key_pairs[j].key, + key_pairs[j].keylen); + else + difference = (skeylen > key_pairs[j].keylen) ? 1 : -1; + } + + if (difference > 0) + ++j; + else if (difference == 0) + ++i, ++j; + else + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es, ps, i), HSTORE_KEYLEN(es, i), + HSTORE_VALLEN(es, i), HSTORE_VALISNULL(es, i)); + ++outcount; + ++i; + } + } + + HS_FINALIZE(out, outcount, bufd, pd); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_delete_hstore); +Datum +hstore_delete_hstore(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + HStore *hs2 = PG_GETARG_HSTORE_P(1); + HStore *out = palloc(VARSIZE(hs)); + int hs_count = HS_COUNT(hs); + int hs2_count = HS_COUNT(hs2); + char *ps, + *ps2, + *bufd, + *pd; + HEntry *es, + *es2, + *ed; + int i, + j; + int outcount = 0; + + SET_VARSIZE(out, VARSIZE(hs)); + HS_SETCOUNT(out, hs_count); /* temporary! */ + + ps = STRPTR(hs); + es = ARRPTR(hs); + ps2 = STRPTR(hs2); + es2 = ARRPTR(hs2); + bufd = pd = STRPTR(out); + ed = ARRPTR(out); + + if (hs2_count == 0) + { + /* return a copy of the input, unchanged */ + memcpy(out, hs, VARSIZE(hs)); + HS_FIXSIZE(out, hs_count); + HS_SETCOUNT(out, hs_count); + PG_RETURN_POINTER(out); + } + + /* + * this is in effect a merge between hs and hs2, both of which are already + * sorted by (keylen,key); we take keys from hs only; for equal keys, we + * take the value from hs unless the values are equal + */ + + for (i = j = 0; i < hs_count;) + { + int difference; + + if (j >= hs2_count) + difference = -1; + else + { + int skeylen = HSTORE_KEYLEN(es, i); + int s2keylen = HSTORE_KEYLEN(es2, j); + + if (skeylen == s2keylen) + difference = memcmp(HSTORE_KEY(es, ps, i), + HSTORE_KEY(es2, ps2, j), + skeylen); + else + difference = (skeylen > s2keylen) ? 1 : -1; + } + + if (difference > 0) + ++j; + else if (difference == 0) + { + int svallen = HSTORE_VALLEN(es, i); + int snullval = HSTORE_VALISNULL(es, i); + + if (snullval != HSTORE_VALISNULL(es2, j) || + (!snullval && (svallen != HSTORE_VALLEN(es2, j) || + memcmp(HSTORE_VAL(es, ps, i), + HSTORE_VAL(es2, ps2, j), + svallen) != 0))) + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es, ps, i), HSTORE_KEYLEN(es, i), + svallen, snullval); + ++outcount; + } + ++i, ++j; + } + else + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es, ps, i), HSTORE_KEYLEN(es, i), + HSTORE_VALLEN(es, i), HSTORE_VALISNULL(es, i)); + ++outcount; + ++i; + } + } + + HS_FINALIZE(out, outcount, bufd, pd); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_concat); +Datum +hstore_concat(PG_FUNCTION_ARGS) +{ + HStore *s1 = PG_GETARG_HSTORE_P(0); + HStore *s2 = PG_GETARG_HSTORE_P(1); + HStore *out = palloc(VARSIZE(s1) + VARSIZE(s2)); + char *ps1, + *ps2, + *bufd, + *pd; + HEntry *es1, + *es2, + *ed; + int s1idx; + int s2idx; + int s1count = HS_COUNT(s1); + int s2count = HS_COUNT(s2); + int outcount = 0; + + SET_VARSIZE(out, VARSIZE(s1) + VARSIZE(s2) - HSHRDSIZE); + HS_SETCOUNT(out, s1count + s2count); + + if (s1count == 0) + { + /* return a copy of the input, unchanged */ + memcpy(out, s2, VARSIZE(s2)); + HS_FIXSIZE(out, s2count); + HS_SETCOUNT(out, s2count); + PG_RETURN_POINTER(out); + } + + if (s2count == 0) + { + /* return a copy of the input, unchanged */ + memcpy(out, s1, VARSIZE(s1)); + HS_FIXSIZE(out, s1count); + HS_SETCOUNT(out, s1count); + PG_RETURN_POINTER(out); + } + + ps1 = STRPTR(s1); + ps2 = STRPTR(s2); + bufd = pd = STRPTR(out); + es1 = ARRPTR(s1); + es2 = ARRPTR(s2); + ed = ARRPTR(out); + + /* + * this is in effect a merge between s1 and s2, both of which are already + * sorted by (keylen,key); we take s2 for equal keys + */ + + for (s1idx = s2idx = 0; s1idx < s1count || s2idx < s2count; ++outcount) + { + int difference; + + if (s1idx >= s1count) + difference = 1; + else if (s2idx >= s2count) + difference = -1; + else + { + int s1keylen = HSTORE_KEYLEN(es1, s1idx); + int s2keylen = HSTORE_KEYLEN(es2, s2idx); + + if (s1keylen == s2keylen) + difference = memcmp(HSTORE_KEY(es1, ps1, s1idx), + HSTORE_KEY(es2, ps2, s2idx), + s1keylen); + else + difference = (s1keylen > s2keylen) ? 1 : -1; + } + + if (difference >= 0) + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es2, ps2, s2idx), HSTORE_KEYLEN(es2, s2idx), + HSTORE_VALLEN(es2, s2idx), HSTORE_VALISNULL(es2, s2idx)); + ++s2idx; + if (difference == 0) + ++s1idx; + } + else + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es1, ps1, s1idx), HSTORE_KEYLEN(es1, s1idx), + HSTORE_VALLEN(es1, s1idx), HSTORE_VALISNULL(es1, s1idx)); + ++s1idx; + } + } + + HS_FINALIZE(out, outcount, bufd, pd); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_slice_to_array); +Datum +hstore_slice_to_array(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + HEntry *entries = ARRPTR(hs); + char *ptr = STRPTR(hs); + ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(1); + ArrayType *aout; + Datum *key_datums; + bool *key_nulls; + Datum *out_datums; + bool *out_nulls; + int key_count; + int i; + + deconstruct_array_builtin(key_array, TEXTOID, &key_datums, &key_nulls, &key_count); + + if (key_count == 0) + { + aout = construct_empty_array(TEXTOID); + PG_RETURN_POINTER(aout); + } + + out_datums = palloc(sizeof(Datum) * key_count); + out_nulls = palloc(sizeof(bool) * key_count); + + for (i = 0; i < key_count; ++i) + { + text *key = (text *) DatumGetPointer(key_datums[i]); + int idx; + + if (key_nulls[i]) + idx = -1; + else + idx = hstoreFindKey(hs, NULL, VARDATA(key), VARSIZE(key) - VARHDRSZ); + + if (idx < 0 || HSTORE_VALISNULL(entries, idx)) + { + out_nulls[i] = true; + out_datums[i] = (Datum) 0; + } + else + { + out_datums[i] = + PointerGetDatum(cstring_to_text_with_len(HSTORE_VAL(entries, ptr, idx), + HSTORE_VALLEN(entries, idx))); + out_nulls[i] = false; + } + } + + aout = construct_md_array(out_datums, out_nulls, + ARR_NDIM(key_array), + ARR_DIMS(key_array), + ARR_LBOUND(key_array), + TEXTOID, -1, false, TYPALIGN_INT); + + PG_RETURN_POINTER(aout); +} + + +PG_FUNCTION_INFO_V1(hstore_slice_to_hstore); +Datum +hstore_slice_to_hstore(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + HEntry *entries = ARRPTR(hs); + char *ptr = STRPTR(hs); + ArrayType *key_array = PG_GETARG_ARRAYTYPE_P(1); + HStore *out; + int nkeys; + Pairs *key_pairs = hstoreArrayToPairs(key_array, &nkeys); + Pairs *out_pairs; + int bufsiz; + int lastidx = 0; + int i; + int out_count = 0; + + if (nkeys == 0) + { + out = hstorePairs(NULL, 0, 0); + PG_RETURN_POINTER(out); + } + + /* hstoreArrayToPairs() checked overflow */ + out_pairs = palloc(sizeof(Pairs) * nkeys); + bufsiz = 0; + + /* + * we exploit the fact that the pairs list is already sorted into strictly + * increasing order to narrow the hstoreFindKey search; each search can + * start one entry past the previous "found" entry, or at the lower bound + * of the last search. + */ + + for (i = 0; i < nkeys; ++i) + { + int idx = hstoreFindKey(hs, &lastidx, + key_pairs[i].key, key_pairs[i].keylen); + + if (idx >= 0) + { + out_pairs[out_count].key = key_pairs[i].key; + bufsiz += (out_pairs[out_count].keylen = key_pairs[i].keylen); + out_pairs[out_count].val = HSTORE_VAL(entries, ptr, idx); + bufsiz += (out_pairs[out_count].vallen = HSTORE_VALLEN(entries, idx)); + out_pairs[out_count].isnull = HSTORE_VALISNULL(entries, idx); + out_pairs[out_count].needfree = false; + ++out_count; + } + } + + /* + * we don't use hstoreUniquePairs here because we know that the pairs list + * is already sorted and uniq'ed. + */ + + out = hstorePairs(out_pairs, out_count, bufsiz); + + PG_RETURN_POINTER(out); +} + + +PG_FUNCTION_INFO_V1(hstore_akeys); +Datum +hstore_akeys(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + Datum *d; + ArrayType *a; + HEntry *entries = ARRPTR(hs); + char *base = STRPTR(hs); + int count = HS_COUNT(hs); + int i; + + if (count == 0) + { + a = construct_empty_array(TEXTOID); + PG_RETURN_POINTER(a); + } + + d = (Datum *) palloc(sizeof(Datum) * count); + + for (i = 0; i < count; ++i) + { + text *t = cstring_to_text_with_len(HSTORE_KEY(entries, base, i), + HSTORE_KEYLEN(entries, i)); + + d[i] = PointerGetDatum(t); + } + + a = construct_array_builtin(d, count, TEXTOID); + + PG_RETURN_POINTER(a); +} + + +PG_FUNCTION_INFO_V1(hstore_avals); +Datum +hstore_avals(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + Datum *d; + bool *nulls; + ArrayType *a; + HEntry *entries = ARRPTR(hs); + char *base = STRPTR(hs); + int count = HS_COUNT(hs); + int lb = 1; + int i; + + if (count == 0) + { + a = construct_empty_array(TEXTOID); + PG_RETURN_POINTER(a); + } + + d = (Datum *) palloc(sizeof(Datum) * count); + nulls = (bool *) palloc(sizeof(bool) * count); + + for (i = 0; i < count; ++i) + { + if (HSTORE_VALISNULL(entries, i)) + { + d[i] = (Datum) 0; + nulls[i] = true; + } + else + { + text *item = cstring_to_text_with_len(HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + + d[i] = PointerGetDatum(item); + nulls[i] = false; + } + } + + a = construct_md_array(d, nulls, 1, &count, &lb, + TEXTOID, -1, false, TYPALIGN_INT); + + PG_RETURN_POINTER(a); +} + + +static ArrayType * +hstore_to_array_internal(HStore *hs, int ndims) +{ + HEntry *entries = ARRPTR(hs); + char *base = STRPTR(hs); + int count = HS_COUNT(hs); + int out_size[2] = {0, 2}; + int lb[2] = {1, 1}; + Datum *out_datums; + bool *out_nulls; + int i; + + Assert(ndims < 3); + + if (count == 0 || ndims == 0) + return construct_empty_array(TEXTOID); + + out_size[0] = count * 2 / ndims; + out_datums = palloc(sizeof(Datum) * count * 2); + out_nulls = palloc(sizeof(bool) * count * 2); + + for (i = 0; i < count; ++i) + { + text *key = cstring_to_text_with_len(HSTORE_KEY(entries, base, i), + HSTORE_KEYLEN(entries, i)); + + out_datums[i * 2] = PointerGetDatum(key); + out_nulls[i * 2] = false; + + if (HSTORE_VALISNULL(entries, i)) + { + out_datums[i * 2 + 1] = (Datum) 0; + out_nulls[i * 2 + 1] = true; + } + else + { + text *item = cstring_to_text_with_len(HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + + out_datums[i * 2 + 1] = PointerGetDatum(item); + out_nulls[i * 2 + 1] = false; + } + } + + return construct_md_array(out_datums, out_nulls, + ndims, out_size, lb, + TEXTOID, -1, false, TYPALIGN_INT); +} + +PG_FUNCTION_INFO_V1(hstore_to_array); +Datum +hstore_to_array(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + ArrayType *out = hstore_to_array_internal(hs, 1); + + PG_RETURN_POINTER(out); +} + +PG_FUNCTION_INFO_V1(hstore_to_matrix); +Datum +hstore_to_matrix(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + ArrayType *out = hstore_to_array_internal(hs, 2); + + PG_RETURN_POINTER(out); +} + +/* + * Common initialization function for the various set-returning + * funcs. fcinfo is only passed if the function is to return a + * composite; it will be used to look up the return tupledesc. + * we stash a copy of the hstore in the multi-call context in + * case it was originally toasted. (At least I assume that's why; + * there was no explanatory comment in the original code. --AG) + */ + +static void +setup_firstcall(FuncCallContext *funcctx, HStore *hs, + FunctionCallInfo fcinfo) +{ + MemoryContext oldcontext; + HStore *st; + + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + st = (HStore *) palloc(VARSIZE(hs)); + memcpy(st, hs, VARSIZE(hs)); + + funcctx->user_fctx = (void *) st; + + if (fcinfo) + { + TupleDesc tupdesc; + + /* 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"); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + } + + MemoryContextSwitchTo(oldcontext); +} + + +PG_FUNCTION_INFO_V1(hstore_skeys); +Datum +hstore_skeys(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + HStore *hs; + int i; + + if (SRF_IS_FIRSTCALL()) + { + hs = PG_GETARG_HSTORE_P(0); + funcctx = SRF_FIRSTCALL_INIT(); + setup_firstcall(funcctx, hs, NULL); + } + + funcctx = SRF_PERCALL_SETUP(); + hs = (HStore *) funcctx->user_fctx; + i = funcctx->call_cntr; + + if (i < HS_COUNT(hs)) + { + HEntry *entries = ARRPTR(hs); + text *item; + + item = cstring_to_text_with_len(HSTORE_KEY(entries, STRPTR(hs), i), + HSTORE_KEYLEN(entries, i)); + + SRF_RETURN_NEXT(funcctx, PointerGetDatum(item)); + } + + SRF_RETURN_DONE(funcctx); +} + + +PG_FUNCTION_INFO_V1(hstore_svals); +Datum +hstore_svals(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + HStore *hs; + int i; + + if (SRF_IS_FIRSTCALL()) + { + hs = PG_GETARG_HSTORE_P(0); + funcctx = SRF_FIRSTCALL_INIT(); + setup_firstcall(funcctx, hs, NULL); + } + + funcctx = SRF_PERCALL_SETUP(); + hs = (HStore *) funcctx->user_fctx; + i = funcctx->call_cntr; + + if (i < HS_COUNT(hs)) + { + HEntry *entries = ARRPTR(hs); + + if (HSTORE_VALISNULL(entries, i)) + { + ReturnSetInfo *rsi; + + /* ugly ugly ugly. why no macro for this? */ + (funcctx)->call_cntr++; + rsi = (ReturnSetInfo *) fcinfo->resultinfo; + rsi->isDone = ExprMultipleResult; + PG_RETURN_NULL(); + } + else + { + text *item; + + item = cstring_to_text_with_len(HSTORE_VAL(entries, STRPTR(hs), i), + HSTORE_VALLEN(entries, i)); + + SRF_RETURN_NEXT(funcctx, PointerGetDatum(item)); + } + } + + SRF_RETURN_DONE(funcctx); +} + + +PG_FUNCTION_INFO_V1(hstore_contains); +Datum +hstore_contains(PG_FUNCTION_ARGS) +{ + HStore *val = PG_GETARG_HSTORE_P(0); + HStore *tmpl = PG_GETARG_HSTORE_P(1); + bool res = true; + HEntry *te = ARRPTR(tmpl); + char *tstr = STRPTR(tmpl); + HEntry *ve = ARRPTR(val); + char *vstr = STRPTR(val); + int tcount = HS_COUNT(tmpl); + int lastidx = 0; + int i; + + /* + * we exploit the fact that keys in "tmpl" are in strictly increasing + * order to narrow the hstoreFindKey search; each search can start one + * entry past the previous "found" entry, or at the lower bound of the + * search + */ + + for (i = 0; res && i < tcount; ++i) + { + int idx = hstoreFindKey(val, &lastidx, + HSTORE_KEY(te, tstr, i), + HSTORE_KEYLEN(te, i)); + + if (idx >= 0) + { + bool nullval = HSTORE_VALISNULL(te, i); + int vallen = HSTORE_VALLEN(te, i); + + if (nullval != HSTORE_VALISNULL(ve, idx) || + (!nullval && (vallen != HSTORE_VALLEN(ve, idx) || + memcmp(HSTORE_VAL(te, tstr, i), + HSTORE_VAL(ve, vstr, idx), + vallen) != 0))) + res = false; + } + else + res = false; + } + + PG_RETURN_BOOL(res); +} + + +PG_FUNCTION_INFO_V1(hstore_contained); +Datum +hstore_contained(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(hstore_contains, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); +} + + +PG_FUNCTION_INFO_V1(hstore_each); +Datum +hstore_each(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + HStore *hs; + int i; + + if (SRF_IS_FIRSTCALL()) + { + hs = PG_GETARG_HSTORE_P(0); + funcctx = SRF_FIRSTCALL_INIT(); + setup_firstcall(funcctx, hs, fcinfo); + } + + funcctx = SRF_PERCALL_SETUP(); + hs = (HStore *) funcctx->user_fctx; + i = funcctx->call_cntr; + + if (i < HS_COUNT(hs)) + { + HEntry *entries = ARRPTR(hs); + char *ptr = STRPTR(hs); + Datum res, + dvalues[2]; + bool nulls[2] = {false, false}; + text *item; + HeapTuple tuple; + + item = cstring_to_text_with_len(HSTORE_KEY(entries, ptr, i), + HSTORE_KEYLEN(entries, i)); + dvalues[0] = PointerGetDatum(item); + + if (HSTORE_VALISNULL(entries, i)) + { + dvalues[1] = (Datum) 0; + nulls[1] = true; + } + else + { + item = cstring_to_text_with_len(HSTORE_VAL(entries, ptr, i), + HSTORE_VALLEN(entries, i)); + dvalues[1] = PointerGetDatum(item); + } + + tuple = heap_form_tuple(funcctx->tuple_desc, dvalues, nulls); + res = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, res); + } + + SRF_RETURN_DONE(funcctx); +} + + +/* + * btree sort order for hstores isn't intended to be useful; we really only + * care about equality versus non-equality. we compare the entire string + * buffer first, then the entry pos array. + */ + +PG_FUNCTION_INFO_V1(hstore_cmp); +Datum +hstore_cmp(PG_FUNCTION_ARGS) +{ + HStore *hs1 = PG_GETARG_HSTORE_P(0); + HStore *hs2 = PG_GETARG_HSTORE_P(1); + int hcount1 = HS_COUNT(hs1); + int hcount2 = HS_COUNT(hs2); + int res = 0; + + if (hcount1 == 0 || hcount2 == 0) + { + /* + * if either operand is empty, and the other is nonempty, the nonempty + * one is larger. If both are empty they are equal. + */ + if (hcount1 > 0) + res = 1; + else if (hcount2 > 0) + res = -1; + } + else + { + /* here we know both operands are nonempty */ + char *str1 = STRPTR(hs1); + char *str2 = STRPTR(hs2); + HEntry *ent1 = ARRPTR(hs1); + HEntry *ent2 = ARRPTR(hs2); + size_t len1 = HSE_ENDPOS(ent1[2 * hcount1 - 1]); + size_t len2 = HSE_ENDPOS(ent2[2 * hcount2 - 1]); + + res = memcmp(str1, str2, Min(len1, len2)); + + if (res == 0) + { + if (len1 > len2) + res = 1; + else if (len1 < len2) + res = -1; + else if (hcount1 > hcount2) + res = 1; + else if (hcount2 > hcount1) + res = -1; + else + { + int count = hcount1 * 2; + int i; + + for (i = 0; i < count; ++i) + if (HSE_ENDPOS(ent1[i]) != HSE_ENDPOS(ent2[i]) || + HSE_ISNULL(ent1[i]) != HSE_ISNULL(ent2[i])) + break; + if (i < count) + { + if (HSE_ENDPOS(ent1[i]) < HSE_ENDPOS(ent2[i])) + res = -1; + else if (HSE_ENDPOS(ent1[i]) > HSE_ENDPOS(ent2[i])) + res = 1; + else if (HSE_ISNULL(ent1[i])) + res = 1; + else if (HSE_ISNULL(ent2[i])) + res = -1; + } + } + } + else + { + res = (res > 0) ? 1 : -1; + } + } + + /* + * this is a btree support function; this is one of the few places where + * memory needs to be explicitly freed. + */ + PG_FREE_IF_COPY(hs1, 0); + PG_FREE_IF_COPY(hs2, 1); + PG_RETURN_INT32(res); +} + + +PG_FUNCTION_INFO_V1(hstore_eq); +Datum +hstore_eq(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res == 0); +} + +PG_FUNCTION_INFO_V1(hstore_ne); +Datum +hstore_ne(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res != 0); +} + +PG_FUNCTION_INFO_V1(hstore_gt); +Datum +hstore_gt(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res > 0); +} + +PG_FUNCTION_INFO_V1(hstore_ge); +Datum +hstore_ge(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res >= 0); +} + +PG_FUNCTION_INFO_V1(hstore_lt); +Datum +hstore_lt(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res < 0); +} + +PG_FUNCTION_INFO_V1(hstore_le); +Datum +hstore_le(PG_FUNCTION_ARGS) +{ + int res = DatumGetInt32(DirectFunctionCall2(hstore_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res <= 0); +} + + +PG_FUNCTION_INFO_V1(hstore_hash); +Datum +hstore_hash(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + Datum hval = hash_any((unsigned char *) VARDATA(hs), + VARSIZE(hs) - VARHDRSZ); + + /* + * This (along with hstore_hash_extended) is the only place in the code + * that cares whether the overall varlena size exactly matches the true + * data size; this assertion should be maintained by all the other code, + * but we make it explicit here. + */ + Assert(VARSIZE(hs) == + (HS_COUNT(hs) != 0 ? + CALCDATASIZE(HS_COUNT(hs), + HSE_ENDPOS(ARRPTR(hs)[2 * HS_COUNT(hs) - 1])) : + HSHRDSIZE)); + + PG_FREE_IF_COPY(hs, 0); + PG_RETURN_DATUM(hval); +} + +PG_FUNCTION_INFO_V1(hstore_hash_extended); +Datum +hstore_hash_extended(PG_FUNCTION_ARGS) +{ + HStore *hs = PG_GETARG_HSTORE_P(0); + uint64 seed = PG_GETARG_INT64(1); + Datum hval; + + hval = hash_any_extended((unsigned char *) VARDATA(hs), + VARSIZE(hs) - VARHDRSZ, + seed); + + /* See comment in hstore_hash */ + Assert(VARSIZE(hs) == + (HS_COUNT(hs) != 0 ? + CALCDATASIZE(HS_COUNT(hs), + HSE_ENDPOS(ARRPTR(hs)[2 * HS_COUNT(hs) - 1])) : + HSHRDSIZE)); + + PG_FREE_IF_COPY(hs, 0); + PG_RETURN_DATUM(hval); +} diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c new file mode 100644 index 0000000..6ac91e0 --- /dev/null +++ b/contrib/hstore/hstore_subs.c @@ -0,0 +1,297 @@ +/*------------------------------------------------------------------------- + * + * hstore_subs.c + * Subscripting support functions for hstore. + * + * This is a great deal simpler than array_subs.c, because the result of + * subscripting an hstore is just a text string (the value for the key). + * We do not need to support array slicing notation, nor multiple subscripts. + * Less obviously, because the subscript result is never a SQL container + * type, there will never be any nested-assignment scenarios, so we do not + * need a fetch_old function. In turn, that means we can drop the + * check_subscripts function and just let the fetch and assign functions + * do everything. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/hstore/hstore_subs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "executor/execExpr.h" +#include "hstore.h" +#include "nodes/nodeFuncs.h" +#include "nodes/subscripting.h" +#include "parser/parse_coerce.h" +#include "parser/parse_expr.h" +#include "utils/builtins.h" + + +/* + * Finish parse analysis of a SubscriptingRef expression for hstore. + * + * Verify there's just one subscript, coerce it to text, + * and set the result type of the SubscriptingRef node. + */ +static void +hstore_subscript_transform(SubscriptingRef *sbsref, + List *indirection, + ParseState *pstate, + bool isSlice, + bool isAssignment) +{ + A_Indices *ai; + Node *subexpr; + + /* We support only single-subscript, non-slice cases */ + if (isSlice || list_length(indirection) != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("hstore allows only one subscript"), + parser_errposition(pstate, + exprLocation((Node *) indirection)))); + + /* Transform the subscript expression to type text */ + ai = linitial_node(A_Indices, indirection); + Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice); + + subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind); + /* If it's not text already, try to coerce */ + subexpr = coerce_to_target_type(pstate, + subexpr, exprType(subexpr), + TEXTOID, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + if (subexpr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("hstore subscript must have type text"), + parser_errposition(pstate, exprLocation(ai->uidx)))); + + /* ... and store the transformed subscript into the SubscriptRef node */ + sbsref->refupperindexpr = list_make1(subexpr); + sbsref->reflowerindexpr = NIL; + + /* Determine the result type of the subscripting operation; always text */ + sbsref->refrestype = TEXTOID; + sbsref->reftypmod = -1; +} + +/* + * Evaluate SubscriptingRef fetch for hstore. + * + * Source container is in step's result variable (it's known not NULL, since + * we set fetch_strict to true), and the subscript expression is in the + * upperindex[] array. + */ +static void +hstore_subscript_fetch(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + HStore *hs; + text *key; + HEntry *entries; + int idx; + text *out; + + /* Should not get here if source hstore is null */ + Assert(!(*op->resnull)); + + /* Check for null subscript */ + if (sbsrefstate->upperindexnull[0]) + { + *op->resnull = true; + return; + } + + /* OK, fetch/detoast the hstore and subscript */ + hs = DatumGetHStoreP(*op->resvalue); + key = DatumGetTextPP(sbsrefstate->upperindex[0]); + + /* The rest is basically the same as hstore_fetchval() */ + entries = ARRPTR(hs); + idx = hstoreFindKey(hs, NULL, + VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key)); + + if (idx < 0 || HSTORE_VALISNULL(entries, idx)) + { + *op->resnull = true; + return; + } + + out = cstring_to_text_with_len(HSTORE_VAL(entries, STRPTR(hs), idx), + HSTORE_VALLEN(entries, idx)); + + *op->resvalue = PointerGetDatum(out); +} + +/* + * Evaluate SubscriptingRef assignment for hstore. + * + * Input container (possibly null) is in result area, replacement value is in + * SubscriptingRefState's replacevalue/replacenull. + */ +static void +hstore_subscript_assign(ExprState *state, + ExprEvalStep *op, + ExprContext *econtext) +{ + SubscriptingRefState *sbsrefstate = op->d.sbsref.state; + text *key; + Pairs p; + HStore *out; + + /* Check for null subscript */ + if (sbsrefstate->upperindexnull[0]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("hstore subscript in assignment must not be null"))); + + /* OK, fetch/detoast the subscript */ + key = DatumGetTextPP(sbsrefstate->upperindex[0]); + + /* Create a Pairs entry for subscript + replacement value */ + p.needfree = false; + p.key = VARDATA_ANY(key); + p.keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key)); + + if (sbsrefstate->replacenull) + { + p.vallen = 0; + p.isnull = true; + } + else + { + text *val = DatumGetTextPP(sbsrefstate->replacevalue); + + p.val = VARDATA_ANY(val); + p.vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(val)); + p.isnull = false; + } + + if (*op->resnull) + { + /* Just build a one-element hstore (cf. hstore_from_text) */ + out = hstorePairs(&p, 1, p.keylen + p.vallen); + } + else + { + /* + * Otherwise, merge the new key into the hstore. Based on + * hstore_concat. + */ + HStore *hs = DatumGetHStoreP(*op->resvalue); + int s1count = HS_COUNT(hs); + int outcount = 0; + int vsize; + char *ps1, + *bufd, + *pd; + HEntry *es1, + *ed; + int s1idx; + int s2idx; + + /* Allocate result without considering possibility of duplicate */ + vsize = CALCDATASIZE(s1count + 1, VARSIZE(hs) + p.keylen + p.vallen); + out = palloc(vsize); + SET_VARSIZE(out, vsize); + HS_SETCOUNT(out, s1count + 1); + + ps1 = STRPTR(hs); + bufd = pd = STRPTR(out); + es1 = ARRPTR(hs); + ed = ARRPTR(out); + + for (s1idx = s2idx = 0; s1idx < s1count || s2idx < 1; ++outcount) + { + int difference; + + if (s1idx >= s1count) + difference = 1; + else if (s2idx >= 1) + difference = -1; + else + { + int s1keylen = HSTORE_KEYLEN(es1, s1idx); + int s2keylen = p.keylen; + + if (s1keylen == s2keylen) + difference = memcmp(HSTORE_KEY(es1, ps1, s1idx), + p.key, + s1keylen); + else + difference = (s1keylen > s2keylen) ? 1 : -1; + } + + if (difference >= 0) + { + HS_ADDITEM(ed, bufd, pd, p); + ++s2idx; + if (difference == 0) + ++s1idx; + } + else + { + HS_COPYITEM(ed, bufd, pd, + HSTORE_KEY(es1, ps1, s1idx), + HSTORE_KEYLEN(es1, s1idx), + HSTORE_VALLEN(es1, s1idx), + HSTORE_VALISNULL(es1, s1idx)); + ++s1idx; + } + } + + HS_FINALIZE(out, outcount, bufd, pd); + } + + *op->resvalue = PointerGetDatum(out); + *op->resnull = false; +} + +/* + * Set up execution state for an hstore subscript operation. + */ +static void +hstore_exec_setup(const SubscriptingRef *sbsref, + SubscriptingRefState *sbsrefstate, + SubscriptExecSteps *methods) +{ + /* Assert we are dealing with one subscript */ + Assert(sbsrefstate->numlower == 0); + Assert(sbsrefstate->numupper == 1); + /* We can't check upperprovided[0] here, but it must be true */ + + /* Pass back pointers to appropriate step execution functions */ + methods->sbs_check_subscripts = NULL; + methods->sbs_fetch = hstore_subscript_fetch; + methods->sbs_assign = hstore_subscript_assign; + methods->sbs_fetch_old = NULL; +} + +/* + * hstore_subscript_handler + * Subscripting handler for hstore. + */ +PG_FUNCTION_INFO_V1(hstore_subscript_handler); +Datum +hstore_subscript_handler(PG_FUNCTION_ARGS) +{ + static const SubscriptRoutines sbsroutines = { + .transform = hstore_subscript_transform, + .exec_setup = hstore_exec_setup, + .fetch_strict = true, /* fetch returns NULL for NULL inputs */ + .fetch_leakproof = true, /* fetch returns NULL for bad subscript */ + .store_leakproof = false /* ... but assignment throws error */ + }; + + PG_RETURN_POINTER(&sbsroutines); +} diff --git a/contrib/hstore/meson.build b/contrib/hstore/meson.build new file mode 100644 index 0000000..20acc45 --- /dev/null +++ b/contrib/hstore/meson.build @@ -0,0 +1,56 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +# .. so that includes of hstore/hstore.h work +hstore_inc = include_directories('.', '../') + +hstore_sources = files( + 'hstore_compat.c', + 'hstore_gin.c', + 'hstore_gist.c', + 'hstore_io.c', + 'hstore_op.c', + 'hstore_subs.c', +) + +if host_system == 'windows' + hstore_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'hstore', + '--FILEDESC', 'hstore - key/value pair data type',]) +endif + +hstore = shared_module('hstore', + hstore_sources, + c_pch: pch_postgres_h, + kwargs: contrib_mod_args, +) +contrib_targets += hstore + +install_data( + 'hstore.control', + 'hstore--1.1--1.2.sql', + 'hstore--1.2--1.3.sql', + 'hstore--1.3--1.4.sql', + 'hstore--1.4.sql', + 'hstore--1.4--1.5.sql', + 'hstore--1.5--1.6.sql', + 'hstore--1.6--1.7.sql', + 'hstore--1.7--1.8.sql', + kwargs: contrib_data_args, +) + +install_headers( + 'hstore.h', + install_dir: dir_include_extension / 'hstore', +) + +tests += { + 'name': 'hstore', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'hstore', + 'hstore_utf8', + ], + }, +} diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql new file mode 100644 index 0000000..efef912 --- /dev/null +++ b/contrib/hstore/sql/hstore.sql @@ -0,0 +1,397 @@ +CREATE EXTENSION hstore; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +set escape_string_warning=off; + +--hstore; + +select ''::hstore; +select 'a=>b'::hstore; +select ' a=>b'::hstore; +select 'a =>b'::hstore; +select 'a=>b '::hstore; +select 'a=> b'::hstore; +select '"a"=>"b"'::hstore; +select ' "a"=>"b"'::hstore; +select '"a" =>"b"'::hstore; +select '"a"=>"b" '::hstore; +select '"a"=> "b"'::hstore; +select 'aa=>bb'::hstore; +select ' aa=>bb'::hstore; +select 'aa =>bb'::hstore; +select 'aa=>bb '::hstore; +select 'aa=> bb'::hstore; +select '"aa"=>"bb"'::hstore; +select ' "aa"=>"bb"'::hstore; +select '"aa" =>"bb"'::hstore; +select '"aa"=>"bb" '::hstore; +select '"aa"=> "bb"'::hstore; + +select 'aa=>bb, cc=>dd'::hstore; +select 'aa=>bb , cc=>dd'::hstore; +select 'aa=>bb ,cc=>dd'::hstore; +select 'aa=>bb, "cc"=>dd'::hstore; +select 'aa=>bb , "cc"=>dd'::hstore; +select 'aa=>bb ,"cc"=>dd'::hstore; +select 'aa=>"bb", cc=>dd'::hstore; +select 'aa=>"bb" , cc=>dd'::hstore; +select 'aa=>"bb" ,cc=>dd'::hstore; + +select 'aa=>null'::hstore; +select 'aa=>NuLl'::hstore; +select 'aa=>"NuLl"'::hstore; + +select e'\\=a=>q=w'::hstore; +select e'"=a"=>q\\=w'::hstore; +select e'"\\"a"=>q>w'::hstore; +select e'\\"a=>q"w'::hstore; + +select ''::hstore; +select ' '::hstore; + +-- invalid input +select ' =>null'::hstore; +select 'aa=>"'::hstore; + +-- also try it with non-error-throwing API +select pg_input_is_valid('a=>b', 'hstore'); +select pg_input_is_valid('a=b', 'hstore'); +select * from pg_input_error_info('a=b', 'hstore'); +select * from pg_input_error_info(' =>b', 'hstore'); + + +-- -> operator + +select 'aa=>b, c=>d , b=>16'::hstore->'c'; +select 'aa=>b, c=>d , b=>16'::hstore->'b'; +select 'aa=>b, c=>d , b=>16'::hstore->'aa'; +select ('aa=>b, c=>d , b=>16'::hstore->'gg') is null; +select ('aa=>NULL, c=>d , b=>16'::hstore->'aa') is null; +select ('aa=>"NULL", c=>d , b=>16'::hstore->'aa') is null; + +-- -> array operator + +select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['aa','c']; +select 'aa=>"NULL", c=>d , b=>16'::hstore -> ARRAY['c','aa']; +select 'aa=>NULL, c=>d , b=>16'::hstore -> ARRAY['aa','c',null]; +select 'aa=>1, c=>3, b=>2, d=>4'::hstore -> ARRAY[['b','d'],['aa','c']]; + +-- exists/defined + +select exist('a=>NULL, b=>qq', 'a'); +select exist('a=>NULL, b=>qq', 'b'); +select exist('a=>NULL, b=>qq', 'c'); +select exist('a=>"NULL", b=>qq', 'a'); +select defined('a=>NULL, b=>qq', 'a'); +select defined('a=>NULL, b=>qq', 'b'); +select defined('a=>NULL, b=>qq', 'c'); +select defined('a=>"NULL", b=>qq', 'a'); +select hstore 'a=>NULL, b=>qq' ? 'a'; +select hstore 'a=>NULL, b=>qq' ? 'b'; +select hstore 'a=>NULL, b=>qq' ? 'c'; +select hstore 'a=>"NULL", b=>qq' ? 'a'; +select hstore 'a=>NULL, b=>qq' ?| ARRAY['a','b']; +select hstore 'a=>NULL, b=>qq' ?| ARRAY['b','a']; +select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','a']; +select hstore 'a=>NULL, b=>qq' ?| ARRAY['c','d']; +select hstore 'a=>NULL, b=>qq' ?| '{}'::text[]; +select hstore 'a=>NULL, b=>qq' ?& ARRAY['a','b']; +select hstore 'a=>NULL, b=>qq' ?& ARRAY['b','a']; +select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','a']; +select hstore 'a=>NULL, b=>qq' ?& ARRAY['c','d']; +select hstore 'a=>NULL, b=>qq' ?& '{}'::text[]; + +-- delete + +select delete('a=>1 , b=>2, c=>3'::hstore, 'a'); +select delete('a=>null , b=>2, c=>3'::hstore, 'a'); +select delete('a=>1 , b=>2, c=>3'::hstore, 'b'); +select delete('a=>1 , b=>2, c=>3'::hstore, 'c'); +select delete('a=>1 , b=>2, c=>3'::hstore, 'd'); +select 'a=>1 , b=>2, c=>3'::hstore - 'a'::text; +select 'a=>null , b=>2, c=>3'::hstore - 'a'::text; +select 'a=>1 , b=>2, c=>3'::hstore - 'b'::text; +select 'a=>1 , b=>2, c=>3'::hstore - 'c'::text; +select 'a=>1 , b=>2, c=>3'::hstore - 'd'::text; +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b'::text) + = pg_column_size('a=>1, b=>2'::hstore); + +-- delete (array) + +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','e']); +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['d','b']); +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY['a','c']); +select delete('a=>1 , b=>2, c=>3'::hstore, ARRAY[['b'],['c'],['a']]); +select delete('a=>1 , b=>2, c=>3'::hstore, '{}'::text[]); +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','e']; +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['d','b']; +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c']; +select 'a=>1 , b=>2, c=>3'::hstore - ARRAY[['b'],['c'],['a']]; +select 'a=>1 , b=>2, c=>3'::hstore - '{}'::text[]; +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ARRAY['a','c']) + = pg_column_size('b=>2'::hstore); +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - '{}'::text[]) + = pg_column_size('a=>1, b=>2, c=>3'::hstore); + +-- delete (hstore) + +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>4, b=>2'::hstore); +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>NULL, c=>3'::hstore); +select delete('aa=>1 , b=>2, c=>3'::hstore, 'aa=>1, b=>2, c=>3'::hstore); +select delete('aa=>1 , b=>2, c=>3'::hstore, 'b=>2'::hstore); +select delete('aa=>1 , b=>2, c=>3'::hstore, ''::hstore); +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>4, b=>2'::hstore; +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>NULL, c=>3'::hstore; +select 'aa=>1 , b=>2, c=>3'::hstore - 'aa=>1, b=>2, c=>3'::hstore; +select 'aa=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore; +select 'aa=>1 , b=>2, c=>3'::hstore - ''::hstore; +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - 'b=>2'::hstore) + = pg_column_size('a=>1, c=>3'::hstore); +select pg_column_size('a=>1 , b=>2, c=>3'::hstore - ''::hstore) + = pg_column_size('a=>1, b=>2, c=>3'::hstore); + +-- || +select 'aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'; +select 'aa=>1 , b=>2, cq=>3'::hstore || 'aq=>l'; +select 'aa=>1 , b=>2, cq=>3'::hstore || 'aa=>l'; +select 'aa=>1 , b=>2, cq=>3'::hstore || ''; +select ''::hstore || 'cq=>l, b=>g, fg=>f'; +select pg_column_size(''::hstore || ''::hstore) = pg_column_size(''::hstore); +select pg_column_size('aa=>1'::hstore || 'b=>2'::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); +select pg_column_size('aa=>1, b=>2'::hstore || ''::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); +select pg_column_size(''::hstore || 'aa=>1, b=>2'::hstore) + = pg_column_size('aa=>1, b=>2'::hstore); + +-- hstore(text,text) +select 'a=>g, b=>c'::hstore || hstore('asd', 'gf'); +select 'a=>g, b=>c'::hstore || hstore('b', 'gf'); +select 'a=>g, b=>c'::hstore || hstore('b', 'NULL'); +select 'a=>g, b=>c'::hstore || hstore('b', NULL); +select ('a=>g, b=>c'::hstore || hstore(NULL, 'b')) is null; +select pg_column_size(hstore('b', 'gf')) + = pg_column_size('b=>gf'::hstore); +select pg_column_size('a=>g, b=>c'::hstore || hstore('b', 'gf')) + = pg_column_size('a=>g, b=>gf'::hstore); + +-- slice() +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['g','h','i']); +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b']); +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['aa','b']); +select slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa']); +select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b'])) + = pg_column_size('b=>2, c=>3'::hstore); +select pg_column_size(slice(hstore 'aa=>1, b=>2, c=>3', ARRAY['c','b','aa'])) + = pg_column_size('aa=>1, b=>2, c=>3'::hstore); + +-- array input +select '{}'::text[]::hstore; +select ARRAY['a','g','b','h','asd']::hstore; +select ARRAY['a','g','b','h','asd','i']::hstore; +select ARRAY[['a','g'],['b','h'],['asd','i']]::hstore; +select ARRAY[['a','g','b'],['h','asd','i']]::hstore; +select ARRAY[[['a','g'],['b','h'],['asd','i']]]::hstore; +select hstore('{}'::text[]); +select hstore(ARRAY['a','g','b','h','asd']); +select hstore(ARRAY['a','g','b','h','asd','i']); +select hstore(ARRAY[['a','g'],['b','h'],['asd','i']]); +select hstore(ARRAY[['a','g','b'],['h','asd','i']]); +select hstore(ARRAY[[['a','g'],['b','h'],['asd','i']]]); +select hstore('[0:5]={a,g,b,h,asd,i}'::text[]); +select hstore('[0:2][1:2]={{a,g},{b,h},{asd,i}}'::text[]); + +-- pairs of arrays +select hstore(ARRAY['a','b','asd'], ARRAY['g','h','i']); +select hstore(ARRAY['a','b','asd'], ARRAY['g','h',NULL]); +select hstore(ARRAY['z','y','x'], ARRAY['1','2','3']); +select hstore(ARRAY['aaa','bb','c','d'], ARRAY[null::text,null,null,null]); +select hstore(ARRAY['aaa','bb','c','d'], null); +select quote_literal(hstore('{}'::text[], '{}'::text[])); +select quote_literal(hstore('{}'::text[], null)); +select hstore(ARRAY['a'], '{}'::text[]); -- error +select hstore('{}'::text[], ARRAY['a']); -- error +select pg_column_size(hstore(ARRAY['a','b','asd'], ARRAY['g','h','i'])) + = pg_column_size('a=>g, b=>h, asd=>i'::hstore); + +-- records +select hstore(v) from (values (1, 'foo', 1.2, 3::float8)) v(a,b,c,d); +create domain hstestdom1 as integer not null default 0; +create table testhstore0 (a integer, b text, c numeric, d float8); +create table testhstore1 (a integer, b text, c numeric, d float8, e hstestdom1); +insert into testhstore0 values (1, 'foo', 1.2, 3::float8); +insert into testhstore1 values (1, 'foo', 1.2, 3::float8); +select hstore(v) from testhstore1 v; +select hstore(null::testhstore0); +select hstore(null::testhstore1); +select pg_column_size(hstore(v)) + = pg_column_size('a=>1, b=>"foo", c=>"1.2", d=>"3", e=>"0"'::hstore) + from testhstore1 v; +select populate_record(v, hstore('c', '3.45')) from testhstore1 v; +select populate_record(v, hstore('d', '3.45')) from testhstore1 v; +select populate_record(v, hstore('e', '123')) from testhstore1 v; +select populate_record(v, hstore('e', null)) from testhstore1 v; +select populate_record(v, hstore('c', null)) from testhstore1 v; +select populate_record(v, hstore('b', 'foo') || hstore('a', '123')) from testhstore1 v; +select populate_record(v, hstore('b', 'foo') || hstore('e', null)) from testhstore0 v; +select populate_record(v, hstore('b', 'foo') || hstore('e', null)) from testhstore1 v; +select populate_record(v, '') from testhstore0 v; +select populate_record(v, '') from testhstore1 v; +select populate_record(null::testhstore1, hstore('c', '3.45') || hstore('a', '123')); +select populate_record(null::testhstore1, hstore('c', '3.45') || hstore('e', '123')); +select populate_record(null::testhstore0, ''); +select populate_record(null::testhstore1, ''); +select v #= hstore('c', '3.45') from testhstore1 v; +select v #= hstore('d', '3.45') from testhstore1 v; +select v #= hstore('e', '123') from testhstore1 v; +select v #= hstore('c', null) from testhstore1 v; +select v #= hstore('e', null) from testhstore0 v; +select v #= hstore('e', null) from testhstore1 v; +select v #= (hstore('b', 'foo') || hstore('a', '123')) from testhstore1 v; +select v #= (hstore('b', 'foo') || hstore('e', '123')) from testhstore1 v; +select v #= hstore '' from testhstore0 v; +select v #= hstore '' from testhstore1 v; +select null::testhstore1 #= (hstore('c', '3.45') || hstore('a', '123')); +select null::testhstore1 #= (hstore('c', '3.45') || hstore('e', '123')); +select null::testhstore0 #= hstore ''; +select null::testhstore1 #= hstore ''; +select v #= h from testhstore1 v, (values (hstore 'a=>123',1),('b=>foo,c=>3.21',2),('a=>null',3),('e=>123',4),('f=>blah',5)) x(h,i) order by i; + +-- keys/values +select akeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); +select akeys('""=>1'); +select akeys(''); +select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); +select avals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL'); +select avals('""=>1'); +select avals(''); + +select hstore_to_array('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore); +select %% 'aa=>1, cq=>l, b=>g, fg=>NULL'; + +select hstore_to_matrix('aa=>1, cq=>l, b=>g, fg=>NULL'::hstore); +select %# 'aa=>1, cq=>l, b=>g, fg=>NULL'; + +select * from skeys('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); +select * from skeys('""=>1'); +select * from skeys(''); +select * from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>f'); +select *, svals is null from svals('aa=>1 , b=>2, cq=>3'::hstore || 'cq=>l, b=>g, fg=>NULL'); +select * from svals('""=>1'); +select * from svals(''); + +select * from each('aaa=>bq, b=>NULL, ""=>1 '); + +-- @> +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>NULL'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, g=>NULL'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'g=>NULL'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>c'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b'; +select 'a=>b, b=>1, c=>NULL'::hstore @> 'a=>b, c=>q'; + +CREATE TABLE testhstore (h hstore); +\copy testhstore from 'data/hstore.data' + +select count(*) from testhstore where h @> 'wait=>NULL'; +select count(*) from testhstore where h @> 'wait=>CC'; +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; +select count(*) from testhstore where h ? 'public'; +select count(*) from testhstore where h ?| ARRAY['public','disabled']; +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + +create index hidx on testhstore using gist(h); +set enable_seqscan=off; + +select count(*) from testhstore where h @> 'wait=>NULL'; +select count(*) from testhstore where h @> 'wait=>CC'; +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; +select count(*) from testhstore where h ? 'public'; +select count(*) from testhstore where h ?| ARRAY['public','disabled']; +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + +drop index hidx; +create index hidx on testhstore using gist(h gist_hstore_ops(siglen=0)); +create index hidx on testhstore using gist(h gist_hstore_ops(siglen=2025)); +create index hidx on testhstore using gist(h gist_hstore_ops(siglen=2024)); +set enable_seqscan=off; + +select count(*) from testhstore where h @> 'wait=>NULL'; +select count(*) from testhstore where h @> 'wait=>CC'; +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; +select count(*) from testhstore where h ? 'public'; +select count(*) from testhstore where h ?| ARRAY['public','disabled']; +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + +drop index hidx; +create index hidx on testhstore using gin (h); +set enable_seqscan=off; + +select count(*) from testhstore where h @> 'wait=>NULL'; +select count(*) from testhstore where h @> 'wait=>CC'; +select count(*) from testhstore where h @> 'wait=>CC, public=>t'; +select count(*) from testhstore where h ? 'public'; +select count(*) from testhstore where h ?| ARRAY['public','disabled']; +select count(*) from testhstore where h ?& ARRAY['public','disabled']; + +select count(*) from (select (each(h)).key from testhstore) as wow ; +select key, count(*) from (select (each(h)).key from testhstore) as wow group by key order by count desc, key; + +-- sort/hash +select count(distinct h) from testhstore; +set enable_hashagg = false; +select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2; +set enable_hashagg = true; +set enable_sort = false; +select count(*) from (select h from (select * from testhstore union all select * from testhstore) hs group by h) hs2; +select distinct * from (values (hstore '' || ''),('')) v(h); +set enable_sort = true; + +-- btree +drop index hidx; +create index hidx on testhstore using btree (h); +set enable_seqscan=off; + +select count(*) from testhstore where h #># 'p=>1'; +select count(*) from testhstore where h = 'pos=>98, line=>371, node=>CBA, indexed=>t'; + +-- json and jsonb +select hstore_to_json('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); +select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as json); +select hstore_to_json_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"'); + +select hstore_to_jsonb('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'); +select cast( hstore '"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4' as jsonb); +select hstore_to_jsonb_loose('"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4, h=> "2016-01-01"'); + +create table test_json_agg (f1 text, f2 hstore); +insert into test_json_agg values ('rec1','"a key" =>1, b => t, c => null, d=> 12345, e => 012345, f=> 1.234, g=> 2.345e+4'), + ('rec2','"a key" =>2, b => f, c => "null", d=> -12345, e => 012345.6, f=> -1.234, g=> 0.345e-4'); +select json_agg(q) from test_json_agg q; +select json_agg(q) from (select f1, hstore_to_json_loose(f2) as f2 from test_json_agg) q; + +-- Test subscripting +insert into test_json_agg default values; +select f2['d'], f2['x'] is null as x_isnull from test_json_agg; +select f2['d']['e'] from test_json_agg; -- error +select f2['d':'e'] from test_json_agg; -- error +update test_json_agg set f2['d'] = f2['e'], f2['x'] = 'xyzzy'; +select f2 from test_json_agg; + +-- Test subscripting in plpgsql +do $$ declare h hstore; +begin h['a'] := 'b'; raise notice 'h = %, h[a] = %', h, h['a']; end $$; + +-- Check the hstore_hash() and hstore_hash_extended() function explicitly. +SELECT v as value, hstore_hash(v)::bit(32) as standard, + hstore_hash_extended(v, 0)::bit(32) as extended0, + hstore_hash_extended(v, 1)::bit(32) as extended1 +FROM (VALUES (NULL::hstore), (''), ('"a key" =>1'), ('c => null'), + ('e => 012345'), ('g => 2.345e+4')) x(v) +WHERE hstore_hash(v)::bit(32) != hstore_hash_extended(v, 0)::bit(32) + OR hstore_hash(v)::bit(32) = hstore_hash_extended(v, 1)::bit(32); diff --git a/contrib/hstore/sql/hstore_utf8.sql b/contrib/hstore/sql/hstore_utf8.sql new file mode 100644 index 0000000..face878 --- /dev/null +++ b/contrib/hstore/sql/hstore_utf8.sql @@ -0,0 +1,19 @@ +/* + * 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; + +-- UTF-8 locale bug on macOS: isspace(0x85) returns true. \u0105 encodes +-- as 0xc4 0x85 in UTF-8; the 0x85 was interpreted here as a whitespace. +SELECT E'key\u0105=>value\u0105'::hstore; +SELECT 'keyą=>valueą'::hstore; +SELECT 'ą=>ą'::hstore; +SELECT 'keyąfoo=>valueą'::hstore; diff --git a/contrib/hstore_plperl/.gitignore b/contrib/hstore_plperl/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/hstore_plperl/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/hstore_plperl/Makefile b/contrib/hstore_plperl/Makefile new file mode 100644 index 0000000..9065f16 --- /dev/null +++ b/contrib/hstore_plperl/Makefile @@ -0,0 +1,41 @@ +# contrib/hstore_plperl/Makefile + +MODULE_big = hstore_plperl +OBJS = \ + $(WIN32RES) \ + hstore_plperl.o +PGFILEDESC = "hstore_plperl - hstore transform for plperl" + + +EXTENSION = hstore_plperl hstore_plperlu +DATA = hstore_plperl--1.0.sql hstore_plperlu--1.0.sql + +REGRESS = hstore_plperl hstore_plperlu create_transform +EXTRA_INSTALL = contrib/hstore + +ifdef USE_PGXS +PG_CPPFLAGS = -I$(includedir_server)/extension +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl -I$(top_srcdir)/contrib +subdir = contrib/hstore_plperl +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +# We must link libperl explicitly +ifeq ($(PORTNAME), win32) +# these settings are the same as for plperl +override CPPFLAGS += -DPLPERL_HAVE_UID_GID -Wno-comment +# ... see silliness in plperl Makefile ... +SHLIB_LINK_INTERNAL += $(sort $(wildcard ../../src/pl/plperl/libperl*.a)) +else +rpathdir = $(perl_archlibexp)/CORE +SHLIB_LINK += $(perl_embed_ldflags) +endif + +# As with plperl we need to include the perl_includespec directory last. +override CPPFLAGS := $(CPPFLAGS) $(perl_embed_ccflags) $(perl_includespec) diff --git a/contrib/hstore_plperl/expected/create_transform.out b/contrib/hstore_plperl/expected/create_transform.out new file mode 100644 index 0000000..dc72395 --- /dev/null +++ b/contrib/hstore_plperl/expected/create_transform.out @@ -0,0 +1,75 @@ +-- general regression test for transforms +DROP EXTENSION IF EXISTS hstore CASCADE; +NOTICE: extension "hstore" does not exist, skipping +DROP EXTENSION IF EXISTS plperl CASCADE; +NOTICE: extension "plperl" does not exist, skipping +DROP EXTENSION IF EXISTS hstore_plperl CASCADE; +NOTICE: extension "hstore_plperl" does not exist, skipping +CREATE EXTENSION hstore; +CREATE EXTENSION plperl; +CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS '$libdir/hstore_plperl'; +CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS '$libdir/hstore_plperl'; +CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +ERROR: type "foo" does not exist +CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +ERROR: language "foo" does not exist +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +ERROR: return data type of FROM SQL function must be internal +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +ERROR: first argument of transform function must be type internal +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +ERROR: transform for type hstore language "plperl" already exists +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal)); -- ok +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok +COMMENT ON TRANSFORM FOR hstore LANGUAGE plperl IS 'test'; +DROP TRANSFORM IF EXISTS FOR fake_type LANGUAGE plperl; +NOTICE: type "fake_type" does not exist, skipping +DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE fake_lang; +NOTICE: transform for type hstore language "fake_lang" does not exist, skipping +DROP TRANSFORM FOR foo LANGUAGE plperl; +ERROR: type "foo" does not exist +DROP TRANSFORM FOR hstore LANGUAGE foo; +ERROR: language "foo" does not exist +DROP TRANSFORM FOR hstore LANGUAGE plperl; +DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl; +NOTICE: transform for type hstore language "plperl" does not exist, skipping +DROP FUNCTION hstore_to_plperl(val internal); +DROP FUNCTION plperl_to_hstore(val internal); +CREATE EXTENSION hstore_plperl; +\dx+ hstore_plperl + Objects in extension "hstore_plperl" + Object description +-------------------------------------- + function hstore_to_plperl(internal) + function plperl_to_hstore(internal) + transform for hstore language plperl +(3 rows) + +ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl; +\dx+ hstore_plperl +Objects in extension "hstore_plperl" + Object description +------------------------------------- + function hstore_to_plperl(internal) + function plperl_to_hstore(internal) +(2 rows) + +ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl; +\dx+ hstore_plperl + Objects in extension "hstore_plperl" + Object description +-------------------------------------- + function hstore_to_plperl(internal) + function plperl_to_hstore(internal) + transform for hstore language plperl +(3 rows) + +DROP EXTENSION hstore CASCADE; +NOTICE: drop cascades to extension hstore_plperl +DROP EXTENSION plperl CASCADE; diff --git a/contrib/hstore_plperl/expected/hstore_plperl.out b/contrib/hstore_plperl/expected/hstore_plperl.out new file mode 100644 index 0000000..1ab09a9 --- /dev/null +++ b/contrib/hstore_plperl/expected/hstore_plperl.out @@ -0,0 +1,67 @@ +CREATE EXTENSION hstore_plperl CASCADE; +NOTICE: installing required extension "hstore" +NOTICE: installing required extension "plperl" +SELECT transforms.udt_schema, transforms.udt_name, + routine_schema, routine_name, + group_name, transform_type +FROM information_schema.transforms JOIN information_schema.routines + USING (specific_catalog, specific_schema, specific_name) +ORDER BY 1, 2, 5, 6; + udt_schema | udt_name | routine_schema | routine_name | group_name | transform_type +------------+----------+----------------+------------------+------------+---------------- + public | hstore | public | hstore_to_plperl | plperl | FROM SQL + public | hstore | public | plperl_to_hstore | plperl | TO SQL +(2 rows) + +-- test perl -> hstore +CREATE FUNCTION test2() RETURNS hstore +LANGUAGE plperl +TRANSFORM FOR TYPE hstore +AS $$ +$val = {a => 1, b => 'boo', c => undef}; +return $val; +$$; +SELECT test2(); + test2 +--------------------------------- + "a"=>"1", "b"=>"boo", "c"=>NULL +(1 row) + +-- test perl -> hstore[] +CREATE FUNCTION test2arr() RETURNS hstore[] +LANGUAGE plperl +TRANSFORM FOR TYPE hstore +AS $$ +$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; +return $val; +$$; +SELECT test2arr(); + test2arr +-------------------------------------------------------------- + {"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""} +(1 row) + +-- check error cases +CREATE OR REPLACE FUNCTION test2() RETURNS hstore +LANGUAGE plperl +TRANSFORM FOR TYPE hstore +AS $$ +return 42; +$$; +SELECT test2(); +ERROR: cannot transform non-hash Perl value to hstore +CONTEXT: PL/Perl function "test2" +CREATE OR REPLACE FUNCTION test2() RETURNS hstore +LANGUAGE plperl +TRANSFORM FOR TYPE hstore +AS $$ +return [1, 2]; +$$; +SELECT test2(); +ERROR: cannot transform non-hash Perl value to hstore +CONTEXT: PL/Perl function "test2" +DROP FUNCTION test2(); +DROP FUNCTION test2arr(); +DROP EXTENSION hstore_plperl; +DROP EXTENSION hstore; +DROP EXTENSION plperl; diff --git a/contrib/hstore_plperl/expected/hstore_plperlu.out b/contrib/hstore_plperl/expected/hstore_plperlu.out new file mode 100644 index 0000000..d719d29 --- /dev/null +++ b/contrib/hstore_plperl/expected/hstore_plperlu.out @@ -0,0 +1,151 @@ +CREATE EXTENSION hstore_plperlu CASCADE; +NOTICE: installing required extension "hstore" +NOTICE: installing required extension "plperlu" +SELECT transforms.udt_schema, transforms.udt_name, + routine_schema, routine_name, + group_name, transform_type +FROM information_schema.transforms JOIN information_schema.routines + USING (specific_catalog, specific_schema, specific_name) +ORDER BY 1, 2, 5, 6; + udt_schema | udt_name | routine_schema | routine_name | group_name | transform_type +------------+----------+----------------+-------------------+------------+---------------- + public | hstore | public | hstore_to_plperlu | plperlu | FROM SQL + public | hstore | public | plperlu_to_hstore | plperlu | TO SQL +(2 rows) + +-- test hstore -> perl +CREATE FUNCTION test1(val hstore) RETURNS int +LANGUAGE plperlu +TRANSFORM FOR TYPE hstore +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_[0])); +return scalar(keys %{$_[0]}); +$$; +SELECT test1('aa=>bb, cc=>NULL'::hstore); +INFO: $VAR1 = {'aa' => 'bb','cc' => undef}; + test1 +------- + 2 +(1 row) + +CREATE FUNCTION test1none(val hstore) RETURNS int +LANGUAGE plperlu +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_[0])); +return scalar(keys %{$_[0]}); +$$; +SELECT test1none('aa=>bb, cc=>NULL'::hstore); +INFO: $VAR1 = '"aa"=>"bb", "cc"=>NULL'; + test1none +----------- + 0 +(1 row) + +CREATE FUNCTION test1list(val hstore) RETURNS int +LANGUAGE plperlu +TRANSFORM FOR TYPE hstore +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_[0])); +return scalar(keys %{$_[0]}); +$$; +SELECT test1list('aa=>bb, cc=>NULL'::hstore); +INFO: $VAR1 = {'aa' => 'bb','cc' => undef}; + test1list +----------- + 2 +(1 row) + +-- test hstore[] -> perl +CREATE FUNCTION test1arr(val hstore[]) RETURNS int +LANGUAGE plperlu +TRANSFORM FOR TYPE hstore +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_[0]->[0], $_[0]->[1])); +return scalar(keys %{$_[0]}); +$$; +SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']); +INFO: $VAR1 = {'aa' => 'bb','cc' => undef};$VAR2 = {'dd' => 'ee'}; + test1arr +---------- + 2 +(1 row) + +-- test as part of prepare/execute +CREATE FUNCTION test3() RETURNS void +LANGUAGE plperlu +TRANSFORM FOR TYPE hstore +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; + +$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1}); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); + +$val = {a => 1, b => 'boo', c => undef}; +$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore"); +$rv = spi_exec_prepared($plan, {}, $val); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); +$$; +SELECT test3(); +INFO: $VAR1 = {'aa' => 'bb','cc' => undef}; +INFO: $VAR1 = '"a"=>"1", "b"=>"boo", "c"=>NULL'; + test3 +------- + +(1 row) + +-- test trigger +CREATE TABLE test1 (a int, b hstore); +INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL'); +SELECT * FROM test1; + a | b +---+------------------------ + 1 | "aa"=>"bb", "cc"=>NULL +(1 row) + +CREATE FUNCTION test4() RETURNS trigger +LANGUAGE plperlu +TRANSFORM FOR TYPE hstore +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_TD->{new})); +if ($_TD->{new}{a} == 1) { + $_TD->{new}{b} = {a => 1, b => 'boo', c => undef}; +} + +return "MODIFY"; +$$; +CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4(); +UPDATE test1 SET a = a; +INFO: $VAR1 = {'a' => '1','b' => {'aa' => 'bb','cc' => undef}}; +SELECT * FROM test1; + a | b +---+--------------------------------- + 1 | "a"=>"1", "b"=>"boo", "c"=>NULL +(1 row) + +DROP TABLE test1; +DROP FUNCTION test1(hstore); +DROP FUNCTION test1none(hstore); +DROP FUNCTION test1list(hstore); +DROP FUNCTION test1arr(hstore[]); +DROP FUNCTION test3(); +DROP FUNCTION test4(); +DROP EXTENSION hstore_plperlu; +DROP EXTENSION hstore; +DROP EXTENSION plperlu; diff --git a/contrib/hstore_plperl/hstore_plperl--1.0.sql b/contrib/hstore_plperl/hstore_plperl--1.0.sql new file mode 100644 index 0000000..af743c8 --- /dev/null +++ b/contrib/hstore_plperl/hstore_plperl--1.0.sql @@ -0,0 +1,17 @@ +/* contrib/hstore_plperl/hstore_plperl--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION hstore_plperl" to load this file. \quit + +CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE TRANSFORM FOR hstore LANGUAGE plperl ( + FROM SQL WITH FUNCTION hstore_to_plperl(internal), + TO SQL WITH FUNCTION plperl_to_hstore(internal) +); diff --git a/contrib/hstore_plperl/hstore_plperl.c b/contrib/hstore_plperl/hstore_plperl.c new file mode 100644 index 0000000..4a1629c --- /dev/null +++ b/contrib/hstore_plperl/hstore_plperl.c @@ -0,0 +1,152 @@ +#include "postgres.h" + +#include "fmgr.h" +#include "hstore/hstore.h" +#include "plperl.h" + +PG_MODULE_MAGIC; + +/* Linkage to functions in hstore module */ +typedef HStore *(*hstoreUpgrade_t) (Datum orig); +static hstoreUpgrade_t hstoreUpgrade_p; +typedef int (*hstoreUniquePairs_t) (Pairs *a, int32 l, int32 *buflen); +static hstoreUniquePairs_t hstoreUniquePairs_p; +typedef HStore *(*hstorePairs_t) (Pairs *pairs, int32 pcount, int32 buflen); +static hstorePairs_t hstorePairs_p; +typedef size_t (*hstoreCheckKeyLen_t) (size_t len); +static hstoreCheckKeyLen_t hstoreCheckKeyLen_p; +typedef size_t (*hstoreCheckValLen_t) (size_t len); +static hstoreCheckValLen_t hstoreCheckValLen_p; + + +/* + * Module initialize function: fetch function pointers for cross-module calls. + */ +void +_PG_init(void) +{ + /* Asserts verify that typedefs above match original declarations */ + AssertVariableIsOfType(&hstoreUpgrade, hstoreUpgrade_t); + hstoreUpgrade_p = (hstoreUpgrade_t) + load_external_function("$libdir/hstore", "hstoreUpgrade", + true, NULL); + AssertVariableIsOfType(&hstoreUniquePairs, hstoreUniquePairs_t); + hstoreUniquePairs_p = (hstoreUniquePairs_t) + load_external_function("$libdir/hstore", "hstoreUniquePairs", + true, NULL); + AssertVariableIsOfType(&hstorePairs, hstorePairs_t); + hstorePairs_p = (hstorePairs_t) + load_external_function("$libdir/hstore", "hstorePairs", + true, NULL); + AssertVariableIsOfType(&hstoreCheckKeyLen, hstoreCheckKeyLen_t); + hstoreCheckKeyLen_p = (hstoreCheckKeyLen_t) + load_external_function("$libdir/hstore", "hstoreCheckKeyLen", + true, NULL); + AssertVariableIsOfType(&hstoreCheckValLen, hstoreCheckValLen_t); + hstoreCheckValLen_p = (hstoreCheckValLen_t) + load_external_function("$libdir/hstore", "hstoreCheckValLen", + true, NULL); +} + + +/* These defines must be after the module init function */ +#define hstoreUpgrade hstoreUpgrade_p +#define hstoreUniquePairs hstoreUniquePairs_p +#define hstorePairs hstorePairs_p +#define hstoreCheckKeyLen hstoreCheckKeyLen_p +#define hstoreCheckValLen hstoreCheckValLen_p + + +PG_FUNCTION_INFO_V1(hstore_to_plperl); + +Datum +hstore_to_plperl(PG_FUNCTION_ARGS) +{ + dTHX; + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + HV *hv; + + hv = newHV(); + + for (i = 0; i < count; i++) + { + const char *key; + SV *value; + + key = pnstrdup(HSTORE_KEY(entries, base, i), + HSTORE_KEYLEN(entries, i)); + value = HSTORE_VALISNULL(entries, i) ? newSV(0) : + cstr2sv(pnstrdup(HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i))); + + (void) hv_store(hv, key, strlen(key), value, 0); + } + + return PointerGetDatum(newRV((SV *) hv)); +} + + +PG_FUNCTION_INFO_V1(plperl_to_hstore); + +Datum +plperl_to_hstore(PG_FUNCTION_ARGS) +{ + dTHX; + SV *in = (SV *) PG_GETARG_POINTER(0); + HV *hv; + HE *he; + int32 buflen; + int32 i; + int32 pcount; + HStore *out; + Pairs *pairs; + + /* Dereference references recursively. */ + while (SvROK(in)) + in = SvRV(in); + + /* Now we must have a hash. */ + if (SvTYPE(in) != SVt_PVHV) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot transform non-hash Perl value to hstore"))); + hv = (HV *) in; + + pcount = hv_iterinit(hv); + + pairs = palloc(pcount * sizeof(Pairs)); + + i = 0; + while ((he = hv_iternext(hv))) + { + char *key = sv2cstr(HeSVKEY_force(he)); + SV *value = HeVAL(he); + + pairs[i].key = pstrdup(key); + pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key)); + pairs[i].needfree = true; + + if (!SvOK(value)) + { + pairs[i].val = NULL; + pairs[i].vallen = 0; + pairs[i].isnull = true; + } + else + { + pairs[i].val = pstrdup(sv2cstr(value)); + pairs[i].vallen = hstoreCheckValLen(strlen(pairs[i].val)); + pairs[i].isnull = false; + } + + i++; + } + + pcount = hstoreUniquePairs(pairs, pcount, &buflen); + out = hstorePairs(pairs, pcount, buflen); + PG_RETURN_POINTER(out); +} diff --git a/contrib/hstore_plperl/hstore_plperl.control b/contrib/hstore_plperl/hstore_plperl.control new file mode 100644 index 0000000..16277f6 --- /dev/null +++ b/contrib/hstore_plperl/hstore_plperl.control @@ -0,0 +1,6 @@ +# hstore_plperl extension +comment = 'transform between hstore and plperl' +default_version = '1.0' +module_pathname = '$libdir/hstore_plperl' +relocatable = true +requires = 'hstore,plperl' diff --git a/contrib/hstore_plperl/hstore_plperlu--1.0.sql b/contrib/hstore_plperl/hstore_plperlu--1.0.sql new file mode 100644 index 0000000..7c3bc86 --- /dev/null +++ b/contrib/hstore_plperl/hstore_plperlu--1.0.sql @@ -0,0 +1,17 @@ +/* contrib/hstore_plperl/hstore_plperlu--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION hstore_plperlu" to load this file. \quit + +CREATE FUNCTION hstore_to_plperlu(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'hstore_to_plperl'; + +CREATE FUNCTION plperlu_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'plperl_to_hstore'; + +CREATE TRANSFORM FOR hstore LANGUAGE plperlu ( + FROM SQL WITH FUNCTION hstore_to_plperlu(internal), + TO SQL WITH FUNCTION plperlu_to_hstore(internal) +); diff --git a/contrib/hstore_plperl/hstore_plperlu.control b/contrib/hstore_plperl/hstore_plperlu.control new file mode 100644 index 0000000..c8d43b4 --- /dev/null +++ b/contrib/hstore_plperl/hstore_plperlu.control @@ -0,0 +1,6 @@ +# hstore_plperlu extension +comment = 'transform between hstore and plperlu' +default_version = '1.0' +module_pathname = '$libdir/hstore_plperl' +relocatable = true +requires = 'hstore,plperlu' diff --git a/contrib/hstore_plperl/meson.build b/contrib/hstore_plperl/meson.build new file mode 100644 index 0000000..00c3803 --- /dev/null +++ b/contrib/hstore_plperl/meson.build @@ -0,0 +1,51 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not perl_dep.found() + subdir_done() +endif + +hstore_plperl_sources = files( + 'hstore_plperl.c', +) + +if host_system == 'windows' + hstore_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'hstore_plperl', + '--FILEDESC', 'hstore_plperl - hstore transform for plperl',]) +endif + +hstore_plperl = shared_module('hstore_plperl', + hstore_plperl_sources, + include_directories: [plperl_inc, hstore_inc], + kwargs: contrib_mod_args + { + 'dependencies': [perl_dep, contrib_mod_args['dependencies']], + 'install_rpath': ':'.join(mod_install_rpaths + ['@0@/CORE'.format(archlibexp)]), + 'build_rpath': '@0@/CORE'.format(archlibexp), + }, +) +contrib_targets += hstore_plperl + +install_data( + 'hstore_plperl.control', + 'hstore_plperl--1.0.sql', + kwargs: contrib_data_args, +) + +install_data( + 'hstore_plperlu.control', + 'hstore_plperlu--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'hstore_plperl', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'hstore_plperl', + 'hstore_plperlu', + 'create_transform', + ], + }, +} diff --git a/contrib/hstore_plperl/sql/create_transform.sql b/contrib/hstore_plperl/sql/create_transform.sql new file mode 100644 index 0000000..d0a12ad --- /dev/null +++ b/contrib/hstore_plperl/sql/create_transform.sql @@ -0,0 +1,49 @@ +-- general regression test for transforms + +DROP EXTENSION IF EXISTS hstore CASCADE; +DROP EXTENSION IF EXISTS plperl CASCADE; +DROP EXTENSION IF EXISTS hstore_plperl CASCADE; + +CREATE EXTENSION hstore; +CREATE EXTENSION plperl; + +CREATE FUNCTION hstore_to_plperl(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS '$libdir/hstore_plperl'; + +CREATE FUNCTION plperl_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS '$libdir/hstore_plperl'; + +CREATE TRANSFORM FOR foo LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +CREATE TRANSFORM FOR hstore LANGUAGE foo (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_out(hstore), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION internal_in(cstring), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail + +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok +CREATE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- fail +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal), TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (FROM SQL WITH FUNCTION hstore_to_plperl(internal)); -- ok +CREATE OR REPLACE TRANSFORM FOR hstore LANGUAGE plperl (TO SQL WITH FUNCTION plperl_to_hstore(internal)); -- ok + +COMMENT ON TRANSFORM FOR hstore LANGUAGE plperl IS 'test'; + +DROP TRANSFORM IF EXISTS FOR fake_type LANGUAGE plperl; +DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE fake_lang; +DROP TRANSFORM FOR foo LANGUAGE plperl; +DROP TRANSFORM FOR hstore LANGUAGE foo; +DROP TRANSFORM FOR hstore LANGUAGE plperl; +DROP TRANSFORM IF EXISTS FOR hstore LANGUAGE plperl; + +DROP FUNCTION hstore_to_plperl(val internal); +DROP FUNCTION plperl_to_hstore(val internal); + +CREATE EXTENSION hstore_plperl; +\dx+ hstore_plperl +ALTER EXTENSION hstore_plperl DROP TRANSFORM FOR hstore LANGUAGE plperl; +\dx+ hstore_plperl +ALTER EXTENSION hstore_plperl ADD TRANSFORM FOR hstore LANGUAGE plperl; +\dx+ hstore_plperl + +DROP EXTENSION hstore CASCADE; +DROP EXTENSION plperl CASCADE; diff --git a/contrib/hstore_plperl/sql/hstore_plperl.sql b/contrib/hstore_plperl/sql/hstore_plperl.sql new file mode 100644 index 0000000..ad1db7e --- /dev/null +++ b/contrib/hstore_plperl/sql/hstore_plperl.sql @@ -0,0 +1,60 @@ +CREATE EXTENSION hstore_plperl CASCADE; + +SELECT transforms.udt_schema, transforms.udt_name, + routine_schema, routine_name, + group_name, transform_type +FROM information_schema.transforms JOIN information_schema.routines + USING (specific_catalog, specific_schema, specific_name) +ORDER BY 1, 2, 5, 6; + + +-- test perl -> hstore +CREATE FUNCTION test2() RETURNS hstore +LANGUAGE plperl +TRANSFORM FOR TYPE hstore +AS $$ +$val = {a => 1, b => 'boo', c => undef}; +return $val; +$$; + +SELECT test2(); + + +-- test perl -> hstore[] +CREATE FUNCTION test2arr() RETURNS hstore[] +LANGUAGE plperl +TRANSFORM FOR TYPE hstore +AS $$ +$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; +return $val; +$$; + +SELECT test2arr(); + +-- check error cases +CREATE OR REPLACE FUNCTION test2() RETURNS hstore +LANGUAGE plperl +TRANSFORM FOR TYPE hstore +AS $$ +return 42; +$$; + +SELECT test2(); + +CREATE OR REPLACE FUNCTION test2() RETURNS hstore +LANGUAGE plperl +TRANSFORM FOR TYPE hstore +AS $$ +return [1, 2]; +$$; + +SELECT test2(); + + +DROP FUNCTION test2(); +DROP FUNCTION test2arr(); + + +DROP EXTENSION hstore_plperl; +DROP EXTENSION hstore; +DROP EXTENSION plperl; diff --git a/contrib/hstore_plperl/sql/hstore_plperlu.sql b/contrib/hstore_plperl/sql/hstore_plperlu.sql new file mode 100644 index 0000000..c714b35 --- /dev/null +++ b/contrib/hstore_plperl/sql/hstore_plperlu.sql @@ -0,0 +1,125 @@ +CREATE EXTENSION hstore_plperlu CASCADE; + +SELECT transforms.udt_schema, transforms.udt_name, + routine_schema, routine_name, + group_name, transform_type +FROM information_schema.transforms JOIN information_schema.routines + USING (specific_catalog, specific_schema, specific_name) +ORDER BY 1, 2, 5, 6; + + +-- test hstore -> perl +CREATE FUNCTION test1(val hstore) RETURNS int +LANGUAGE plperlu +TRANSFORM FOR TYPE hstore +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_[0])); +return scalar(keys %{$_[0]}); +$$; + +SELECT test1('aa=>bb, cc=>NULL'::hstore); + +CREATE FUNCTION test1none(val hstore) RETURNS int +LANGUAGE plperlu +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_[0])); +return scalar(keys %{$_[0]}); +$$; + +SELECT test1none('aa=>bb, cc=>NULL'::hstore); + +CREATE FUNCTION test1list(val hstore) RETURNS int +LANGUAGE plperlu +TRANSFORM FOR TYPE hstore +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_[0])); +return scalar(keys %{$_[0]}); +$$; + +SELECT test1list('aa=>bb, cc=>NULL'::hstore); + + +-- test hstore[] -> perl +CREATE FUNCTION test1arr(val hstore[]) RETURNS int +LANGUAGE plperlu +TRANSFORM FOR TYPE hstore +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_[0]->[0], $_[0]->[1])); +return scalar(keys %{$_[0]}); +$$; + +SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']); + + +-- test as part of prepare/execute +CREATE FUNCTION test3() RETURNS void +LANGUAGE plperlu +TRANSFORM FOR TYPE hstore +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; + +$rv = spi_exec_query(q{SELECT 'aa=>bb, cc=>NULL'::hstore AS col1}); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); + +$val = {a => 1, b => 'boo', c => undef}; +$plan = spi_prepare(q{SELECT $1::text AS col1}, "hstore"); +$rv = spi_exec_prepared($plan, {}, $val); +elog(INFO, Dumper($rv->{rows}[0]->{col1})); +$$; + +SELECT test3(); + + +-- test trigger +CREATE TABLE test1 (a int, b hstore); +INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL'); +SELECT * FROM test1; + +CREATE FUNCTION test4() RETURNS trigger +LANGUAGE plperlu +TRANSFORM FOR TYPE hstore +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_TD->{new})); +if ($_TD->{new}{a} == 1) { + $_TD->{new}{b} = {a => 1, b => 'boo', c => undef}; +} + +return "MODIFY"; +$$; + +CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4(); + +UPDATE test1 SET a = a; +SELECT * FROM test1; + + +DROP TABLE test1; + +DROP FUNCTION test1(hstore); +DROP FUNCTION test1none(hstore); +DROP FUNCTION test1list(hstore); +DROP FUNCTION test1arr(hstore[]); +DROP FUNCTION test3(); +DROP FUNCTION test4(); + + +DROP EXTENSION hstore_plperlu; +DROP EXTENSION hstore; +DROP EXTENSION plperlu; diff --git a/contrib/hstore_plpython/.gitignore b/contrib/hstore_plpython/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/hstore_plpython/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/hstore_plpython/Makefile b/contrib/hstore_plpython/Makefile new file mode 100644 index 0000000..9d88cda --- /dev/null +++ b/contrib/hstore_plpython/Makefile @@ -0,0 +1,39 @@ +# contrib/hstore_plpython/Makefile + +MODULE_big = hstore_plpython$(python_majorversion) +OBJS = \ + $(WIN32RES) \ + hstore_plpython.o +PGFILEDESC = "hstore_plpython - hstore transform for plpython" + +EXTENSION = hstore_plpython3u +DATA = hstore_plpython3u--1.0.sql + +REGRESS = hstore_plpython + +PG_CPPFLAGS = $(python_includespec) -DPLPYTHON_LIBNAME='"plpython$(python_majorversion)"' + +ifdef USE_PGXS +PG_CPPFLAGS += -I$(includedir_server)/extension +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +PG_CPPFLAGS += -I$(top_srcdir)/src/pl/plpython -I$(top_srcdir)/contrib +subdir = contrib/hstore_plpython +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +# We must link libpython explicitly +ifeq ($(PORTNAME), win32) +# ... see silliness in plpython Makefile ... +SHLIB_LINK_INTERNAL += $(sort $(wildcard ../../src/pl/plpython/libpython*.a)) +else +rpathdir = $(python_libdir) +SHLIB_LINK += $(python_libspec) $(python_additional_libs) +endif + +REGRESS_OPTS += --load-extension=hstore +EXTRA_INSTALL += contrib/hstore diff --git a/contrib/hstore_plpython/expected/hstore_plpython.out b/contrib/hstore_plpython/expected/hstore_plpython.out new file mode 100644 index 0000000..5fb56a2 --- /dev/null +++ b/contrib/hstore_plpython/expected/hstore_plpython.out @@ -0,0 +1,161 @@ +CREATE EXTENSION hstore_plpython3u CASCADE; +NOTICE: installing required extension "plpython3u" +-- test hstore -> python +CREATE FUNCTION test1(val hstore) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +assert isinstance(val, dict) +plpy.info(sorted(val.items())) +return len(val) +$$; +SELECT test1('aa=>bb, cc=>NULL'::hstore); +INFO: [('aa', 'bb'), ('cc', None)] + test1 +------- + 2 +(1 row) + +-- the same with the versioned language name +CREATE FUNCTION test1n(val hstore) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +assert isinstance(val, dict) +plpy.info(sorted(val.items())) +return len(val) +$$; +SELECT test1n('aa=>bb, cc=>NULL'::hstore); +INFO: [('aa', 'bb'), ('cc', None)] + test1n +-------- + 2 +(1 row) + +-- test that a non-mapping result is correctly rejected +CREATE FUNCTION test1bad() RETURNS hstore +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +return "foo" +$$; +SELECT test1bad(); +ERROR: not a Python mapping +CONTEXT: while creating return value +PL/Python function "test1bad" +-- test hstore[] -> python +CREATE FUNCTION test1arr(val hstore[]) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +assert(val == [{'aa': 'bb', 'cc': None}, {'dd': 'ee'}]) +return len(val) +$$; +SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']); + test1arr +---------- + 2 +(1 row) + +-- test python -> hstore +CREATE FUNCTION test2(a int, b text) RETURNS hstore +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +val = {'a': a, 'b': b, 'c': None} +return val +$$; +SELECT test2(1, 'boo'); + test2 +--------------------------------- + "a"=>"1", "b"=>"boo", "c"=>NULL +(1 row) + +--- test ruleutils +\sf test2 +CREATE OR REPLACE FUNCTION public.test2(a integer, b text) + RETURNS hstore + TRANSFORM FOR TYPE hstore + LANGUAGE plpython3u +AS $function$ +val = {'a': a, 'b': b, 'c': None} +return val +$function$ +-- test python -> hstore[] +CREATE FUNCTION test2arr() RETURNS hstore[] +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}] +return val +$$; +SELECT test2arr(); + test2arr +-------------------------------------------------------------- + {"\"a\"=>\"1\", \"b\"=>\"boo\", \"c\"=>NULL","\"d\"=>\"2\""} +(1 row) + +-- test python -> domain over hstore +CREATE DOMAIN hstore_foo AS hstore CHECK(VALUE ? 'foo'); +CREATE FUNCTION test2dom(fn text) RETURNS hstore_foo +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +return {'a': 1, fn: 'boo', 'c': None} +$$; +SELECT test2dom('foo'); + test2dom +----------------------------------- + "a"=>"1", "c"=>NULL, "foo"=>"boo" +(1 row) + +SELECT test2dom('bar'); -- fail +ERROR: value for domain hstore_foo violates check constraint "hstore_foo_check" +CONTEXT: while creating return value +PL/Python function "test2dom" +-- test as part of prepare/execute +CREATE FUNCTION test3() RETURNS void +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1") +assert(rv[0]["col1"] == {'aa': 'bb', 'cc': None}) + +val = {'a': 1, 'b': 'boo', 'c': None} +plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"]) +rv = plpy.execute(plan, [val]) +assert(rv[0]["col1"] == '"a"=>"1", "b"=>"boo", "c"=>NULL') +$$; +SELECT test3(); + test3 +------- + +(1 row) + +-- test trigger +CREATE TABLE test1 (a int, b hstore); +INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL'); +SELECT * FROM test1; + a | b +---+------------------------ + 1 | "aa"=>"bb", "cc"=>NULL +(1 row) + +CREATE FUNCTION test4() RETURNS trigger +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +assert(TD["new"] == {'a': 1, 'b': {'aa': 'bb', 'cc': None}}) +if TD["new"]["a"] == 1: + TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None} + +return "MODIFY" +$$; +CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4(); +UPDATE test1 SET a = a; +SELECT * FROM test1; + a | b +---+--------------------------------- + 1 | "a"=>"1", "b"=>"boo", "c"=>NULL +(1 row) + diff --git a/contrib/hstore_plpython/hstore_plpython.c b/contrib/hstore_plpython/hstore_plpython.c new file mode 100644 index 0000000..310f63c --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython.c @@ -0,0 +1,190 @@ +#include "postgres.h" + +#include "fmgr.h" +#include "hstore/hstore.h" +#include "plpy_typeio.h" +#include "plpython.h" + +PG_MODULE_MAGIC; + +/* Linkage to functions in plpython module */ +typedef char *(*PLyObject_AsString_t) (PyObject *plrv); +static PLyObject_AsString_t PLyObject_AsString_p; +typedef PyObject *(*PLyUnicode_FromStringAndSize_t) (const char *s, Py_ssize_t size); +static PLyUnicode_FromStringAndSize_t PLyUnicode_FromStringAndSize_p; + +/* Linkage to functions in hstore module */ +typedef HStore *(*hstoreUpgrade_t) (Datum orig); +static hstoreUpgrade_t hstoreUpgrade_p; +typedef int (*hstoreUniquePairs_t) (Pairs *a, int32 l, int32 *buflen); +static hstoreUniquePairs_t hstoreUniquePairs_p; +typedef HStore *(*hstorePairs_t) (Pairs *pairs, int32 pcount, int32 buflen); +static hstorePairs_t hstorePairs_p; +typedef size_t (*hstoreCheckKeyLen_t) (size_t len); +static hstoreCheckKeyLen_t hstoreCheckKeyLen_p; +typedef size_t (*hstoreCheckValLen_t) (size_t len); +static hstoreCheckValLen_t hstoreCheckValLen_p; + + +/* + * Module initialize function: fetch function pointers for cross-module calls. + */ +void +_PG_init(void) +{ + /* Asserts verify that typedefs above match original declarations */ + AssertVariableIsOfType(&PLyObject_AsString, PLyObject_AsString_t); + PLyObject_AsString_p = (PLyObject_AsString_t) + load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyObject_AsString", + true, NULL); + AssertVariableIsOfType(&PLyUnicode_FromStringAndSize, PLyUnicode_FromStringAndSize_t); + PLyUnicode_FromStringAndSize_p = (PLyUnicode_FromStringAndSize_t) + load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyUnicode_FromStringAndSize", + true, NULL); + AssertVariableIsOfType(&hstoreUpgrade, hstoreUpgrade_t); + hstoreUpgrade_p = (hstoreUpgrade_t) + load_external_function("$libdir/hstore", "hstoreUpgrade", + true, NULL); + AssertVariableIsOfType(&hstoreUniquePairs, hstoreUniquePairs_t); + hstoreUniquePairs_p = (hstoreUniquePairs_t) + load_external_function("$libdir/hstore", "hstoreUniquePairs", + true, NULL); + AssertVariableIsOfType(&hstorePairs, hstorePairs_t); + hstorePairs_p = (hstorePairs_t) + load_external_function("$libdir/hstore", "hstorePairs", + true, NULL); + AssertVariableIsOfType(&hstoreCheckKeyLen, hstoreCheckKeyLen_t); + hstoreCheckKeyLen_p = (hstoreCheckKeyLen_t) + load_external_function("$libdir/hstore", "hstoreCheckKeyLen", + true, NULL); + AssertVariableIsOfType(&hstoreCheckValLen, hstoreCheckValLen_t); + hstoreCheckValLen_p = (hstoreCheckValLen_t) + load_external_function("$libdir/hstore", "hstoreCheckValLen", + true, NULL); +} + + +/* These defines must be after the module init function */ +#define PLyObject_AsString PLyObject_AsString_p +#define PLyUnicode_FromStringAndSize PLyUnicode_FromStringAndSize_p +#define hstoreUpgrade hstoreUpgrade_p +#define hstoreUniquePairs hstoreUniquePairs_p +#define hstorePairs hstorePairs_p +#define hstoreCheckKeyLen hstoreCheckKeyLen_p +#define hstoreCheckValLen hstoreCheckValLen_p + + +PG_FUNCTION_INFO_V1(hstore_to_plpython); + +Datum +hstore_to_plpython(PG_FUNCTION_ARGS) +{ + HStore *in = PG_GETARG_HSTORE_P(0); + int i; + int count = HS_COUNT(in); + char *base = STRPTR(in); + HEntry *entries = ARRPTR(in); + PyObject *dict; + + dict = PyDict_New(); + if (!dict) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + + for (i = 0; i < count; i++) + { + PyObject *key; + + key = PLyUnicode_FromStringAndSize(HSTORE_KEY(entries, base, i), + HSTORE_KEYLEN(entries, i)); + if (HSTORE_VALISNULL(entries, i)) + PyDict_SetItem(dict, key, Py_None); + else + { + PyObject *value; + + value = PLyUnicode_FromStringAndSize(HSTORE_VAL(entries, base, i), + HSTORE_VALLEN(entries, i)); + PyDict_SetItem(dict, key, value); + Py_XDECREF(value); + } + Py_XDECREF(key); + } + + return PointerGetDatum(dict); +} + + +PG_FUNCTION_INFO_V1(plpython_to_hstore); + +Datum +plpython_to_hstore(PG_FUNCTION_ARGS) +{ + PyObject *dict; + PyObject *volatile items; + Py_ssize_t pcount; + HStore *volatile out; + + dict = (PyObject *) PG_GETARG_POINTER(0); + + /* + * As of Python 3, PyMapping_Check() is unreliable unless one first checks + * that the object isn't a sequence. (Cleaner solutions exist, but not + * before Python 3.10, which we're not prepared to require yet.) + */ + if (PySequence_Check(dict) || !PyMapping_Check(dict)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("not a Python mapping"))); + + pcount = PyMapping_Size(dict); + items = PyMapping_Items(dict); + + PG_TRY(); + { + int32 buflen; + Py_ssize_t i; + Pairs *pairs; + + pairs = palloc(pcount * sizeof(*pairs)); + + for (i = 0; i < pcount; i++) + { + PyObject *tuple; + PyObject *key; + PyObject *value; + + tuple = PyList_GetItem(items, i); + key = PyTuple_GetItem(tuple, 0); + value = PyTuple_GetItem(tuple, 1); + + pairs[i].key = PLyObject_AsString(key); + pairs[i].keylen = hstoreCheckKeyLen(strlen(pairs[i].key)); + pairs[i].needfree = true; + + if (value == Py_None) + { + pairs[i].val = NULL; + pairs[i].vallen = 0; + pairs[i].isnull = true; + } + else + { + pairs[i].val = PLyObject_AsString(value); + pairs[i].vallen = hstoreCheckValLen(strlen(pairs[i].val)); + pairs[i].isnull = false; + } + } + + pcount = hstoreUniquePairs(pairs, pcount, &buflen); + out = hstorePairs(pairs, pcount, buflen); + } + PG_FINALLY(); + { + Py_DECREF(items); + } + PG_END_TRY(); + + PG_RETURN_POINTER(out); +} diff --git a/contrib/hstore_plpython/hstore_plpython3u--1.0.sql b/contrib/hstore_plpython/hstore_plpython3u--1.0.sql new file mode 100644 index 0000000..0b410ab --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython3u--1.0.sql @@ -0,0 +1,19 @@ +/* contrib/hstore_plpython/hstore_plpython3u--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION hstore_plpython3u" to load this file. \quit + +CREATE FUNCTION hstore_to_plpython3(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'hstore_to_plpython'; + +CREATE FUNCTION plpython3_to_hstore(val internal) RETURNS hstore +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'plpython_to_hstore'; + +CREATE TRANSFORM FOR hstore LANGUAGE plpython3u ( + FROM SQL WITH FUNCTION hstore_to_plpython3(internal), + TO SQL WITH FUNCTION plpython3_to_hstore(internal) +); + +COMMENT ON TRANSFORM FOR hstore LANGUAGE plpython3u IS 'transform between hstore and Python dict'; diff --git a/contrib/hstore_plpython/hstore_plpython3u.control b/contrib/hstore_plpython/hstore_plpython3u.control new file mode 100644 index 0000000..d86f38e --- /dev/null +++ b/contrib/hstore_plpython/hstore_plpython3u.control @@ -0,0 +1,6 @@ +# hstore_plpython3u extension +comment = 'transform between hstore and plpython3u' +default_version = '1.0' +module_pathname = '$libdir/hstore_plpython3' +relocatable = true +requires = 'hstore,plpython3u' diff --git a/contrib/hstore_plpython/meson.build b/contrib/hstore_plpython/meson.build new file mode 100644 index 0000000..0bd7475 --- /dev/null +++ b/contrib/hstore_plpython/meson.build @@ -0,0 +1,45 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not python3_dep.found() + subdir_done() +endif + +hstore_plpython_sources = files( + 'hstore_plpython.c', +) + +if host_system == 'windows' + hstore_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'hstore_plpython3', + '--FILEDESC', 'hstore_plpython - hstore transform for plpython',]) +endif + +hstore_plpython = shared_module('hstore_plpython3', + hstore_plpython_sources, + include_directories: [plpython_inc, hstore_inc, ], + c_args: ['-DPLPYTHON_LIBNAME="plpython3"'], + kwargs: contrib_mod_args + { + 'dependencies': [python3_dep, contrib_mod_args['dependencies']], + }, +) +contrib_targets += hstore_plpython + +install_data( + 'hstore_plpython3u--1.0.sql', + 'hstore_plpython3u.control', + kwargs: contrib_data_args, +) + +hstore_plpython_regress = [ + 'hstore_plpython' +] + +tests += { + 'name': 'hstore_plpython', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': hstore_plpython_regress, + 'regress_args': ['--load-extension=hstore'], + }, +} diff --git a/contrib/hstore_plpython/sql/hstore_plpython.sql b/contrib/hstore_plpython/sql/hstore_plpython.sql new file mode 100644 index 0000000..ebd61e6 --- /dev/null +++ b/contrib/hstore_plpython/sql/hstore_plpython.sql @@ -0,0 +1,130 @@ +CREATE EXTENSION hstore_plpython3u CASCADE; + + +-- test hstore -> python +CREATE FUNCTION test1(val hstore) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +assert isinstance(val, dict) +plpy.info(sorted(val.items())) +return len(val) +$$; + +SELECT test1('aa=>bb, cc=>NULL'::hstore); + + +-- the same with the versioned language name +CREATE FUNCTION test1n(val hstore) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +assert isinstance(val, dict) +plpy.info(sorted(val.items())) +return len(val) +$$; + +SELECT test1n('aa=>bb, cc=>NULL'::hstore); + + +-- test that a non-mapping result is correctly rejected +CREATE FUNCTION test1bad() RETURNS hstore +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +return "foo" +$$; + +SELECT test1bad(); + + +-- test hstore[] -> python +CREATE FUNCTION test1arr(val hstore[]) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +assert(val == [{'aa': 'bb', 'cc': None}, {'dd': 'ee'}]) +return len(val) +$$; + +SELECT test1arr(array['aa=>bb, cc=>NULL'::hstore, 'dd=>ee']); + + +-- test python -> hstore +CREATE FUNCTION test2(a int, b text) RETURNS hstore +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +val = {'a': a, 'b': b, 'c': None} +return val +$$; + +SELECT test2(1, 'boo'); + +--- test ruleutils +\sf test2 + + +-- test python -> hstore[] +CREATE FUNCTION test2arr() RETURNS hstore[] +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +val = [{'a': 1, 'b': 'boo', 'c': None}, {'d': 2}] +return val +$$; + +SELECT test2arr(); + + +-- test python -> domain over hstore +CREATE DOMAIN hstore_foo AS hstore CHECK(VALUE ? 'foo'); + +CREATE FUNCTION test2dom(fn text) RETURNS hstore_foo +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +return {'a': 1, fn: 'boo', 'c': None} +$$; + +SELECT test2dom('foo'); +SELECT test2dom('bar'); -- fail + + +-- test as part of prepare/execute +CREATE FUNCTION test3() RETURNS void +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +rv = plpy.execute("SELECT 'aa=>bb, cc=>NULL'::hstore AS col1") +assert(rv[0]["col1"] == {'aa': 'bb', 'cc': None}) + +val = {'a': 1, 'b': 'boo', 'c': None} +plan = plpy.prepare("SELECT $1::text AS col1", ["hstore"]) +rv = plpy.execute(plan, [val]) +assert(rv[0]["col1"] == '"a"=>"1", "b"=>"boo", "c"=>NULL') +$$; + +SELECT test3(); + + +-- test trigger +CREATE TABLE test1 (a int, b hstore); +INSERT INTO test1 VALUES (1, 'aa=>bb, cc=>NULL'); +SELECT * FROM test1; + +CREATE FUNCTION test4() RETURNS trigger +LANGUAGE plpython3u +TRANSFORM FOR TYPE hstore +AS $$ +assert(TD["new"] == {'a': 1, 'b': {'aa': 'bb', 'cc': None}}) +if TD["new"]["a"] == 1: + TD["new"]["b"] = {'a': 1, 'b': 'boo', 'c': None} + +return "MODIFY" +$$; + +CREATE TRIGGER test4 BEFORE UPDATE ON test1 FOR EACH ROW EXECUTE PROCEDURE test4(); + +UPDATE test1 SET a = a; +SELECT * FROM test1; diff --git a/contrib/intagg/Makefile b/contrib/intagg/Makefile new file mode 100644 index 0000000..c645930 --- /dev/null +++ b/contrib/intagg/Makefile @@ -0,0 +1,15 @@ +# contrib/intagg/Makefile + +EXTENSION = intagg +DATA = intagg--1.1.sql intagg--1.0--1.1.sql + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/intagg +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/intagg/intagg--1.0--1.1.sql b/contrib/intagg/intagg--1.0--1.1.sql new file mode 100644 index 0000000..c0cc17a --- /dev/null +++ b/contrib/intagg/intagg--1.0--1.1.sql @@ -0,0 +1,23 @@ +/* contrib/intagg/intagg--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION intagg UPDATE TO '1.1'" to load this file. \quit + +ALTER FUNCTION int_agg_state(internal, int4) PARALLEL SAFE; +ALTER FUNCTION int_agg_final_array(internal) PARALLEL SAFE; +ALTER FUNCTION int_array_enum(int4[]) PARALLEL SAFE; +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_proc SET proparallel = 's' +WHERE oid = (my_schema || '.int_array_aggregate(int4)')::pg_catalog.regprocedure; + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; diff --git a/contrib/intagg/intagg--1.1.sql b/contrib/intagg/intagg--1.1.sql new file mode 100644 index 0000000..3796a2a --- /dev/null +++ b/contrib/intagg/intagg--1.1.sql @@ -0,0 +1,37 @@ +/* contrib/intagg/intagg--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION intagg" to load this file. \quit + +-- Internal function for the aggregate +-- Is called for each item in an aggregation +CREATE FUNCTION int_agg_state (internal, int4) +RETURNS internal +AS 'array_agg_transfn' +PARALLEL SAFE +LANGUAGE INTERNAL; + +-- Internal function for the aggregate +-- Is called at the end of the aggregation, and returns an array. +CREATE FUNCTION int_agg_final_array (internal) +RETURNS int4[] +AS 'array_agg_finalfn' +PARALLEL SAFE +LANGUAGE INTERNAL; + +-- The aggregate function itself +-- uses the above functions to create an array of integers from an aggregation. +CREATE AGGREGATE int_array_aggregate(int4) ( + SFUNC = int_agg_state, + STYPE = internal, + FINALFUNC = int_agg_final_array, + PARALLEL = SAFE +); + +-- The enumeration function +-- returns each element in a one dimensional integer array +-- as a row. +CREATE FUNCTION int_array_enum(int4[]) +RETURNS setof integer +AS 'array_unnest' +LANGUAGE INTERNAL IMMUTABLE STRICT PARALLEL SAFE; diff --git a/contrib/intagg/intagg.control b/contrib/intagg/intagg.control new file mode 100644 index 0000000..a733bbf --- /dev/null +++ b/contrib/intagg/intagg.control @@ -0,0 +1,4 @@ +# intagg extension +comment = 'integer aggregator and enumerator (obsolete)' +default_version = '1.1' +relocatable = true diff --git a/contrib/intagg/meson.build b/contrib/intagg/meson.build new file mode 100644 index 0000000..7416412 --- /dev/null +++ b/contrib/intagg/meson.build @@ -0,0 +1,8 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +install_data( + 'intagg.control', + 'intagg--1.0--1.1.sql', + 'intagg--1.1.sql', + kwargs: contrib_data_args, +) diff --git a/contrib/intarray/.gitignore b/contrib/intarray/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/intarray/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/intarray/Makefile b/contrib/intarray/Makefile new file mode 100644 index 0000000..3817c16 --- /dev/null +++ b/contrib/intarray/Makefile @@ -0,0 +1,31 @@ +# contrib/intarray/Makefile + +MODULE_big = _int +OBJS = \ + $(WIN32RES) \ + _int_bool.o \ + _int_gin.o \ + _int_gist.o \ + _int_op.o \ + _int_selfuncs.o \ + _int_tool.o \ + _intbig_gist.o + +EXTENSION = intarray +DATA = intarray--1.4--1.5.sql intarray--1.3--1.4.sql intarray--1.2--1.3.sql \ + intarray--1.2.sql intarray--1.1--1.2.sql \ + intarray--1.0--1.1.sql +PGFILEDESC = "intarray - functions and operators for arrays of integers" + +REGRESS = _int + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/intarray +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/intarray/_int.h b/contrib/intarray/_int.h new file mode 100644 index 0000000..0352cbd --- /dev/null +++ b/contrib/intarray/_int.h @@ -0,0 +1,191 @@ +/* + * contrib/intarray/_int.h + */ +#ifndef ___INT_H__ +#define ___INT_H__ + +#include "utils/array.h" +#include "utils/memutils.h" + +/* number ranges for compression */ +#define G_INT_NUMRANGES_DEFAULT 100 +#define G_INT_NUMRANGES_MAX ((GISTMaxIndexKeySize - VARHDRSZ) / \ + (2 * sizeof(int32))) +#define G_INT_GET_NUMRANGES() (PG_HAS_OPCLASS_OPTIONS() ? \ + ((GISTIntArrayOptions *) PG_GET_OPCLASS_OPTIONS())->num_ranges : \ + G_INT_NUMRANGES_DEFAULT) + +/* gist__int_ops opclass options */ +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int num_ranges; /* number of ranges */ +} GISTIntArrayOptions; + +/* useful macros for accessing int4 arrays */ +#define ARRPTR(x) ( (int32 *) ARR_DATA_PTR(x) ) +#define ARRNELEMS(x) ArrayGetNItems(ARR_NDIM(x), ARR_DIMS(x)) + +/* reject arrays we can't handle; to wit, those containing nulls */ +#define CHECKARRVALID(x) \ + do { \ + if (ARR_HASNULL(x) && array_contains_nulls(x)) \ + ereport(ERROR, \ + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), \ + errmsg("array must not contain nulls"))); \ + } while(0) + +#define ARRISEMPTY(x) (ARRNELEMS(x) == 0) + +/* sort the elements of the array */ +#define SORT(x) \ + do { \ + int _nelems_ = ARRNELEMS(x); \ + if (_nelems_ > 1) \ + isort(ARRPTR(x), _nelems_); \ + } while(0) + +/* sort the elements of the array and remove duplicates */ +#define PREPAREARR(x) \ + do { \ + int _nelems_ = ARRNELEMS(x); \ + if (_nelems_ > 1) \ + if (isort(ARRPTR(x), _nelems_)) \ + (x) = _int_unique(x); \ + } while(0) + +/* "wish" function */ +#define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) ) + + +/* bigint defines */ +#define SIGLEN_DEFAULT (63 * 4) +#define SIGLEN_MAX GISTMaxIndexKeySize +#define SIGLENBIT(siglen) ((siglen) * BITS_PER_BYTE) +#define GET_SIGLEN() (PG_HAS_OPCLASS_OPTIONS() ? \ + ((GISTIntArrayBigOptions *) PG_GET_OPCLASS_OPTIONS())->siglen : \ + SIGLEN_DEFAULT) + +typedef char *BITVECP; + +#define LOOPBYTE(siglen) \ + for (i = 0; i < siglen; i++) + +/* beware of multiple evaluation of arguments to these macros! */ +#define GETBYTE(x,i) ( *( (BITVECP)(x) + (int)( (i) / BITS_PER_BYTE ) ) ) +#define GETBITBYTE(x,i) ( (*((char*)(x)) >> (i)) & 0x01 ) +#define CLRBIT(x,i) GETBYTE(x,i) &= ~( 0x01 << ( (i) % BITS_PER_BYTE ) ) +#define SETBIT(x,i) GETBYTE(x,i) |= ( 0x01 << ( (i) % BITS_PER_BYTE ) ) +#define GETBIT(x,i) ( (GETBYTE(x,i) >> ( (i) % BITS_PER_BYTE )) & 0x01 ) +#define HASHVAL(val, siglen) (((unsigned int)(val)) % SIGLENBIT(siglen)) +#define HASH(sign, val, siglen) SETBIT((sign), HASHVAL(val, siglen)) + +/* gist__intbig_ops opclass options */ +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int siglen; /* signature length in bytes */ +} GISTIntArrayBigOptions; + +/* + * type of index key + */ +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int32 flag; + char data[FLEXIBLE_ARRAY_MEMBER]; +} GISTTYPE; + +#define ALLISTRUE 0x04 + +#define ISALLTRUE(x) ( ((GISTTYPE*)x)->flag & ALLISTRUE ) + +#define GTHDRSIZE (VARHDRSZ + sizeof(int32)) +#define CALCGTSIZE(flag, siglen) ( GTHDRSIZE+(((flag) & ALLISTRUE) ? 0 : (siglen)) ) + +#define GETSIGN(x) ( (BITVECP)( (char*)x+GTHDRSIZE ) ) + +/* + * useful functions + */ +bool isort(int32 *a, int len); +ArrayType *new_intArrayType(int num); +ArrayType *copy_intArrayType(ArrayType *a); +ArrayType *resize_intArrayType(ArrayType *a, int num); +int internal_size(int *a, int len); +ArrayType *_int_unique(ArrayType *r); +int32 intarray_match_first(ArrayType *a, int32 elem); +ArrayType *intarray_add_elem(ArrayType *a, int32 elem); +ArrayType *intarray_concat_arrays(ArrayType *a, ArrayType *b); +ArrayType *int_to_intset(int32 elem); +bool inner_int_overlap(ArrayType *a, ArrayType *b); +bool inner_int_contains(ArrayType *a, ArrayType *b); +ArrayType *inner_int_union(ArrayType *a, ArrayType *b); +ArrayType *inner_int_inter(ArrayType *a, ArrayType *b); +void rt__int_size(ArrayType *a, float *size); +void gensign(BITVECP sign, int *a, int len, int siglen); + + +/***************************************************************************** + * Boolean Search + *****************************************************************************/ + +#define BooleanSearchStrategy 20 + +/* + * item in polish notation with back link + * to left operand + */ +typedef struct ITEM +{ + int16 type; + int16 left; + int32 val; +} ITEM; + +typedef struct QUERYTYPE +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int32 size; /* number of ITEMs */ + ITEM items[FLEXIBLE_ARRAY_MEMBER]; +} QUERYTYPE; + +#define HDRSIZEQT offsetof(QUERYTYPE, items) +#define COMPUTESIZE(size) ( HDRSIZEQT + (size) * sizeof(ITEM) ) +#define QUERYTYPEMAXITEMS ((MaxAllocSize - HDRSIZEQT) / sizeof(ITEM)) +#define GETQUERY(x) ( (x)->items ) + +/* "type" codes for ITEM */ +#define END 0 +#define ERR 1 +#define VAL 2 +#define OPR 3 +#define OPEN 4 +#define CLOSE 5 + +/* fmgr macros for QUERYTYPE objects */ +#define DatumGetQueryTypeP(X) ((QUERYTYPE *) PG_DETOAST_DATUM(X)) +#define DatumGetQueryTypePCopy(X) ((QUERYTYPE *) PG_DETOAST_DATUM_COPY(X)) +#define PG_GETARG_QUERYTYPE_P(n) DatumGetQueryTypeP(PG_GETARG_DATUM(n)) +#define PG_GETARG_QUERYTYPE_P_COPY(n) DatumGetQueryTypePCopy(PG_GETARG_DATUM(n)) + +bool signconsistent(QUERYTYPE *query, BITVECP sign, int siglen, bool calcnot); +bool execconsistent(QUERYTYPE *query, ArrayType *array, bool calcnot); + +bool gin_bool_consistent(QUERYTYPE *query, bool *check); +bool query_has_required_values(QUERYTYPE *query); + +int compASC(const void *a, const void *b); +int compDESC(const void *a, const void *b); + +/* sort, either ascending or descending */ +#define QSORT(a, direction) \ + do { \ + int _nelems_ = ARRNELEMS(a); \ + if (_nelems_ > 1) \ + qsort((void*) ARRPTR(a), _nelems_, sizeof(int32), \ + (direction) ? compASC : compDESC ); \ + } while(0) + +#endif /* ___INT_H__ */ diff --git a/contrib/intarray/_int_bool.c b/contrib/intarray/_int_bool.c new file mode 100644 index 0000000..8fc6ad8 --- /dev/null +++ b/contrib/intarray/_int_bool.c @@ -0,0 +1,670 @@ +/* + * contrib/intarray/_int_bool.c + */ +#include "postgres.h" + +#include "_int.h" +#include "miscadmin.h" +#include "utils/builtins.h" + +PG_FUNCTION_INFO_V1(bqarr_in); +PG_FUNCTION_INFO_V1(bqarr_out); +PG_FUNCTION_INFO_V1(boolop); +PG_FUNCTION_INFO_V1(rboolop); +PG_FUNCTION_INFO_V1(querytree); + + +/* parser's states */ +#define WAITOPERAND 1 +#define WAITENDOPERAND 2 +#define WAITOPERATOR 3 + +/* + * node of query tree, also used + * for storing polish notation in parser + */ +typedef struct NODE +{ + int32 type; + int32 val; + struct NODE *next; +} NODE; + +typedef struct +{ + char *buf; + int32 state; + int32 count; + struct Node *escontext; + /* reverse polish notation in list (for temporary usage) */ + NODE *str; + /* number in str */ + int32 num; +} WORKSTATE; + +/* + * get token from query string + */ +static int32 +gettoken(WORKSTATE *state, int32 *val) +{ + char nnn[16]; + int innn; + + *val = 0; /* default result */ + + innn = 0; + while (1) + { + if (innn >= sizeof(nnn)) + return ERR; /* buffer overrun => syntax error */ + switch (state->state) + { + case WAITOPERAND: + innn = 0; + if ((*(state->buf) >= '0' && *(state->buf) <= '9') || + *(state->buf) == '-') + { + state->state = WAITENDOPERAND; + nnn[innn++] = *(state->buf); + } + else if (*(state->buf) == '!') + { + (state->buf)++; + *val = (int32) '!'; + return OPR; + } + else if (*(state->buf) == '(') + { + state->count++; + (state->buf)++; + return OPEN; + } + else if (*(state->buf) != ' ') + return ERR; + break; + case WAITENDOPERAND: + if (*(state->buf) >= '0' && *(state->buf) <= '9') + { + nnn[innn++] = *(state->buf); + } + else + { + long lval; + + nnn[innn] = '\0'; + errno = 0; + lval = strtol(nnn, NULL, 0); + *val = (int32) lval; + if (errno != 0 || (long) *val != lval) + return ERR; + state->state = WAITOPERATOR; + return (state->count && *(state->buf) == '\0') + ? ERR : VAL; + } + break; + case WAITOPERATOR: + if (*(state->buf) == '&' || *(state->buf) == '|') + { + state->state = WAITOPERAND; + *val = (int32) *(state->buf); + (state->buf)++; + return OPR; + } + else if (*(state->buf) == ')') + { + (state->buf)++; + state->count--; + return (state->count < 0) ? ERR : CLOSE; + } + else if (*(state->buf) == '\0') + return (state->count) ? ERR : END; + else if (*(state->buf) != ' ') + return ERR; + break; + default: + return ERR; + break; + } + (state->buf)++; + } +} + +/* + * push new one in polish notation reverse view + */ +static void +pushquery(WORKSTATE *state, int32 type, int32 val) +{ + NODE *tmp = (NODE *) palloc(sizeof(NODE)); + + tmp->type = type; + tmp->val = val; + tmp->next = state->str; + state->str = tmp; + state->num++; +} + +#define STACKDEPTH 16 + +/* + * make polish notation of query + */ +static int32 +makepol(WORKSTATE *state) +{ + int32 val, + type; + int32 stack[STACKDEPTH]; + int32 lenstack = 0; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + while ((type = gettoken(state, &val)) != END) + { + switch (type) + { + case VAL: + pushquery(state, type, val); + while (lenstack && (stack[lenstack - 1] == (int32) '&' || + stack[lenstack - 1] == (int32) '!')) + { + lenstack--; + pushquery(state, OPR, stack[lenstack]); + } + break; + case OPR: + if (lenstack && val == (int32) '|') + pushquery(state, OPR, val); + else + { + if (lenstack == STACKDEPTH) + ereturn(state->escontext, ERR, + (errcode(ERRCODE_STATEMENT_TOO_COMPLEX), + errmsg("statement too complex"))); + stack[lenstack] = val; + lenstack++; + } + break; + case OPEN: + if (makepol(state) == ERR) + return ERR; + while (lenstack && (stack[lenstack - 1] == (int32) '&' || + stack[lenstack - 1] == (int32) '!')) + { + lenstack--; + pushquery(state, OPR, stack[lenstack]); + } + break; + case CLOSE: + while (lenstack) + { + lenstack--; + pushquery(state, OPR, stack[lenstack]); + }; + return END; + break; + case ERR: + default: + ereturn(state->escontext, ERR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error"))); + } + } + + while (lenstack) + { + lenstack--; + pushquery(state, OPR, stack[lenstack]); + }; + return END; +} + +typedef struct +{ + int32 *arrb; + int32 *arre; +} CHKVAL; + +/* + * is there value 'val' in (sorted) array or not ? + */ +static bool +checkcondition_arr(void *checkval, ITEM *item, void *options) +{ + int32 *StopLow = ((CHKVAL *) checkval)->arrb; + int32 *StopHigh = ((CHKVAL *) checkval)->arre; + int32 *StopMiddle; + + /* Loop invariant: StopLow <= val < StopHigh */ + + while (StopLow < StopHigh) + { + StopMiddle = StopLow + (StopHigh - StopLow) / 2; + if (*StopMiddle == item->val) + return true; + else if (*StopMiddle < item->val) + StopLow = StopMiddle + 1; + else + StopHigh = StopMiddle; + } + return false; +} + +static bool +checkcondition_bit(void *checkval, ITEM *item, void *siglen) +{ + return GETBIT(checkval, HASHVAL(item->val, (int) (intptr_t) siglen)); +} + +/* + * evaluate boolean expression, using chkcond() to test the primitive cases + */ +static bool +execute(ITEM *curitem, void *checkval, void *options, bool calcnot, + bool (*chkcond) (void *checkval, ITEM *item, void *options)) +{ + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + if (curitem->type == VAL) + return (*chkcond) (checkval, curitem, options); + else if (curitem->val == (int32) '!') + { + return calcnot ? + ((execute(curitem - 1, checkval, options, calcnot, chkcond)) ? false : true) + : true; + } + else if (curitem->val == (int32) '&') + { + if (execute(curitem + curitem->left, checkval, options, calcnot, chkcond)) + return execute(curitem - 1, checkval, options, calcnot, chkcond); + else + return false; + } + else + { /* |-operator */ + if (execute(curitem + curitem->left, checkval, options, calcnot, chkcond)) + return true; + else + return execute(curitem - 1, checkval, options, calcnot, chkcond); + } +} + +/* + * signconsistent & execconsistent called by *_consistent + */ +bool +signconsistent(QUERYTYPE *query, BITVECP sign, int siglen, bool calcnot) +{ + return execute(GETQUERY(query) + query->size - 1, + (void *) sign, (void *) (intptr_t) siglen, calcnot, + checkcondition_bit); +} + +/* Array must be sorted! */ +bool +execconsistent(QUERYTYPE *query, ArrayType *array, bool calcnot) +{ + CHKVAL chkval; + + CHECKARRVALID(array); + chkval.arrb = ARRPTR(array); + chkval.arre = chkval.arrb + ARRNELEMS(array); + return execute(GETQUERY(query) + query->size - 1, + (void *) &chkval, NULL, calcnot, + checkcondition_arr); +} + +typedef struct +{ + ITEM *first; + bool *mapped_check; +} GinChkVal; + +static bool +checkcondition_gin(void *checkval, ITEM *item, void *options) +{ + GinChkVal *gcv = (GinChkVal *) checkval; + + return gcv->mapped_check[item - gcv->first]; +} + +bool +gin_bool_consistent(QUERYTYPE *query, bool *check) +{ + GinChkVal gcv; + ITEM *items = GETQUERY(query); + int i, + j = 0; + + if (query->size <= 0) + return false; + + /* + * Set up data for checkcondition_gin. This must agree with the query + * extraction code in ginint4_queryextract. + */ + gcv.first = items; + gcv.mapped_check = (bool *) palloc(sizeof(bool) * query->size); + for (i = 0; i < query->size; i++) + { + if (items[i].type == VAL) + gcv.mapped_check[i] = check[j++]; + } + + return execute(GETQUERY(query) + query->size - 1, + (void *) &gcv, NULL, true, + checkcondition_gin); +} + +static bool +contains_required_value(ITEM *curitem) +{ + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + if (curitem->type == VAL) + return true; + else if (curitem->val == (int32) '!') + { + /* + * Assume anything under a NOT is non-required. For some cases with + * nested NOTs, we could prove there's a required value, but it seems + * unlikely to be worth the trouble. + */ + return false; + } + else if (curitem->val == (int32) '&') + { + /* If either side has a required value, we're good */ + if (contains_required_value(curitem + curitem->left)) + return true; + else + return contains_required_value(curitem - 1); + } + else + { /* |-operator */ + /* Both sides must have required values */ + if (contains_required_value(curitem + curitem->left)) + return contains_required_value(curitem - 1); + else + return false; + } +} + +bool +query_has_required_values(QUERYTYPE *query) +{ + if (query->size <= 0) + return false; + return contains_required_value(GETQUERY(query) + query->size - 1); +} + +/* + * boolean operations + */ +Datum +rboolop(PG_FUNCTION_ARGS) +{ + /* just reverse the operands */ + return DirectFunctionCall2(boolop, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0)); +} + +Datum +boolop(PG_FUNCTION_ARGS) +{ + ArrayType *val = PG_GETARG_ARRAYTYPE_P_COPY(0); + QUERYTYPE *query = PG_GETARG_QUERYTYPE_P(1); + CHKVAL chkval; + bool result; + + CHECKARRVALID(val); + PREPAREARR(val); + chkval.arrb = ARRPTR(val); + chkval.arre = chkval.arrb + ARRNELEMS(val); + result = execute(GETQUERY(query) + query->size - 1, + &chkval, NULL, true, + checkcondition_arr); + pfree(val); + + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(result); +} + +static void +findoprnd(ITEM *ptr, int32 *pos) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + +#ifdef BS_DEBUG + elog(DEBUG3, (ptr[*pos].type == OPR) ? + "%d %c" : "%d %d", *pos, ptr[*pos].val); +#endif + if (ptr[*pos].type == VAL) + { + ptr[*pos].left = 0; + (*pos)--; + } + else if (ptr[*pos].val == (int32) '!') + { + ptr[*pos].left = -1; + (*pos)--; + findoprnd(ptr, pos); + } + else + { + ITEM *curitem = &ptr[*pos]; + int32 tmp = *pos; + + (*pos)--; + findoprnd(ptr, pos); + curitem->left = *pos - tmp; + findoprnd(ptr, pos); + } +} + + +/* + * input + */ +Datum +bqarr_in(PG_FUNCTION_ARGS) +{ + char *buf = (char *) PG_GETARG_POINTER(0); + WORKSTATE state; + int32 i; + QUERYTYPE *query; + int32 commonlen; + ITEM *ptr; + NODE *tmp; + int32 pos = 0; + struct Node *escontext = fcinfo->context; + +#ifdef BS_DEBUG + StringInfoData pbuf; +#endif + + state.buf = buf; + state.state = WAITOPERAND; + state.count = 0; + state.num = 0; + state.str = NULL; + state.escontext = escontext; + + /* make polish notation (postfix, but in reverse order) */ + if (makepol(&state) == ERR) + PG_RETURN_NULL(); + if (!state.num) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("empty query"))); + + if (state.num > QUERYTYPEMAXITEMS) + ereturn(escontext, (Datum) 0, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of query items (%d) exceeds the maximum allowed (%d)", + state.num, (int) QUERYTYPEMAXITEMS))); + commonlen = COMPUTESIZE(state.num); + + query = (QUERYTYPE *) palloc(commonlen); + SET_VARSIZE(query, commonlen); + query->size = state.num; + ptr = GETQUERY(query); + + for (i = state.num - 1; i >= 0; i--) + { + ptr[i].type = state.str->type; + ptr[i].val = state.str->val; + tmp = state.str->next; + pfree(state.str); + state.str = tmp; + } + + pos = query->size - 1; + findoprnd(ptr, &pos); +#ifdef BS_DEBUG + initStringInfo(&pbuf); + for (i = 0; i < query->size; i++) + { + if (ptr[i].type == OPR) + appendStringInfo(&pbuf, "%c(%d) ", ptr[i].val, ptr[i].left); + else + appendStringInfo(&pbuf, "%d ", ptr[i].val); + } + elog(DEBUG3, "POR: %s", pbuf.data); + pfree(pbuf.data); +#endif + + PG_RETURN_POINTER(query); +} + + +/* + * out function + */ +typedef struct +{ + ITEM *curpol; + char *buf; + char *cur; + int32 buflen; +} INFIX; + +#define RESIZEBUF(inf,addsize) while( ( (inf)->cur - (inf)->buf ) + (addsize) + 1 >= (inf)->buflen ) { \ + int32 len = inf->cur - inf->buf; \ + inf->buflen *= 2; \ + inf->buf = (char*) repalloc( (void*)inf->buf, inf->buflen ); \ + inf->cur = inf->buf + len; \ +} + +static void +infix(INFIX *in, bool first) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (in->curpol->type == VAL) + { + RESIZEBUF(in, 11); + sprintf(in->cur, "%d", in->curpol->val); + in->cur = strchr(in->cur, '\0'); + in->curpol--; + } + else if (in->curpol->val == (int32) '!') + { + bool isopr = false; + + RESIZEBUF(in, 1); + *(in->cur) = '!'; + in->cur++; + *(in->cur) = '\0'; + in->curpol--; + if (in->curpol->type == OPR) + { + isopr = true; + RESIZEBUF(in, 2); + sprintf(in->cur, "( "); + in->cur = strchr(in->cur, '\0'); + } + infix(in, isopr); + if (isopr) + { + RESIZEBUF(in, 2); + sprintf(in->cur, " )"); + in->cur = strchr(in->cur, '\0'); + } + } + else + { + int32 op = in->curpol->val; + INFIX nrm; + + in->curpol--; + if (op == (int32) '|' && !first) + { + RESIZEBUF(in, 2); + sprintf(in->cur, "( "); + in->cur = strchr(in->cur, '\0'); + } + + nrm.curpol = in->curpol; + nrm.buflen = 16; + nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen); + + /* get right operand */ + infix(&nrm, false); + + /* get & print left operand */ + in->curpol = nrm.curpol; + infix(in, false); + + /* print operator & right operand */ + RESIZEBUF(in, 3 + (nrm.cur - nrm.buf)); + sprintf(in->cur, " %c %s", op, nrm.buf); + in->cur = strchr(in->cur, '\0'); + pfree(nrm.buf); + + if (op == (int32) '|' && !first) + { + RESIZEBUF(in, 2); + sprintf(in->cur, " )"); + in->cur = strchr(in->cur, '\0'); + } + } +} + + +Datum +bqarr_out(PG_FUNCTION_ARGS) +{ + QUERYTYPE *query = PG_GETARG_QUERYTYPE_P(0); + INFIX nrm; + + if (query->size == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("empty query"))); + + nrm.curpol = GETQUERY(query) + query->size - 1; + nrm.buflen = 32; + nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen); + *(nrm.cur) = '\0'; + infix(&nrm, true); + + PG_FREE_IF_COPY(query, 0); + PG_RETURN_POINTER(nrm.buf); +} + + +/* Useless old "debugging" function for a fundamentally wrong algorithm */ +Datum +querytree(PG_FUNCTION_ARGS) +{ + elog(ERROR, "querytree is no longer implemented"); + PG_RETURN_NULL(); +} diff --git a/contrib/intarray/_int_gin.c b/contrib/intarray/_int_gin.c new file mode 100644 index 0000000..b7958d8 --- /dev/null +++ b/contrib/intarray/_int_gin.c @@ -0,0 +1,180 @@ +/* + * contrib/intarray/_int_gin.c + */ +#include "postgres.h" + +#include "_int.h" +#include "access/gin.h" +#include "access/stratnum.h" + +PG_FUNCTION_INFO_V1(ginint4_queryextract); + +Datum +ginint4_queryextract(PG_FUNCTION_ARGS) +{ + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + StrategyNumber strategy = PG_GETARG_UINT16(2); + int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); + Datum *res = NULL; + + *nentries = 0; + + if (strategy == BooleanSearchStrategy) + { + QUERYTYPE *query = PG_GETARG_QUERYTYPE_P(0); + ITEM *items = GETQUERY(query); + int i; + + /* empty query must fail */ + if (query->size <= 0) + PG_RETURN_POINTER(NULL); + + /* + * If the query doesn't have any required primitive values (for + * instance, it's something like '! 42'), we have to do a full index + * scan. + */ + if (query_has_required_values(query)) + *searchMode = GIN_SEARCH_MODE_DEFAULT; + else + *searchMode = GIN_SEARCH_MODE_ALL; + + /* + * Extract all the VAL items as things we want GIN to check for. + */ + res = (Datum *) palloc(sizeof(Datum) * query->size); + *nentries = 0; + + for (i = 0; i < query->size; i++) + { + if (items[i].type == VAL) + { + res[*nentries] = Int32GetDatum(items[i].val); + (*nentries)++; + } + } + } + else + { + ArrayType *query = PG_GETARG_ARRAYTYPE_P(0); + + CHECKARRVALID(query); + *nentries = ARRNELEMS(query); + if (*nentries > 0) + { + int32 *arr; + int32 i; + + res = (Datum *) palloc(sizeof(Datum) * (*nentries)); + + arr = ARRPTR(query); + for (i = 0; i < *nentries; i++) + res[i] = Int32GetDatum(arr[i]); + } + + switch (strategy) + { + case RTOverlapStrategyNumber: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; + case RTContainedByStrategyNumber: + case RTOldContainedByStrategyNumber: + /* empty set is contained in everything */ + *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; + break; + case RTSameStrategyNumber: + if (*nentries > 0) + *searchMode = GIN_SEARCH_MODE_DEFAULT; + else + *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; + break; + case RTContainsStrategyNumber: + case RTOldContainsStrategyNumber: + if (*nentries > 0) + *searchMode = GIN_SEARCH_MODE_DEFAULT; + else /* everything contains the empty set */ + *searchMode = GIN_SEARCH_MODE_ALL; + break; + default: + elog(ERROR, "ginint4_queryextract: unknown strategy number: %d", + strategy); + } + } + + PG_RETURN_POINTER(res); +} + +PG_FUNCTION_INFO_V1(ginint4_consistent); + +Datum +ginint4_consistent(PG_FUNCTION_ARGS) +{ + bool *check = (bool *) PG_GETARG_POINTER(0); + StrategyNumber strategy = PG_GETARG_UINT16(1); + int32 nkeys = PG_GETARG_INT32(3); + + /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ + bool *recheck = (bool *) PG_GETARG_POINTER(5); + bool res = false; + int32 i; + + switch (strategy) + { + case RTOverlapStrategyNumber: + /* result is not lossy */ + *recheck = false; + /* at least one element in check[] is true, so result = true */ + res = true; + break; + case RTContainedByStrategyNumber: + case RTOldContainedByStrategyNumber: + /* we will need recheck */ + *recheck = true; + /* at least one element in check[] is true, so result = true */ + res = true; + break; + case RTSameStrategyNumber: + /* we will need recheck */ + *recheck = true; + /* Must have all elements in check[] true */ + res = true; + for (i = 0; i < nkeys; i++) + { + if (!check[i]) + { + res = false; + break; + } + } + break; + case RTContainsStrategyNumber: + case RTOldContainsStrategyNumber: + /* result is not lossy */ + *recheck = false; + /* Must have all elements in check[] true */ + res = true; + for (i = 0; i < nkeys; i++) + { + if (!check[i]) + { + res = false; + break; + } + } + break; + case BooleanSearchStrategy: + { + QUERYTYPE *query = PG_GETARG_QUERYTYPE_P(2); + + /* result is not lossy */ + *recheck = false; + res = gin_bool_consistent(query, check); + } + break; + default: + elog(ERROR, "ginint4_consistent: unknown strategy number: %d", + strategy); + } + + PG_RETURN_BOOL(res); +} diff --git a/contrib/intarray/_int_gist.c b/contrib/intarray/_int_gist.c new file mode 100644 index 0000000..a09b7fa --- /dev/null +++ b/contrib/intarray/_int_gist.c @@ -0,0 +1,637 @@ +/* + * contrib/intarray/_int_gist.c + */ +#include "postgres.h" + +#include +#include + +#include "_int.h" +#include "access/gist.h" +#include "access/reloptions.h" +#include "access/stratnum.h" + +#define GETENTRY(vec,pos) ((ArrayType *) DatumGetPointer((vec)->vector[(pos)].key)) + +/* + * Control the maximum sparseness of compressed keys. + * + * The upper safe bound for this limit is half the maximum allocatable array + * size. A lower bound would give more guarantees that pathological data + * wouldn't eat excessive CPU and memory, but at the expense of breaking + * possibly working (after a fashion) indexes. + */ +#define MAXNUMELTS (Min((MaxAllocSize / sizeof(Datum)),((MaxAllocSize - ARR_OVERHEAD_NONULLS(1)) / sizeof(int)))/2) +/* or: #define MAXNUMELTS 1000000 */ + +/* +** GiST support methods +*/ +PG_FUNCTION_INFO_V1(g_int_consistent); +PG_FUNCTION_INFO_V1(g_int_compress); +PG_FUNCTION_INFO_V1(g_int_decompress); +PG_FUNCTION_INFO_V1(g_int_penalty); +PG_FUNCTION_INFO_V1(g_int_picksplit); +PG_FUNCTION_INFO_V1(g_int_union); +PG_FUNCTION_INFO_V1(g_int_same); +PG_FUNCTION_INFO_V1(g_int_options); + + +/* +** The GiST Consistent method for _intments +** Should return false if for all data items x below entry, +** the predicate x op query == false, where op is the oper +** corresponding to strategy in the pg_amop table. +*/ +Datum +g_int_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + ArrayType *query = PG_GETARG_ARRAYTYPE_P_COPY(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + bool retval = false; /* silence compiler warning */ + + /* this is exact except for RTSameStrategyNumber */ + *recheck = (strategy == RTSameStrategyNumber); + + if (strategy == BooleanSearchStrategy) + { + retval = execconsistent((QUERYTYPE *) query, + (ArrayType *) DatumGetPointer(entry->key), + GIST_LEAF(entry)); + + pfree(query); + PG_RETURN_BOOL(retval); + } + + /* sort query for fast search, key is already sorted */ + CHECKARRVALID(query); + PREPAREARR(query); + + switch (strategy) + { + case RTOverlapStrategyNumber: + retval = inner_int_overlap((ArrayType *) DatumGetPointer(entry->key), + query); + break; + case RTSameStrategyNumber: + if (GIST_LEAF(entry)) + DirectFunctionCall3(g_int_same, + entry->key, + PointerGetDatum(query), + PointerGetDatum(&retval)); + else + retval = inner_int_contains((ArrayType *) DatumGetPointer(entry->key), + query); + break; + case RTContainsStrategyNumber: + case RTOldContainsStrategyNumber: + retval = inner_int_contains((ArrayType *) DatumGetPointer(entry->key), + query); + break; + case RTContainedByStrategyNumber: + case RTOldContainedByStrategyNumber: + + /* + * This code is unreachable as of intarray 1.4, because the <@ + * operator has been removed from the opclass. We keep it for now + * to support older versions of the SQL definitions. + */ + if (GIST_LEAF(entry)) + retval = inner_int_contains(query, + (ArrayType *) DatumGetPointer(entry->key)); + else + { + /* + * Unfortunately, because empty arrays could be anywhere in + * the index, we must search the whole tree. + */ + retval = true; + } + break; + default: + retval = false; + } + pfree(query); + PG_RETURN_BOOL(retval); +} + +Datum +g_int_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int *size = (int *) PG_GETARG_POINTER(1); + int32 i, + *ptr; + ArrayType *res; + int totlen = 0; + + for (i = 0; i < entryvec->n; i++) + { + ArrayType *ent = GETENTRY(entryvec, i); + + CHECKARRVALID(ent); + totlen += ARRNELEMS(ent); + } + + res = new_intArrayType(totlen); + ptr = ARRPTR(res); + + for (i = 0; i < entryvec->n; i++) + { + ArrayType *ent = GETENTRY(entryvec, i); + int nel; + + nel = ARRNELEMS(ent); + memcpy(ptr, ARRPTR(ent), nel * sizeof(int32)); + ptr += nel; + } + + QSORT(res, 1); + res = _int_unique(res); + *size = VARSIZE(res); + PG_RETURN_POINTER(res); +} + +/* +** GiST Compress and Decompress methods +*/ +Datum +g_int_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; + ArrayType *r; + int num_ranges = G_INT_GET_NUMRANGES(); + int len, + lenr; + int *dr; + int i, + j, + cand; + int64 min; + + if (entry->leafkey) + { + r = DatumGetArrayTypePCopy(entry->key); + CHECKARRVALID(r); + PREPAREARR(r); + + if (ARRNELEMS(r) >= 2 * num_ranges) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("input array is too big (%d maximum allowed, %d current), use gist__intbig_ops opclass instead", + 2 * num_ranges - 1, ARRNELEMS(r)))); + + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, entry->offset, false); + + PG_RETURN_POINTER(retval); + } + + /* + * leaf entries never compress one more time, only when entry->leafkey + * ==true, so now we work only with internal keys + */ + + r = DatumGetArrayTypeP(entry->key); + CHECKARRVALID(r); + if (ARRISEMPTY(r)) + { + if (r != (ArrayType *) DatumGetPointer(entry->key)) + pfree(r); + PG_RETURN_POINTER(entry); + } + + if ((len = ARRNELEMS(r)) >= 2 * num_ranges) + { /* compress */ + if (r == (ArrayType *) DatumGetPointer(entry->key)) + r = DatumGetArrayTypePCopy(entry->key); + r = resize_intArrayType(r, 2 * (len)); + + dr = ARRPTR(r); + + /* + * "len" at this point is the number of ranges we will construct. + * "lenr" is the number of ranges we must eventually remove by + * merging, we must be careful to remove no more than this number. + */ + lenr = len - num_ranges; + + /* + * Initially assume we can merge consecutive ints into a range. but we + * must count every value removed and stop when lenr runs out + */ + for (j = i = len - 1; i > 0 && lenr > 0; i--, j--) + { + int r_end = dr[i]; + int r_start = r_end; + + while (i > 0 && lenr > 0 && dr[i - 1] == r_start - 1) + --r_start, --i, --lenr; + dr[2 * j] = r_start; + dr[2 * j + 1] = r_end; + } + /* just copy the rest, if any, as trivial ranges */ + for (; i >= 0; i--, j--) + dr[2 * j] = dr[2 * j + 1] = dr[i]; + + if (++j) + { + /* + * shunt everything down to start at the right place + */ + memmove(&dr[0], &dr[2 * j], 2 * (len - j) * sizeof(int32)); + } + + /* + * make "len" be number of array elements, not ranges + */ + len = 2 * (len - j); + cand = 1; + while (len > num_ranges * 2) + { + min = PG_INT64_MAX; + for (i = 2; i < len; i += 2) + if (min > ((int64) dr[i] - (int64) dr[i - 1])) + { + min = ((int64) dr[i] - (int64) dr[i - 1]); + cand = i; + } + memmove(&dr[cand - 1], &dr[cand + 1], (len - cand - 1) * sizeof(int32)); + len -= 2; + } + + /* + * check sparseness of result + */ + lenr = internal_size(dr, len); + if (lenr < 0 || lenr > MAXNUMELTS) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("data is too sparse, recreate index using gist__intbig_ops opclass instead"))); + + r = resize_intArrayType(r, len); + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, entry->offset, false); + PG_RETURN_POINTER(retval); + } + else + PG_RETURN_POINTER(entry); +} + +Datum +g_int_decompress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; + ArrayType *r; + int num_ranges = G_INT_GET_NUMRANGES(); + int *dr, + lenr; + ArrayType *in; + int lenin; + int *din; + int i; + + in = DatumGetArrayTypeP(entry->key); + + CHECKARRVALID(in); + if (ARRISEMPTY(in)) + { + if (in != (ArrayType *) DatumGetPointer(entry->key)) + { + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(in), + entry->rel, entry->page, entry->offset, false); + PG_RETURN_POINTER(retval); + } + + PG_RETURN_POINTER(entry); + } + + lenin = ARRNELEMS(in); + + if (lenin < 2 * num_ranges) + { /* not compressed value */ + if (in != (ArrayType *) DatumGetPointer(entry->key)) + { + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(in), + entry->rel, entry->page, entry->offset, false); + + PG_RETURN_POINTER(retval); + } + PG_RETURN_POINTER(entry); + } + + din = ARRPTR(in); + lenr = internal_size(din, lenin); + if (lenr < 0 || lenr > MAXNUMELTS) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("compressed array is too big, recreate index using gist__intbig_ops opclass instead"))); + + r = new_intArrayType(lenr); + dr = ARRPTR(r); + + for (i = 0; i < lenin; i += 2) + { + /* use int64 for j in case din[i + 1] is INT_MAX */ + for (int64 j = din[i]; j <= din[i + 1]; j++) + if ((!i) || *(dr - 1) != j) + *dr++ = (int) j; + } + + if (in != (ArrayType *) DatumGetPointer(entry->key)) + pfree(in); + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(r), + entry->rel, entry->page, entry->offset, false); + + PG_RETURN_POINTER(retval); +} + +/* +** The GiST Penalty method for _intments +*/ +Datum +g_int_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *result = (float *) PG_GETARG_POINTER(2); + ArrayType *ud; + float tmp1, + tmp2; + + ud = inner_int_union((ArrayType *) DatumGetPointer(origentry->key), + (ArrayType *) DatumGetPointer(newentry->key)); + rt__int_size(ud, &tmp1); + rt__int_size((ArrayType *) DatumGetPointer(origentry->key), &tmp2); + *result = tmp1 - tmp2; + pfree(ud); + + PG_RETURN_POINTER(result); +} + + + +Datum +g_int_same(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *b = PG_GETARG_ARRAYTYPE_P(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + int32 n = ARRNELEMS(a); + int32 *da, + *db; + + CHECKARRVALID(a); + CHECKARRVALID(b); + + if (n != ARRNELEMS(b)) + { + *result = false; + PG_RETURN_POINTER(result); + } + *result = true; + da = ARRPTR(a); + db = ARRPTR(b); + while (n--) + { + if (*da++ != *db++) + { + *result = false; + break; + } + } + + PG_RETURN_POINTER(result); +} + +/***************************************************************** +** Common GiST Method +*****************************************************************/ + +typedef struct +{ + OffsetNumber pos; + float cost; +} SPLITCOST; + +static int +comparecost(const void *a, const void *b) +{ + if (((const SPLITCOST *) a)->cost == ((const SPLITCOST *) b)->cost) + return 0; + else + return (((const SPLITCOST *) a)->cost > ((const SPLITCOST *) b)->cost) ? 1 : -1; +} + +/* +** The GiST PickSplit method for _intments +** We use Guttman's poly time split algorithm +*/ +Datum +g_int_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + OffsetNumber i, + j; + ArrayType *datum_alpha, + *datum_beta; + ArrayType *datum_l, + *datum_r; + ArrayType *union_d, + *union_dl, + *union_dr; + ArrayType *inter_d; + bool firsttime; + float size_alpha, + size_beta, + size_union, + size_inter; + float size_waste, + waste; + float size_l, + size_r; + int nbytes; + OffsetNumber seed_1 = 0, + seed_2 = 0; + OffsetNumber *left, + *right; + OffsetNumber maxoff; + SPLITCOST *costvector; + +#ifdef GIST_DEBUG + elog(DEBUG3, "--------picksplit %d", entryvec->n); +#endif + + maxoff = entryvec->n - 2; + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + firsttime = true; + waste = 0.0; + for (i = FirstOffsetNumber; i < maxoff; i = OffsetNumberNext(i)) + { + datum_alpha = GETENTRY(entryvec, i); + for (j = OffsetNumberNext(i); j <= maxoff; j = OffsetNumberNext(j)) + { + datum_beta = GETENTRY(entryvec, j); + + /* compute the wasted space by unioning these guys */ + /* size_waste = size_union - size_inter; */ + union_d = inner_int_union(datum_alpha, datum_beta); + rt__int_size(union_d, &size_union); + inter_d = inner_int_inter(datum_alpha, datum_beta); + rt__int_size(inter_d, &size_inter); + size_waste = size_union - size_inter; + + pfree(union_d); + pfree(inter_d); + + /* + * are these a more promising split that what we've already seen? + */ + + if (size_waste > waste || firsttime) + { + waste = size_waste; + seed_1 = i; + seed_2 = j; + firsttime = false; + } + } + } + + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + if (seed_1 == 0 || seed_2 == 0) + { + seed_1 = 1; + seed_2 = 2; + } + + datum_alpha = GETENTRY(entryvec, seed_1); + datum_l = copy_intArrayType(datum_alpha); + rt__int_size(datum_l, &size_l); + datum_beta = GETENTRY(entryvec, seed_2); + datum_r = copy_intArrayType(datum_beta); + rt__int_size(datum_r, &size_r); + + maxoff = OffsetNumberNext(maxoff); + + /* + * sort entries + */ + costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff); + for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) + { + costvector[i - 1].pos = i; + datum_alpha = GETENTRY(entryvec, i); + union_d = inner_int_union(datum_l, datum_alpha); + rt__int_size(union_d, &size_alpha); + pfree(union_d); + union_d = inner_int_union(datum_r, datum_alpha); + rt__int_size(union_d, &size_beta); + pfree(union_d); + costvector[i - 1].cost = fabsf((size_alpha - size_l) - (size_beta - size_r)); + } + qsort(costvector, maxoff, sizeof(SPLITCOST), comparecost); + + /* + * Now split up the regions between the two seeds. An important property + * of this split algorithm is that the split vector v has the indices of + * items to be split in order in its left and right vectors. We exploit + * this property by doing a merge in the code that actually splits the + * page. + * + * For efficiency, we also place the new index tuple in this loop. This is + * handled at the very end, when we have placed all the existing tuples + * and i == maxoff + 1. + */ + + + for (j = 0; j < maxoff; j++) + { + i = costvector[j].pos; + + /* + * If we've already decided where to place this item, just put it on + * the right list. Otherwise, we need to figure out which page needs + * the least enlargement in order to store the item. + */ + + if (i == seed_1) + { + *left++ = i; + v->spl_nleft++; + continue; + } + else if (i == seed_2) + { + *right++ = i; + v->spl_nright++; + continue; + } + + /* okay, which page needs least enlargement? */ + datum_alpha = GETENTRY(entryvec, i); + union_dl = inner_int_union(datum_l, datum_alpha); + union_dr = inner_int_union(datum_r, datum_alpha); + rt__int_size(union_dl, &size_alpha); + rt__int_size(union_dr, &size_beta); + + /* pick which page to add it to */ + if (size_alpha - size_l < size_beta - size_r + WISH_F(v->spl_nleft, v->spl_nright, 0.01)) + { + pfree(datum_l); + pfree(union_dr); + datum_l = union_dl; + size_l = size_alpha; + *left++ = i; + v->spl_nleft++; + } + else + { + pfree(datum_r); + pfree(union_dl); + datum_r = union_dr; + size_r = size_beta; + *right++ = i; + v->spl_nright++; + } + } + pfree(costvector); + *right = *left = FirstOffsetNumber; + + v->spl_ldatum = PointerGetDatum(datum_l); + v->spl_rdatum = PointerGetDatum(datum_r); + + PG_RETURN_POINTER(v); +} + +Datum +g_int_options(PG_FUNCTION_ARGS) +{ + local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0); + + init_local_reloptions(relopts, sizeof(GISTIntArrayOptions)); + add_local_int_reloption(relopts, "numranges", + "number of ranges for compression", + G_INT_NUMRANGES_DEFAULT, 1, G_INT_NUMRANGES_MAX, + offsetof(GISTIntArrayOptions, num_ranges)); + + PG_RETURN_VOID(); +} diff --git a/contrib/intarray/_int_op.c b/contrib/intarray/_int_op.c new file mode 100644 index 0000000..5b164f6 --- /dev/null +++ b/contrib/intarray/_int_op.c @@ -0,0 +1,432 @@ +/* + * contrib/intarray/_int_op.c + */ +#include "postgres.h" + +#include "_int.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(_int_different); +PG_FUNCTION_INFO_V1(_int_same); +PG_FUNCTION_INFO_V1(_int_contains); +PG_FUNCTION_INFO_V1(_int_contained); +PG_FUNCTION_INFO_V1(_int_overlap); +PG_FUNCTION_INFO_V1(_int_union); +PG_FUNCTION_INFO_V1(_int_inter); + +Datum +_int_contained(PG_FUNCTION_ARGS) +{ + /* just reverse the operands and call _int_contains */ + return DirectFunctionCall2(_int_contains, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0)); +} + +Datum +_int_contains(PG_FUNCTION_ARGS) +{ + /* Force copy so we can modify the arrays in-place */ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + ArrayType *b = PG_GETARG_ARRAYTYPE_P_COPY(1); + bool res; + + CHECKARRVALID(a); + CHECKARRVALID(b); + PREPAREARR(a); + PREPAREARR(b); + res = inner_int_contains(a, b); + pfree(a); + pfree(b); + PG_RETURN_BOOL(res); +} + +Datum +_int_different(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(!DatumGetBool(DirectFunctionCall2(_int_same, + PointerGetDatum(PG_GETARG_POINTER(0)), + PointerGetDatum(PG_GETARG_POINTER(1))))); +} + +Datum +_int_same(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + ArrayType *b = PG_GETARG_ARRAYTYPE_P_COPY(1); + int na, + nb; + int n; + int *da, + *db; + bool result; + + CHECKARRVALID(a); + CHECKARRVALID(b); + na = ARRNELEMS(a); + nb = ARRNELEMS(b); + da = ARRPTR(a); + db = ARRPTR(b); + + result = false; + + if (na == nb) + { + SORT(a); + SORT(b); + result = true; + + for (n = 0; n < na; n++) + { + if (da[n] != db[n]) + { + result = false; + break; + } + } + } + + pfree(a); + pfree(b); + + PG_RETURN_BOOL(result); +} + +/* _int_overlap -- does a overlap b? + */ +Datum +_int_overlap(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + ArrayType *b = PG_GETARG_ARRAYTYPE_P_COPY(1); + bool result; + + CHECKARRVALID(a); + CHECKARRVALID(b); + if (ARRISEMPTY(a) || ARRISEMPTY(b)) + return false; + + SORT(a); + SORT(b); + + result = inner_int_overlap(a, b); + + pfree(a); + pfree(b); + + PG_RETURN_BOOL(result); +} + +Datum +_int_union(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + ArrayType *b = PG_GETARG_ARRAYTYPE_P_COPY(1); + ArrayType *result; + + CHECKARRVALID(a); + CHECKARRVALID(b); + + SORT(a); + SORT(b); + + result = inner_int_union(a, b); + + pfree(a); + pfree(b); + + PG_RETURN_POINTER(result); +} + +Datum +_int_inter(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + ArrayType *b = PG_GETARG_ARRAYTYPE_P_COPY(1); + ArrayType *result; + + CHECKARRVALID(a); + CHECKARRVALID(b); + + SORT(a); + SORT(b); + + result = inner_int_inter(a, b); + + pfree(a); + pfree(b); + + PG_RETURN_POINTER(result); +} + + +PG_FUNCTION_INFO_V1(intset); +PG_FUNCTION_INFO_V1(icount); +PG_FUNCTION_INFO_V1(sort); +PG_FUNCTION_INFO_V1(sort_asc); +PG_FUNCTION_INFO_V1(sort_desc); +PG_FUNCTION_INFO_V1(uniq); +PG_FUNCTION_INFO_V1(idx); +PG_FUNCTION_INFO_V1(subarray); +PG_FUNCTION_INFO_V1(intarray_push_elem); +PG_FUNCTION_INFO_V1(intarray_push_array); +PG_FUNCTION_INFO_V1(intarray_del_elem); +PG_FUNCTION_INFO_V1(intset_union_elem); +PG_FUNCTION_INFO_V1(intset_subtract); + +Datum +intset(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(int_to_intset(PG_GETARG_INT32(0))); +} + +Datum +icount(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P(0); + int32 count = ARRNELEMS(a); + + PG_FREE_IF_COPY(a, 0); + PG_RETURN_INT32(count); +} + +Datum +sort(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + text *dirstr = (fcinfo->nargs == 2) ? PG_GETARG_TEXT_PP(1) : NULL; + int32 dc = (dirstr) ? VARSIZE_ANY_EXHDR(dirstr) : 0; + char *d = (dirstr) ? VARDATA_ANY(dirstr) : NULL; + int dir = -1; + + CHECKARRVALID(a); + if (ARRNELEMS(a) < 2) + PG_RETURN_POINTER(a); + + if (dirstr == NULL || (dc == 3 + && (d[0] == 'A' || d[0] == 'a') + && (d[1] == 'S' || d[1] == 's') + && (d[2] == 'C' || d[2] == 'c'))) + dir = 1; + else if (dc == 4 + && (d[0] == 'D' || d[0] == 'd') + && (d[1] == 'E' || d[1] == 'e') + && (d[2] == 'S' || d[2] == 's') + && (d[3] == 'C' || d[3] == 'c')) + dir = 0; + if (dir == -1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("second parameter must be \"ASC\" or \"DESC\""))); + QSORT(a, dir); + PG_RETURN_POINTER(a); +} + +Datum +sort_asc(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + + CHECKARRVALID(a); + QSORT(a, 1); + PG_RETURN_POINTER(a); +} + +Datum +sort_desc(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + + CHECKARRVALID(a); + QSORT(a, 0); + PG_RETURN_POINTER(a); +} + +Datum +uniq(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + + CHECKARRVALID(a); + if (ARRNELEMS(a) < 2) + PG_RETURN_POINTER(a); + a = _int_unique(a); + PG_RETURN_POINTER(a); +} + +Datum +idx(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P(0); + int32 result; + + CHECKARRVALID(a); + result = ARRNELEMS(a); + if (result) + result = intarray_match_first(a, PG_GETARG_INT32(1)); + PG_FREE_IF_COPY(a, 0); + PG_RETURN_INT32(result); +} + +Datum +subarray(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P(0); + int32 start = PG_GETARG_INT32(1); + int32 len = (fcinfo->nargs == 3) ? PG_GETARG_INT32(2) : 0; + int32 end = 0; + int32 c; + ArrayType *result; + + start = (start > 0) ? start - 1 : start; + + CHECKARRVALID(a); + if (ARRISEMPTY(a)) + { + PG_FREE_IF_COPY(a, 0); + PG_RETURN_POINTER(new_intArrayType(0)); + } + + c = ARRNELEMS(a); + + if (start < 0) + start = c + start; + + if (len < 0) + end = c + len; + else if (len == 0) + end = c; + else + end = start + len; + + if (end > c) + end = c; + + if (start < 0) + start = 0; + + if (start >= end || end <= 0) + { + PG_FREE_IF_COPY(a, 0); + PG_RETURN_POINTER(new_intArrayType(0)); + } + + result = new_intArrayType(end - start); + if (end - start > 0) + memcpy(ARRPTR(result), ARRPTR(a) + start, (end - start) * sizeof(int32)); + PG_FREE_IF_COPY(a, 0); + PG_RETURN_POINTER(result); +} + +Datum +intarray_push_elem(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *result; + + result = intarray_add_elem(a, PG_GETARG_INT32(1)); + PG_FREE_IF_COPY(a, 0); + PG_RETURN_POINTER(result); +} + +Datum +intarray_push_array(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *b = PG_GETARG_ARRAYTYPE_P(1); + ArrayType *result; + + result = intarray_concat_arrays(a, b); + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_POINTER(result); +} + +Datum +intarray_del_elem(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + int32 elem = PG_GETARG_INT32(1); + int32 c; + int32 *aa; + int32 n = 0, + i; + + CHECKARRVALID(a); + if (!ARRISEMPTY(a)) + { + c = ARRNELEMS(a); + aa = ARRPTR(a); + for (i = 0; i < c; i++) + { + if (aa[i] != elem) + { + if (i > n) + aa[n++] = aa[i]; + else + n++; + } + } + a = resize_intArrayType(a, n); + } + PG_RETURN_POINTER(a); +} + +Datum +intset_union_elem(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *result; + + result = intarray_add_elem(a, PG_GETARG_INT32(1)); + PG_FREE_IF_COPY(a, 0); + QSORT(result, 1); + PG_RETURN_POINTER(_int_unique(result)); +} + +Datum +intset_subtract(PG_FUNCTION_ARGS) +{ + ArrayType *a = PG_GETARG_ARRAYTYPE_P_COPY(0); + ArrayType *b = PG_GETARG_ARRAYTYPE_P_COPY(1); + ArrayType *result; + int32 ca; + int32 cb; + int32 *aa, + *bb, + *r; + int32 n = 0, + i = 0, + k = 0; + + CHECKARRVALID(a); + CHECKARRVALID(b); + + QSORT(a, 1); + a = _int_unique(a); + ca = ARRNELEMS(a); + QSORT(b, 1); + b = _int_unique(b); + cb = ARRNELEMS(b); + result = new_intArrayType(ca); + aa = ARRPTR(a); + bb = ARRPTR(b); + r = ARRPTR(result); + while (i < ca) + { + if (k == cb || aa[i] < bb[k]) + r[n++] = aa[i++]; + else if (aa[i] == bb[k]) + { + i++; + k++; + } + else + k++; + } + result = resize_intArrayType(result, n); + pfree(a); + pfree(b); + PG_RETURN_POINTER(result); +} diff --git a/contrib/intarray/_int_selfuncs.c b/contrib/intarray/_int_selfuncs.c new file mode 100644 index 0000000..01d6fe1 --- /dev/null +++ b/contrib/intarray/_int_selfuncs.c @@ -0,0 +1,333 @@ +/*------------------------------------------------------------------------- + * + * _int_selfuncs.c + * Functions for selectivity estimation of intarray operators + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/intarray/_int_selfuncs.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "_int.h" +#include "access/htup_details.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/selfuncs.h" +#include "utils/syscache.h" + +PG_FUNCTION_INFO_V1(_int_overlap_sel); +PG_FUNCTION_INFO_V1(_int_contains_sel); +PG_FUNCTION_INFO_V1(_int_contained_sel); +PG_FUNCTION_INFO_V1(_int_overlap_joinsel); +PG_FUNCTION_INFO_V1(_int_contains_joinsel); +PG_FUNCTION_INFO_V1(_int_contained_joinsel); +PG_FUNCTION_INFO_V1(_int_matchsel); + + +static Selectivity int_query_opr_selec(ITEM *item, Datum *mcelems, float4 *mcefreqs, + int nmcelems, float4 minfreq); +static int compare_val_int4(const void *a, const void *b); + +/* + * Wrappers around the default array selectivity estimation functions. + * + * The default array selectivity operators for the @>, && and @< operators + * work fine for integer arrays. However, if we tried to just use arraycontsel + * and arraycontjoinsel directly as the cost estimator functions for our + * operators, they would not work as intended, because they look at the + * operator's OID. Our operators behave exactly like the built-in anyarray + * versions, but we must tell the cost estimator functions which built-in + * operators they correspond to. These wrappers just replace the operator + * OID with the corresponding built-in operator's OID, and call the built-in + * function. + */ + +Datum +_int_overlap_sel(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall4(arraycontsel, + PG_GETARG_DATUM(0), + ObjectIdGetDatum(OID_ARRAY_OVERLAP_OP), + PG_GETARG_DATUM(2), + PG_GETARG_DATUM(3))); +} + +Datum +_int_contains_sel(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall4(arraycontsel, + PG_GETARG_DATUM(0), + ObjectIdGetDatum(OID_ARRAY_CONTAINS_OP), + PG_GETARG_DATUM(2), + PG_GETARG_DATUM(3))); +} + +Datum +_int_contained_sel(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall4(arraycontsel, + PG_GETARG_DATUM(0), + ObjectIdGetDatum(OID_ARRAY_CONTAINED_OP), + PG_GETARG_DATUM(2), + PG_GETARG_DATUM(3))); +} + +Datum +_int_overlap_joinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall5(arraycontjoinsel, + PG_GETARG_DATUM(0), + ObjectIdGetDatum(OID_ARRAY_OVERLAP_OP), + PG_GETARG_DATUM(2), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4))); +} + +Datum +_int_contains_joinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall5(arraycontjoinsel, + PG_GETARG_DATUM(0), + ObjectIdGetDatum(OID_ARRAY_CONTAINS_OP), + PG_GETARG_DATUM(2), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4))); +} + +Datum +_int_contained_joinsel(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall5(arraycontjoinsel, + PG_GETARG_DATUM(0), + ObjectIdGetDatum(OID_ARRAY_CONTAINED_OP), + PG_GETARG_DATUM(2), + PG_GETARG_DATUM(3), + PG_GETARG_DATUM(4))); +} + + +/* + * _int_matchsel -- restriction selectivity function for intarray @@ query_int + */ +Datum +_int_matchsel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + VariableStatData vardata; + Node *other; + bool varonleft; + Selectivity selec; + QUERYTYPE *query; + Datum *mcelems = NULL; + float4 *mcefreqs = NULL; + int nmcelems = 0; + float4 minfreq = 0.0; + float4 nullfrac = 0.0; + AttStatsSlot sslot; + + /* + * If expression is not "variable @@ something" or "something @@ variable" + * then punt and return a default estimate. + */ + if (!get_restriction_variable(root, args, varRelid, + &vardata, &other, &varonleft)) + PG_RETURN_FLOAT8(DEFAULT_EQ_SEL); + + /* + * Variable should be int[]. We don't support cases where variable is + * query_int. + */ + if (vardata.vartype != INT4ARRAYOID) + PG_RETURN_FLOAT8(DEFAULT_EQ_SEL); + + /* + * Can't do anything useful if the something is not a constant, either. + */ + if (!IsA(other, Const)) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(DEFAULT_EQ_SEL); + } + + /* + * The "@@" operator is strict, so we can cope with NULL right away. + */ + if (((Const *) other)->constisnull) + { + ReleaseVariableStats(vardata); + PG_RETURN_FLOAT8(0.0); + } + + /* The caller made sure the const is a query, so get it now */ + query = DatumGetQueryTypeP(((Const *) other)->constvalue); + + /* Empty query matches nothing */ + if (query->size == 0) + { + ReleaseVariableStats(vardata); + return (Selectivity) 0.0; + } + + /* + * Get the statistics for the intarray column. + * + * We're interested in the Most-Common-Elements list, and the NULL + * fraction. + */ + if (HeapTupleIsValid(vardata.statsTuple)) + { + Form_pg_statistic stats; + + stats = (Form_pg_statistic) GETSTRUCT(vardata.statsTuple); + nullfrac = stats->stanullfrac; + + /* + * For an int4 array, the default array type analyze function will + * collect a Most Common Elements list, which is an array of int4s. + */ + if (get_attstatsslot(&sslot, vardata.statsTuple, + STATISTIC_KIND_MCELEM, InvalidOid, + ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)) + { + Assert(sslot.valuetype == INT4OID); + + /* + * There should be three more Numbers than Values, because the + * last three (for intarray) cells are taken for minimal, maximal + * and nulls frequency. Punt if not. + */ + if (sslot.nnumbers == sslot.nvalues + 3) + { + /* Grab the lowest frequency. */ + minfreq = sslot.numbers[sslot.nnumbers - (sslot.nnumbers - sslot.nvalues)]; + + mcelems = sslot.values; + mcefreqs = sslot.numbers; + nmcelems = sslot.nvalues; + } + } + } + else + memset(&sslot, 0, sizeof(sslot)); + + /* Process the logical expression in the query, using the stats */ + selec = int_query_opr_selec(GETQUERY(query) + query->size - 1, + mcelems, mcefreqs, nmcelems, minfreq); + + /* MCE stats count only non-null rows, so adjust for null rows. */ + selec *= (1.0 - nullfrac); + + free_attstatsslot(&sslot); + ReleaseVariableStats(vardata); + + CLAMP_PROBABILITY(selec); + + PG_RETURN_FLOAT8((float8) selec); +} + +/* + * Estimate selectivity of single intquery operator + */ +static Selectivity +int_query_opr_selec(ITEM *item, Datum *mcelems, float4 *mcefreqs, + int nmcelems, float4 minfreq) +{ + Selectivity selec; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + if (item->type == VAL) + { + Datum *searchres; + + if (mcelems == NULL) + return (Selectivity) DEFAULT_EQ_SEL; + + searchres = (Datum *) bsearch(&item->val, mcelems, nmcelems, + sizeof(Datum), compare_val_int4); + if (searchres) + { + /* + * The element is in MCELEM. Return precise selectivity (or at + * least as precise as ANALYZE could find out). + */ + selec = mcefreqs[searchres - mcelems]; + } + else + { + /* + * The element is not in MCELEM. Punt, but assume that the + * selectivity cannot be more than minfreq / 2. + */ + selec = Min(DEFAULT_EQ_SEL, minfreq / 2); + } + } + else if (item->type == OPR) + { + /* Current query node is an operator */ + Selectivity s1, + s2; + + s1 = int_query_opr_selec(item - 1, mcelems, mcefreqs, nmcelems, + minfreq); + switch (item->val) + { + case (int32) '!': + selec = 1.0 - s1; + break; + + case (int32) '&': + s2 = int_query_opr_selec(item + item->left, mcelems, mcefreqs, + nmcelems, minfreq); + selec = s1 * s2; + break; + + case (int32) '|': + s2 = int_query_opr_selec(item + item->left, mcelems, mcefreqs, + nmcelems, minfreq); + selec = s1 + s2 - s1 * s2; + break; + + default: + elog(ERROR, "unrecognized operator: %d", item->val); + selec = 0; /* keep compiler quiet */ + break; + } + } + else + { + elog(ERROR, "unrecognized int query item type: %u", item->type); + selec = 0; /* keep compiler quiet */ + } + + /* Clamp intermediate results to stay sane despite roundoff error */ + CLAMP_PROBABILITY(selec); + + return selec; +} + +/* + * Comparison function for binary search in mcelem array. + */ +static int +compare_val_int4(const void *a, const void *b) +{ + int32 key = *(int32 *) a; + const Datum *t = (const Datum *) b; + + return key - DatumGetInt32(*t); +} diff --git a/contrib/intarray/_int_tool.c b/contrib/intarray/_int_tool.c new file mode 100644 index 0000000..68f624e --- /dev/null +++ b/contrib/intarray/_int_tool.c @@ -0,0 +1,410 @@ +/* + * contrib/intarray/_int_tool.c + */ +#include "postgres.h" + +#include + +#include "_int.h" +#include "catalog/pg_type.h" +#include "lib/qunique.h" + +/* arguments are assumed sorted & unique-ified */ +bool +inner_int_contains(ArrayType *a, ArrayType *b) +{ + int na, + nb; + int i, + j, + n; + int *da, + *db; + + na = ARRNELEMS(a); + nb = ARRNELEMS(b); + da = ARRPTR(a); + db = ARRPTR(b); + + i = j = n = 0; + while (i < na && j < nb) + { + if (da[i] < db[j]) + i++; + else if (da[i] == db[j]) + { + n++; + i++; + j++; + } + else + break; /* db[j] is not in da */ + } + + return (n == nb); +} + +/* arguments are assumed sorted */ +bool +inner_int_overlap(ArrayType *a, ArrayType *b) +{ + int na, + nb; + int i, + j; + int *da, + *db; + + na = ARRNELEMS(a); + nb = ARRNELEMS(b); + da = ARRPTR(a); + db = ARRPTR(b); + + i = j = 0; + while (i < na && j < nb) + { + if (da[i] < db[j]) + i++; + else if (da[i] == db[j]) + return true; + else + j++; + } + + return false; +} + +ArrayType * +inner_int_union(ArrayType *a, ArrayType *b) +{ + ArrayType *r = NULL; + + CHECKARRVALID(a); + CHECKARRVALID(b); + + if (ARRISEMPTY(a) && ARRISEMPTY(b)) + return new_intArrayType(0); + if (ARRISEMPTY(a)) + r = copy_intArrayType(b); + if (ARRISEMPTY(b)) + r = copy_intArrayType(a); + + if (!r) + { + int na = ARRNELEMS(a), + nb = ARRNELEMS(b); + int *da = ARRPTR(a), + *db = ARRPTR(b); + int i, + j, + *dr; + + r = new_intArrayType(na + nb); + dr = ARRPTR(r); + + /* union */ + i = j = 0; + while (i < na && j < nb) + { + if (da[i] == db[j]) + { + *dr++ = da[i++]; + j++; + } + else if (da[i] < db[j]) + *dr++ = da[i++]; + else + *dr++ = db[j++]; + } + + while (i < na) + *dr++ = da[i++]; + while (j < nb) + *dr++ = db[j++]; + + r = resize_intArrayType(r, dr - ARRPTR(r)); + } + + if (ARRNELEMS(r) > 1) + r = _int_unique(r); + + return r; +} + +ArrayType * +inner_int_inter(ArrayType *a, ArrayType *b) +{ + ArrayType *r; + int na, + nb; + int *da, + *db, + *dr; + int i, + j, + k; + + if (ARRISEMPTY(a) || ARRISEMPTY(b)) + return new_intArrayType(0); + + na = ARRNELEMS(a); + nb = ARRNELEMS(b); + da = ARRPTR(a); + db = ARRPTR(b); + r = new_intArrayType(Min(na, nb)); + dr = ARRPTR(r); + + i = j = k = 0; + while (i < na && j < nb) + { + if (da[i] < db[j]) + i++; + else if (da[i] == db[j]) + { + if (k == 0 || dr[k - 1] != db[j]) + dr[k++] = db[j]; + i++; + j++; + } + else + j++; + } + + if (k == 0) + { + pfree(r); + return new_intArrayType(0); + } + else + return resize_intArrayType(r, k); +} + +void +rt__int_size(ArrayType *a, float *size) +{ + *size = (float) ARRNELEMS(a); +} + +/* qsort_arg comparison function for isort() */ +static int +isort_cmp(const void *a, const void *b, void *arg) +{ + int32 aval = *((const int32 *) a); + int32 bval = *((const int32 *) b); + + if (aval < bval) + return -1; + if (aval > bval) + return 1; + + /* + * Report if we have any duplicates. If there are equal keys, qsort must + * compare them at some point, else it wouldn't know whether one should go + * before or after the other. + */ + *((bool *) arg) = true; + return 0; +} + +/* Sort the given data (len >= 2). Return true if any duplicates found */ +bool +isort(int32 *a, int len) +{ + bool r = false; + + qsort_arg(a, len, sizeof(int32), isort_cmp, &r); + return r; +} + +/* Create a new int array with room for "num" elements */ +ArrayType * +new_intArrayType(int num) +{ + ArrayType *r; + int nbytes; + + /* if no elements, return a zero-dimensional array */ + if (num <= 0) + { + Assert(num == 0); + r = construct_empty_array(INT4OID); + return r; + } + + nbytes = ARR_OVERHEAD_NONULLS(1) + sizeof(int) * num; + + r = (ArrayType *) palloc0(nbytes); + + SET_VARSIZE(r, nbytes); + ARR_NDIM(r) = 1; + r->dataoffset = 0; /* marker for no null bitmap */ + ARR_ELEMTYPE(r) = INT4OID; + ARR_DIMS(r)[0] = num; + ARR_LBOUND(r)[0] = 1; + + return r; +} + +ArrayType * +resize_intArrayType(ArrayType *a, int num) +{ + int nbytes; + int i; + + /* if no elements, return a zero-dimensional array */ + if (num <= 0) + { + Assert(num == 0); + a = construct_empty_array(INT4OID); + return a; + } + + if (num == ARRNELEMS(a)) + return a; + + nbytes = ARR_DATA_OFFSET(a) + sizeof(int) * num; + + a = (ArrayType *) repalloc(a, nbytes); + + SET_VARSIZE(a, nbytes); + /* usually the array should be 1-D already, but just in case ... */ + for (i = 0; i < ARR_NDIM(a); i++) + { + ARR_DIMS(a)[i] = num; + num = 1; + } + return a; +} + +ArrayType * +copy_intArrayType(ArrayType *a) +{ + ArrayType *r; + int n = ARRNELEMS(a); + + r = new_intArrayType(n); + memcpy(ARRPTR(r), ARRPTR(a), n * sizeof(int32)); + return r; +} + +/* num for compressed key */ +int +internal_size(int *a, int len) +{ + int i; + int64 size = 0; + + for (i = 0; i < len; i += 2) + { + if (!i || a[i] != a[i - 1]) /* do not count repeated range */ + size += (int64) (a[i + 1]) - (int64) (a[i]) + 1; + } + + if (size > (int64) INT_MAX || size < (int64) INT_MIN) + return -1; /* overflow */ + return (int) size; +} + +/* unique-ify elements of r in-place ... r must be sorted already */ +ArrayType * +_int_unique(ArrayType *r) +{ + int num = ARRNELEMS(r); + bool duplicates_found; /* not used */ + + num = qunique_arg(ARRPTR(r), num, sizeof(int), isort_cmp, + &duplicates_found); + + return resize_intArrayType(r, num); +} + +void +gensign(BITVECP sign, int *a, int len, int siglen) +{ + int i; + + /* we assume that the sign vector is previously zeroed */ + for (i = 0; i < len; i++) + { + HASH(sign, *a, siglen); + a++; + } +} + +int32 +intarray_match_first(ArrayType *a, int32 elem) +{ + int32 *aa, + c, + i; + + CHECKARRVALID(a); + c = ARRNELEMS(a); + aa = ARRPTR(a); + for (i = 0; i < c; i++) + if (aa[i] == elem) + return (i + 1); + return 0; +} + +ArrayType * +intarray_add_elem(ArrayType *a, int32 elem) +{ + ArrayType *result; + int32 *r; + int32 c; + + CHECKARRVALID(a); + c = ARRNELEMS(a); + result = new_intArrayType(c + 1); + r = ARRPTR(result); + if (c > 0) + memcpy(r, ARRPTR(a), c * sizeof(int32)); + r[c] = elem; + return result; +} + +ArrayType * +intarray_concat_arrays(ArrayType *a, ArrayType *b) +{ + ArrayType *result; + int32 ac = ARRNELEMS(a); + int32 bc = ARRNELEMS(b); + + CHECKARRVALID(a); + CHECKARRVALID(b); + result = new_intArrayType(ac + bc); + if (ac) + memcpy(ARRPTR(result), ARRPTR(a), ac * sizeof(int32)); + if (bc) + memcpy(ARRPTR(result) + ac, ARRPTR(b), bc * sizeof(int32)); + return result; +} + +ArrayType * +int_to_intset(int32 elem) +{ + ArrayType *result; + int32 *aa; + + result = new_intArrayType(1); + aa = ARRPTR(result); + aa[0] = elem; + return result; +} + +int +compASC(const void *a, const void *b) +{ + if (*(const int32 *) a == *(const int32 *) b) + return 0; + return (*(const int32 *) a > *(const int32 *) b) ? 1 : -1; +} + +int +compDESC(const void *a, const void *b) +{ + if (*(const int32 *) a == *(const int32 *) b) + return 0; + return (*(const int32 *) a < *(const int32 *) b) ? 1 : -1; +} diff --git a/contrib/intarray/_intbig_gist.c b/contrib/intarray/_intbig_gist.c new file mode 100644 index 0000000..8c6c4b5 --- /dev/null +++ b/contrib/intarray/_intbig_gist.c @@ -0,0 +1,596 @@ +/* + * contrib/intarray/_intbig_gist.c + */ +#include "postgres.h" + +#include + +#include "_int.h" +#include "access/gist.h" +#include "access/reloptions.h" +#include "access/stratnum.h" +#include "port/pg_bitutils.h" + +#define GETENTRY(vec,pos) ((GISTTYPE *) DatumGetPointer((vec)->vector[(pos)].key)) +/* +** _intbig methods +*/ +PG_FUNCTION_INFO_V1(g_intbig_consistent); +PG_FUNCTION_INFO_V1(g_intbig_compress); +PG_FUNCTION_INFO_V1(g_intbig_decompress); +PG_FUNCTION_INFO_V1(g_intbig_penalty); +PG_FUNCTION_INFO_V1(g_intbig_picksplit); +PG_FUNCTION_INFO_V1(g_intbig_union); +PG_FUNCTION_INFO_V1(g_intbig_same); +PG_FUNCTION_INFO_V1(g_intbig_options); + +PG_FUNCTION_INFO_V1(_intbig_in); +PG_FUNCTION_INFO_V1(_intbig_out); + +Datum +_intbig_in(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type %s", "intbig_gkey"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +Datum +_intbig_out(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot display a value of type %s", "intbig_gkey"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +static GISTTYPE * +_intbig_alloc(bool allistrue, int siglen, BITVECP sign) +{ + int flag = allistrue ? ALLISTRUE : 0; + int size = CALCGTSIZE(flag, siglen); + GISTTYPE *res = (GISTTYPE *) palloc(size); + + SET_VARSIZE(res, size); + res->flag = flag; + + if (!allistrue) + { + if (sign) + memcpy(GETSIGN(res), sign, siglen); + else + memset(GETSIGN(res), 0, siglen); + } + + return res; +} + + +/********************************************************************* +** intbig functions +*********************************************************************/ +static bool +_intbig_overlap(GISTTYPE *a, ArrayType *b, int siglen) +{ + int num = ARRNELEMS(b); + int32 *ptr = ARRPTR(b); + + CHECKARRVALID(b); + + while (num--) + { + if (GETBIT(GETSIGN(a), HASHVAL(*ptr, siglen))) + return true; + ptr++; + } + + return false; +} + +static bool +_intbig_contains(GISTTYPE *a, ArrayType *b, int siglen) +{ + int num = ARRNELEMS(b); + int32 *ptr = ARRPTR(b); + + CHECKARRVALID(b); + + while (num--) + { + if (!GETBIT(GETSIGN(a), HASHVAL(*ptr, siglen))) + return false; + ptr++; + } + + return true; +} + +Datum +g_intbig_same(PG_FUNCTION_ARGS) +{ + GISTTYPE *a = (GISTTYPE *) PG_GETARG_POINTER(0); + GISTTYPE *b = (GISTTYPE *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + int siglen = GET_SIGLEN(); + + if (ISALLTRUE(a) && ISALLTRUE(b)) + *result = true; + else if (ISALLTRUE(a)) + *result = false; + else if (ISALLTRUE(b)) + *result = false; + else + { + int32 i; + BITVECP sa = GETSIGN(a), + sb = GETSIGN(b); + + *result = true; + LOOPBYTE(siglen) + { + if (sa[i] != sb[i]) + { + *result = false; + break; + } + } + } + PG_RETURN_POINTER(result); +} + +Datum +g_intbig_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int siglen = GET_SIGLEN(); + + if (entry->leafkey) + { + GISTENTRY *retval; + ArrayType *in = DatumGetArrayTypeP(entry->key); + int32 *ptr; + int num; + GISTTYPE *res = _intbig_alloc(false, siglen, NULL); + + CHECKARRVALID(in); + if (ARRISEMPTY(in)) + { + ptr = NULL; + num = 0; + } + else + { + ptr = ARRPTR(in); + num = ARRNELEMS(in); + } + + while (num--) + { + HASH(GETSIGN(res), *ptr, siglen); + ptr++; + } + + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(res), + entry->rel, entry->page, + entry->offset, false); + + PG_RETURN_POINTER(retval); + } + else if (!ISALLTRUE(DatumGetPointer(entry->key))) + { + GISTENTRY *retval; + int i; + BITVECP sign = GETSIGN(DatumGetPointer(entry->key)); + GISTTYPE *res; + + LOOPBYTE(siglen) + { + if ((sign[i] & 0xff) != 0xff) + PG_RETURN_POINTER(entry); + } + + res = _intbig_alloc(true, siglen, sign); + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(res), + entry->rel, entry->page, + entry->offset, false); + + PG_RETURN_POINTER(retval); + } + + PG_RETURN_POINTER(entry); +} + + +static int32 +sizebitvec(BITVECP sign, int siglen) +{ + return pg_popcount(sign, siglen); +} + +static int +hemdistsign(BITVECP a, BITVECP b, int siglen) +{ + int i, + diff, + dist = 0; + + LOOPBYTE(siglen) + { + diff = (unsigned char) (a[i] ^ b[i]); + /* Using the popcount functions here isn't likely to win */ + dist += pg_number_of_ones[diff]; + } + return dist; +} + +static int +hemdist(GISTTYPE *a, GISTTYPE *b, int siglen) +{ + if (ISALLTRUE(a)) + { + if (ISALLTRUE(b)) + return 0; + else + return SIGLENBIT(siglen) - sizebitvec(GETSIGN(b), siglen); + } + else if (ISALLTRUE(b)) + return SIGLENBIT(siglen) - sizebitvec(GETSIGN(a), siglen); + + return hemdistsign(GETSIGN(a), GETSIGN(b), siglen); +} + +Datum +g_intbig_decompress(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(PG_GETARG_DATUM(0)); +} + +static int32 +unionkey(BITVECP sbase, GISTTYPE *add, int siglen) +{ + int32 i; + BITVECP sadd = GETSIGN(add); + + if (ISALLTRUE(add)) + return 1; + LOOPBYTE(siglen) + sbase[i] |= sadd[i]; + return 0; +} + +Datum +g_intbig_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int *size = (int *) PG_GETARG_POINTER(1); + int siglen = GET_SIGLEN(); + int32 i; + GISTTYPE *result = _intbig_alloc(false, siglen, NULL); + BITVECP base = GETSIGN(result); + + for (i = 0; i < entryvec->n; i++) + { + if (unionkey(base, GETENTRY(entryvec, i), siglen)) + { + result->flag |= ALLISTRUE; + SET_VARSIZE(result, CALCGTSIZE(ALLISTRUE, siglen)); + break; + } + } + + *size = VARSIZE(result); + + PG_RETURN_POINTER(result); +} + +Datum +g_intbig_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); /* always ISSIGNKEY */ + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *penalty = (float *) PG_GETARG_POINTER(2); + GISTTYPE *origval = (GISTTYPE *) DatumGetPointer(origentry->key); + GISTTYPE *newval = (GISTTYPE *) DatumGetPointer(newentry->key); + int siglen = GET_SIGLEN(); + + *penalty = hemdist(origval, newval, siglen); + PG_RETURN_POINTER(penalty); +} + + +typedef struct +{ + OffsetNumber pos; + int32 cost; +} SPLITCOST; + +static int +comparecost(const void *a, const void *b) +{ + return ((const SPLITCOST *) a)->cost - ((const SPLITCOST *) b)->cost; +} + + +Datum +g_intbig_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + int siglen = GET_SIGLEN(); + OffsetNumber k, + j; + GISTTYPE *datum_l, + *datum_r; + BITVECP union_l, + union_r; + int32 size_alpha, + size_beta; + int32 size_waste, + waste = -1; + int32 nbytes; + OffsetNumber seed_1 = 0, + seed_2 = 0; + OffsetNumber *left, + *right; + OffsetNumber maxoff; + BITVECP ptr; + int i; + SPLITCOST *costvector; + GISTTYPE *_k, + *_j; + + maxoff = entryvec->n - 2; + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k)) + { + _k = GETENTRY(entryvec, k); + for (j = OffsetNumberNext(k); j <= maxoff; j = OffsetNumberNext(j)) + { + size_waste = hemdist(_k, GETENTRY(entryvec, j), siglen); + if (size_waste > waste) + { + waste = size_waste; + seed_1 = k; + seed_2 = j; + } + } + } + + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + + if (seed_1 == 0 || seed_2 == 0) + { + seed_1 = 1; + seed_2 = 2; + } + + /* form initial .. */ + datum_l = _intbig_alloc(ISALLTRUE(GETENTRY(entryvec, seed_1)), siglen, + GETSIGN(GETENTRY(entryvec, seed_1))); + datum_r = _intbig_alloc(ISALLTRUE(GETENTRY(entryvec, seed_2)), siglen, + GETSIGN(GETENTRY(entryvec, seed_2))); + + maxoff = OffsetNumberNext(maxoff); + /* sort before ... */ + costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff); + for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) + { + costvector[j - 1].pos = j; + _j = GETENTRY(entryvec, j); + size_alpha = hemdist(datum_l, _j, siglen); + size_beta = hemdist(datum_r, _j, siglen); + costvector[j - 1].cost = abs(size_alpha - size_beta); + } + qsort(costvector, maxoff, sizeof(SPLITCOST), comparecost); + + union_l = GETSIGN(datum_l); + union_r = GETSIGN(datum_r); + + for (k = 0; k < maxoff; k++) + { + j = costvector[k].pos; + if (j == seed_1) + { + *left++ = j; + v->spl_nleft++; + continue; + } + else if (j == seed_2) + { + *right++ = j; + v->spl_nright++; + continue; + } + _j = GETENTRY(entryvec, j); + size_alpha = hemdist(datum_l, _j, siglen); + size_beta = hemdist(datum_r, _j, siglen); + + if (size_alpha < size_beta + WISH_F(v->spl_nleft, v->spl_nright, 0.00001)) + { + if (ISALLTRUE(datum_l) || ISALLTRUE(_j)) + { + if (!ISALLTRUE(datum_l)) + memset(union_l, 0xff, siglen); + } + else + { + ptr = GETSIGN(_j); + LOOPBYTE(siglen) + union_l[i] |= ptr[i]; + } + *left++ = j; + v->spl_nleft++; + } + else + { + if (ISALLTRUE(datum_r) || ISALLTRUE(_j)) + { + if (!ISALLTRUE(datum_r)) + memset(union_r, 0xff, siglen); + } + else + { + ptr = GETSIGN(_j); + LOOPBYTE(siglen) + union_r[i] |= ptr[i]; + } + *right++ = j; + v->spl_nright++; + } + } + + *right = *left = FirstOffsetNumber; + pfree(costvector); + + v->spl_ldatum = PointerGetDatum(datum_l); + v->spl_rdatum = PointerGetDatum(datum_r); + + PG_RETURN_POINTER(v); +} + +Datum +g_intbig_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + ArrayType *query = PG_GETARG_ARRAYTYPE_P(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + int siglen = GET_SIGLEN(); + bool retval; + + /* All cases served by this function are inexact */ + *recheck = true; + + if (ISALLTRUE(DatumGetPointer(entry->key))) + PG_RETURN_BOOL(true); + + if (strategy == BooleanSearchStrategy) + { + retval = signconsistent((QUERYTYPE *) query, + GETSIGN(DatumGetPointer(entry->key)), + siglen, + false); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(retval); + } + + CHECKARRVALID(query); + + switch (strategy) + { + case RTOverlapStrategyNumber: + retval = _intbig_overlap((GISTTYPE *) DatumGetPointer(entry->key), + query, siglen); + break; + case RTSameStrategyNumber: + if (GIST_LEAF(entry)) + { + int i, + num = ARRNELEMS(query); + int32 *ptr = ARRPTR(query); + BITVECP dq = palloc0(siglen), + de; + + while (num--) + { + HASH(dq, *ptr, siglen); + ptr++; + } + + de = GETSIGN((GISTTYPE *) DatumGetPointer(entry->key)); + retval = true; + LOOPBYTE(siglen) + { + if (de[i] != dq[i]) + { + retval = false; + break; + } + } + + pfree(dq); + } + else + retval = _intbig_contains((GISTTYPE *) DatumGetPointer(entry->key), + query, siglen); + break; + case RTContainsStrategyNumber: + case RTOldContainsStrategyNumber: + retval = _intbig_contains((GISTTYPE *) DatumGetPointer(entry->key), + query, siglen); + break; + case RTContainedByStrategyNumber: + case RTOldContainedByStrategyNumber: + + /* + * This code is unreachable as of intarray 1.4, because the <@ + * operator has been removed from the opclass. We keep it for now + * to support older versions of the SQL definitions. + */ + if (GIST_LEAF(entry)) + { + int i, + num = ARRNELEMS(query); + int32 *ptr = ARRPTR(query); + BITVECP dq = palloc0(siglen), + de; + + while (num--) + { + HASH(dq, *ptr, siglen); + ptr++; + } + + de = GETSIGN((GISTTYPE *) DatumGetPointer(entry->key)); + retval = true; + LOOPBYTE(siglen) + { + if (de[i] & ~dq[i]) + { + retval = false; + break; + } + } + } + else + { + /* + * Unfortunately, because empty arrays could be anywhere in + * the index, we must search the whole tree. + */ + retval = true; + } + break; + default: + retval = false; + } + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(retval); +} + +Datum +g_intbig_options(PG_FUNCTION_ARGS) +{ + local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0); + + init_local_reloptions(relopts, sizeof(GISTIntArrayBigOptions)); + add_local_int_reloption(relopts, "siglen", + "signature length in bytes", + SIGLEN_DEFAULT, 1, SIGLEN_MAX, + offsetof(GISTIntArrayBigOptions, siglen)); + + PG_RETURN_VOID(); +} diff --git a/contrib/intarray/bench/bench.pl b/contrib/intarray/bench/bench.pl new file mode 100755 index 0000000..0676549 --- /dev/null +++ b/contrib/intarray/bench/bench.pl @@ -0,0 +1,140 @@ +#!/usr/bin/perl + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +use strict; +use warnings; + +# make sure we are in a sane environment. +use DBI(); +use DBD::Pg(); +use Time::HiRes qw( usleep ualarm gettimeofday tv_interval ); +use Getopt::Std; + +my %opt; +getopts('d:b:s:veorauc', \%opt); + +if (!(scalar %opt && defined $opt{s})) +{ + print <connect('DBI:Pg:dbname=' . $opt{d}); + +my %table; +my @where; + +$table{message} = 1; + +if ($opt{a}) +{ + if ($opt{r}) + { + push @where, "message.sections @ '{$opt{s}}'"; + } + else + { + foreach my $sid (split(/[,\s]+/, $opt{s})) + { + push @where, "message.mid = msp$sid.mid"; + push @where, "msp$sid.sid = $sid"; + $table{"message_section_map msp$sid"} = 1; + } + } +} +else +{ + if ($opt{r}) + { + push @where, "message.sections && '{$opt{s}}'"; + } + else + { + $table{message_section_map} = 1; + push @where, "message.mid = message_section_map.mid"; + push @where, "message_section_map.sid in ($opt{s})"; + } +} + +my $outf; +if ($opt{c}) +{ + $outf = + ($opt{u}) ? 'count( distinct message.mid )' : 'count( message.mid )'; +} +else +{ + $outf = ($opt{u}) ? 'distinct( message.mid )' : 'message.mid'; +} +my $sql = + "select $outf from " + . join(', ', keys %table) + . " where " + . join(' AND ', @where) . ';'; + +if ($opt{v}) +{ + print "$sql\n"; +} + +if ($opt{e}) +{ + my @plan = + map { "$_->[0]\n" } @{ $dbi->selectall_arrayref("explain $sql") }; + print @plan; +} + +my $t0 = [gettimeofday]; +my $count = 0; +my $b = $opt{b}; +$b ||= 1; +my @a; +foreach (1 .. $b) +{ + @a = exec_sql($dbi, $sql); + $count = $#a; +} +my $elapsed = tv_interval($t0, [gettimeofday]); +if ($opt{o}) +{ + foreach (@a) + { + print "$_->{mid}\t$_->{sections}\n"; + } +} +print sprintf( + "total: %.02f sec; number: %d; for one: %.03f sec; found %d docs\n", + $elapsed, $b, $elapsed / $b, + $count + 1); +$dbi->disconnect; + +sub exec_sql +{ + my ($dbi, $sql, @keys) = @_; + my $sth = $dbi->prepare($sql) || die; + $sth->execute(@keys) || die; + my $r; + my @row; + while (defined($r = $sth->fetchrow_hashref)) + { + push @row, $r; + } + $sth->finish; + return @row; +} diff --git a/contrib/intarray/bench/create_test.pl b/contrib/intarray/bench/create_test.pl new file mode 100755 index 0000000..6efe915 --- /dev/null +++ b/contrib/intarray/bench/create_test.pl @@ -0,0 +1,91 @@ +#!/usr/bin/perl + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# contrib/intarray/bench/create_test.pl + +use strict; +use warnings; + +print <', "message.tmp") || die; +open(my $map, '>', "message_section_map.tmp") || die; + +srand(1); + +#foreach my $i ( 1..1778 ) { +#foreach my $i ( 1..3443 ) { +#foreach my $i ( 1..5000 ) { +#foreach my $i ( 1..29362 ) { +#foreach my $i ( 1..33331 ) { +#foreach my $i ( 1..83268 ) { +foreach my $i (1 .. 200000) +{ + my @sect; + if (rand() < 0.7) + { + $sect[0] = int((rand()**4) * 100); + } + else + { + my %hash; + @sect = + grep { $hash{$_}++; $hash{$_} <= 1 } + map { int((rand()**4) * 100) } 0 .. (int(rand() * 5)); + } + if ($#sect < 0 || rand() < 0.1) + { + print $msg "$i\t\\N\n"; + } + else + { + print $msg "$i\t{" . join(',', @sect) . "}\n"; + print $map "$i\t$_\n" foreach @sect; + } +} +close $map; +close $msg; + +copytable('message'); +copytable('message_section_map'); + +print <) { print; } + close $fff; + print "\\.\n"; + return; +} diff --git a/contrib/intarray/data/test__int.data b/contrib/intarray/data/test__int.data new file mode 100644 index 0000000..0a7fac3 --- /dev/null +++ b/contrib/intarray/data/test__int.data @@ -0,0 +1,7001 @@ +{18,31,54,95} +{23,50,13,9,39} +{99,54,77} +{79,83,16,63,32} +{52,41,61,79,94,87} +{76,59,39,36,21} +{} +{41,79,76,96,3} +{25,59,5,96,32} +{92,58,12,57} +{24,48,41,88} +{39,5,17} +{10,41,78,25,35} +{31,89,4} +{68,74,94} +{97,78,44,68,81,16} +{87,76} +{30,81} +{72,20,99,26} +{87,90,98,40,44} +{24,99,66,61} +{79,8,48,16} +{62,99,48,80,75,39} +{10,60,35,15} +{45,71,10,97,56} +{64,79,19,31} +{30,57,42,31,45} +{61,42,14,26} +{12,38,65,36,56,36} +{17,62,18,56} +{84,85,90,60,55,17} +{27,11,82,20,43} +{14,27,18,48,39,51} +{53,13,52} +{56,35,81,60,27} +{79,89,89,7} +{65,17,31,17,29,85} +{21,3} +{53,55,16,83,4} +{62,3,63} +{73,40,99} +{23,80} +{2,74,42,37,21} +{12,16} +{80,60} +{19,62,34} +{38,19,31,6,15,2} +{63,96,64,4,36,15} +{9,3} +{91,87,15,18,7,66} +{17,10} +{77,96} +{11,43,31,2,89} +{17,77,89,50} +{24,6,61,88,51} +{61,50,59,90,5,89} +{58,1,39,48} +{78,36,70,92} +{43,3,22,95,51} +{} +{88,64,25,64,86} +{34,6,49,90,25} +{86,35,13,22} +{21,44,83} +{42,88,72,65,59,96} +{36,33,1,98} +{16,54} +{35,16,44} +{73,23,20} +{84,25,1,52,35} +{27,36,54,87,31} +{38,47,83,3} +{64,13} +{65,84,85,16,22} +{57,9,39,73} +{89,11,67,55,73} +{78,39,84,63,62,45} +{50,63,8} +{} +{96,36,58,65,96} +{59,86,41,30} +{90,60,39,47,19} +{70,100,73,99} +{} +{85,14,39} +{76,53} +{96,38,52,13,87,85} +{97,51,15,30,53,87} +{30,59,9,40,13} +{31,91,68,79} +{37,56,39,78,75} +{82,2,47} +{33,25,45,40} +{51,21,92,20,18,76} +{84,93,36,95,34,69} +{66,25,5,40} +{77,6,57,42} +{} +{88,81,85,37,12} +{56,73,38} +{70,70,6,19} +{82,54,91} +{75,8} +{45,33,64,90,95} +{8,71,66,12} +{56,26,68,94} +{70,77,4,96,62,83} +{23,87} +{34,34,4,33} +{28,84} +{78,75,77} +{88,53} +{27,38} +{2,2,82} +{30,52,88,61,33} +{29,72,94,68} +{85,72} +{88,4} +{63,90,43,66,24,33} +{88,48,47} +{3,11,98,37,61} +{45,65,63,15,38} +{79,45,56,94} +{56,74,78,19,76} +{24,81,64,13,100} +{93,27,63,71,27,3} +{74,13,85,86,32,60} +{98,40,63,13} +{41,95,19,93,17,84} +{90,28,100,100,19,2} +{35,15,54} +{29,81,77} +{54,64,63,12,18} +{38,43,85,21,35} +{84,28,27,4,80,27} +{80,77,55,98} +{13,71,48,55,89,38} +{58,43,27,5,57} +{5,33,96,6} +{73,93,87,69,100,24} +{58,96,38,85,55,51} +{37,30,88,4,8,59} +{24,68,43,48,18,84} +{23,100,82,30,42} +{23,36,16,99,27} +{41,75} +{66,41,10,37,16,6} +{54,49,60} +{4,56,44,72,40} +{71,96,67,100,59} +{7,41} +{8,3,27} +{38,69,47,68,5,24} +{43,100,59,62} +{92,14,34,5,71,48} +{72,5,91,29,99,36} +{62,71,37,80,62,50} +{32,45,17} +{89,68} +{52,17,55} +{21,47,15,92} +{36,100,5} +{14,76,59,11,15} +{59,72} +{37,55,89,49} +{87,79,96,20,93} +{6,44} +{32,46,25} +{27,47,76,4,54} +{2,16} +{90,36} +{11,19,27,79} +{54,4} +{72,88} +{14,85,71,69,5,22} +{31,48} +{28,35,18} +{77,55,100,73,57,62} +{} +{14,59,53} +{98,3} +{13,56} +{26,61,88,54,88,33} +{70,12} +{55,16,15,42,76} +{13,75} +{97,38,82,51,86,53} +{41,76,39,84,32} +{94,66,47} +{55,28} +{} +{94,65,59,20} +{55,50,56,14,58} +{14,94,52,25,69,95} +{20,96} +{37,38} +{26,35,9,98,74} +{11,9,41,79} +{36,57,87,69,92,89} +{11,39,60,4,47,3} +{97,5} +{16,58,38,98,42} +{46,69} +{35,54} +{36,79,54} +{} +{63,78} +{12,86,52,29,60,30} +{29,27,58,86,42,62} +{42,12,60} +{90,93,85,29} +{16,8,45} +{29,33,85} +{32,14,6,47,74} +{14,85,14,26,3} +{46,71,10,16} +{30,63} +{} +{91,30,56} +{46,36,68,91,36,88} +{24,61} +{66,21,80,14} +{43,63,50,21,11} +{38,46,18,51} +{38,28,70} +{17,41,76,1,30} +{47,63} +{56,80,85,1,7,97} +{75,5,79,32} +{5,17,66,51,68} +{6,83,2} +{25,40,79,84} +{58,38,12,68} +{55,86,20,67,27} +{58,64} +{14,51} +{12,86,57,68} +{61,91,65,3,83,68} +{40,31,82,21} +\N +{24,64,35,32} +{32,83,18,27,43,32} +{50,83} +{94,84,58,3,25,79} +{66,2,27,36,24} +{71,34} +{17,57} +{22,40,49,50,10} +{79,62,94,78} +{92,79,24,72} +{23,41} +{69,60,77,70,18,48} +{39,45,91,85} +{27,43,22,21,85} +{84,51,96,7,18} +{100,38,69,93,66,39} +{73,42,35,15,69,98} +{100,17,37,15,40} +{1,91,2,17,90,48} +{18,12,52,24} +{39,43,89} +{16,13,88} +{69,8,75} +{34,91,54,81} +{37,68,89,1,56} +{81,83,39,36,14} +{12,15,2} +{14,16,88,43} +{59,12} +{1,62,21,94} +{29,43,70,52,93} +{29,36,56,78} +{91,56,86,89,53} +{14,83,39,94} +{29,58,72,4,45} +{76,56,84,28,58} +{4,52,6,88,43,17} +{21,1,35,62,77,6} +{78,74} +{1,20,93,43} +\N +{30,100,35,94,74,64} +{81,3,21,4} +{9,19,33} +{28,62,40,64,26} +{69,72,26,30,90} +{52,70,78,43} +{91,58,33,22,92,26} +{98,36,96,94,66} +{86,43,82} +{93,52,4,58,51} +\N +{49,61,80,79,90} +{50,81,72} +{57,29} +{54,31,36} +{52,31,6,48,2} +{4,51,37,83,17} +{60,20,94,82,18} +{52,64,26,81,69,61} +{39,8,22,2,8} +{31,25,95,99} +{11,72,30,95,20,28} +{78,87} +{21,40,98,41,73,33} +{67,88,42,62,11,47} +{85,1} +{4,68,100,72,24} +{82,43} +{97,55,47,52} +{51,52} +{20,21} +{69,46,34,59,54,61} +{9,31,43} +{68,20} +{73,63} +{71,12,93,8,48,10} +{44,46,42,91,21} +{98,52} +{45,60} +{95,38,30,3} +{27,77,2,46,53,18} +{99,5} +{79,33,34,48,82} +{3,29,82,72,35} +{73,75,83} +{25,43,37,26} +\N +{51,95,40} +{18,23,10,90,15,20} +{85,66} +{25,76,22,87,88,18} +{92,4} +{27,51} +{25,77,12,37} +{44,52,69,39,21,63} +{94,30,74,36} +{60,18} +{62,88,94,93,26} +{5,72,96,25} +{99,1,85,98,85,70} +{33,21,37,19} +{44,78} +{47,2,73,32,3} +{91,35,10,81} +{80,64,7,45,84} +{86,16,96,8,88} +{32,29,84,81,30,8} +{51,28,6,16} +{88,51,50,54,56,96} +{79,19,41,40} +{40,26,10,26,2} +{60,34,3,29} +{68,80,70,56} +{60,23,39} +{50,69,6,71,70,25} +{98,53,94,14,45,11} +{98,39,64,89,98,32} +\N +{45,5,15,23,41,63} +{54,31,55,58,32} +{36,56} +{38,78,65,4,75,38} +\N +{40,6,93,40,13,59} +{42,50,10,65,96} +{6,94,49} +{63,44,36,55} +{40,79} +{39,75,27} +{8,31} +{81,75} +{99,82,85,34,24,89} +{86,82,20} +{63,96} +{47,83,29} +{70,46,48} +{44,11} +{94,19,84,79,77,22} +{68,47,100,48,65,77} +\N +{76,12,86,58} +{13,14,79,61,12} +{68,65,16,93,89} +{95,18,29,89,92,43} +{19,12,50,47} +{82,93,85} +{71,40,85} +{95,96,100,86} +{2,40,71,36,25} +{11,95,25} +{79,46,41,35,39} +\N +\N +{88,29} +{54,14,94,88} +{59,67,81,41} +{46,68,78,56,47,30} +{5,76,87} +{23,89,47,46} +{47,98,14,31,1,60} +{32,14,96,61,37} +{79,66,93} +{98,1,77,44} +{21,68,2,31,17} +{94,23,15} +{48,47,57,94,49,71} +{54,3} +{99,40,81,86,81} +{85,12,98,81,5} +{60,41,21} +{38,82,55,41,96} +{11,98,12,69,93} +{11,70,66,44} +{23,92,80} +{10,8,43,97} +{17,30} +{78,56,58} +{84,87,84} +{12,32,7,58,47,48} +{29,46} +{87,34} +{59,30,72,85,71} +{67,48,83,98} +{35,10,73,71,1,77} +{21,51,16,60,64,12} +{36,61} +{54,98} +{44,74,84} +{83,14} +{71,52,48,48,15,92} +{79,78,98,35} +{52,29,47,86,96} +{10,37} +{21,25} +{57,22,28,30,97,47} +{15,28} +{88,24,98,45,65} +{78,42} +{36,70} +{40,48} +{72,3,78,69,57,33} +\N +{21,96,16,21,75,23} +{55,5,72,45} +{99,2,72,29} +{48,17} +{84,84,40,1,59} +{34,11} +{34,80,45,31} +{56,82,25,65,22,64} +{10,4,55} +{74,67,42,74,80} +{84,22,42,6,87,30} +{6,51,89,2,84,78} +{19,95,93,87,8} +{45,84,25} +{7,12,16,92} +{89,82,16} +{22,64} +{16,31,49,48,45,14} +{69,64,19,14,39,8} +{40,96,26,48,65} +{17,45,4,57} +{73,8} +{85,89,1,15,74,51} +\N +{57,89} +{25,12,55} +{39,62,35} +{85,88,71,98,83} +{64,63,75,72} +{100,40,38,1} +{2,44} +{13,46,59,43} +{87,9,93,50} +{77,7,11,30} +{61,11,18} +{19,25,68,83} +{67,25} +{54,18,85} +{96,81,38,11,3} +{87,32,47,79,62,56} +{42,49} +{41,65,24,13,79,75} +{85,32,96} +\N +{3,63,47,84,67,13} +{53,57,59,61} +{95,27,8,89,35} +{76,78,76,76,14,37} +{31,62,65} +{97,57,60,80} +{18,81,93,67} +{8,10} +{65,25} +{68,1,62,63,64,88} +{27,56,74} +{29,61,78,40} +{54,72} +{96,30,71,21,99} +{67,11,67} +{26,65,31} +{89,90,89,68} +{56,39,63,39} +{50,67} +{72,100,24,84,9} +{29,57,65,37,3} +{72,75,79,30} +{78,44,87,67} +{100,19} +{35,60,82} +{16,83,69,38} +{29,98,13,60} +{42,60,87} +{18,67,60} +{31,77,50} +{3,22,40,59,7} +{82,80} +\N +{32,92,70,30,18,35} +{48,38,92,82} +{10,92,66,59} +{4,67,42,21,71} +{27,88,20,21,9} +{46,22,27,85,36} +{42,55,36} +{24,2,96} +{96,48,40,48,52} +{15,5,90,10,68,20} +{30,2,67,92,96,63} +{16,82,87,26} +{88,98,76,29} +{29,11,94,23} +{58,20} +{52,18,55,73} +{20,81,52,19,37} +{93,21,97} +{2,77} +{46,91,80,48,71,20} +{87,7,93} +{68,77,61} +{59,33,52} +{67,62,89,2,62,90} +{30,82,72,44} +{72,18,60,38} +{11,14,59} +{74,65,54,58,67,66} +{74,56,40,73,50,66} +{42,17,56,59,53,19} +{75,25,76,9,72,50} +{14,57} +{61,47} +{90,11,72,13} +{52,27} +{80,84,53,55,98} +{16,26,55,17,79,96} +{42,73,77} +{6,84,67,54,96} +{99,48,99,63,73,77} +{5,41,72,5,88,81} +{19,20,20} +{21,89,55,44} +{82,67,11,64,61,5} +{44,34,8,62,53} +{75,53,66,36,100} +{46,65,6,70,4} +{84,10,56,35,18} +{65,60} +{88,56,27,11} +{10,9,97} +{97,49,100,100,76,32} +{2,98,57} +{47,57,84,74,79} +{80,9,24} +{96,33,86,28,19} +{43,76} +{46,14,55,92} +{60,69,66,62,22} +{45,85} +{45,9,36,13,45,1} +{24,49,8,37,66,64} +{98,53,96,47,2} +{36,44,32,4} +{77,36,78,51,63} +{82,36} +\N +{54,55,33,45,69,18} +{82,93} +{65,59,2,62,10,25} +{75,70,76,69,7,23} +{10,34,67,85} +{94,66,28,40,64,41} +{35,73,64,28,45,68} +{75,2} +{58,49,4,87,19} +{91,99,11,66,75,70} +{26,64} +\N +{13,51,18} +{39,33,21,18} +{27,50,82,2,3,71} +{51,89,44,53} +{88,91,34} +{45,96,27,12,51,52} +{31,96} +{2,9,54,89} +\N +{57,99} +{87,84,70,7,98,42} +{32,80} +{57,64,28} +{24,39,76,4,30} +{59,38,15,45,47,28} +{71,20,37,1} +{72,59} +{7,44} +{50,37,18,1,58,40} +{13,18,21,56} +{72,3,26,74,91} +{60,22,71,49} +{55,82,61,8,48,66} +{28,22,75,41,52} +{51,63,27,41,16} +{59,89,40,85,86} +{12,1} +{52,11,6} +{37,10,43,88,15,7} +{14,94,81} +{34,56,57,4} +{81,43,11,88,74,76} +\N +{67,10,50,79,70,35} +{14,51} +{49,50,23,84} +{51,41,57,100,19,14} +{31,55,40,96} +{8,42,33} +{83,34,1} +{56,80,22,93} +\N +{8,77,91} +{58,39} +{55,30,74} +{50,22,63,73} +{80,19,67,70,18} +{7,99,45,23,59,78} +{36,97,10,33,22,45} +{43,78,90} +\N +{1,68} +{63,95,54} +{5,67,61,37,89} +{32,97,2,56} +{83,31,6,80,63} +\N +{34,15,30,40,16} +{13,43,6} +{35,86,31} +{45,59,4,95,26} +{63,48,25} +{56,97,89,45,87,21} +{42,81,69} +{49,99,87} +{81,21,15,36,70,2} +{93,41,53} +{54,71,82} +{88,90,51} +{100,35,18} +{88,81} +{76,16,87,14} +{16,83,81,44} +{16,53,100,91} +{55,75,92} +{27,97,76,88,66} +{14,100,95,95} +{95,84,93,29,67} +{32,10} +{82,12,51} +{40,6,18,14,29,70} +{3,100,81} +{83,69} +{35,63,31,15} +{5,100,81,54,37,78} +{99,76,33} +{88,85,16} +{46,20,15,10,6,90} +{53,15,75,76,94} +{5,76} +{16,7,21,70} +{3,84,15} +{29,58,73,91} +{82,39,64} +{49,66,83,76} +{79,49,19,67,18,76} +{9,56,41} +{12,22,19} +{62,54} +{20,73,40} +{34,53,58,68,96} +{97,14,61,63} +{38,55,90,63} +{83,78,81,29,12,46} +{96,97,40,89,10} +{67,33,19,19,74,47} +{78,31} +{92,74,93} +{59,54,90,52,29,87} +{92,39,55,89,81,21} +{20,85,64} +{13,97} +{88,18,85,24,54,90} +{67,51,47} +{27,29,90} +{48,27,7,92} +{100,37,24,41,68,66} +{45,7,100,83,51} +{34,10} +{60,36,44} +{55,46,4} +{86,64} +{61,77,98,64} +{14,82,14,50,1} +\N +{53,31} +{64,43,35,44,98,75} +{98,15,52,58,76} +{55,94,92,40,80} +{1,14,100,42,45,74} +{13,90,84,97,18,92} +\N +{13,91} +{67,33,15} +{18,96,38} +{95,70,34,100} +{17,29,64,32} +{19,14,83,69,60,99} +{69,29,64,61,45,17} +{78,48,24} +{40,60,61,93,17} +{19,89,22,71} +{48,8,13,11,56} +{75,18,77,100} +{29,78} +{51,92,97,31} +{83,5,2,97,68,69} +{39,86,86,94,41} +{66,21,27} +{30,84,11,60} +{50,61,28,46,38,45} +{12,59,66,80,15,64} +{69,22} +{30,54,58,99} +{14,28,80,22} +{44,31,14,61,83,72} +{55,53,78,91,76,55} +{43,3,90,22,7} +{51,34,24} +{3,99,5,72,82} +{95,38,61} +{22,8} +{78,40,93,65,18,26} +{21,17,19,8,89} +\N +\N +{94,88,27} +{49,45} +{67,24,64,86,18,1} +{5,33,18,84,51} +{15,71,89,48,94,81} +{71,69} +{98,63,73,64} +{14,75,12} +{47,42,88,13} +{35,51,60} +{63,41} +{73,11,66,99,8} +\N +{2,17,6,44,97} +{95,24} +{2,13,35,21} +{76,29} +{81,37,21} +{23,63,27,53} +{70,66,58,27,4} +{69,62,22} +{62,96,44} +{68,87,99} +{51,40,81,52,93} +{81,11,45,92,22,21} +{5,39,46} +{44,7} +{14,63,62,9,12} +{9,19,90,72,51} +{70,61,24,36} +\N +{29,19,3,30} +{76,86,28,58,38} +{59,27} +{9,65,65,10,37,6} +{89,51,50,23} +{65,2} +{33,51} +{25,55,69,55,1,78} +{76,71,93,46,23} +{70,30,50,11,2,89} +{74,39} +{4,29,22,80,15,23} +{16,30,69,76,61,67} +{43,34,4,70,36} +{59,32,25,93,32,98} +{64,4} +{52,33,47} +{31,49,7,62,7,95} +{44,69,12,45,34,8} +{81,37,83,35,3} +{24,74,16,89,94,27} +{79,71,72,49,88,35} +{17,96,72,87,48} +{81,18,50} +{11,19,70} +{42,95,42,58,90} +{27,65,83,86,33} +{55,7} +{43,55,92,79} +{97,55} +{85,25} +{93,42,69,44,26,78} +{2,40,46,19,84} +{8,42,16,26,87} +{36,8,42} +{31,47,61,44,13} +{85,97,47} +{27,30,71,88,15,100} +{69,27,4,19} +{3,52,31,62,98} +{64,86} +{91,6} +{76,40} +{57,77,7,40} +{71,34,48,53,37} +{36,72} +{61,99,53,2,31,6} +{86,15} +{52,93,59,51} +{57,27,52} +{48,67,31,69} +{34,90,37,73,60,83} +{71,24,49,59} +{93,71,90} +{77,31,77} +{47,40,32,20} +{97,40,63,80,44} +{88,55,10,40} +{86,36,40,72,38,9} +{31,97} +{56,19,55,62,60} +{53,95} +{33,36} +{50,12,55,42,96,100} +{41,17,100,76} +{65,1,61,69,64,21} +{90,92} +\N +{74,42,86} +{2,4} +{99,78,5,92,1,61} +{1,69} +{80,73,60,31} +\N +{10,25,13} +{50,34,75} +{12,90,6,36,42} +{23,54,46} +{67,28,66,87} +{8,88,88,51,55,32} +{15,19,24,91,75} +{80,16,70} +{41,7,90,37} +{97,57,32,21} +{54,74,29,12,55,78} +{60,76,37,92,44,73} +{1,56,14} +{40,79} +{97,1,30,78,56} +{36,25,61} +{33,3,51,30,38} +{2,94,19,15} +{7,38,72} +{96,18,3} +{18,95,15,62,74,53} +{59,61} +{18,66,66,65,4,33} +{49,83,10} +{17,52,90,39,61,87} +{38,92,55,26} +{8,43} +{77,51,68,23,47} +{27,65,24,43,88,73} +{54,34,30,2,19,62} +{12,36,81,24,66,8} +{38,91,90,99,84} +{51,55,94,97,91,15} +{50,42,20,22} +{70,4,22} +{64,26} +{56,86,16,21,31,61} +{7,19,86,49,10,53} +{81,16,74} +{95,9,11,46,47} +{34,23,16} +{94,38,19,4,4} +{39,79} +{41,3,62} +{84,67,53,90,46} +{17,46,23} +{62,1,5,58,52,1} +{23,83,80,62,19} +{99,61,77} +{51,95,48,96,75} +{39,2,6,95,43,36} +{69,9,59} +{62,97,31} +{75,96} +{33,29,35,13,94,78} +{28,71,16,99} +{72,86,25} +{5,28,15,33} +\N +{13,13,52,20} +{58,98,83,85,81} +{13,75,42} +{7,91,3,83,82,37} +{72,91} +{10,67,61} +\N +{43,86,76} +{36,62} +{64,56} +{63,13,22,24} +{76,49,38,23,9,8} +\N +{92,58,24,19,96,90} +{24,37,76,37,1,95} +{91,9} +{46,35,48,37,91,76} +{72,21,6} +{30,80,39,90,89,18} +{83,30,67,17} +{43,36,46,43} +{4,31,34,36,33,48} +\N +{16,49} +{75,56,8,91,4} +{92,80} +{74,72,68,89,7,82} +{79,17,26} +{14,15,28,72,58} +{42,21,9} +{71,39,98,98,61} +{68,63,23,74,74,48} +{91,80,22,100,57,30} +{63,60} +{90,9,10,67,89,14} +{53,93} +{75,49,34,30,38} +{2,43} +{32,4,24,48,23,31} +{45,24,31,15,51} +{65,62,21} +{83,50} +{10,90,98,86,87,1} +{63,2,9,17,30} +\N +{77,46,60} +{49,39} +{37,86,4,63} +{33,28,37,33} +{4,88,80,14,47,45} +{90,64,17,65} +{60,90,12} +{7,2,38,33} +\N +{39,90,7} +{89,32} +{27,47,63,31} +{54,10,10,73,84,87} +{55,58,25,87} +{41,24} +{71,26,8,31} +{74,19,33,81,74} +{47,58} +{44,16,22,59} +{2,10,97,16,25} +{1,98,3,41,6,80} +{12,13} +{3,50,61,85} +{54,5,44,97,71,86} +{54,72,94} +{59,13,28,79} +{73,68,7,13} +{90,49,63,45} +{95,47,84} +{31,79,98,22} +\N +{13,15,83,89,87,20} +{1,58,87} +{15,21,39} +{93,27} +{40,81,13,31} +{29,52} +{28,48,36,41} +\N +{71,23,89} +{29,59,31,45,35} +{49,83,24,19,44,26} +{41,61,36,34,38,88} +{66,17,18,9} +{55,38,93,33} +{84,42,71,15,12} +{11,38,78,80,90,92} +{1,6,28,68,58} +{96,63,73,22,74,29} +{65,97,68} +{92,29,92,36} +{47,25,30} +{25,44,67,95,16} +{7,26,41} +{79,12,44,69} +{17,27,4,60} +{45,30,57} +{68,24,63} +{39,64,94,92} +{27,68,39,68,75,8} +{88,48,48} +{86,86,8,54,7,45} +{93,60,14,90,14} +{97,42,54,67,38} +{13,38} +{84,34,30} +{34,71,77,71,13} +{82,18} +{53,7,79,79} +{28,65,38,20,93,100} +{96,10} +{94,12,93,48,51,20} +{12,4,41,11,25,59} +{95,69,23,25,1,19} +\N +{44,38} +{12,4,96,7,48} +{18,24,52,81,58,77} +{15,36,1,50,81,23} +{39,66,74} +{52,22,99} +{51,11,77,44,22} +{51,19,18,91,75} +{20,17,5,96,63,30} +{31,56,9,21} +{45,70,31,62,9} +{84,22} +{99,62,97,44,87,90} +{95,94,95,24,25,47} +{79,72,57,18,3,26} +{54,67,37,57} +{3,90,9,3} +{95,90,40,7} +{36,70,76,68,14,71} +{15,59,7,1,48} +{91,29,79,62,94} +{76,36,92,82} +{50,79,68} +{55,63,88,87} +{86,89,49,17} +{19,74,14,52,8,59} +{8,58} +\N +{77,74,20,39,26,29} +{38,89} +{58,21,44,81,17,16} +{40,72,12,32,90} +{93,34,92,17,39,85} +{39,2} +{43,21,83} +{81,3,59,28} +{34,97,52} +\N +{84,90,6,74,43,70} +{41,6,10,98,86,41} +{13,72,78,11,37,5} +{100,40,54,75,33} +{66,31} +{58,58,75,83} +{81,90,8,73,87,41} +{9,63,22} +{19,66,19,93,52} +{39,88,13,25,66} +{80,85,66} +{66,76,11,71,97,77} +{70,35,87} +{36,17,69,2,41} +{30,85,65,39,38} +{39,35} +{64,100} +{83,53} +{25,29,29,72} +{19,63} +{32,2,82,15} +{31,31,46,11,2} +{41,1} +\N +{55,41,15} +{18,61,43,22,100} +{47,60,16} +{80,5} +{52,2,76} +{40,26} +{81,12,16,25} +{31,93,89,20,95,75} +{26,75,86,1} +{36,69,70,73,79} +{38,39} +{45,49,52} +{88,53,45,10,49,31} +{21,14,1,83} +{7,71} +{59,38,83,64,44} +{6,52} +{99,99,26,54,47,8} +{13,46,72,5,23} +{7,86,40,73,55} +{28,47,50,62,44} +{32,89} +{39,48,50,100,62,95} +{66,56,11,21,58,59} +{7,44,95,53,95,36} +{83,33,79} +{34,65,51,52} +{67,95,46,45,61} +{69,84,71,38,46} +\N +{24,57,48,27,97} +{83,91,97,94,37,44} +{22,31,38,77,21} +{72,32,53} +{30,45} +{93,94,27,95} +{95,4,79,3} +{33,90,92,54} +{55,8,76,39,85,64} +{82,54,93} +{31,42,5} +{38,14,73,12,14} +{64,13,64,28,32,89} +{5,28,4,22,72} +{37,78,94} +{58,73} +{24,57,33} +{48,28} +{69,42} +{97,91,75,84} +{95,69} +{64,95} +{1,3} +{76,38,81,11,90} +{21,30,54} +{92,100,97,21} +{10,76,64} +{85,79,100,79,76,63} +{13,96} +{91,47,84} +{100,19,45,49} +{99,71,21,10,69} +{19,41,7,63,56,85} +{16,32,6,92} +\N +{62,7,22,65} +{1,86,67,47,83} +{26,2,100,51,1} +{20,22,86} +{74,95,79} +{8,53} +{85,59,61,45,83,8} +{2,76,63,26} +{40,42,84,55,56,23} +{37,7,25,14,2,47} +{86,16,98,41,33} +{76,30} +\N +{16,88,61,4,41,42} +{59,92,94,76} +{96,76,57,62,99,61} +{14,30,23,13,9,32} +{47,49,86} +{48,19} +{73,25,40} +{29,75,31} +{53,26} +{28,95,78,84} +\N +{22,77,13,64,68} +{15,69,82,26} +{42,37} +{64,59,95} +{37,72,86,95} +{9,59,92,57} +{65,37,13} +{93,67,81,54,89} +{21,52,78,59,30} +{98,90} +{17,35,57,4} +{44,56} +\N +\N +{25,26,13} +{62,41,60} +{28,92,16,74,4} +{92,19,85,77,11} +{20,67,85,22} +{75,69,34,29,64,73} +{70,40,2,29} +{87,27,70,54,6} +{10,8,9,62} +{71,41,14,22,23} +{83,79,46,37,99} +{79,42,3,54,20} +{12,60,42,100,39,33} +{13,79} +{95,28,54,52,77,3} +{55,50,25,41,42,16} +{96,67,23,54} +{65,54,32,52,16} +{100,11,69,96,95} +{1,18,93} +{53,78} +{24,40,47,30,40,11} +{87,7,12,10,52,90} +{3,72,95,15,32} +{60,69,19,8,43,72} +{88,10,11,55,37} +{67,48,31,48} +{98,70,38,97,14} +\N +{52,12,94} +{41,26} +{81,65} +{66,74,9,66,12,3} +{47,6,33,92} +{95,2,12,90,73,97} +{23,76,34,23,2,20} +{7,22,37,83,15} +{44,61,21,8,36} +{88,52,8} +{66,3,67,43,87} +{16,51,10} +{66,43,28,69,70} +{47,2,94} +{57,67,55} +{40,59,6} +{63,19} +{51,71,68,28} +{73,97,48,56,70} +{3,4,28,48,18} +{31,94,27,70,44} +{85,18,40,6} +{78,91,79,88,33} +{11,90,78,49,97} +{74,91,27,79,75,53} +{1,70,3,40,43,99} +{97,35} +{58,27,40,6,47,33} +{43,42,60,94} +{41,34,23,53} +{57,44,50} +{8,10} +{49,53,22} +{91,2,90,13} +{46,80,27,82,42,99} +{12,96,72,23,83,56} +{48,82,71,8,35,16} +{38,69,38,49,47} +{80,28,13,9} +\N +{84,13,12,33} +{31,57} +{68,86} +{4,96,64,19,48,29} +{66,8} +{33,86} +{32,38,86,86,41,84} +{38,51,31} +{59,17,76,36} +{52,87,60,54} +{7,58} +{34,52,58,90} +\N +{30,67,97,2,1} +{93,10} +{47,16,46,8,39,84} +{90,77,37} +{92,58} +{38,94,49,53,11} +{70,49,35,67,18,28} +{58,81} +{79,100,9} +\N +{97,13,56} +{99,40,87,67,58} +{24,47,19,16} +{12,27,47,48,3,59} +{1,58,15} +{97,28,6} +{94,50,31} +{71,34,94,53} +{26,5} +{46,66,56,27,37} +{76,4,1} +{80,63,40} +{89,82} +{39,100,71,82,95,8} +{81,86,27,83,57,47} +{30,30,92,8,33} +{95,20} +{4,19,8,74} +{20,32,83,62,19,18} +{75,29} +{100,13,6,41,23} +{63,5,93,72,43} +{64,13,73} +{35,91,61,26,41,96} +{49,56} +{2,28,80,84} +{15,48} +{32,49,96} +{72,73,57,69,16} +{95,1,88,64} +{70,55,88,66} +{76,66,30,92,1} +{88,21,74,65,93} +{72,75,75,3,26} +{55,32,85,68,84} +{45,40,93,33,72,20} +{83,89,6} +{4,60} +{72,56} +{73,7,69,25,96,74} +{100,72,41,48,63,37} +{21,72,70,94,67,54} +{6,9,58,77,35} +{70,59,35,25} +{86,96,87,62,13,5} +{93,52,74,57,58} +{93,23,88,50,56} +\N +{95,72,68} +{63,52,58,41,54,90} +{52,23,53,32} +{93,87,39} +{23,73,6,46,79,72} +{44,17,12} +{79,59} +{31,62,14,26,75,23} +{64,72,18,48,63,50} +{71,40,59,87} +\N +{82,17,10} +{44,29} +{6,4,39,16,21} +{94,17} +{91,61,37,36,9} +{53,38,7,28,92} +{95,93,35,18,48} +{35,77,53,87,97,92} +{56,28,68,19,28,86} +\N +{23,91,56} +{97,5,89,24} +{18,81,17,78,63} +{83,19,46,10,22,66} +{100,17,45} +{25,87,61,79} +{17,57,99,1,39,1} +\N +{2,51,26} +{93,69,84,85,87} +{40,58,70} +{86,84,96,41} +{28,36} +{39,85} +{16,84,75,68,87,17} +{14,84,57} +{25,85,35,82,56} +\N +\N +{7,30,17,2,66,91} +{45,17,57,27,98,65} +{57,86,15,40,68,23} +{82,32,28,89,41,79} +{28,3,35,61} +{76,95,19,81,48,50} +{34,6,85,47,65,2} +{70,23,91,33,15} +{30,24,47,96,61,47} +{78,88,64,60} +{87,40,86,97} +{47,14,54,37,100} +{48,95,32,77,69} +{58,12} +{63,20,49} +{78,85,41,72,6} +{39,20,89,21,62,76} +{71,6,10} +{63,4,71} +{51,21,37,63,54} +{66,6,63,12,58} +{89,97} +{64,70} +{53,1,65} +{57,73,30,26} +{15,99,47,89,95,99} +{12,86,7} +{50,68,1,31,67} +{47,86,54,44} +{78,7,86,76,22} +{46,71,98,62,67} +\N +{64,91,80,63} +{82,61,17,58} +{85,64,90} +{37,26,64,97} +{68,25,26,61,68} +{11,21} +{63,53} +\N +{87,88,75,65,10,48} +{32,7,38,72,44} +{99,81,59,10} +{31,58,60,66,41,28} +{23,27,57,74,4} +{20,94,28,29} +{91,5,15,61,50,29} +{34,58,15,85,65,29} +{52,50,2,95,87} +{3,94,54} +{7,61,96,49} +{51,70,23} +{87,49,27,6,7} +{83,61} +{36,92,48,57,20,83} +{53,12,60} +{60,11} +{68,43,74,23,66,55} +{66,8,54,24} +{48,72,41,74} +{81,99,50,33,20,13} +{27,80,60,83,26,74} +{80,1,59,50,15,99} +{11,70,20,29} +{23,84,63} +{63,24,91,19,28} +{25,17,95} +{94,13,81,69,26,89} +{31,48} +{45,20,74,51,62,33} +{77,55,17,63,4,18} +{89,14} +{85,85} +{23,11,85,74} +{29,76} +{62,40,96} +{1,29,25} +{56,26,12} +{5,22,6} +{61,9,6,85} +\N +{31,34,49,11,19} +\N +{14,20,64,73} +{63,1,85} +{2,58,61,100,9} +{89,92} +{37,13,81,77} +{36,26,16,76} +{78,10,10,92,63} +{68,6,35,71,92,27} +{2,88,33,14,85,27} +{80,95,71,98} +{8,33,33,55,90} +{62,74,15,10,64} +{60,18} +{6,77} +{27,38,4,49,27,89} +{94,84,94,8,98} +{15,73,47,47,26} +{73,38,69,90,9,13} +{17,33,32} +{51,57,25,40,41,37} +{77,70} +{66,10} +{50,90} +{96,88,30,65} +{30,49,100} +{34,46,19,89,52,24} +{83,85,62,72,10,64} +{98,56,23,77,79} +{97,90,83,85} +{19,66,70} +{70,89,59,12,71} +{24,96,22,4} +{43,32} +\N +{92,85,41} +{96,90} +\N +{4,5,82} +{58,32,34,86,30} +{51,8,44} +{31,96,37,47} +{51,15,41,97} +{86,41} +{41,26,61} +{62,79,68,73,5} +{32,9,88,30} +{89,34,64} +{70,18} +{64,31} +{14,73,1,50,75} +{57,1} +{53,92,38,13,56} +{41,1,87,40,60} +{83,75,19} +{69,98,25,64} +{69,75} +{84,13,25,8,81} +{41,52} +{90,80,17} +{19,53,72,62,94} +{29,30,99,32} +{32,85,73,26,47} +{6,48,89,93,23} +{73,47,93,10,48} +{60,21,26,60,63} +{85,41} +{75,61,61,45} +{51,7,5} +{9,46} +{83,36,7,84,96} +{71,78,55} +{43,53,88} +{8,1,80,69} +{88,86,51,12,37} +{45,69,40,85} +\N +{36,53,60,15,7,7} +\N +{92,5} +\N +{51,13,34} +{39,23} +{16,26,93} +{91,96,19} +{89,64,2} +{8,74,29,24,66} +{26,19,30,27} +{81,59,52} +{99,28} +{5,12,63,79} +{14,80,90,83,47,79} +{67,64,32,58,44,19} +{27,32,52,79,55} +{68,87} +{14,31,20,12} +{38,99,65,32,15} +{27,57,35,17,53} +{63,64,6,60} +{70,38,47,65} +{24,87,20,4} +{86,27,19,56} +{62,44,1} +{46,10,26,48} +{40,57} +{61,9,59,80,51,20} +{83,44} +{77,1} +{78,63,42} +{75,93,95,76,9,52} +{20,58,10,37} +{72,75,41,73} +{63,93,5} +{57,65,47} +{34,6,51,38,21} +{54,7,19,9} +{61,6,47,64,100,86} +{39,45,55,17} +{81,53,67,33,70} +{11,94} +{57,98} +{78,81} +{75,71,20,8,13} +{3,2,58,95} +{37,58,5,46,54} +{40,50,36,27,69} +{73,42,86} +{97,73,87,80,38} +{27,56,94,73} +{80,81,74} +{53,79,86} +{79,4,55,21,34,74} +{84,63,21,97,92,38} +{72,38,76,63,97,79} +\N +{64,91,100,98} +{34,10} +{97,73,7} +{49,31} +{87,39,65,96} +{54,88,60,55,18,4} +{20,72,96,26} +{40,51} +{37,46,89} +{88,53,3,52,39} +{10,34,77,95} +{20,66,84,12} +{51,19,61} +{67,35} +{73,56,89,43,35} +{94,54,27,63} +{63,53,21,79,76,49} +{79,23,28,63,49} +{47,94,75,11} +{67,95,56} +{80,86} +\N +{62,73} +{98,69,11,57,24,90} +{87,41,77,21,94} +{21,87} +{3,40,75} +{67,53,78,29,16} +{18,46,70,76,98} +{14,67,50,63,22} +{4,2,92,4,8} +\N +{41,76,79,95,96,61} +{35,30,18,57} +{34,91,89,27} +{22,25,9,34,85} +{4,53} +{23,6,65,86,56,93} +{54,81,8,59,36,47} +{90,10,4,25,31,46} +{91,82,82,80} +\N +{64,12,59,21,10} +{49,93,76,26} +{22,10,21,15,57} +{14,29,93,31} +{68,21} +{62,95,12} +{34,74,55,4} +{26,39,93,31} +{67,31,63} +{23,89,98,88} +{48,93,22,79,28} +{1,88} +{95,74,84,18,38} +\N +{82,29,22,45,15,81} +{15,48} +\N +{17,36,97,77} +{93,59,71,15,51,35} +{67,33,57,11} +{35,80,72,43} +{69,89,69,48} +{52,29,16,52,100,22} +{60,30,45,19,25} +{28,3,39,86,13} +{81,40,25,20,39,5} +{77,14,93,47,23,6} +{42,19} +{52,52,98} +{9,29} +{78,77,6,72} +{2,59,73} +{13,85,77,26,29} +{64,63,94} +{54,76,3} +{7,1,5,91,100} +{24,94,57,94,79,55} +{4,22,1,75} +{34,53,19,87} +{69,75} +{71,47,47,61,42,89} +{3,32} +{84,61,4,13,73} +{74,61} +{47,65,85} +{50,84,83,18} +{51,97,11,3} +{59,92,4} +{49,42,65,27,97,52} +{19,33,40,44,71,100} +{82,68,99,60,47,59} +{47,33,82} +{3,45} +{47,28,60} +{3,98,60,30,50} +\N +{11,40} +{33,67,72,43,74} +{9,49} +{42,47,48} +{53,88} +{17,87,28} +{20,4,72,62} +{65,25,22,76,64} +{9,62,57} +{59,93,52,93,60} +{85,85,1,55,50} +{69,22,57} +{8,50,81,32,4} +{80,47} +{60,88} +{16,54,80,66} +{99,87,66,65} +{60,19,58,18} +{14,77,66,48,59,41} +{75,96,82} +{42,72,93,79} +{14,23,78,82,40} +\N +{29,47,16,41} +{13,11,45,67,23,92} +\N +{8,3,52,41,56} +{57,41,63} +{5,50,59,87,50,58} +{58,99,9} +{60,99,15,63} +{59,14,9} +{68,81,34} +{83,18,3,94,39} +{27,52,100,66,48,82} +{10,23,50,96} +{72,14,12,68,62} +\N +{45,30,55,86,89,48} +{5,80,97} +{52,67,86,81} +{99,4,38,79} +{21,98,78,71,73} +{10,23,38,61} +{12,17,19,70} +{79,23} +{55,66,65,60,19} +{7,34,68,88} +{37,70,5} +{41,57,86,31,10,6} +{70,59,96,78} +{88,18,32,22,56,21} +{93,72,81,47,89,72} +{100,14,49} +{83,80} +{73,11,97,14} +{60,47,32,34,13,29} +{39,6,88,24,6} +{54,66,55,52,47} +{56,89,88,98,94,48} +{2,37} +{13,54} +{68,39,68} +{60,81,10,85} +{74,54,14} +{30,52} +{41,74,47} +{77,28,8} +{90,3,43,89,4} +{29,46,84,63,79,83} +{26,15,80,19} +{76,28,77,6,47,91} +{51,15} +{93,15,51} +{8,68,34,58,15} +{5,56,57,81} +{27,87,61,54,41} +{31,37} +{68,80,3,98,49,6} +{96,10,39} +{25,19,21,72,79} +{69,1} +{5,51,61,80} +{76,25} +{36,92} +{54,46,31,87,13,8} +{25,13,83,57} +{29,53,73} +{83,60,26,19} +{27,89,34,13,20,38} +{29,74,26,67,65} +{90,36} +\N +{32,15,43,50} +\N +{55,86,68,51} +{91,14,53,70,49,38} +{75,29,79} +{19,59,38,44,18,79} +{43,31,24,20,16} +{43,83,59,37} +{61,17,95,61} +{67,89,1} +{65,20,46,58,49} +{72,54,38,52,49} +{75,12} +{63,95} +{99,17,79,11,35} +{62,60} +\N +{69,83,89,73,20} +{30,60,39,73} +{78,99,29,45,61,21} +{38,61} +{51,15,47,11,4} +{34,75} +{57,26,42,42} +{8,90,4,68} +{63,70,99,3} +{74,70,33,50,59} +{27,18,73,83} +{36,90} +{82,77,2,83} +{90,99} +{15,25} +{65,30,39,82,89,34} +{12,24,64,54,49,83} +{54,59} +{63,49,81,36,75,52} +{6,59,90,55,87} +\N +{97,52,54,97,3} +{8,53,89,42,30} +{68,42,64} +{97,42,99,74} +{19,31,32,52,7} +{69,83} +{61,17,35,39} +{81,47,70,7,63} +{78,10,63,97,31,48} +{84,92} +{64,82,40,39,57,44} +{39,25,92,33,5} +{27,74,85} +{90,67,21,28,84} +{36,33,62} +{77,87,98,82,11,88} +\N +{11,41,17,91,56} +{1,1} +{84,100,8,22,20} +{57,39,85,5} +{55,47} +{13,2,36,59,45} +{95,66,53,32,29} +{21,92} +{35,32,9,58,77} +{19,71,99,82} +{19,37,87,43} +{100,18} +{67,86,29,40} +\N +{66,54,64,55} +{67,25,18,31} +{60,26,59,86,26,67} +{26,21} +{70,67,30} +{93,82} +{89,58,39,91,95} +{15,86,25,8,12} +{59,20,41,33,78,87} +{10,72,89} +\N +{52,17,99} +{77,29,7,7,1} +{49,96,57,24,66,67} +{10,26,83,84} +{82,7,25} +{66,77,57,25} +{92,77} +{24,48} +{44,26,37,75,11} +{73,80} +{51,47,93,21,25,78} +{76,49,15,98} +{12,85,63,59,6} +{25,51,47,58} +{16,10} +{17,30} +{67,5} +\N +{54,96,21} +{12,47} +{29,90,69,22,89,82} +{78,93,86,65,66} +{83,84,58,67,13} +{85,35,81,27,1,2} +{76,29} +{64,82,91} +{35,89,38,89,10} +{19,40,96} +{83,70,85} +{72,85,70,99} +{34,1,39,16} +{84,53,22,86,73} +{32,23,70,49} +{15,67,91,11} +{73,95} +{71,57,64} +{88,91,56} +{12,16} +\N +{62,82,26,84} +{70,51,52,63,96} +{34,93,49,57} +{16,5,47} +{18,59,12,82,83,51} +{61,93,87,9} +{46,9,45,38} +{15,85,28,73} +{31,99,26,3} +{66,91,48,73} +{98,80,9} +{31,55,42,69,13,58} +{43,8,70,29,83} +{39,57,53,70,74} +{89,13,60,38,89,3} +{37,28,15} +{67,77} +{30,100,89,36,53,75} +{36,19,48} +{7,8} +{12,76,26} +{14,56,52,47,39,67} +{87,83,51,2,97,25} +{51,1} +{59,69,37} +{95,93,21} +{100,92,37} +{37,23,66,95,7,63} +{52,56,77,86,46} +{31,62,17} +{57,48,79} +{26,96,40,5,43,54} +{40,92} +{75,83,1,73,71} +{75,61} +{6,38} +{35,23,76} +{52,3,38,25,100,99} +{45,15,44} +{96,9,11,35,16,58} +{9,80,76} +{22,43,34,43,46} +{34,68,21} +{95,70,83} +{60,7} +{34,22,68,2} +{78,30} +{46,70,90,96} +{5,24,69,61,32} +{41,17,79,27} +{59,88,64} +{12,48,41,68,15,98} +{43,84,59,62,36,14} +{84,8,71,88,4,23} +{45,67,67,17} +{14,96,72,66} +{91,23,4,11,28} +{18,5} +{65,51} +{31,87,33} +{17,97,76,81,69} +{56,71} +{95,23} +{33,58,66,47} +{46,99,69} +{43,87,40} +{49,1,26} +{18,36,89,87,25,100} +{76,37,19} +{57,91,9,100,23,59} +{80,60} +{55,23,32,49} +{15,73} +{87,50} +{43,62,50,54} +{65,3,89,49,77} +\N +{73,12,25,78} +{79,89,38,59} +\N +{44,62,25} +{96,13,57} +{35,14,3} +{90,71} +{34,8,59,81,63,90} +{15,90,89,32,69} +{90,61,54,10,29} +{22,3,85,41,66} +{17,4,99,91,45,57} +{89,32,43,39,61,9} +{45,40,6} +{47,100,75,8,85} +{88,43,89} +{45,41} +{54,48,87,66,100,5} +{58,65,39} +{17,82} +{95,14,31,51} +{30,3,46} +{8,66,22,52,51,24} +{61,62,38} +{4,50,83,32,76} +{96,36} +{87,27} +{82,100,44} +{30,91,44} +{29,48,8,38,43,96} +{56,65} +{34,36,99,11} +{11,1,25,65,12,89} +{17,100,62,53,24} +{86,81,63} +{17,63,30,82,87,91} +{12,63,76,78,85} +{52,19} +{21,91,53,86,49,83} +{67,65,78} +{8,77} +{89,1,56,100,72,96} +{20,51,41,21,30,20} +{41,73,37,92,9,5} +{95,34,21,12} +{28,14,2,62} +{14,74,33,32} +{37,82,67} +{65,99,56,11,21,83} +{99,51} +{56,42} +{59,30,74,40} +{18,27,63,44,86} +{48,25,41} +{5,26,63,88} +\N +{24,66,64,1,26} +{72,74,11,61,70} +{28,27,90,30} +{96,35,21} +{64,100,75,94,88,3} +{93,79,42} +\N +{37,51,4,41} +{31,68} +{93,42} +{76,96,47} +{8,6,16,57,51,72} +{67,72} +{50,36,40} +{69,28} +{17,92,40} +{72,74} +{76,87,93,22,95,30} +{14,88} +{39,56,74,36,25,87} +{55,68} +{32,9} +{35,2,17,86} +{92,73,82} +{40,13,95} +{15,28,95} +{65,40} +{47,56} +{63,72,78,20,22} +{71,49,4,80} +{68,16,50,44,29,38} +{81,96,23} +{44,73} +{4,68} +{30,54,41,66,89} +{92,33} +{10,92,49,46,59,42} +{14,91,18,96,27,37} +{40,32,12} +{14,97,15,96,44} +{75,96,52} +{50,20,9} +{39,84,83} +\N +{14,48,3} +{47,85,76,27} +{5,3,25} +{55,36,29,76,41,44} +{34,56} +{62,29,83,6,58} +{67,32,85} +{75,62,4,66,100} +{47,31,27,43,9,57} +{92,44,36} +{31,22} +{14,88} +{18,25} +{82,63} +{54,67,6,59} +{90,42,19,91,37,75} +{70,39,87,52,32} +{51,20,34} +{85,62} +\N +{95,6,55,93} +{44,67,15} +{93,58,20,12} +{42,6,22,29,36} +{46,81} +{57,95,56,52} +{3,79,69,45,8,74} +{75,44} +{4,17,78,96,66,41} +{27,100} +{85,76,22,17,45,58} +{9,12,70,29,96} +{5,68} +{54,79,5,19,17,24} +{99,13,9,52,86} +{94,6,99,57} +{71,62} +{63,50,9} +{42,42,80} +{25,96} +{93,20,10} +{83,73} +{14,76,36} +{57,31,29} +{17,25,18,18,54,95} +{34,27,86,37,92,83} +{57,57,28,32} +{98,53,60} +{8,59,41,88,49,46} +{95,42,30} +{12,51,98,74,76} +{6,49,26} +{21,35,27,32,83,93} +{16,56,89} +{85,34,73,74} +{52,95,22,4,71} +{96,42,63,88,80,91} +{78,34,41,99} +{11,68,27} +{50,14} +{78,52,66,15} +{100,82,1} +{35,2,93,71,45} +{4,56,8} +{83,19,5} +{82,39,63} +{50,64,83,87,76} +{47,59,93,88,22,67} +{16,6} +{86,98,88} +{32,4,52,34,30,71} +{68,25,97} +\N +{19,17,91,84} +{97,88,89,98,33} +{37,56,70} +{27,17} +{56,58,51} +{69,80,47,84} +{89,22,89,88,16,1} +{95,14} +{14,95,97} +{47,15} +\N +{19,20,65,74,83,38} +{57,56} +{78,67,68,89,1,95} +{61,84,93} +{10,56,96,31,56} +{3,51,90} +{15,85,42,25,15,41} +\N +{50,7,89,89,96} +{90,10,44} +{11,43,15,27,30} +{55,68,48,30,44} +{38,69,3,95,39,6} +{57,51,88,94,82,23} +{69,37,2,67,49} +{93,94,5,84,39,47} +{45,47} +{58,55,79,63,64} +{63,65,59} +{42,36,76,75,89,86} +{41,83,98} +{13,90,13,46,11,37} +{76,33,52,65} +{52,29} +{20,60,45,23,29} +{89,6,14,8} +{91,69,64,72,41} +{46,91,31,66,83,33} +{6,58,61,65} +\N +\N +{90,65,16,5} +{24,46,33,36,47,45} +{11,62,40,98,21,88} +{28,95,58,33,27} +{45,63,99,31,38,90} +{11,49,41} +{23,24,82,25,28} +{42,3,34} +{52,10,58,88,97,37} +{20,41,11} +{86,30} +{36,92,93,10} +{5,36,85,50,71} +{51,75,100,46} +{55,81,31,45,87,8} +{83,10,45,81,33} +{16,94,91,23,76,44} +{62,73,14,39} +{16,14,83,100,82,7} +{25,69,86,12,71} +{29,86,45} +{76,62,100,47,57,52} +{41,21} +{33,56,58} +{23,96,44,16,91,86} +{65,15} +{3,92,56,4,21} +{32,39,95} +{95,87} +{65,96} +{16,96,93,100,35,78} +{64,33,55} +{96,75,41,40,62} +{50,50,86,11} +{93,34,83} +{19,30,62,67,93,19} +{53,67} +{55,46,99} +{70,32,38,4,84,91} +{50,36,40} +{21,93} +{29,6,10} +{4,73,45} +{72,33} +{36,73,18,55,27,100} +{65,73,98,90} +{20,1} +{59,36,60,87} +{20,79,63,93,34,31} +{60,18,92,6} +{48,34} +{63,70,78,1,2} +{15,32} +{5,15,84,73} +{32,35,90,11,40,23} +{91,41,7,52} +{84,90,88,30} +{12,10} +{84,86,36,79} +{76,45,84,66} +{41,25,61,96,97} +{18,100} +{63,39,17,34,32} +{22,45,74} +{83,24,45,48,69,84} +{43,41,12,44,75,91} +{69,75,95} +{100,28,14,66,1,14} +{94,91,60,36} +{88,28,54,63} +{68,78} +{29,68,6,100} +{12,84,35,44,59,55} +{30,59} +{64,18,40,57} +{97,97} +{85,64,73,82,49,88} +{99,31,24,6,90} +{23,89,38,20,40,95} +{84,64} +{21,3,91,7,7,87} +{91,74,32,76,43} +{13,22,96,8,75} +{59,71} +\N +{34,94,45} +{14,5} +{95,10,37,74} +{69,82} +{6,58,45,49,81} +{72,72} +{17,58,10} +{62,77,9,6,44,62} +{37,53,49,41} +{24,11,11} +{10,57} +{26,72} +{18,15,83,60,54,80} +{88,49,73,92,67} +{26,88,64,2,59} +{49,50,3,90,44,49} +{58,54,43} +\N +{86,78,40} +{42,17,65} +{1,86,17,6} +{79,27,37,60,8} +{46,62,46,22} +{9,75,17,68,54,35} +{99,86,64,10,20} +{3,21,35,6,24,64} +{25,62,9,50} +{63,2,79,42,81} +{44,41,2} +{99,93,98,78} +{2,92,9,96} +{79,82,25,64} +{47,84,52} +{97,77} +\N +{47,94,38} +{22,33,76} +{35,52,11} +{17,48} +{1,100,27} +{87,93,19} +{72,3,32,78,81} +{47,28,4,23,79} +{27,88,7,85} +{49,40,47} +\N +{91,89} +{80,2} +{86,78,42,6,81} +{7,50,25,4,8,22} +{23,3,64,59,53} +{1,42,63} +{95,81,86,31} +\N +{81,83,52,47,25,43} +{17,57,100,49,59,63} +{44,91,95,72,29,100} +{80,78,55,41} +{14,52,20,64,9,87} +{48,14,82} +{31,5} +{64,50,66,38,97} +{61,2,90,2,64} +{64,69,26} +\N +{64,62,68,89,12} +{12,10,88,71} +{41,66} +\N +{67,77,25,6} +{14,75,15,66,19} +\N +{88,52} +{78,56,61} +{93,88,47,38,52} +{72,100,54,34,18} +{77,99,89,53,25} +{38,51} +{3,25} +{83,39,85} +{60,15,77,59,69} +{38,64,91,97} +{65,35,30,8} +{46,6,48} +{63,91,29,91,85} +{43,100,56,60,74,53} +{95,30} +{86,63,28,62,37,79} +{2,48,29} +{1,44,20,47,56} +{43,34,86,86,64,14} +{11,82,99,71,63,41} +{77,45,74,17,56} +{18,25} +{51,82} +{27,35} +{1,20,84} +\N +{89,37,16,90} +{58,83,34,88,50,21} +{61,25,1} +{41,6} +{9,100,32,54,38,66} +{40,53} +{29,76,16,13,55,31} +{71,67,54,83,3,82} +{19,62,18,94,73,38} +{17,83,8,45,52} +{80,25,50,59,53} +{4,2} +{52,48,6,72} +{50,32,70} +{36,97} +{17,82,36,97,20} +{22,87} +{46,29,96,98,14,90} +{14,92,5} +{69,9,68} +{20,86,29,61,54} +{62,67,87} +{86,18,31,80,82,45} +{65,89,67,34,41} +{44,8,48,38,91} +{47,32} +{85,25,56,39} +{15,54} +{84,57,44,46} +{65,61,29,86,77,53} +\N +{26,58} +{76,1,57,93} +{57,91} +{13,15,66,11} +{84,12} +{43,32} +{83,24,31} +{82,9,65,84,27,94} +{62,93,55,7,39,46} +{90,100,33,22,61,46} +{9,51} +{87,93,82,94} +{49,45,95,95,66,39} +{100,56} +{11,5,78,42,45,37} +{3,57,80,46,13,34} +{1,74,53,31,33} +{11,84,8} +{27,99,21,31,96,58} +{99,81,90,17} +\N +{66,49,47,55} +{88,30} +{76,62,17,88,83} +{40,7,42,61} +{17,57,9,64,54,1} +{9,54,84} +{50,61} +{72,15,25,30,6} +{64,95,69,89,11} +{64,18,86,25} +{81,59,70,6,92} +{78,76} +{33,40,29} +{15,63,1,12,14,57} +{33,81,8,65,26} +{58,15,56,37,67} +{2,50,35,92,11,27} +{17,13} +{91,100,15,27,39,24} +{58,48,46} +{5,95,28} +{7,21,99} +{5,15,6,10} +{82,99} +{66,22,86,83,76} +{99,68,39} +{43,90,22} +{31,94} +{21,64,56,26,95,40} +{7,81,3,53,83} +{29,42,90,60} +{53,49} +\N +{26,31,14,73,88,51} +{69,2,100,9,34,16} +{78,35,97} +{68,16} +{34,45,42,73} +{7,19,55,70,69,11} +{11,62,61} +{32,17,51,33,87,6} +\N +{54,97,36,13,45,12} +{46,2,26} +{14,6,17} +{99,20,31,61,6,4} +{60,72,53,31,34,25} +{88,46,68,78} +{56,94,49} +\N +{33,65} +{70,51,84} +{55,91,27,33} +{22,19} +{34,78,11,94,3} +{16,67,91} +\N +\N +{64,5} +{76,18,83,5} +{57,13,30,56} +{60,92,25,31,43} +{38,17,54,5,2} +{56,58,39} +{42,43,5,69,56,89} +\N +{50,23,97,85,70,39} +{97,56,33,90,64,2} +{9,54,51,26,24,99} +{18,7,59} +{44,5,40,69,18} +{77,96} +{44,58,47,17,26,45} +{90,71} +{88,32,11,96,17,13} +{42,3} +{97,28,56,10} +{38,36} +{50,52,47,31} +{64,5,99,77,83} +{11,56,1} +{91,92} +{7,53,35,52} +{93,65,47,97,44,82} +\N +{64,66} +\N +{62,4,57,23,34,91} +{52,55,75,99,27} +{29,54,44,87,61,96} +{21,3,66,35,25,80} +{96,68} +{3,41,66,81,78} +{49,98,79,65} +{71,38} +{88,79,70,37,3,82} +{49,74} +{19,29} +{57,68,9,8,99} +{81,88,14} +{99,29,24,99} +{55,96,29,89,49} +\N +{56,2,84,79,74} +{30,52,64,74,62,5} +{88,32,19,25,9} +{40,11,49} +{98,52,27} +{11,86,29,86,6} +{91,53,63,53,44,28} +{88,10,30,48} +{75,64,75} +{14,92} +{98,62,35,67,66,35} +{40,65,11,80,73} +{1,1,63} +{85,32,53} +{91,27,68,50,66,63} +{66,54,38} +\N +{45,43,14,94} +{62,84} +{54,24,83,33,46} +{93,72,2} +{43,4,14} +{18,11,5,99,79,94} +{26,59,9,2} +{58,69,70,45,14,54} +{84,5,42,97} +{7,82,41} +{69,53,8,55,20} +{4,13,6,45,83} +{41,92,41,98,51,85} +{72,85,74} +{19,50,79} +{79,47,47} +{25,25} +{17,56,46,30,73,78} +\N +{92,42,83,34,92,29} +{8,52,76,80,9,55} +{80,100,2,52,24,4} +{55,15,92,27,86,50} +{83,79,41,88,86,53} +\N +{44,16,90,54} +{99,20,64} +{44,30,26,26} +{35,35,24,74,72} +{97,24,94,55} +{78,42,32,76,100,98} +{31,86,12,87,72,86} +{87,35,33,88,33} +{31,83,23} +{46,51,5,6,71,31} +{39,97,91,53,39} +{19,18,25} +{16,4} +{65,77,13} +{61,30,13,26,75} +{67,9} +\N +{31,3} +{15,19} +{97,39,71,30} +{12,96} +{36,96,82,62,5,74} +{81,22,46,11,19} +{97,55} +{58,67} +{10,68,79,74,23} +{29,71} +{50,59,8,1} +{12,51,32,7} +{62,16} +{48,82} +{84,21,24,13} +{46,86} +{100,96,32,54,13} +{72,41,3,67} +{61,9,7,75} +{39,44,50,30,38,6} +{63,63,6} +{69,35,6} +\N +{7,91,82,48,55} +{57,22,31,57} +{55,72,91} +\N +{76,98,43,71,10} +{100,34} +{78,53,14,73,23} +{42,90,28,44,44} +{90,34,22,81} +{60,32,56} +{98,53,58,58,61} +{61,70,59,78} +{2,96,27} +{83,99,25,47,13} +{17,54,11,47,70} +{70,43,11,89} +{93,70,82} +{72,57} +{35,95,49,36,19} +{82,25,16,49,43,93} +{2,51,96,48,88} +{20,81} +{74,4} +{66,83} +{90,75,98} +{25,87,59,92,55,96} +\N +{20,80,92,93} +{59,63,39,3,7,38} +{64,10,85,22} +{63,32,18,38,83} +{49,38,83,54,1} +{27,97} +{18,34,84,58,7,86} +{93,4,67} +{43,49,32} +\N +{29,14,5,50,30} +{59,15} +\N +{76,31,31,47,17,35} +{95,41,71,27} +{47,43} +{75,80,56,78} +{56,75,43} +{99,10,100,76,44,1} +{5,31,72,3,25} +{21,90,59} +{59,45,75,93,78,88} +{76,55,4} +{20,87,44,94,56,78} +{38,87,71,13,23} +{33,6,79,91,92,27} +{13,15,31,15,11} +{57,18,57,71,11} +{67,60,64} +{66,15} +{57,45,74} +{93,91,97,30,12,94} +{37,83,62,18,28} +{94,88} +{12,11,85,10} +{42,96,89} +{15,65,5,65} +{52,58,36,27,10} +{72,88,76,50,96} +{40,70,55,93} +{80,33,24} +{53,35,50} +{11,37,55} +{25,80,32,91,68} +{11,2,52,39,37} +{17,51,45,44,85,84} +{81,21,77,15} +{67,93,27,70,72,94} +{86,99,95,98,83,28} +{9,65} +{1,26,5,23,5,17} +{57,82,42,60} +{46,67,65,98,69,79} +{41,50,94} +{77,81} +{87,82,18,57} +{88,27} +\N +{32,58,81,88,94,90} +{23,37,65,38,29} +{61,11,65,77,25} +{50,53} +{38,2,11,9,27,94} +{64,9} +{1,45,97} +{61,41,67,46} +{13,41,90,15,80,82} +{83,6,9,22,25,37} +{95,74,22,64} +{16,17,4,80,66,33} +{25,42,43,84,96,85} +{25,93,50,87,6} +{35,67,90} +{82,37,59} +{4,44,83,2,81} +{78,46} +{64,79} +{18,41,3} +{56,20,51,83} +{26,77,52,70,93,13} +{54,53,12,47,57,63} +{94,48} +{39,12,41,5,3} +{28,33,93} +{20,29,9} +{75,38,10} +{96,54,96} +{47,87} +{19,35,11,3,80,72} +{75,56,84,24,55,48} +{58,5,13,6} +{10,53,32,6} +{23,8,59} +{71,2,35} +{41,16,99} +{77,6,16} +{30,27,56,85,11} +{47,21,93,82} +{50,68,85,34,19,57} +{14,76,58} +{78,81} +{68,99} +{19,79,67} +{91,73,82,88,44,36} +{49,18,75,32} +{54,18,99,74,9} +{51,58,60,30} +{99,86,83,22,88} +{24,42,76,30} +{86,16,54,69} +{37,39,72,45} +{92,62,3,36} +{31,80} +{43,22,11,15} +{38,88,95,25,49} +{92,21,10,28,47,55} +{99,18} +{26,64,72} +{29,12,17} +{54,69,49,84} +{57,42,4,61,10} +{60,85,74} +{24,29} +{91,71} +{96,49} +{47,51} +{88,67,59,18,86} +{32,18,64,54,41,27} +{78,100} +{77,30,85,93,2,20} +{80,90,68} +{49,37,5,42} +{88,12,94,51} +{85,65,2,41} +{60,38} +{87,37,20} +{27,81,94,37,54,84} +\N +\N +{38,74,78,78,89} +{3,100} +{42,80,68} +{34,17,96,91} +{7,29,83,71,87,26} +{28,81,76,8,43,48} +{74,11} +{28,85,84,78,59,69} +{30,22} +{3,83,75,60,78,11} +{20,62,18} +{74,69} +{91,44,50,62} +{57,18,9} +{14,48,21,33} +{91,1,53,58,92,51} +{64,41,90,52} +{81,95,38,78,7,44} +{65,25,15,90,40,51} +{66,41,31} +{5,92} +{17,98,7,57} +{97,36,99,77,50,88} +{96,56} +{40,62,88,8,53,62} +{18,91,63,93,94} +{88,79,43} +{31,87,98,85} +{12,88,58,53,77,38} +{83,10,37,69,1,7} +{13,47,66} +{85,33,39,48,6,39} +{74,87} +\N +{26,50,26} +{48,78,10,39,17} +{27,43} +{58,17,18,80} +{86,43,58,77,67} +{53,12} +{9,79,9} +{85,79,89,88} +{35,77,22,52} +{93,64} +{47,52,90,17} +{75,15,25,68,28} +{35,6,68,37,18,53} +{80,14,2,89,4} +{52,49,5,66,59,44} +{5,26,96,1,84} +{71,8,61,19,72} +{17,94,84,72,55,83} +{72,10,16,40,17,75} +{6,70,15} +{22,99,7,19} +{55,19,4} +{6,47,69,42} +{17,9,63,44,15} +{23,20,72} +{10,80,20,87} +{99,3,23} +{11,76,8,77,58,38} +{45,14} +{22,89,73,63,54,9} +{16,19} +{1,26,29} +{92,20,65,33,16,40} +{27,64} +{22,19,95} +{36,16,9,30,31,14} +{40,50,68,52,77,58} +{35,15,11} +{67,2} +\N +{63,48,76,25} +{14,65,42,60} +{61,58,31,51,70,4} +{35,41,72,29,46,62} +{98,48} +{90,23} +{1,79,80} +{10,5,54,59,16} +{15,1,9,75} +{34,100,90} +{73,76,25,77} +{98,82,77,67} +{79,5,20} +{9,69,9,52,2} +{23,22,77,80,79} +{32,51} +{23,52,5,33} +{95,20,96,78,48} +{100,37,6,15} +{98,1,53,20,97} +{5,28,26,78,33} +{19,75} +{49,42,30,72} +{50,98,56,26,87} +{76,59} +{51,16,18,48,46,97} +{80,60,51,43,58,28} +{23,12,70} +{40,16,14,18,46,21} +{72,79,47,57,23} +{7,17} +{49,95,6} +{14,24,29,13,90} +{82,28,34,55,15,87} +{31,24,3,50,45} +{86,95} +{97,22,17} +{27,14,27} +{61,63,31,74} +{55,81,87,67,90} +{81,9,79} +{100,29,43} +{41,88,37,29} +{62,57,16,91,60,65} +{94,90,34,94,27,48} +{15,36,80,30,23,90} +{47,91,6,42,93} +{53,74,5} +{84,14,56} +{30,56} +{10,12,92} +{33,7,75} +{96,39,50,77} +{89,85} +{20,39,63,22,44,18} +\N +{90,23,79,91,85,8} +\N +{73,70,52} +{75,100} +{27,4,29,96,25} +{56,31,80,59} +{1,91} +{16,67} +\N +{17,88,59,41} +{13,49,29,76,71,9} +{41,38,16,29,66,40} +{68,67} +{39,74,47,71,63,80} +{4,74,33,92} +{17,60,82,7,52} +{62,88,39,19,22} +{77,21,1,95,42,2} +{98,62} +{55,17,81,31,11,88} +{73,52,18,94} +{16,64,90,33} +{87,41,81,95,85} +{20,55,96,75} +{71,72,11,11,83} +{75,94,89,47,41,7} +{56,48} +{76,29,74,31,67} +{47,70,68,36,70} +{5,69,10,94,54,32} +{29,96,71} +{64,28,86,58} +{82,57} +{42,2} +{64,48,59,8,45} +{61,69,43,40,1} +{69,84} +{68,51} +{32,20} +{21,7,5,60,35} +{100,40,18,98,37} +{50,96} +{87,10,12,27} +{47,3,46,43} +{60,87,10,31} +{92,87,50,37,72,73} +{99,61,77,87,29} +{23,95,31} +{96,100,43} +{17,64,84} +{13,19,57} +{65,86,4,75,46,69} +{49,60,99,37,50,11} +{77,82,88,12} +{12,95,66,98,63} +{83,78,68} +{76,14,87,25,29,14} +{20,9,99,73,67} +{42,51} +{36,22,33,6,63} +{53,46,22} +{40,89} +{37,7,89,17} +{32,89,16} +{65,87,4} +{16,16,57,35} +{34,90} +{80,54,1} +{11,93,34} +{5,19,31,50,99,33} +{98,1,33,54,7} +{45,39,23,78} +{37,47,98,83,10,5} +{55,88} +{42,76,18,99} +{86,31,25,5,45} +{67,87,47,1} +{23,15} +{78,88,66,96} +{58,55,41,67,86,41} +{21,53} +{90,14,28,38,95,71} +{20,5,13,52,1,88} +{29,98,50,75} +{91,3,24,75,82} +{62,33} +{56,69,31,95,66} +{46,85,40} +{17,22,67,57,39,16} +{58,25,92} +{31,53,82,64,69,40} +{40,12,30,1,39} +{78,10,42,40,25} +{58,27,1,12} +{28,11,80} +{36,89,69} +{50,95} +{61,63,59,62} +{51,77} +{90,24,88,84} +{61,27,57} +{51,81,33,75,48} +{47,30,33,23,44} +\N +{79,51} +{62,44,5} +{98,83,80} +{31,33,89,69,19,40} +{22,38,61} +\N +{90,70,10} +{37,90,49,65,95,52} +{95,42,4,47} +{92,100,43,31,27,1} +{39,17,88,20,2,80} +{82,64,36,84} +{31,18,21,18,52,34} +{3,35,36,33} +{26,39,69} +{67,63,66} +{54,85} +{65,71} +{26,14,84} +{63,82,73,30} +{66,79,21} +{71,13} +{57,25,40,14,22} +{63,9,91,31} +{70,63,36,32,93} +\N +{11,85,47,31,54} +{34,47} +{42,78,92,85} +{75,64,12,83} +{33,98,52,91} +{22,25,91,79,33} +{33,51,20,77,23,65} +{83,66,46,24} +{54,65,31} +{43,41} +{52,47,66} +\N +{59,85,85,63,83,53} +{16,76} +{44,97,48,52} +{26,36,72} +{26,55,98,7} +{70,88,7,87,62,37} +{11,42} +{98,38,36,72} +{51,90,82,33,92} +{59,80,79} +{76,77,18,71} +{34,56,62} +{85,12,37,66} +{34,64,74} +{77,63,28,76,11} +{2,63,87,50} +{60,98,60,19,15,57} +{93,66,33,71,36,3} +{41,94} +{62,72,87,19} +{57,83,36} +{63,64,21,13,70,32} +{71,36,9,55,34} +{92,52,90,45,88} +{59,54} +{4,51} +{55,25,35,90,93,2} +\N +{75,15} +{25,86,43,18,77} +\N +{31,40} +{55,49} +{67,1,84,20,9} +{15,1,48,18,100} +{62,46} +{4,39,86,55} +{49,17} +{65,20,71,49,55,49} +{40,57,63,14,3} +{48,68} +{67,97,58,55,5,34} +{3,73} +{79,97} +{82,63,87,66,32} +{19,49,96,50,55} +{32,19,41} +{17,53} +{64,81,70} +{66,75,18,92,54,93} +{7,94,38,86} +{16,62,45,19,10,11} +{18,47} +{58,96,69} +{65,25,58,98} +{29,51,37,40,44} +{91,78} +{37,84,85,65} +{70,61,31,22,32,22} +{67,12,99,39,78} +{41,79,46,54,84,22} +{38,26,43,4,45,75} +{29,68,35} +{69,59,69,33} +{4,46,52,49} +{1,25,44,12,71,29} +{38,75,99} +{83,58,86,6} +{93,35,35,34} +{85,26} +{15,46,95,60} +{62,63,65,49,10} +{44,67,19,80,83} +{63,41,30,43,85} +{13,46} +\N +{13,95,1,34,72,37} +{4,32,22,47,6} +{67,65,77,3} +{40,70,22,44} +{74,9} +{44,28,5,32,67,51} +{55,14} +{41,3,72,68} +{64,82,72} +\N +{11,88} +{91,90,92} +{68,66,95,80,58,54} +{30,49,11} +{54,86,59,69,67} +{56,83,36} +{15,67,9,47} +{92,30,78,2,87} +{12,54,2,1,59,36} +{84,25,67,38,19,53} +{28,45} +{54,84,9,75,59,26} +{47,35,54,93} +{36,96,59,75} +{78,78,52,93} +{87,96,67} +{5,61,15,13,27} +{53,58,6,78,86} +{43,70} +{72,38,15,61,58} +{75,27,30,12,35,71} +{18,72,35,62,81} +{45,10} +{36,91,73,25} +{81,85,22,34,29} +\N +{15,97,82,44,19,83} +{51,23,18,6,74} +{53,75} +{62,9,73,95,37} +{58,42,33,41,71} +{5,97} +{30,2,89,81,93,61} +{32,3,18,84,24} +{6,97,20,89,23} +{27,74} +{22,86,81} +{77,19,42} +{92,9} +{58,90,59,91,30,54} +{29,51,92,34} +{85,68,59} +{36,83,75} +{37,50,86,9} +{79,70} +{33,46,93} +{97,17,6,88,10} +{18,42,88,4} +{41,95,71,27,95} +{8,2,81,56} +{54,94,54,28,70} +{34,87,20,10,5} +{36,76,87,5,100} +{97,91,25,89,89,95} +{76,26,73} +{82,23,7,42,58,72} +{53,16,99} +{10,34,57,47,2,96} +{81,93,26,19} +{8,1} +{79,55,37,61,62,3} +{34,16,69,58} +\N +{41,7,99,87} +{70,21,86} +{59,2,49,45,91,97} +{37,2,74,2,61,68} +{97,39,15,4,13,1} +{67,71,8} +{51,2,84,38} +{55,8} +\N +{75,27} +{37,36,49,70,82,41} +{70,20,85,89,99,90} +{69,61,100,49,75,35} +{11,4,67,4,91,17} +{77,56,65,78,25,8} +{16,58,6} +\N +{88,38,19,88,27,27} +{12,46} +{36,67} +{62,33,96,94,80,96} +{56,94,12,1,65,54} +{58,73} +{19,80,27,72} +{47,55} +{14,91} +{94,75,92,32,19} +{99,12,91,4,85} +{56,55} +{86,83,77,66,66,87} +{46,68,13,45} +{49,75,62,35,39} +{20,25,33} +\N +{91,47,56,68,14} +{88,43,24,42,4} +{50,24,94,18} +\N +{71,54,91,66,97,22} +{81,16,19,67,6} +{78,46,81} +{63,93,71,75,87} +{90,38,10,85,12} +{11,24,93,42,25,77} +{30,14,32,67,70} +\N +{86,91,77} +{73,74,64,66} +\N +{7,18} +{85,94} +{37,15,55,100,59} +{55,18,44,79,57} +\N +{52,40,97,75} +{60,53} +{38,9} +{27,67,77} +\N +{43,83,82,24,35,64} +{22,75,29} +{9,19} +{67,1} +{15,35,11} +{65,45,95} +{65,9} +{63,84,99,89,6,77} +{20,44,31} +{82,50,88} +{29,12,46,21,98,7} +{98,71,3,73,6,86} +{61,44,74,2,45,33} +{16,56} +{31,87} +{72,30,37,94} +{65,30,82,17,12} +{86,19} +{55,76,96,61} +\N +{44,92,83} +{41,22,79,95,20} +{36,33,86,9,61} +{22,88,8,57,73,30} +{63,97} +{36,53} +{56,52,48} +\N +{35,8,3,93} +\N +{53,52} +{7,48,78,46,70,14} +{33,92,55,17} +{39,57} +{71,43,72,7} +{92,85,55,38,35} +{68,30,67,8,18,92} +{9,85,82,24} +{46,46,19,14} +{96,97,31,59} +{35,99} +{54,7,20,28,29} +{20,21,56,82,19,40} +{2,39} +{33,49,63,49,93} +{35,40,26} +{30,35} +{94,70,2,23,91,74} +{34,37,72,19,15} +{92,21} +{72,63,64,35,40} +{59,11,9} +{24,3} +{93,75} +{22,14} +{63,99} +{39,47,10,14,3,45} +{51,74,5,85,70} +{6,33,15,4,89,20} +{97,82,29,15,66} +{47,47} +{88,79,57,10,68} +{18,22,13,100,100,67} +{75,50,9} +{3,12,34} +{39,51,20} +{56,5,63,18} +{83,44,86,46,37} +\N +\N +{60,16,54,75,62} +{91,95} +{39,55,11} +{37,7} +{29,49} +{38,4,52,85,67,38} +{36,56,2} +{52,14,92,39,77,16} +{42,25,49,55} +{70,10,33} +{53,46} +{83,15,28,59} +{35,69,82,4,58,46} +{73,55,64,9} +\N +\N +{60,25,8,8,39} +{50,71,61,64,64} +\N +{65,67,67,34} +{77,59,18,64,16} +{43,72,32,44,59} +{55,57} +{12,47} +{30,75,89,81} +{23,92,16,31} +{64,45,21,74,19} +{4,47,49,47,96} +{37,14,20,18,87} +{61,45,38,39,1,87} +{4,98,99,52,27} +\N +{23,6,50} +{22,61,46,79} +{90,54,60,9,49,42} +{73,27,51,72} +{73,11,23,60} +{7,31,52,34} +{27,68,39} +{39,8,21,48,64} +{86,64,92,60} +{55,36,40,46,23,46} +{32,79,86,44} +{72,29} +{33,87,57} +{57,87,61,22} +{67,84} +{32,99,26,92} +{22,27,34,82,8} +{99,25,99} +\N +{29,75} +{39,63,25,45,7} +{39,67,18,13,18} +{23,83} +{77,69,22} +{60,13,46} +{2,10,42} +{37,20,27} +{30,21} +{85,15,52} +{6,89,38} +{68,22,26,37,96} +{6,85} +{93,51,63,46,26,64} +{79,77,15,26} +{90,6,39} +\N +{50,58,85,27} +{69,8,72,47} +{7,59} +{55,16,54,95} +{96,5,50} +\N +{77,92,13} +{46,30} +{43,65} +{17,65,32} +{10,6,46,1,47,75} +{48,82,71} +{63,12} +{68,14,10,97,34} +{15,45,58,100,7,74} +{9,23,88,1,95} +{61,60,15,12,58} +{84,51,46,41,71,26} +{58,62,39} +{86,67,31} +{32,31,89,2,30} +\N +{90,74} +{65,79,76} +{22,30,77,47,40,23} +{67,99,56,73} +{11,24,30,93,89} +{70,17,65,78} +{100,6,67,29} +{39,4,22,59} +{84,29,70,9} +{74,43,72,27,55,27} +{12,39} +{1,83,100} +{48,23,9} +{21,88,21,35,16} +{92,34,44} +{91,96,13} +{93,57,40,79,81} +{86,3,94,82,43} +{78,70,19,97,49} +{47,22,98,36} +{20,59,65,54,81,27} +{58,13,73,19,54,96} +{26,20} +{70,75,14,70,82} +{77,67,53,33,83} +{2,43,36} +{84,17,28} +{68,25,95,62,92} +{47,90,15,69,85,23} +{92,92,24,37} +{96,14,14,38,38} +{80,4} +{66,86,28,15} +{18,90,74} +{93,76} +{64,96,14} +{76,41,86,67,64} +{58,95,2,86} +{12,60,96,70} +{22,37,58} +{1,67} +{75,23,24,7} +{3,57,66} +{57,30,68,100} +{68,57,33} +{26,32,65,51,75} +{40,14,60,97,83} +{88,96,42} +{66,21,21,78,34} +{15,56} +{86,60,66,66,16} +{94,6,58} +{99,63,70,57,10} +{82,59,62,38,82,51} +{48,61,9,46,28,57} +{29,23,61} +{12,30,42,20} +{99,65,24,7,97} +{20,5} +{6,49,85,56,97,4} +{62,93,88,86,75,29} +{46,2,94} +{57,71,45} +{38,60,21,78} +{95,53,92} +{61,1,88} +{67,80,49} +{59,82,1,48} +{19,94} +{25,64,16} +{96,73,50,85} +{28,17,46} +{81,51,50,18} +{57,99,66,93} +\N +{23,62,57,94,40} +{21,6,83} +{4,11} +{83,16,50} +{46,41,23,1} +{4,15,8} +{86,51,29,80} +{48,34,55,81,89} +{5,2,43,67,66} +{42,59,37,91,1} +{14,98,27,80,33} +{18,58} +{49,93,60,91,94,88} +{32,62,64,63,48} +{51,1,90} +{56,8,68,49} +{16,34,79,18,76} +{66,88,41} +{31,66,93,44,96,40} +{100,99,30} +{37,49,95,91,18,43} +{95,2,94} +{84,15,70,31,30,84} +{31,41,45} +{9,73,2,7,34} +{17,35,43,1,25,72} +{8,70,8} +{1,93,32,16,71,61} +{98,51,27,56,46,65} +{1,11,57,72,33,7} +{48,96,64,55,75} +{83,82} +{7,74,70,29,59,60} +{29,44,5,77,52} +{84,58} +{87,63,62,52,69} +{29,58,32,11,13,17} +{35,99,67,67,93} +{54,31} +{53,24} +{58,59,32,22} +{8,76,23,63,94,54} +{3,88,75,17,64,91} +{29,30} +{3,81,39,9,77,82} +{77,85,59,56,8} +{47,12,63,13,40} +{66,81} +{67,33} +{39,46,28,79,95,67} +{49,13,98,63,10,58} +{14,42} +{80,70,60,92} +{63,54} +{30,70} +{60,89,14,62} +{56,40,94,55} +{70,31,46,20,95} +{18,65,89,7,75} +{60,33,80,43,37,4} +{85,19,98,79,36,84} +{69,1,48} +\N +{30,87,9,22,99,60} +\N +{23,96,9,85} +{22,94,39,58} +{30,38,4,97} +{16,70,62,5} +{35,52} +{32,10,72} +{35,34,40,31,66,80} +{7,77,14,48,97} +{67,64,37,22,69} +{51,53} +{67,71,90} +{87,71,45} +{44,84} +{19,58,11,34,45,85} +{68,19,55} +{27,16} +{7,14,92,22,33,46} +{47,2,49,53,63,32} +{15,39} +{13,47,84} +{29,74,97} +{51,74} +{70,26,46,33,51} +{31,86,14,23,61} +{20,85} +{21,10,57} +{90,94,59,72,97} +{97,30,74,84} +{15,89,69} +{11,40,2} +{68,19,47,28} +{47,65} +{2,7,52,53,44} +{40,74,34,36,78,71} +{22,60} +\N +{37,75,47} +{53,78,2} +{4,32,42} +{35,76,69,88} +{95,13,3,38,3} +{74,74,62,90} +{8,72,42,2} +{11,43,5,43,70,16} +{69,19} +{61,37,26,49} +{16,100,69,32,35} +{58,77,26,76} +{74,87,37,47,84} +{8,82,29,93,15} +{74,88,93,85,97,95} +\N +{29,23,99,98,36,93} +{8,36,87,64} +{71,90,43} +{7,28,78,46,52} +{62,25} +{33,90,7} +{60,72,39,18,86} +{98,59,73,24} +{17,69,2} +{49,16,63,56} +{13,37,62,1,95} +{98,89,69,92} +{50,26,34} +{90,16} +\N +{40,54,3,79,51,19} +{29,24} +{6,12,82,24} +{92,52} +{89,2} +{64,25,68,55,81,2} +{64,77} +{71,46,58,50,56,34} +{94,17,35,30,60,33} +{37,30,2,40} +{98,15,16,92,2,50} +{44,19,82,57} +{37,34,6} +{59,43,1,53,79} +{7,37,14,14,92} +{80,78,49,81,23,17} +\N +{91,51,12,35,79} +{9,14,2,84} +{62,3,77} +{25,5,40,12,40,79} +{65,88,82,94,89,90} +{20,35} +{80,71,83} +{6,9,83} +{94,58} +{2,76,55,61,42,53} +{60,53,45,82,3} +{1,37,75,96} +{82,61,81,10} +{36,46,1,31,90,45} +{22,55,11,25,21} +{69,13,29,20} +{95,54} +{16,79,82,67} +{4,58,84,84} +{52,7} +{25,14,94} +{69,8,67,54} +{30,71,36} +{81,78,23,38,76,58} +{86,59,61} +{11,42,63,74,99} +{66,4,55,34,16} +{39,57} +{10,81,9,8,21,10} +{75,55,64,97,7,45} +{8,46,86} +{39,100,52} +{30,51,7,13,54} +{72,85} +{10,52} +\N +{61,7} +{93,1} +\N +{74,31,3} +{90,96,26,84} +{88,58,74} +{28,45,74,24,74} +{95,88} +{42,70,43,64,22} +{46,83,48,36} +{81,99,100,43,11} +{47,24} +{46,67,63} +{26,15,36,89} +{90,11,78,70,81,87} +{65,90} +{89,99,21,81,47,38} +{37,42} +\N +{94,51} +{12,57,95,63,29} +\N +{68,99} +{27,8} +{16,52,11} +{72,5,85,44,57,51} +{11,6,91,7} +{87,80} +{94,61,1,38,77,89} +{93,60,6,98,46} +{52,47,44} +{93,66,61,22} +{7,61} +{15,83,93,91,12,40} +{66,3,5,72,72,36} +{67,72,68} +{42,42} +{38,17} +{75,60,47,39} +{58,28,51} +{61,8,61,81,65} +{46,52,97,84,27,47} +{97,53,47} +{64,93,83,72,27} +{34,79,34,36} +{25,5,92,37} +{12,20,55,94} +{17,43} +{39,37,16,70} +{79,62,15,16,64,28} +{80,87,96,41} +{51,55,1,94,72} +{75,22,56} +{2,55,7,20,39} +{8,91} +{73,8,42,73,31} +{90,90,23} +{82,68} +{63,64,68,12,59,19} +{100,80,23,24,17} +{23,46} +{25,13,31} +{43,95,54,85} +{40,62,21,21,82} +{70,20,16} +{90,11,23,18} +{16,9} +{51,57,30,27,21} +{50,55,75,77,53,33} +{84,92} +{14,66,32} +{44,100,16,30,82} +{41,48,58,60,7,44} +{81,76,13} +{18,26,82} +{84,35,15,98} +{52,84} +{13,80,36,35,28} +{91,16,71,55} +{87,89,6,20,28} +{12,75,92} +{48,41,55} +{59,75,26} +{48,19,48,72} +{91,4,100,25,17} +{46,52,97,78,94} +{7,81,76} +{54,54,49} +{89,37} +{78,22,57} +{75,25,83} +{25,89,10,38,96} +{52,12,1,74,35} +{13,48,88,7} +{6,97,20,19,91} +{53,2,99,76} +{4,58,46} +{30,30,89} +{97,2,87,47,55} +{14,11,72,83,97,74} +{44,69,11,51} +{47,17,86,27} +{15,19,56,96,24,94} +{81,67} +{11,11} +{20,94,49,36,39} +{39,78,40,46} +{33,87} +{76,89,58} +{94,74,25} +{33,77,5,47,55} +{28,67,99,81,93,83} +{31,10,19,65,60} +{53,25,74,24,48} +{73,69,23,45,88} +{70,56,41} +{21,73,72,28,99,5} +{75,69} +{78,99} +{66,49,89,86,2} +{30,53,18,21} +{67,69} +{1,98,38} +{91,25,16,39} +\N +{75,54,93,39,18} +{96,84} +\N +{64,71} +{6,15,78,50} +{8,45,26,15,25} +{8,90,94} +{52,66,13,98,86,69} +{3,25,28,56,88} +{84,72,89} +{10,33,46,6,57,100} +{13,91,99,2,49} +{83,59} +{88,64,42,50,77,16} +{81,12,27,45} +{12,17,31,93,22,53} +\N +{28,84,85,35,3} +\N +{42,12,86,76,37,63} +{46,23,18} +{45,80,76} +{94,18,100} +{17,80,84,80} +{84,88,29,16,10} +{7,42,90,51,33,40} +{79,51,22,2} +{31,30,72,24,23,84} +\N +{55,50} +{69,47,82,29,83} +{94,56,69,18} +{7,81,71} +{95,13,32} +{66,59,68,62} +{52,19,62,51,80,32} +{38,18} +{73,24,81,58,22} +{11,59,41,19,96} +{61,11,56,89,89} +{61,72,9} +{63,12,8} +{76,54,75,84,6,44} +{74,3,11,62,30,74} +{46,60,71,55} +{28,47,52,71,33,33} +{35,51,37} +{38,89,40,29,30} +{18,26,99,89} +{36,44,8,100,72} +{1,23,6,5,23} +\N +{84,17,16,44,81} +{29,70,24,85} +{23,57} +{20,98,30,23,1,66} +{82,3} +{70,7} +{15,49,58} +{19,40,70,88,35} +{45,10} +{62,89,47,71,55} +{34,100,88,44,3,91} +{92,65,16,24,7,9} +{20,12,61,95,7} +\N +{57,49,42,87,88,14} +{89,99,86,31} +{32,55,51,78} +{55,66,78,10,12} +{37,19} +{13,5,36,66} +{89,7,40,45} +{41,58,41,24,11} +{98,8,9,27,40} +{49,83,89} +{91,36,78,44,100,62} +{76,78,9,52,57,27} +{100,59,37} +{51,1} +{92,83} +{45,1,85} +{8,81,45,94,32} +{52,26,9,98,7} +{97,52,4,85,13,11} +{94,38,58,4,72,91} +{5,39,26,14,74,51} +{31,44,37,24,89} +{8,74} +{56,93,36,3} +{23,46,25,90,42} +{4,98} +{31,95,27,26,20} +{3,7,79,9,90} +{29,22} +\N +{35,34} +{80,28,12,21} +\N +\N +\N +{36,49,94,83,25,9} +{6,62,89,93,59} +{67,75,3,93} +\N +{94,62,3} +{97,36} +{43,89,26,94} +{46,56,22} +{50,15} +{45,47,39,61} +{23,32,24,45,43,11} +{97,66,29,8,52,67} +{37,1,48} +{30,84,86,91} +{4,46,59,35} +{76,37,41,90} +{26,28,92,27,88,17} +{76,37,27,41} +{74,51,31} +{16,33} +{66,85,68} +{4,81,72,62} +{65,14} +\N +{11,43,28,14,9,43} +{60,88,95,1} +{52,92,69,48} +{37,81,85} +{57,73,8,79} +{50,26} +{52,41,99,6,33} +{9,34,58,22,9} +{56,37,19,77,50} +{93,21,18,90,41,40} +{28,89,76} +{4,36} +{89,54} +{70,28} +{66,11,3,47,30,43} +{69,54,86} +{45,41,57,34,18} +{91,46,32,68,42,68} +{25,87} +{75,57,12} +{55,15,68} +{6,63} +{22,39,88} +{77,39,10} +{39,49,69,61,66,77} +{78,25,42,73,89} +{17,47,36,27,79} +{33,83,44} +{27,75,12,96,94,87} +{50,17,95,42,25} +{67,13,22} +{59,85,95,2} +{81,57,83} +{25,11,72} +{32,84,97,6,65,52} +{62,25,24,27,50} +{80,64,23,74,54,75} +{97,17,15,100} +{50,11,41} +{57,82,40} +{10,90,41,52,39} +{4,11,86} +{79,17,51} +{48,100,92,77,58} +{88,67,19} +{40,96,52,35,16} +{89,63,32,81,28,63} +{44,56,66,50,55} +{28,73,46} +{32,40} +{52,65,85} +\N +{51,34,18,82,83} +{49,49,90,71} +{84,16,74,78,86,10} +\N +{73,9,47} +{51,59,49,90} +{85,13,78} +{98,77,18,15,92,85} +{40,94,66,94} +{89,51,80,12} +{23,26,75,17} +{96,2,51} +{88,62,90,32} +{85,19,87,89,30,15} +{33,38,9,46,19,87} +{27,45,15} +{39,79,82,88} +{31,33} +{41,64,10,1} +{35,61,22,76,74} +{75,11,90,16} +{71,23,43} +{35,3,97} +{88,4,97} +{100,61,28} +\N +{64,74} +{9,44,81,98,55} +\N +\N +{76,89} +{18,34,80} +{77,83,91,50,20,41} +{65,50,26,65} +{79,18,90} +{5,60} +{42,21} +{31,70,80} +{20,98,15,14} +{58,65,45,6,64} +\N +\N +{88,82,98} +{75,81,32,34,59} +{37,14} +{30,36,55,70,65} +{84,55,26} +{56,64,1} +{31,41,89} +{46,43,43,90,34,100} +{78,36,21,14,69} +{100,10,45} +{73,69} +{60,86,5,70,78,99} +{6,89,92,8} +{86,68} +{44,4,71} +{41,36} +{95,80,42,94,34} +{73,29,50,49} +{61,20,57,17,36} +{37,58,67} +{56,83,77,37} +{98,67,40,10,35,76} +{54,84,6} +{7,71} +{65,74,43,6} +{62,98,74} +{81,26,17,22,47} +{49,32,59,35,11,94} +{80,50} +{91,1,50,97} +{71,35,84} +{97,4,46,45,8,36} +\N +{81,62,76} +{69,78} +{89,3,16,64,17,17} +{78,72,26,88,81} +{25,34,9} +{50,27,34} +\N +{55,44} +{61,51,39,53,44,46} +{23,94,32,92,90} +{91,47,67} +{1,13,76,57,63} +{77,19,73,18,95} +{100,82,87,6,83,23} +{69,58,48,97,60,50} +{4,83,85,6} +{3,5,91,37,94} +{91,72,31,32,80} +{57,23,39,46,50,20} +{92,28,53} +{71,27,46} +\N +{59,73,29,21,89,30} +{1,83,96} +{34,41,65,35} +{52,89} +{62,68,80,7} +{82,82} +\N +{11,2,62,46,28,9} +{9,16} +\N +{22,44,73,82,39,86} +{97,52} +{46,36,76,56} +{17,97,26,72} +{16,78,9,70} +{65,55,30,91,16} +{27,45,76} +{17,5,90} +{86,52,93,89,42,27} +{51,70,41,35,1} +{91,57,66} +{53,59,62,57,52,56} +{100,100} +{32,78,87} +{61,57,74} +{86,79} +{55,94,64} +{81,20,26,22,23} +{9,96} +{86,65,35,19,88} +{1,37,90,51} +{79,47} +{93,93} +{32,85,69} +{49,12,6,69} +{6,44,92,37} +{28,42,57,28,2,69} +\N +{63,90,25} +{53,28,74,42} +{83,79,94,37,64,69} +{93,100,57,84,80} +{39,93,80} +{97,92,55} +{27,6} +{20,100} +{19,66,3,66} +{7,76,15} +{7,56,92,11} +{61,76,6,98,52} +{20,46,51} +{12,77,45,67} +{78,79,32,22,21,47} +{62,35,1} +{86,66,57,10,47,43} +{43,24,76,18,87,68} +{39,52,71,35,87} +{81,78,8,10} +{33,70,53,54} +{25,77,27,68,95} +{29,53,89,62,51} +{21,76,33,72,39} +{13,22} +{1,1,51,73,20} +{26,97} +{64,75,23,94,62,68} +{25,20,84,57,27} +{26,7} +{92,80,17,48,72,73} +{73,49,88} +{24,36,70,53} +{7,79} +{80,58,33,25,91} +{19,43,61} +{54,49,73} +{51,88,4} +{9,32,5,83} +{17,68,90,15,30} +{98,50,42} +{29,52} +{32,41,4} +{33,97,69,34} +{94,2,60,5,83} +{23,86,43,74,35} +{63,37,38,58,39,14} +{56,7,82} +{88,81} +{50,75} +{78,49,67,68} +{10,61,58} +{84,35,20,30} +{36,34,48,31,16} +{35,7,47,22} +{98,40,56,43} +{16,4,7,9,44,55} +{86,90,30,80,47,91} +{34,91} +\N +{12,67,77,23,11} +{94,8} +{5,68,31,82} +{26,65} +{51,19,86} +{55,83,39,39,96,51} +{31,22,70} +{20,50,15,93} +{1,55,64} +{8,2,14,3,40} +{2,71,25,41,5,5} +{98,61} +{21,64} +{100,76,99,18,78} +{17,4,69,97,61} +{52,79,97} +{52,26} +\N +{90,54,2,62,11,51} +{33,12,34,45,2} +{91,63,51,42,82} +{100,79,73,70,54,14} +{57,94,81,55} +{13,18,94,17,16,34} +{58,79} +{90,64,68,46,95} +\N +{37,46} +{91,94,10,85,100,24} +{65,86} +{94,89,7} +{72,79,77,53,95} +{65,19,92} +{41,79,53,8,63} +{28,60,50,42,9,32} +\N +{6,23,97,23,10} +{12,28,16,39,70,50} +{26,97,61,48,79,23} +{38,98,21,34,65,89} +{29,13,36,19,13,45} +{72,65,58,81} +{43,98,84,5} +{79,41,100} +{35,30,69,42} +{59,13} +{65,90} +{40,38,21,23} +{2,19,26,38,66} +{5,16} +{84,85,97,84} +{34,26} +{87,17,21,32,29,25} +{75,66,87,90,18} +{84,32,29,51,71,68} +{57,25,73,24,53,2} +{74,16,92} +{99,60,19} +{98,14,70,72} +{24,34} +{37,34,81,100} +{67,10,17,60,16,55} +{39,58,5,23,85,95} +{75,93,19,31,47} +{13,27} +{42,14,32,90} +{59,79,70} +{48,96,45,38,58} +{96,87,84} +{23,70} +{25,31,81,36,75,32} +{64,49} +{30,18,38} +{69,27} +{76,82,43,96,73,17} +{84,95,97,12,20} +{57,69,36} +{60,79,19,67,9,12} +{32,39,3,21} +{55,83,51,48} +\N +{37,11,98,53,11} +{2,73,24,18,77,74} +{69,96,17,49} +{53,2} +{1,76,72} +{35,93} +{35,36,36,25} +{59,77,30,13} +{35,69,36,31} +\N +{20,23,51} +{81,83,57} +{87,43,40,56,81,64} +{24,63} +{29,51,45,93} +{73,85} +{59,1} +\N +{13,57,14,11,34,91} +{69,1,4,28,77} +{63,68,41,53,64,43} +{11,1,46,40,6,88} +{51,19,77,10,86,66} +{74,40} +{25,54,46,62} +{94,17,64,15,20,36} +{100,71} +{63,66} +{33,88,5,92} +{92,86} +{91,69,75,13,20} +{57,22,32,33} +{72,87,44,64,46,6} +{50,56} +{36,23,7} +{74,63,3,6,14,29} +{91,42,8,11,49} +{32,64,94,88} +{91,78,55,27,59} +{2,20} +{52,95} +{57,59,35} +{51,15,52,24,14,13} +{64,16,18} +{50,98,71,10} +{92,99,92,80,77,73} +{96,12,70,85,54,73} +{10,44,30,77} +{29,47} +{40,55,62,58,30} +{59,93,7,21,6,20} +{58,91} +{5,70} +{36,23,58,80} +{16,93,54} +{20,8,97} +{78,32} +{10,31} +{24,10} +{56,14,28,10,45} +{1,79,53} +{56,58,86} +{93,83,17,89,93} +{12,4,26,45,97,17} +{42,67,17,13} +{31,90,59,38,4,20} +{86,52,67,10} +\N +{49,59,10,25} +{69,88,31,38,7,36} +{84,21,57} +\N +{60,8,19} +{35,81,66,96} +{13,95,54,38,31} +{27,25,34,11,65,64} +{54,43,20,20,65,95} +\N +{19,27,100,69,43} +{91,8} +{30,65,98,87,84} +{83,85,100,16,20,18} +{80,48,56} +{61,5,92} +{14,94,43,91} +{35,52,60,43} +{73,25,26,61} +{66,41,39,16} +{2,96,90,37,99,92} +{25,31} +{72,57,50,82} +{40,69,5} +{98,34,66} +{90,44} +{34,78,93,15,65,71} +{98,1,28,36} +{16,59,79} +{88,1,14,45} +{41,91,87,20,72} +{46,9,81,90,63,32} +{2,84,29,56} +{2,57,92,69,63,46} +{3,32,76,62,36} +{11,81,3,81,90,16} +{36,1,42,51} +{29,86,53,51,85} +{17,66,16} +{4,21,25,17,65,92} +{13,26,33} +{74,6,46} +{69,19} +{47,78,85,46,41} +{41,62,100,85} +{22,71,66} +{28,15,58,84,22,92} +{68,82,82,85,15,54} +{34,58,72,35,99} +{51,100,40,13,61} +{80,89,94,31,96} +{48,29,33} +{32,85,75} +{76,43,17} +{79,70,3,64} +{76,64,85} +{94,90,3,85} +{86,21,1,8,54} +{87,92,30,36,59} +{20,51,62,17} +{81,61,86,96,47,67} +{5,75,97} +{60,24,79,3} +{85,49,49,48,61} +{66,60,58,92,88,90} +{2,18} +{42,54} +{42,83,68} +{98,76,42,25,90,32} +{64,36,39,45,34,95} +{56,43,78,10,63,18} +{51,40,98} +{85,11,74,41,14,25} +{37,12} +{76,32} +{6,77,16,86,36,25} +{23,93,18} +{75,51,67,29} +{22,9} +{18,58,25,88} +{95,31,12,20,62,54} +{23,97,89,63,73} +{77,41,11,27} +{91,86,97,98} +{84,6} +{74,69,55} +{58,42,92,65,52} +{77,31} +{8,91} +{5,83} +{64,48} +{1,37} +{51,4,49,97,64} +{29,70,3,62,57,1} +{91,8,31} +{86,71} +\N +{61,50,8,6,59,76} +{83,8,54} +{50,45,66,86,52} +{75,48,18,88,82} +{1,52,60,78,45} +{46,65} +{53,2,63} +\N +{89,97} +\N +{75,23} +{30,58,13,50,2} +{59,73,52,8,70,39} +{20,35,77,34,10} +{55,86,14,74,14} +{67,46,48} +{20,9} +{20,92,62,75,34,98} +\N +{72,14,18,46} +{48,71,92,17,31,13} +{47,46,42} +{42,75,14,86} +{97,55,97,78,72} +{8,4,96} +{44,13,13,18,15} +{16,40,87} +{87,66,79} +{14,44} +{35,79,25,57,99,96} +{23,66} +{90,49,24,11,8} +{50,3,24,55} +{60,94,68,36} +{11,20,83} +{66,100,47,4,56,38} +{36,34,69} +{41,57,15,32,84} +{32,25,100,45,44,44} +{70,32} +{15,37,67,63,71,34} +\N +{81,62,20,91} +{32,62,1,68,86,54} +{20,91,40} +{79,69,22,98,14} +{45,42,24,2} +{30,53,15,62} +{81,100,42,20,96,42} +{93,19,7,59,100,49} +{25,7,18,64} +{11,27,1} +{89,67,65} +{39,97} +{47,62,30,61,58} +{4,11,83,18} +{38,30,95,58,13,81} +{83,6,33,73,64} +{89,51,77,45,58,16} +{13,11,88} +{96,79,71} +\N +{18,66,83,52,84,76} +{52,17} +{74,95,16,5,16,51} +{21,20,16,39,84,71} +\N +{75,47,36} +{65,45,12,5,100} +{41,74,84,21,73} +\N +{8,90,46,39,30} +{47,84,42,49,17} +{76,100,35,89,17} +{61,53,50,31,8} +{94,53,20,33,15} +{97,46,62,85,74} +{8,59,40} +{95,71,21,41,85,81} +{55,71,20,74} +{70,95} +{61,42} +{83,74,25,84,18} +{56,43,46,40} +{42,78} +{95,48,98,93,35,98} +{77,34} +{4,54,58} +\N +{13,54} +{87,66} +{12,88,90,95,6,95} +{65,20,10} +{62,74,59} +{49,17,51} +{14,17,65,3,27,41} +{43,42,43,46,79} +{88,75} +{21,46,84,95,31} +{17,17,28} +{32,73,29,11,46,94} +{3,34,81} +{80,83,1,92,69,100} +{9,24,56,17} +{3,80,57,36,14,94} +{39,89,54,17,31} +{70,19,67,21,31,72} +{82,48,68,52} +{96,81} +{92,18,39,50,18} +{6,54,27,52,28,100} +{23,40,7,74,93,50} +{87,51,38,88} +{98,42,43,30,8,71} +{33,26} +{20,21,83,35,99,100} +{28,77,94,32,1,13} +{17,15} +{35,100,9} +{42,6} +{16,28,55} +{7,94,81,60,91} +{100,63,21,28} +{65,20,35,16,76} +{95,3,88,62,1} +{73,44,46,13,55,69} +\N +{60,49,71,77} +{93,39,75,63,69} +{97,36} +\N +{77,16} +\N +{57,30} +{39,31,56,51} +{62,78,62,38,54} +{69,86,99,10,12} +{11,43} +{60,70,83} +{83,82,3,1,60} +{24,55,61,85} +{65,72,13,77,79,100} +\N +\N +{28,97,71,78,68,95} +{34,1,72,79,84} +{10,49,91,44,27,51} +{15,48,80,37,69} +{42,46,32,34,86} +{80,21,26,50,5,8} +{61,71,100,78,54,50} +{36,20,80} +{67,40,47,68} +{60,7,36,36,55,2} +{32,91,13,98,88} +{15,56,65,23,13} +{20,66,81} +{19,36,99,54,86,92} +{82,28} +{43,32,91,37,70,68} +{71,78,82,50} +{1,31,23,48,10,12} +{88,96,1,44} +{27,49,97,29,89,35} +{63,72,58} +{79,9,32,64} +{75,67} +{46,31,83,54} +{66,24,6,89} +{82,10,64} +\N +{19,31,52,34,89} +{16,36,11,12,23} +{55,50,6,20} +{81,72} +{71,74,8,6,31} +{6,20,96,80} +{95,85,56,91} +{36,33,88,12,50} +{77,44,52,50,50} +{94,12,7} +{97,44,40,43,8,21} +\N +{61,14,40,75,87} +{43,21,67,66} +{46,19,80,12,46,28} +{56,11,14,59} +{31,94,50} +{45,26,61,15} +{84,45,44,82} +{9,16,86,54,93,30} +{50,39,37} +{35,60,64,55,73,90} +{61,65,87,20,30} +{12,59,44} +{23,8,97} +{30,59,7} +{85,32,14,95,38} +{18,91} +{10,40,20,8,58} +{5,58,4,94} +{100,11,96,70} +{66,72,7} +{5,31,89,89,4} +{81,68,44,37} +{22,22,76,67,72} +{22,26,30} +{73,47,27,18,54,30} +{44,13,73,95,83} +{18,93,72} +{30,22,73,13,16} +{14,11,66} +{45,33,59,72,92,81} +{97,82} +{30,4} +{1,9,46,70} +{47,50,20,71,48,60} +{26,62,53,70,63,49} +{39,26} +{47,94,9} +{55,3,18,1,75,22} +{42,87,74,57,60,55} +{95,46,21,38,27} +\N +{13,35,48} +{24,39,24,67} +{44,83,49,72} +{22,8} +{77,39,87} +{37,41,44} +{100,57} +{48,54,58,79} +{14,84,40} +{11,51} +{23,80} +{80,82,43,59,2} +{92,53,56,44,90,66} +{44,67,78,9} +{43,91} +{70,74,100,69} +{12,5,75} +{65,51,22,65,56,36} +{52,54} +{38,78} +{30,45,38,99} +{18,88,88,63,51} +{61,24,53} +{72,24,77} +{61,46} +{11,83,49,86,27,60} +{86,60,83,34,33,28} +{65,15,10,51} +{98,92} +{49,49,60,3} +{58,56,43} +{19,25,15} +{24,40,36,49,61} +{5,62,9} +{72,8,71} +{64,85} +{72,84,67} +\N +\N +{80,87,30,70,21} +{30,86,95,19,21} +{17,90,15,89,81} +{40,51} +{77,88} +{14,89,82,62} +{40,66,93,16,55,45} +{22,46,31,17,4,71} +{8,41,88,94,25,61} +{80,8,23,71,59,53} +{61,70,23} +{2,4,79,6,67} +{27,70,42,68,33} +{46,27,10} +{1,93,42,12,8} +{31,9,19,32,62,15} +{16,42,81} +{56,29,12,17,61} +{52,100,98,42} +\N +{29,38} +{49,40,47,63,22,4} +{99,70,13} +{70,28,67,100} +{37,75,65,63,35} +{45,67,37,28} +{42,78,71,39} +{33,35,76,69} +{65,84,57,63} +{17,12,86,23} +{31,62,79} +{3,22} +{85,81,59} +{38,5,15,100,1,27} +{36,96,93,46,75} +{44,61,85,70,71} +{79,72,86,71,77,9} +{23,51,47} +{4,59,48,38,44} +{93,54,86,98} +{60,29} +{49,38} +{54,84} +{72,25} +{51,40,25,27,68} +{24,17} +{95,3,82,31,95} +{56,37,57} +{15,84,98,16,53} +{47,36,15} +{27,36,76} +{38,82,26} +{47,70} +{60,89} +{59,73,99,7,28,89} +{87,49,70,76} +{71,93,76,81,11,46} +{74,87,92,24,43,22} +\N +{26,1,85} +{18,73,43,94} +{92,2,73} +{5,58,85} +{20,7,39,18,59,90} +{11,16,19,77,60,56} +{77,1,95} +{4,4,11} +{48,40,56,74,96,29} +{71,1,62,69} +\N +{34,61,26} +{86,75,13,73,28} +{17,35} +{100,29,37,26,47} +{69,36,52,61} +\N +{81,51,54} +{54,78,46} +{1,78,96} +{33,54} +{72,9,37,30,100} +{67,10,52} +{77,19,74} +{52,27,41,37,98,73} +{8,74,86} +{4,40,99,6,59} +\N +{98,43} +{74,91} +{69,45,73,59,19} +{87,43,31,85} +{2,51,54,3} +{45,73,8,86,4,40} +{2,51,96} +{74,5,8,64,1,46} +{5,64,86,63,12,75} +{6,62,71,24} +{56,84,54} +{61,37,79,63} +{81,39,78,23,86,74} +{50,79,34,23} +{85,36,78,80,19} +{34,94,1,46} +{5,23,38,4,78,2} +{85,100,80,13,73} +{48,86,9} +{47,22,65} +{49,81,18,52,36} +{84,85} +{89,15,71,88,44} +{1,21,81,52,2} +{53,18,7,53,50,11} +{91,89} +\N +{20,6,20,70,12,32} +{98,94,70,52,41,35} +{43,25,2,63} +{95,86,6,82,2,41} +{79,24,63} +{12,96,7,18,48,67} +{55,35,4,75,28,39} +{48,46,33,75} +{10,99,5,5,98,25} +{43,87,5,53,76,64} +\N +{100,13,9,4} +{4,35,65,56} +{27,74,88} +{59,66,10} +\N +{59,85,39,48,17,29} +{59,42,17} +{27,99,12,21} +{9,10} +{15,4,80,25,67,59} +{12,89,96} +{50,32,92,49} +{40,74,10,6,26,43} +{80,71,29,54} +{74,82} +{22,25,27,65,12} +{84,88,53,43,75} +{84,16,51,84,46} +{10,9,44,95} +{87,19,22,10,44,80} +{18,20,87,41,86} +\N +{9,64,4,33} +{65,87,23,65,32,92} +{50,2,23,68} +{29,8,82,28} +{54,92,6,2,28,70} +{23,11,65,78,34} +{77,85} +{30,49,59,8,60} +{77,30,34} +{55,73} +{89,68,55,81,8,81} +{54,28} +{35,22,67,63,48} +{43,37,46,56,81} +{16,78,32,81,77,37} +{35,80,41,76} +{4,93} +{3,32,23} +{43,18,50} +{87,5} +{30,40,91} +{36,69,17,82,70,57} +{73,71,47,63,58} +{24,11,36} +{2,72,61,76,9} +{61,97,10,85,92,56} +{5,44,47} +{24,57,79} +{69,39,97,8} +{78,16} +{62,52,17,35,28} +{48,79,66,64,36} +{14,72,75,30} +{17,21,41,25} +{28,100,66,56,15} +{89,3,32,86,6} +{67,34,16} +\N +{48,27,70,60,1,40} +{69,34,36,46,95} +{59,24,84} +{44,21,90} +{22,30,5,62,13,58} +{79,67,44,10,1} +{67,8} +{40,48} +{64,5,65,35} +{74,45,75,15,31,69} +{42,3,49,33,52,97} +{86,59,69,84,53} +{64,64,41,64,99} +{47,95,16,78,73,68} +{54,11,52,90} +{54,62,79,58,96,59} +{28,34} +{52,94,17,42,9} +{94,22,77,7,56} +{72,24,47} +{6,11,3,23} +{9,6,97,82,40,39} +{73,47,57,8,7,97} +{27,26,1,2} +{64,45,38} +{71,6,6,83,33} +{78,28,40} +{25,8,17,15} +{24,67,53} +{72,42} +{66,25,56,36,32,93} +{18,11,22} +{88,9,75,23} +{20,32,24,44,51,34} +{76,86,11,7,1,61} +{11,77,41,55,87,59} +{62,53,94,46} +{77,20} +{74,97,59,78,9} +{7,94,26,18,77} +\N +{49,59} +{72,22,42,89,14,80} +{49,14,38,19} +{43,88,25,58,39,24} +{21,34,37,65} +{85,3,46} +\N +{11,60,86,65,49,83} +{51,98,7,28} +{85,17,34,59,14,86} +{89,81,48} +{67,40,11,60,75} +{13,45,42,22,82,82} +{98,21,89} +{30,63} +{35,45,68} +{9,29} +{43,71} +{82,44,59,72,48} +{1,48,29,44,14,11} +{75,33,85} +{7,32,92} +{62,14} +{29,31,1,36,51} +{92,12,28,20} +{13,67} +{88,72,14,22,61,42} +{15,98,49} +{65,27,9,76} +\N +{15,95,26,12,52,40} +{17,20,74} +{57,63,15,22,38} +{93,71,8} +{26,84,82} +{20,52,3,3} +{72,95} +{10,9,80} +{9,9,18,51} +{74,24,63,63,57,89} +{64,91,95,18,15} +{64,37,20,36,74} +{52,9,53,6} +{17,31,42} +{3,73,92,13,62} +{57,81,58,49} +{52,56,2,26,18} +\N +\N +{90,90} +{16,92} +{66,51,7,19,10} +{100,81,69,86,95} +{48,64,81} +{87,54,73} +{6,80,100,24,26,8} +{44,67} +{27,94,2,25,34} +{80,25} +{12,2,77,75,15} +{63,14,30} +{85,75,59} +{72,73,54,44,25,76} +{95,44,69,91,62} +{94,73,78,5} +{28,52} +{86,31} +{69,90,95,66} +{6,10} +{68,72,112} +{9,165} +{91,132,164} +{57,82,144,167,184} +{3,6,101,118} +{111,158} +{22,29,30,174} +{41,66} +{39,76,189} +{7,20,21,196} +{52,126,169,171,184} +{21,77,91,176,196} +{16,97,121} +{83,135,137} +{8,140,160,164,165,195} +{38,65,185} +{112,152} +{111,129,134,148} +{47,80,114,135,147,165} +{24,98,119,123} +{43,48,60,147,154} +{19,54,138,171,186} +{156,175} +{20,51,123,193,193} +{37,41,136,173,192} +{14,22,111,125} +{44,125,160,184} +{19,75,99,103,107,164} +{24,113,145} +{27,157} +{12,107,133,134} +{72,94,102,158,194} +{104,157} +{122,171} +{28,47,89,104,112} +{25,35,82,105,155} +{106,107,139,181} +{50,110,132,136} +{90,110,166} +{1,1,55,60,85,108} +{8,22,31,106,172,196} +{24,69,109,121,154} +{0,26,44,59,132,175} +{103,125,172,188,190} +{11,23,78,109,131} +{81,146,169,181,196} +{2,84,113,189} +{8,46,126,131} +{13,73,73,125,127} +{67,117,139,184} +{29,65,77,120,182} +{0,87,100,102,135} +{111,146,156} +{13,87,123,137,182,197} +{60,61,164} +{7,20,186} +{0,24,53,135,147} +{94,136} +{47,168} +{70,80} +{43,148} +{3,81,104,191} +{104,171,189} +{9,14,117,160,180} +{67,158} +{50,57,66,78,170,197} +{31,60,73,101,193,197} +{37,89,92,96,127} +{29,179} +{17,47,137,155,157,187} +{33,77,154} +{48,63,85,150,184} +{32,53,61,95,172} +{20,35,47,171,179,196} +{2,17,40,169,184} +{116,127,131,142} +{16,26,27,87,164,198} +{58,129} +{67,98,108,132,157,197} +{145,157} +{13,49,56} +{59,103,180,196} +{35,65,104,106,120,126} +{18,96,115,133} +{27,61} +{61,194,197} +{11,27,36,94} +{15,36,101,128,197} +{51,62,115,149} +{83,198} +{30,120,127,145,184} +{50,149} +{13,35,87,117,135,158} +{57,60,74,113,128,178} +{11,90,123,163,170} +{39,121,148,171,198,199} +{30,77,78,137,140,162} +{52,69,120,141} +{9,100,137} +{56,161} +{44,57,75,110,154} +{98,123,155,167} +{10,60,85,105,164,168} +{13,92,179,186} +{13,171,173,176,178} +{33,53,88,123,144,172} +{21,57,70,131,151} +{13,51,63,169,169} +{36,104,119,166} +{54,59,84,166,172} +{7,87,100,102,142,187} +{2,5,6,43,174} +{4,26,29,59,77} +{10,82,98,103,104} +{104,147} +{47,55,99} +{102,154,165} +{0,96,107,139,157,159} +{66,167,174} +{92,97,117} +{21,75,180,185} +{54,64,139,180} +{23,141,189} +{32,38,147} +{82,87} +{6,34,34,161,183} +{25,64,69,97,122} +{80,152,170,189} +{44,78,143,162} +{52,53,64,69,112,158} +{77,80,123,150,175} +{110,121,125,125,128,198} +{0,8,57,104,127,188} +{17,46,48,93,129,150} +{135,193} +{89,111,135,166,184} +{132,181} +{47,54,101,108,125} +{18,55,103,142} +{11,125} +{18,49,58,68,122,153} +{37,47,137,179,185} +{57,78,167,187,192} +{28,32,38,67,77,184} +{67,83} +{43,104,191} +{22,40,118,194} +{24,53,66,195} +{27,87,89,101,130,191} +{71,86,157,167,183} +{31,87,102} +{48,53,70,101,149,174} +{21,33,59,129,195} +{144,160} +{4,8,174,194} +{69,103,127,127,160} +{6,29,62,77,132} +{61,69,108,144,174} +{51,55,109,128,153} +{10,30} +{2,5,6,70,146,183} +{0,1,75,97,166,180} +{53,78,104} +{31,45,68,108,161} +{3,40,78,103,109,130} +{33,44,159} +{28,82,93,136,148,157} +{31,32,76,143,157} +{2,55,106} +{21,66,80,129,129,152} +{1,34,59,128,154,195} +{10,154,172,177} +{2,7,31,47,82,125} +{60,131,149,156} +{20,141} +{23,38,43,100} +{51,70} +{3,41,164} +{126,160,165,169} +{61,71,143} +{65,70,81,100,146} +{40,48,57,75,85,85} +{116,153} +{31,42,49,103,183} +{28,44,62,85,133,177} +{50,68,164,170} +{4,26,60,87,119,141} +{5,102,160} +{20,129,177} +{98,120,135,157,164,168} +{66,150} +{101,101} +{164,187} +{43,65,96,166,189} +{18,36,58,109,118} +{25,32,135,161,170} +{55,104,183} +{69,139,144,181,182} +{84,131,155} +{6,18,63,156,159} +{7,66,67,88} +{8,46,52,95,178} +{58,58,83,119,119,163} +{27,143} +{78,80,122,149,164,176} +{6,83,107,183,198} +{86,199} +{22,74} +{28,62,64,114} +{15,56} +{41,97,139,152,161,161} +{48,192} +{16,62,99,138,155} +{32,84,145} +{108,137} +{93,112,120,155} +{73,117} +{20,26,197} +{4,141} +{110,132} +{95,133,142,152,183,193} +{85,141} +{53,76,86,131} +{5,59,73,74,101,130} +{0,1,64,151,188} +{15,131,131,174} +{80,98,106,187} +{41,102,167,173} +{9,42,133} +{103,110,110,134,175,185} +{168,187} +{42,47,108,121,165,198} +{81,171} +{38,122,123,149} +{16,79} +{45,64,131,176,182,197} +{35,82,87,100,123,196} +{41,52} +{33,68} +{60,140} +{12,41,152} +{54,71} +{88,95,95,146,148,180} +{47,66,89,121,168,182} +{15,70,94,122,137,154} +{42,48,129,162} +{70,151} +{11,55,89,118} +{36,74,121,130,152} +{46,48,52,120,179} +{70,81} +{96,146,183} +{76,78,108,153} +{71,168} +{66,106,108,167} +{22,44,49,85,87,195} +{17,52,143,175} +{86,103} +{16,46,176} +{95,111,162,173,198} +{44,191} +{7,48,107,115,116} +{12,120,141,179,184} +{83,188} +{83,85,86,139,154} +{50,74,89,154,179} +{79,87,120,128,183} +{13,121} +{16,52,62,86,168,199} +{7,16,29,35,157,181} +{23,48,65,119,180} +{10,173} +{7,98,128,143,145,162} +{23,27,88,91,127} +{35,53,56,56,118} +{7,161} +{0,42,67,174} +{44,161} +{75,80,110,170} +{17,93,117,140,168,196} +{18,100,150,184} +{108,132} +{54,90,97,103,149} +{9,12,30,43,82,95} +{131,163} +{67,99,168} +{91,150,172} +{47,164,195} +{72,90,98} +{24,78,130,172} +{1,27,32,64,66,156} +{7,26,72,88,130,140} +{56,126,130} +{1,76,81,122,169,188} +{60,154} +{101,103,135,150} +{22,25,33} +{99,117} +{24,95,122,186} +{48,95,102,108,125,170} +{13,113,154} +{155,177} +{37,73,106} +{7,64,124,195} +{101,124,133,157,166,188} +{27,34,60,100} +{26,104,163} +{34,43,108,133,165} +{64,79,89,122,132} +{10,96,168} +{2,22,89,118,122,198} +{122,192} +{42,101,104,135,168,181} +{7,38,63,86,101,152} +{29,84,89,114,123,184} +{33,46,59,137,153,175} +{3,54,66,92} +{31,34,148,159,185} +{3,52,97,99} +{3,26} +{42,57,62,148,199} +{15,26,198} +{14,34,109,111,128,193} +{107,197} +{16,107} +{9,21,136,169} +{67,97,99,153,165,173} +{46,76,89,100,164} +{96,102,150,167,180} +{31,103,137,146,180} +{21,40,157,163,170,183} +{139,170} +{1,75,82,148,169,198} +{13,39,107} +{13,50,97,101,106} +{52,176} +{18,169} +{129,140,146,183,189} +{95,122,145} +{5,6,102,130,151} +{5,118,140,153} +{27,78,140,164,182} +{36,140,148} +{58,100,127} +{9,16} +{26,33,119} +{1,17,18,165} +{14,182} +{11,13,48,89,140,165} +{9,19,78,113} +{121,171} +{18,23,46,113,159,162} +{17,104} +{50,104,132,167,179} +{55,89,102,132,176} +{19,109} +{60,70,73,153,163} +{18,127,145} +{80,106,146,170} +{10,39,72,74,84,150} +{3,71} +{1,10,64} +{82,95,127,132,141,152} +{43,55,57,89,120,197} +{155,182} +{23,34,57,111,153} +{99,188} +{86,114,124} +{113,191} +{31,129,184} +{125,159,159} +{22,27,81,156} +{3,54,80,122,128,168} +{76,112} +{152,174} +{22,27,70,172} +{26,86} +{49,59,102,186} +{53,55,75,125} +{152,199} +{11,15,46,102,105,168} +{132,148,154} +{24,114,121,126,138,165} +{82,107} +{36,93,122,184,194} +{1,59,76,146} +{73,165} +{38,98,176} +{53,72,121,153} +{127,147} +{31,77,128,177} +{107,186,189} +{119,126,127,160} +{24,74,148,197} +{85,126,134,146} +{76,77,81,134} +{67,112,159,174,183} +{22,169,170} +{79,112,177,199} +{1,56} +{21,42,50,172} +{6,63,105,166,189} +{31,95,106,152,171,177} +{21,49,99,101,122,187} +{63,104,113,161,186} +{37,126,144,166,173} +{32,53,147} +{123,123,130} +{78,85,177} +{2,69,95,146,187} +{6,11,14,43,121} +{76,105,184} +{63,96,114,122,195} +{11,22,34,45,120,156} +{22,83,119,131,138,167} +{9,56,96,106,114} +{92,132,162} +{25,45,83,119,139,150} +{19,21,56,59,141} +{14,26,62,119,180,190} +{6,34,49,99,139,170} +{10,56,150,166,166} +{14,57,119,153,167,198} +{26,41,150,158,169} +{152,167} +{1,61,93,180} +{46,110,138,199} +{4,56,81,110,173} +{28,32,148,185} +{8,9,28,29,39,195} +{14,39,68,144} +{26,37,79,81,110} +{115,158,161} +{6,39,145,191} +{67,118,125,142,184,198} +{127,163} +{52,118} +{22,78,131,156} +{46,68,86,142,145,197} +{85,188} +{37,54,64,147,158} +{31,134,141,183,185} +{10,33,135,198} +{41,124,173,180} +{0,14,92,129,154,198} +{39,73,128,154,182,196} +{40,83,94,168} +{106,142} +{76,99} +{19,62,77,108,165,186} +{68,90,97,119,176} +{44,108,193} +{2,124} +{137,174,175,176,180} +{28,62,81,132,165,186} +{98,112,148,181} +{86,125} +{70,161} +{5,13,188} +{136,168} +{82,87} +{30,42,57} +{132,136,152} +{20,59,87,98,195} +{6,53,112,113,183,195} +{64,147,157} +{61,140,192} +{44,59,88,123,161} +{90,175} +{38,46,105,121,159} +{35,62,66,90,155} +{2,2,21,38} +{123,144} +{117,155} +{60,86} +{4,39,129,146,179} +{66,71,87,135,148,157} +{29,67,108,196} +{30,64,76,124,172} +{36,39,79,130,140,149} +{30,44,136,196} +{5,15,20,117,198} +{20,87,87,121} +{42,136,142,148} +{0,56} +{16,38,56,57} +{52,138} +{103,115} +{10,29,43,93,120,134} +{44,140,150,180} +{74,98,132,160} +{2,62,98,160} +{14,32,43,63,92} +{23,87,128,152,177,197} +{30,86,111,178,180} +{49,61,114,195,196} +{133,158,195} +{18,105,165,190} +{77,83,175} +{29,33,51,166,188} +{37,51,96,103,127} +{119,125,128,140} +{8,80,93,189} +{76,96,110,131,170} +{81,90} +{13,25,28,41,128,142} +{56,62,73,110} +{60,62,128,136,166,193} +{34,34,61,74} +{32,84,87,92,112,181} +{10,66,93,153} +{23,77,182} +{2,7,156} +{5,13,49,61,103,179} +{67,136,136,163,181,196} +{26,60,74,100,160} +{39,59,69,93,111} +{9,77,90} +{1,20,52,75,156,169} +{25,95,103,157,163,193} +{95,136} +{47,108,137,157,164} +{37,99,151,153,169,189} +{112,126,139,171,184,195} +{39,188} +{4,20,71,80,136,156} +{24,33,77,82} +{103,188} +{74,116} +{82,90,110,154,194,195} +{25,149,180} +{120,123,130,171} +{20,38,104,126,175,176} +{14,62,97,130,135,193} +{35,118} +{20,42,64,73,76,120} +{11,40,60,74,144,148} +{13,26,46,63,76} +{24,29,98,106} +{6,139,171,186} +{5,109,197} +{20,45,84,125} +{1,137,150,195} +{1,8,80,111} +{57,90,102,167} +{53,186} +{8,31,115,145,156,165} +{10,18,31,116,164} +{43,47} +{33,143,154} +{106,153,174,190} +{73,106,158} +{18,137,158,173} +{73,80,107,123,141,199} +{17,43,123,130,130,155} +{15,31,37,91,164,181} +{38,86} +{49,105,142,145,173,190} +{18,107,108,135,138} +{43,65,107,112,193} +{8,68,68,74} +{54,106,108,109,164} +{53,153} +{59,134,154,173,180} +{34,93} +{11,33,124} +{8,104} +{27,37,46,65,125,174} +{0,122,189} +{15,74,107,147,188} +{35,63,78} +{28,49,123,129,177,193} +{11,89,104} +{117,171,197} +{11,15,62,136,145,145} +{2,127,193} +{17,28,42,113,145} +{31,44,118,148} +{52,103,128,161,182} +{45,47,70,102,161,184} +{15,52,82,86} +{60,87,102,108,127,170} +{24,57,102,145,181} +{12,53} +{5,52,92,129,164} +{87,128} +{80,143,170} +{59,85,134,139} +{61,67,110,117,156,157} +{6,8,60,112,154,170} +{92,122,133} +{121,148,161} +{9,22,61,187} +{12,40,78,107,176} +{30,45,58,189,198} +{83,107,123,148} +{3,66,98,124,126,150} +{13,34} +{16,41,132} +{16,85} +{3,25} +{30,58,138,167} +{24,36,87,151,159,186} +{2,4,121,196} +{79,95,99,107} +{11,49,146,169} +{51,90} +{76,155} +{26,26,116,120,146,182} +{44,66,72,117,132,174} +{7,161,179,197} +{2,81,158} +{4,22,59,107,146,170} +{0,0,133,192} +{57,82} +{17,61} +{28,29,42,77,89,124} +{53,78,127,188} +{31,57,103,104,162} +{9,84,100} +{3,52,114,133,161,188} +{8,37,97,158,189} +{0,13,88} +{29,79,92,158,160,171} +{59,63,77,139,165} +{25,77,116,169} +{50,88,151,166} +{52,162,167} +{32,149,191,194,194} +{47,57,74,95,97} +{30,65,96,153,184} +{80,130,150,172} +{79,91,141,153,157} +{93,110,114,194} +{62,66,156,175} +{55,56,97,117} +{74,152,171,186} +{13,24,50,50,131} +{0,16,95,141,146,161} +{1,51,158} +{37,71,96,122} +{71,104,145} +{47,52,124,131,169} +{111,188} +{59,61,95,152,156,157} +{5,31,106,164,176} +{44,82,113,134,188} +{13,55,65,99,150} +{25,73,130,192} +{88,120,193} +{79,123,153,175} +{24,158,162} +{52,53,81} +{5,32,78,102} +{73,97,111,151} +{71,72,102,151} +{5,61,73,85,129,151} +{66,177} +{26,77,139,152} +{46,117} +{55,72,122,148,157,174} +{3,53,76,184,196} +{34,36,41,61,194} +{8,153,163,182} +{51,59} +{113,115,149} +{54,57,78} +{39,137} +{75,81,93} +{5,30,44,80,86,126} +{68,107,128,160,179} +{98,108,162} +{55,126} +{24,54,121,122} +{75,90} +{10,83,139} +{16,120,148} +{97,175} +{53,70,71,120,135,189} +{9,110,123,150} +{24,42,44,96,138,170} +{17,61} +{23,65,110,135,155,157} +{19,59,139} +{50,65,127,179} +{15,138,152,162} +{15,34} +{25,29,63,135,161} +{47,113,123,129,163} +{25,138,157,184} +{50,92,199} +{110,116} +{15,36,134,145,165,182} +{4,75,82,175} +{24,49,63,89,128} +{174,182} +{103,116,119} +{101,125,180,192} +{47,66,113,127,148} +{15,60,118} +{20,51,90,91,117} +{25,72,146,199} +{34,93,199} +{31,71,106,115,186} +{1,10,119,144,188,197} +{49,80,185} +{134,178,188} +{42,67,170,172} +{13,43,91,91} +{13,31,48,98,155,158} +{37,44,70,76,141,160} +{50,60,72} +{51,65,166,188} +{11,103,129,144} +{136,167,181} +{165,178} +{34,107} +{54,120} +{33,132,136,165,178} +{60,79,119,127,187,197} +{27,31,130,132} +{125,129} +{97,111} +{71,171,187,191} +{68,91,94} +{94,119,159,178} +{2,29,51,173} +{37,61,97,113,147} +{11,35,79,91} +{67,71} +{4,20,103,107,169,179} +{35,77} +{71,94} +{29,31,67,101,172,174} +{52,122} +{87,125} +{129,142,164} +{13,30,85,139} +{17,57,65,170,179} +{46,65,151,167,192,197} +{31,78,132,136,158} +{38,161} +{15,101,111,134} +{42,118,139,142,178} +{57,95,132,134} +{5,42,116,152,173,192} +{144,199} +{38,70,77,143,175,188} +{38,84,93,149} +{56,98,153,165,170,191} +{1,52,112,112,131,145} +{16,132,150,184} +{14,60,111,153} +{49,109,112,165} +{69,136,152} +{59,90,94,158,168} +{42,47} +{18,194} +{33,70,94,167,175,177} +{40,57,125,138,159} +{3,10,31} +{2,5,8,26,141,181} +{27,29,142,175,186,195} +{31,49,99,120} +{109,123} +{21,76,112,119,124} +{41,49,146,173} +{101,173} +{49,73,85,89,179} +{22,36,154,192} +{136,163} +{111,165} +{94,128} +{81,167} +{35,165} +{41,109,119} +{13,74,80,114} +{72,106,189} +{65,172} +{30,31,35,52,63} +{80,116} +{0,149} +{139,189} +{0,65,107,153,179} +{15,40,46,51,75,160} +{12,28,48,79,105} +{76,98,146,157,180} +{45,62,79,83,113,155} +{130,162,184} +{78,140,145,181,196,198} +{108,168} +{3,13,14,15,77} +{22,29,68,117,142,143} +{67,110,122,167,183} +{22,25,58,93,143,151} +{53,82,170} +{1,18,50,98,108,174} +{58,140} +{49,179,196} +{109,171} +{38,82,132,183} +{32,151,175} +{53,90,106,169,187} +{99,136,141,146,171} +{27,108,111,155,192} +{28,77,86} +{11,109,118,149,154,183} +{7,74,122,137,185} +{70,110,151,154,175} +{7,48,88,181,181,182} +{97,101,105,123,139,156} +{19,139} +{17,107,134} +{63,64,178} +{100,133,143} +{64,173} +{1,88,109,120,145,160} +{113,198} +{84,112,121,184} +{90,185,193} +{91,135,155,185} +{56,191} +{14,15,48,61,92,171} +{18,139,152,199} +{16,80,107,125,144,166} +{8,92,112,173,176} +{27,196} +{9,169,183,190} +{20,29,40,98,106,182} +{77,115,149,181} +{31,65} +{7,29,62,90,157,178} +{10,33,79,186} +{42,74,113,178,192} +{17,86,88,118} +{27,58,104,122,166} +{16,97,102,105,192} +{16,59,115,127} +{27,56,60} +{104,175} +{52,84} +{127,137} +{7,13,18,81,139,140} +{11,31,81,150,189} +{44,55,107} +{45,58,127,137} +{70,76,80,93,145} +{27,60} +{40,76,172} +{7,123,192} +{55,170} +{61,137,137,184,187} +{49,50,190} +{99,126,152,164} +{56,79,88,98,132} +{45,74,119,123,158,175} +{66,96} +{100,114} +{62,84,111,122} +{8,22,141,172,181} +{70,141} +{3,48,106,193} +{33,114,168,174,183} +{46,186,194} +{58,71,82,122,190} +{60,67} +{14,30,132,144,174} +{9,113,124} +{11,14,29,63,110,182} +{4,64,102,168,178} +{90,108,110,160,165,199} +{44,86,191} +{6,19,84,125,125,156} +{53,105,122,154,175,190} +{83,177,183} +{96,103,181} +{38,156} +{2,6,60,116,131} +{12,144} +{13,73,93,132} +{142,167} +{37,61,71,75,121,144} +{32,43,146} +{41,59,144,176} +{11,14,44,54,92,177} +{37,198} +{39,80,81,104,138,193} +{13,73,92,127,149,194} +{34,57,69,104,118,186} +{7,48,84,96,108} +{32,41,64,111} +{108,131,150,174,195} +{50,53,184,191} +{8,32} +{26,76,88} +{4,50,100,134,134} +{36,40,148,158,177} +{7,16,57,59} +{35,96,113,129,167} +{46,63,128,163} +{8,46,94,97,105,178} +{12,70} +{45,93,134,135,188,195} +{11,52,76,103,131,192} +{19,45,57,119,123,136} +{19,62} +{1,49,64,197} +{0,42,60,102,134,147} +{102,152,156,160} +{51,54,129} +{50,68,71,72,170} +{0,11,184} +{19,105} +{144,185,191} +{17,51,76,98,118,135} +{52,64,143,171} +{1,46,62,74,81} +{8,36,129} +{5,25,96,113,146,152} +{19,28,59,110,131,142} +{7,18,176,179} +{17,21,48,63,121} +{34,79,81,85,152,155} +{8,82,104,122,139,193} +{34,50,128,140,175} +{51,173} +{48,128,138} +{126,129,178} +{42,51,61,141,170,180} +{59,91,144} +{64,74,118,170,191} +{12,55,116,157,159} +{97,157} +{32,34,102,105,178} +{36,103,125} +{15,36,184} +{6,13} +{0,100,144,185,198} +{32,47,64,66,118,143} +{23,112,117} +{34,44,47,81,124,135} +{21,49,115} +{29,158} +{34,114,127,151} +{111,199} +{23,53,76,113,122,123} +{89,113,117,137} +{52,76,126,155,164} +{4,48,78,114,147,179} +{27,56,151,191} +{3,183} +{30,41,72,145} +{15,41,152,177,196} +{44,58,124,164,177} +{9,51,70,174} +{13,18,81,136,178} +{85,139,142} +{12,62,118,156} +{50,142,149,175} +{35,38,99,100,128} +{53,54,92,123,153,160} +{121,133} +{12,63,117,148,149,187} +{88,153,170,192,195} +{22,51,67,104,141} +{186,198} +{39,40,82,159,189} +{59,74,149} +{88,99,136,145,191} +{5,48,90,120,138,193} +{22,76,155,180} +{118,122,141,176} +{87,104,116,159,172,191} +{63,104,155} +{8,153,168} +{119,141,178,179} +{100,110} +{14,65,164} +{2,92,97,117,188} +{47,59,64,141,148,187} +{109,137,139,151,169} +{68,78,156} +{37,39,103,183,190,194} +{50,58,74,180} +{12,121,155,175} +{26,43,97} +{102,159,161} +{3,138,163,179} +{55,69,78,164} +{67,87,136} +{67,150} +{74,113,199} +{103,126,187} +{39,141,155} +{6,19,25,75,157} +{10,49,71,105,114,154} +{3,24,35,54,88} +{16,25,73,114,181,191} +{2,2,63,154} +{68,74,107,187,199} +{13,235} +{40,122,203,232,233,235} +{115,152,193,202,242} +{3,50,86,111,248} +{25,66,181,188,279} +{80,116} +{38,83,106,119,134} +{29,63,203} +{7,27,186,200,201} +{88,92,94,272,295} +{35,68,136,158} +{148,225,293} +{1,87,195} +{48,100,203} +{0,35,61,91,280} +{130,160,168,216} +{4,104,148,164} +{35,40,91,145,155,214} +{46,107} +{21,276} +{42,143,150,238,291} +{64,70,140,165,228,257} +{0,148,197} +{72,131,195,202,251,270} +{99,195,224,264,292} +{5,184,186,243} +{93,132,137,148,228,251} +{66,125,198,211,285} +{29,79,180} +{41,60,65,66,254} +{4,69,79,207} +{113,182,203,252,259,298} +{10,20} +{99,200,276} +{109,262} +{4,87,193,207,220,273} +{30,183,215} +{7,138,202,215,217} +{25,79,194,203,260} +{128,178} +{62,152,211,279} +{57,99,204,280} +{41,59} +{18,52,200} +{81,132,190,275} +{89,158} +{32,72,122,228,245,249} +{24,72,196,233,299} +{0,5,46,122,213} +{197,242} +{43,105,241,272} +{74,118,158,173,208,288} +{145,149,197,238,252,297} +{32,39,189} +{98,240} +{65,140,149,197,203,204} +{103,225,266} +{84,277,283} +{35,246} +{10,101,239} +{40,75,192,253} +{106,152,247,272,287} +{50,293} +{85,134} +{59,204} +{54,64,88,269,286} +{4,92,111,121,154,182} +{80,163,202,234,277,298} +{129,147,158,196,283,290} +{49,144,232,293} +{20,29,226,244,274} +{64,101,185,189,234,268} +{23,157} +{56,93,133} +{9,57,241,289} +{50,124,181,194,238} +{11,38,67,69,213} +{149,220} +{168,189,267} +{34,133,235,264,284} +{81,239,241,260} +{35,78,80,201,262,297} +{0,196,285} +{71,108,239,258,277,278} +{4,94} +{77,132,140,251} +{11,78,132} +{43,145,188} +{97,144,148,161,254} +{109,132} +{48,83,189,242} +{115,176,276} +{162,210} +{88,109,136,153,154,159} +{265,280} +{74,86,195} +{17,112,188,213,231,266} +{36,136,160,218,239} +{179,273} +{79,118,136,154,200,259} +{161,212} +{24,98,178} +{161,187} +{45,169,227,236} +{218,253} +{10,18,74,258} +{70,199,210,213,285,291} +{12,50,69,92,184,186} +{130,131,163,295} +{198,239,297} +{49,86,125,176,234,282} +{7,129,146,223,269} +{144,173} +{30,52,133,228} +{21,88,176} +{5,70,299} +{37,69,285} +{14,17,45,72,99,197} +{125,196} +{30,220} +{55,103,127,251} +{108,114,156,200,207,217} +{7,195,250} +{64,111,193,202,236} +{92,115,232,233,240} +{22,232,260} +{18,44,191,253,294} +{40,158} +{86,92,103,154,207,294} +{33,177,191,223,235} +{65,116,158,253} +{49,125,152,194} +{100,149,191,266,288} +{13,64,103,254,283} +{42,75,80,103,155} +{77,128,198,280} +{118,218,287} +{0,36,52,101,148} +{1,64,181,201,221} +{6,44,47,71,150,225} +{13,85,88,167} +{31,40,69,91,99,281} +{60,115,157,224,252,273} +{30,87,200,270,285} +{171,293} +{24,33} +{59,69,74,118,190,216} +{147,258,288} +{62,73,219,232,266} +{50,74,225,238,271} +{6,88,115,185,205,262} +{97,230} +{76,76,150,211,228,262} +{134,195} +{104,235} +{38,41,204} +{64,71,124} +{44,63,111,231} +{186,188} +{5,132,225} +{113,286} +{43,161,276} +{8,294} +{18,90,105,169} +{213,213} +{29,45,161,289} +{79,152} +{10,110,162,177,217,238} +{63,98,192,244} +{118,147,187,281} +{5,15,36,94,263} +{40,81,220} +{29,74,76,157,162,206} +{11,28,53,68,126,222} +{73,73,181,239} +{36,60,164} +{16,47,82,152,167,289} +{149,149,219,268,294} +{97,169} +{32,160,210,257} +{32,69} +{7,63,73,195} +{54,110} +{61,75,135,270} +{22,43,127,174,184,208} +{106,113,174} +{0,70,90,239} +{191,260} +{43,80,168} +{25,54,257,263} +{118,213} +{110,207,220,251,287} +{126,139,161,248,252} +{51,79,116,132,190,291} +{183,199,200,254} +{86,233} +{105,109,176,211} +{12,109} +{3,65,158} +{21,86} +{12,15,191} +{181,223,224,256,259,276} +{112,191,219,232,239} +{51,215} +{36,46,278} +{68,75,169,228,244,270} +{10,16,52,172,189,274} +{177,191,197,209,222,282} +{41,119,190,202} +{128,277,292,298} +{34,38} +{22,36,81,117} +{81,161,248,279} +{75,85,103,149,190,211} +{127,279} +{50,74,152} +{122,168,209,240,276,282} +{66,102,208,239,291} +{9,113} +{72,199,237} +{110,112,135,141,270} +{26,109,130,159,291} +{108,206} +{2,289} +{63,238} +{4,57,104,119,142,214} +{46,97,239} +{210,297} +{207,268} +{13,64,80} +{62,109,171,195,232} +{11,260,262,276,292} +{21,75,78,80,140,226} +{38,56} +{122,251,297} +{108,180,213} +{57,58,135,231,233} +{75,136,185,211} +{52,109,122,174,178,255} +{65,91,234,249} +{5,24,53,218} +{90,211,246} +{106,242,260} +{61,136} +{49,87,177,280} +{38,89,104,189,297} +{43,76,293,298} +{182,255,289} +{25,57,64,272} +{23,122,149} +{49,50,129,153} +{183,188,204} +{27,164,226,230} +{0,13,67,145,160,259} +{22,32,43,85,105,124} +{20,199} +{31,119} +{14,16,152,158,196} +{5,59,91,202,217,280} +{100,128,187} +{20,193,214,258,272} +{17,27,55,151,177,219} +{53,55,63,208,213,230} +{15,160,258,260} +{71,147,235,258} +{26,49,173,234,271} +{50,52,58,167,257} +{15,154,213,232} +{6,35,86,94,286} +{0,4,83,262,281} +{93,148,284} +{28,165,262,290} +{18,99,160,266} +{63,223,291,295} +{103,154,180} +{12,110,144,221} +{9,158,203} +{20,207,275} +{9,20,48,88,120,289} +{67,110,133,151,225,297} +{71,102} +{168,208} +{48,137,163,164,280,287} +{90,209} +{28,244} +{107,224,293} +{86,206} +{8,113,147,165,285,286} +{7,159,160,237} +{0,66,87,146,225,294} +{58,100,112,124,189} +{13,108} +{121,168,216,253} +{147,242,282} +{236,240} +{21,28,83,103,166} +{30,88,108,280,295} +{23,136,298} +{125,290} +{140,249,276,277} +{49,81,135,147,164,267} +{28,63,198,297} +{30,101,216,232,267,287} +{54,195,204,223,236,251} +{27,176,179,204,264,291} +{136,164,172,273} +{43,67,81,121,277} +{128,131,256,269} +{176,219,289} +{127,175,259} +{35,94,153,177,222,253} +{29,154,178,240,260} +{165,176,201,243,259} +{17,298} +{29,203,232,241,289} +{107,136,153,238} +{49,198} +{68,179,202,253} +{157,178} +{23,199,287} +{131,228} +{19,19,39,111,138,277} +{49,86,178,194,223,226} +{114,201} +{149,282} +{109,147,150,176,209,229} +{122,131,167,228,258} +{5,40,120,154,266} +{135,207,238,263} +{75,128} +{80,117,296} +{60,82,122,131,138} +{57,146,159,233,244,278} +{15,80,157,182,244,272} +{114,116,160,176,287} +{10,133,279} +{27,115,126,293} +{89,161} +{95,120,218} +{26,269} +{109,281} +{53,62,103,107,118,239} +{185,186,227,252} +{3,125,146,161,288} +{171,245,256,283} +{23,153,201,238} +{0,82,93,218,242} +{101,124,137,150,194} +{21,96,104,201,244,266} +{88,121,147,155,173,225} +{24,106,112,193} +{26,67,115,212,283} +{23,120,280} +{45,99} +{30,66,136,199} +{17,213} +{14,37,55,103,265} +{52,258,284} +{119,213,272,274,285} +{43,45,105,254,288} +{64,81,123,126,164,292} +{88,229,260} +{25,117} +{7,149,197,227,258} +{74,83,240,246,284,292} +{2,4,63,103,115,289} +{92,239} +{12,26,130,228,265} +{53,99,131,142,164,291} +{63,248,259,283} +{186,215,282} +{67,110,160} +{166,191} +{33,156,224} +{152,166,190,250,297} +{123,126,153,199,204} +{49,70,199,238,238,289} +{14,18,65,74,146,235} +{63,77,172,180,186,225} +{1,48,105,170} +{37,56,113,133,196} +{193,261,266} +{190,273} +{38,129,261} +{251,252,253,254,275,296} +{249,275} +{167,205,266} +{27,152,256} +{19,72,248} +{40,73,141,249} +{105,197} +{156,243,277,282} +{165,168,227,298} +{8,31,202,271} +{10,101,109,167,236,277} +{33,91,165,192,206,211} +{102,122,232} +{190,239,283} +{160,185} +{2,13,65,70} +{11,68,170,192,229,284} +{66,90,228,237} +{1,6,92,99,222,242} +{42,128,133,207,289} +{12,100,164,191} +{26,31,120,176,204,220} +{13,39,95,105,120,182} +{114,120,295} +{31,34,55,181,197,235} +{24,52,64,80,142} +{3,49,148,255,268} +{132,175,254} +{32,71,141} +{112,116,186,270,271} +{64,106,209,228,297} +{128,268} +{107,208,299} +{151,173,187,192,213} +{3,296} +{20,31,135,153,289} +{138,193,212,269,277,288} +{73,92,130,295} +{73,80,105} +{50,96,138,199,265} +{4,7,8,183,260,267} +{66,71,118,145} +{15,63,116,160,175,181} +{88,217} +{56,69,106,106,127,274} +{84,205} +{83,101,241,269} +{21,254} +{22,32,83,150,293} +{198,221} +{30,46,95,179,197} +{46,85,208} +{56,112,236} +{71,217} +{31,57,145,253} +{34,133,170} +{48,53,119,187,268,287} +{111,203,229,239} +{62,136} +{49,54,187,254,298} +{20,26,148,159,190,286} +{3,13,193,252,284} +{40,137,154,167,248,259} +{3,47,242,278} +{77,100,143,232} +{51,130} +{66,90,148,220,242,273} +{143,151,211} +{10,23} +{21,30,179} +{17,47,105,156,193,213} +{0,23,25,125,144,146} +{179,209} +{79,113,117,192} +{5,53,216,275,285} +{187,197} +{22,68,218,221} +{0,71,78,110,120,173} +{46,97,117,149,253,286} +{10,20,129,162,171,195} +{60,97,130,163,190} +{57,145,179,283} +{99,274} +{151,161,228,251} +{3,177,192,286} +{21,81,142} +{180,283} +{13,102,131,149,246} +{19,99,132,162,167,257} +{15,86,188,260} +{203,251,281} +{5,45,138,155,157} +{1,2,4,213,278} +{21,123,208,219,263,267} +{36,106,181,231,238} +{103,120,168,184,224,287} +{53,104,139,251} +{1,91,141,202,268} +{75,115,216,253} +{56,167,268,296} +{66,158,235,249} +{82,124,198} +{56,67,112,140,170,176} +{16,75,266} +{38,165,200,219,291,297} +{86,151,229,241,275} +{0,57,141,176,229,258} +{18,72,164,195,235} +{94,282} +{83,139,242,269,294} +{9,44,145,251,272} +{132,203,249,282} +{7,41,170,254} +{6,153,193,291} +{18,134,137,227,261} +{14,36,115,124,172,229} +{54,206} +{49,91,131,185,204} +{7,242} +{41,57,161} +{93,224,241,288} +{119,288} +{90,99,117,196,296} +{67,85,154} +{147,169,216,264} +{79,92,164} +{19,120,132,197,267} +{76,264} +{30,133} +{27,37,93,138,218} +{152,155,244} +{41,149,182,259} +{29,178,224} +{115,201,268} +{141,166,253,282} +{3,65,125,245,264} +{6,150,159,202,206,277} +{217,276} +{28,96,144,193} +{7,59,190} +{144,217} +{10,79,96,100,126,222} +{7,61,253} +{14,69,263} +{3,30,63,125,186,277} +{2,10,79,100,223} +{131,131,239} +{116,195,199,240} +{87,99,158} +{52,180} +{7,12,140,208,275} +{65,67,83,280} +{4,52,125,126,137,176} +{9,48,79,203,217,243} +{43,206,251} +{19,112,196,263,266} +{29,70,256} +{161,236,258} +{8,25,42,97,291} +{63,144,242,271} +{7,17} +{1,85,250} +{104,244,250} +{18,22,31,99,266,281} +{51,138,237,268,288} +{8,40,91,221,273} +{0,176,230,249,254,255} +{44,140,176,194,197} +{56,197,264} +{229,246,283} +{53,128,173,233,282} +{45,193,221} +{21,80,286} +{4,18,267} +{15,97,220} +{62,70,83,147,149,244} +{120,134,159,174,250} +{116,269} +{23,108} +{10,91,239} +{7,128,142,243,286} +{134,201,245,275,278} +{13,208,227,288} +{30,78,85} +{107,179} +{31,59,153,217,240,298} +{27,130,233,282,286} +{15,59,136,262} +{85,186,233} +{10,152,165,181,181} +{137,183} +{40,56,125,256,265,280} +{12,22,120,183} +{62,229} +{38,59,81,113,261} +{67,194,229} +{7,173} +{37,43,296} +{59,162,285} +{171,200,213,213} +{116,123,209,234,277} +{52,175} +{189,213} +{30,94,99,228,238} +{46,101,154,260,272,274} +{30,32,59} +{65,172,292} +{18,22,131,170,271} +{2,53,88,104,264,265} +{60,194,288} +{15,108,121,161,201} +{40,85,173,195,201,221} +{54,86,107,174,287} +{20,71,190,227} +{16,46,66,175,197,252} +{130,243,252,282} +{142,219,266,272} +{14,202,204,231,241,276} +{161,172,212,222} +{15,183,275} +{83,270} +{67,204} +{65,184,264} +{73,119,183,190,242} +{53,287} +{24,171} +{72,220,220} +{101,136,176,204,224,280} +{39,47,282} +{106,162,238,252} +{23,242,247,265} +{98,108,189,209,273} +{122,245,270} +{109,127,128,244,299} +{41,162,186,191} +{60,196} +{0,123,129,213,248} +{29,79,89,91} +{172,298} +{122,140,162,228,263,268} +{2,116,247,294} +{6,138} +{17,98,287} +{53,166,187,219,248,296} +{15,26,90,175,196} +{184,193,198} +{17,69,76,105,183,264} +{56,101,110} +{15,108,139,168,272} +{5,71,104,141} +{136,179} +{72,189} +{54,79,208} +{98,113,150,184,190,246} +{37,69,132,210,285} +{1,29,45,74,109,145} +{11,72,133,149,216} +{34,57,84,212,280} +{131,211,294} +{70,84,173} +{193,213,230,266,285,299} +{57,94,163,182,227} +{44,133,143} +{31,32,211} +{130,142,165,188,194,231} +{52,61,139,226,239,287} +{7,103,157} +{155,224,230} +{127,135,139} +{77,237,294} +{10,213,278} +{28,90,185,274} +{59,105,282,297} +{39,128,174,268} +{32,158,215} +{24,145,189,213,278} +{78,148,230,263} +{42,68,93,160,287,299} +{4,12,70,91,191,237} +{20,294} +{45,53,77,113,211,240} +{232,237} +{125,152,284} +{58,81,155,215,296} +{4,8,44} +{1,52,102,128,184,218} +{185,199,226,299} +{10,178,262,285} +{80,95,230,240,266} +{4,5,213} +{156,187,271,298} +{88,298} +{109,233,290} +{47,65,91,105,249,269} +{97,129} +{46,92,207} +{2,163,249,259,291} +{89,102,140,158,231} +{162,184,283} +{36,213} +{163,259} +{47,220,250} +{37,89,105,124,143,198} +{3,71} +{142,165,190,256,269,269} +{152,256} +{27,49,191,198,220,285} +{71,73,87,189,260} +{11,54,90,106,130,216} +{193,245,252} +{2,8,57,91,163,184} +{18,171,283} +{28,41,110,112} +{5,57} +{137,262,285} +{19,57,156,229,269} +{138,179,190,199,281} +{35,98,196,242} +{122,152} +{83,132,181,212,280,288} +{219,298} +{57,88,103} +{5,203} +{98,156,266} +{10,45,72,169,211} +{45,101,156,214,269} +{68,73,81} +{16,127,259} +{9,32,246} +{66,173,261,261,274} +{17,115,157,169,251} +{49,158} +{25,37} +{2,73,103,178,194,236} +{238,269,273} +{162,178,276} +{48,52,160,237,288} +{54,82,130,135,169,275} +{29,142} +{205,249,253,275,291} +{60,76,84,115,126} +{48,108,153,213,231} +{23,124,175,210,226,293} +{9,181} +{20,99,112,166,201,242} +{102,150,201} +{41,98,240,244,260} +{7,44,98,293} +{0,125,177,283} +{28,118,124,148,241,290} +{73,91,122} +{9,72,109,130,202,290} +{70,111,120,160,216,262} +{59,175,296} +{2,201} +{83,297} +{76,293} +{83,127,136,242,275,285} +{169,190,195} +{83,122,186,189,217,229} +{98,210,229} +{117,133} +{74,294} +{6,31,59,143,156,273} +{98,180,241} +{26,52,114,243} +{112,240} +{104,217} +{148,162,259,279} +{92,101,150,226,272,295} +{55,86,118,202,237,275} +{81,203} +{79,126,177,265} +{57,193} +{169,240,244} +{21,171,190,250,263} +{23,37,215,235} +{40,54,240,286} +{105,177,190,276,285} +{44,45,122,151} +{28,31,187} +{127,135,211} +{5,13,150,194,259} +{136,181,280} +{20,147,158,189,200} +{15,83,88,128,169} +{10,14,25,26,150,158} +{42,101,172,205} +{85,185,226,236,271} +{34,127,188,250,268} +{27,143} +{26,48,99,110,117,207} +{22,56,190,269,287} +{200,278} +{70,134,138,204,216,298} +{175,219,297} +{99,273} +{206,216} +{23,214} +{131,140} +{11,140,240} +{73,148} +{7,66,125,210} +{2,61,92} +{0,137} +{143,188,265} +{177,238} +{0,93,163,229} +{35,49} +{8,8,111,144,165} +{99,278} +{21,44,71,224,252,270} +{119,150,175,233,245,294} +{15,87} +{84,211,217,225} +{20,41,87,123,124,299} +{62,120,169} +{37,43,92,175,206,222} +{95,168,180,250,269,296} +{60,228,278,285} +{173,195,232,276} +{1,2,139,256,278} +{51,119} +{212,238,291} +{120,172,292} +{138,279} +{251,261} +{151,181,278,296} +{163,207,220,289,295} +{89,278,290} +{24,137,157,206,271,278} +{7,63,83,89,155,189} +{2,5,172,195,215,260} +{243,281} +{60,125} +{74,87,222,236} +{45,70,159,194} +{69,159,250} +{150,214,296} +{101,158,250} +{56,134} +{57,87,160,167,247,285} +{123,269} +{235,242} +{79,95,115,167,287} +{31,56,132,244,276} +{25,218,241,241} +{57,82,151,170,204} +{69,103,288} +{88,138,154,292} +{14,98,138,227,245,249} +{175,222,274} +{38,139,193,208,277} +{79,141} +{5,77,197,209} +{15,37,77,110,116} +{26,226} +{68,93,101,140,233} +{53,96,170,192,290} +{29,89,102,216,220} +{11,85,136,239} +{158,180,195,200,226} +{10,49,118,137,172} +{144,172,183} +{14,176,188,215,272} +{42,97,125} +{114,166} +{52,61,162,171,249} +{140,195,242} +{59,99,233} +{31,76,136,181,187} +{81,112,157,168,271,294} +{8,35,44,48,190,297} +{145,195,201} +{160,248,291} +{94,270,285} +{116,139,225} +{111,131,140} +{158,277} +{59,229,257} +{25,47,99,123,239} +{8,36,205,274,295} +{132,152,178,192,235} +{19,40,96,204} +{7,77} +{211,282} +{26,100,180,244,281,296} +{200,212,286} +{5,94,151,290} +{75,80,128,179,269,269} +{7,111} +{7,26,69,158,269,276} +{7,36,74,94,171,215} +{2,62,65,93,124,271} +{78,96,109,189} +{182,197,280,298} +{17,78,82,85,85,208} +{6,122,155} +{14,33,130} +{1,21,167,169} +{49,85,158,175,213} +{59,194} +{125,132,259,285} +{20,38,81,89,234,274} +{106,140,156,287} +{57,125} +{53,103,158,204,234,267} +{0,49,160,189,235} +{34,115,142,207} +{162,173,181,190,298} +{11,76,116,166,191} +{2,87,99,236,279} +{40,203} +{2,33,39,215,254} +{53,69,83,224,228} +{79,136,183,216,226,227} +{10,109,137,163,240} +{24,126,141} +{69,255} +{103,138,230,246,259,283} +{136,290} +{13,34,78,145,166,242} +{38,74,83,242,294} +{54,248,273} +{107,162} +{50,170,176,191,207,275} +{32,134,166,288,292} +{163,167,186,274,291,296} +{31,86,123,156,160} +{114,133,136,176,281,290} +{105,147,211} +{124,151,179,222,299} +{87,101} +{145,169,181,205,247} +{6,266} +{26,33,52,56,106,116} +{19,21,65,89,104,168} +{164,181,208} +{36,67,92,116,248} +{145,200,247} +{155,215} +{49,212} +{29,57,105,117,131} +{2,13,68,128,139,140} +{193,273,273} +{3,78,105,111,297} +{49,142,244} +{32,259} +{161,205} +{96,146,179,259} +{44,45,211,233} +{56,91,146,166,285} +{87,107,120,262,299} +{76,160,276,297} +{248,266} +{5,12,188,240,247} +{164,206,293} +{15,18,60,163} +{53,134,172,230,287,290} +{117,137,146,153,155} +{72,270} +{171,251} +{80,125,137,141,169} +{52,108,200,219,225,271} +{29,78,106,221} +{21,74,110,273} +{28,88,98,170} +{83,104} +{12,152} +{7,69,143,246,265,269} +{62,106,157,200} +{113,260,272,272,294} +{16,35,80,121,165,176} +{96,154,172,198,263} +{29,53,109,128,129,195} +{131,230,271,273,295,299} +{53,160,208,231} +{23,180,208,249,272} +{45,208,264} +{14,29,169} +{116,147,272} +{7,193,237,271} +{158,198,253} +{41,60,71} +{110,133,200,249} +{24,159,255} +{26,39,61,114,218,229} +{141,286,299} +{74,278} +{67,71,155} +{151,257,284} +{13,28,72,131,206} +{60,152,275,295} +{88,105,184,185} +{85,190,205,256,283,285} +{202,285} +{14,92,160,200,246,279} +{42,95,157,195} +{50,99,224,276} +{32,97,101,122} +{66,85} +{19,146,180,242,269,286} +{24,86,247,274} +{54,264,270,284} +{72,77,85,124,127,285} +{47,249} +{25,73,102,237} +{33,68,84,117,120} +{29,62,172,240,242,273} +{42,140,182,248,261,282} +{118,228,284} +{1,89,158,294} +{29,89,122,155,208,283} +{173,208,229} +{6,22,142,267,299} +{22,122,173,245,293} +{1,2,101,102,201,202,2147483647} diff --git a/contrib/intarray/expected/_int.out b/contrib/intarray/expected/_int.out new file mode 100644 index 0000000..b39ab82 --- /dev/null +++ b/contrib/intarray/expected/_int.out @@ -0,0 +1,971 @@ +CREATE EXTENSION intarray; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + amname | opcname +--------+--------- +(0 rows) + +SELECT intset(1234); + intset +-------- + {1234} +(1 row) + +SELECT icount('{1234234,234234}'); + icount +-------- + 2 +(1 row) + +SELECT sort('{1234234,-30,234234}'); + sort +---------------------- + {-30,234234,1234234} +(1 row) + +SELECT sort('{1234234,-30,234234}','asc'); + sort +---------------------- + {-30,234234,1234234} +(1 row) + +SELECT sort('{1234234,-30,234234}','desc'); + sort +---------------------- + {1234234,234234,-30} +(1 row) + +SELECT sort_asc('{1234234,-30,234234}'); + sort_asc +---------------------- + {-30,234234,1234234} +(1 row) + +SELECT sort_desc('{1234234,-30,234234}'); + sort_desc +---------------------- + {1234234,234234,-30} +(1 row) + +SELECT uniq('{1234234,-30,-30,234234,-30}'); + uniq +-------------------------- + {1234234,-30,234234,-30} +(1 row) + +SELECT uniq(sort_asc('{1234234,-30,-30,234234,-30}')); + uniq +---------------------- + {-30,234234,1234234} +(1 row) + +SELECT idx('{1234234,-30,-30,234234,-30}',-30); + idx +----- + 2 +(1 row) + +SELECT subarray('{1234234,-30,-30,234234,-30}',2,3); + subarray +------------------ + {-30,-30,234234} +(1 row) + +SELECT subarray('{1234234,-30,-30,234234,-30}',-1,1); + subarray +---------- + {-30} +(1 row) + +SELECT subarray('{1234234,-30,-30,234234,-30}',0,-1); + subarray +-------------------------- + {1234234,-30,-30,234234} +(1 row) + +SELECT #'{1234234,234234}'::int[]; + ?column? +---------- + 2 +(1 row) + +SELECT '{123,623,445}'::int[] + 1245; + ?column? +-------------------- + {123,623,445,1245} +(1 row) + +SELECT '{123,623,445}'::int[] + 445; + ?column? +------------------- + {123,623,445,445} +(1 row) + +SELECT '{123,623,445}'::int[] + '{1245,87,445}'; + ?column? +--------------------------- + {123,623,445,1245,87,445} +(1 row) + +SELECT '{123,623,445}'::int[] - 623; + ?column? +----------- + {123,445} +(1 row) + +SELECT '{123,623,445}'::int[] - '{1623,623}'; + ?column? +----------- + {123,445} +(1 row) + +SELECT '{123,623,445}'::int[] | 623; + ?column? +--------------- + {123,445,623} +(1 row) + +SELECT '{123,623,445}'::int[] | 1623; + ?column? +-------------------- + {123,445,623,1623} +(1 row) + +SELECT '{123,623,445}'::int[] | '{1623,623}'; + ?column? +-------------------- + {123,445,623,1623} +(1 row) + +SELECT '{123,623,445}'::int[] & '{1623,623}'; + ?column? +---------- + {623} +(1 row) + +SELECT '{-1,3,1}'::int[] & '{1,2}'; + ?column? +---------- + {1} +(1 row) + +SELECT '{1}'::int[] & '{2}'::int[]; + ?column? +---------- + {} +(1 row) + +SELECT array_dims('{1}'::int[] & '{2}'::int[]); + array_dims +------------ + +(1 row) + +SELECT ('{1}'::int[] & '{2}'::int[]) = '{}'::int[]; + ?column? +---------- + t +(1 row) + +SELECT ('{}'::int[] & '{}'::int[]) = '{}'::int[]; + ?column? +---------- + t +(1 row) + +--test query_int +SELECT '1'::query_int; + query_int +----------- + 1 +(1 row) + +SELECT ' 1'::query_int; + query_int +----------- + 1 +(1 row) + +SELECT '1 '::query_int; + query_int +----------- + 1 +(1 row) + +SELECT ' 1 '::query_int; + query_int +----------- + 1 +(1 row) + +SELECT ' ! 1 '::query_int; + query_int +----------- + !1 +(1 row) + +SELECT '!1'::query_int; + query_int +----------- + !1 +(1 row) + +SELECT '1|2'::query_int; + query_int +----------- + 1 | 2 +(1 row) + +SELECT '1|!2'::query_int; + query_int +----------- + 1 | !2 +(1 row) + +SELECT '!1|2'::query_int; + query_int +----------- + !1 | 2 +(1 row) + +SELECT '!1|!2'::query_int; + query_int +----------- + !1 | !2 +(1 row) + +SELECT '!(!1|!2)'::query_int; + query_int +-------------- + !( !1 | !2 ) +(1 row) + +SELECT '!(!1|2)'::query_int; + query_int +------------- + !( !1 | 2 ) +(1 row) + +SELECT '!(1|!2)'::query_int; + query_int +------------- + !( 1 | !2 ) +(1 row) + +SELECT '!(1|2)'::query_int; + query_int +------------ + !( 1 | 2 ) +(1 row) + +SELECT '1&2'::query_int; + query_int +----------- + 1 & 2 +(1 row) + +SELECT '!1&2'::query_int; + query_int +----------- + !1 & 2 +(1 row) + +SELECT '1&!2'::query_int; + query_int +----------- + 1 & !2 +(1 row) + +SELECT '!1&!2'::query_int; + query_int +----------- + !1 & !2 +(1 row) + +SELECT '(1&2)'::query_int; + query_int +----------- + 1 & 2 +(1 row) + +SELECT '1&(2)'::query_int; + query_int +----------- + 1 & 2 +(1 row) + +SELECT '!(1)&2'::query_int; + query_int +----------- + !1 & 2 +(1 row) + +SELECT '!(1&2)'::query_int; + query_int +------------ + !( 1 & 2 ) +(1 row) + +SELECT '1|2&3'::query_int; + query_int +----------- + 1 | 2 & 3 +(1 row) + +SELECT '1|(2&3)'::query_int; + query_int +----------- + 1 | 2 & 3 +(1 row) + +SELECT '(1|2)&3'::query_int; + query_int +--------------- + ( 1 | 2 ) & 3 +(1 row) + +SELECT '1|2&!3'::query_int; + query_int +------------ + 1 | 2 & !3 +(1 row) + +SELECT '1|!2&3'::query_int; + query_int +------------ + 1 | !2 & 3 +(1 row) + +SELECT '!1|2&3'::query_int; + query_int +------------ + !1 | 2 & 3 +(1 row) + +SELECT '!1|(2&3)'::query_int; + query_int +------------ + !1 | 2 & 3 +(1 row) + +SELECT '!(1|2)&3'::query_int; + query_int +---------------- + !( 1 | 2 ) & 3 +(1 row) + +SELECT '(!1|2)&3'::query_int; + query_int +---------------- + ( !1 | 2 ) & 3 +(1 row) + +SELECT '1|(2|(4|(5|6)))'::query_int; + query_int +------------------------------- + 1 | ( 2 | ( 4 | ( 5 | 6 ) ) ) +(1 row) + +SELECT '1|2|4|5|6'::query_int; + query_int +------------------------------- + ( ( ( 1 | 2 ) | 4 ) | 5 ) | 6 +(1 row) + +SELECT '1&(2&(4&(5&6)))'::query_int; + query_int +------------------- + 1 & 2 & 4 & 5 & 6 +(1 row) + +SELECT '1&2&4&5&6'::query_int; + query_int +------------------- + 1 & 2 & 4 & 5 & 6 +(1 row) + +SELECT '1&(2&(4&(5|6)))'::query_int; + query_int +----------------------- + 1 & 2 & 4 & ( 5 | 6 ) +(1 row) + +SELECT '1&(2&(4&(5|!6)))'::query_int; + query_int +------------------------ + 1 & 2 & 4 & ( 5 | !6 ) +(1 row) + +-- test non-error-throwing input +SELECT str as "query_int", + pg_input_is_valid(str,'query_int') as ok, + errinfo.sql_error_code, + errinfo.message, + errinfo.detail, + errinfo.hint +FROM (VALUES ('1&(2&(4&(5|6)))'), + ('1#(2&(4&(5&6)))'), + ('foo')) + AS a(str), + LATERAL pg_input_error_info(a.str, 'query_int') as errinfo; + query_int | ok | sql_error_code | message | detail | hint +-----------------+----+----------------+--------------+--------+------ + 1&(2&(4&(5|6))) | t | | | | + 1#(2&(4&(5&6))) | f | 42601 | syntax error | | + foo | f | 42601 | syntax error | | +(3 rows) + +CREATE TABLE test__int( a int[] ); +\copy test__int from 'data/test__int.data' +ANALYZE test__int; +SELECT count(*) from test__int WHERE a && '{23,50}'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23|50'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{23,50}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23&50'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; + count +------- + 10 +(1 row) + +SELECT count(*) from test__int WHERE a = '{73,23,20}'; + count +------- + 1 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '50&68'; + count +------- + 9 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '20 | !21'; + count +------- + 6567 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + count +------- + 6344 +(1 row) + +SET enable_seqscan = off; -- not all of these would use index by default +CREATE INDEX text_idx on test__int using gist ( a gist__int_ops ); +SELECT count(*) from test__int WHERE a && '{23,50}'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23|50'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{23,50}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23&50'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; + count +------- + 10 +(1 row) + +SELECT count(*) from test__int WHERE a = '{73,23,20}'; + count +------- + 1 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '50&68'; + count +------- + 9 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '20 | !21'; + count +------- + 6567 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + count +------- + 6344 +(1 row) + +INSERT INTO test__int SELECT array(SELECT x FROM generate_series(1, 1001) x); -- should fail +ERROR: input array is too big (199 maximum allowed, 1001 current), use gist__intbig_ops opclass instead +DROP INDEX text_idx; +CREATE INDEX text_idx on test__int using gist (a gist__int_ops(numranges = 0)); +ERROR: value 0 out of bounds for option "numranges" +DETAIL: Valid values are between "1" and "252". +CREATE INDEX text_idx on test__int using gist (a gist__int_ops(numranges = 253)); +ERROR: value 253 out of bounds for option "numranges" +DETAIL: Valid values are between "1" and "252". +CREATE INDEX text_idx on test__int using gist (a gist__int_ops(numranges = 252)); +SELECT count(*) from test__int WHERE a && '{23,50}'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23|50'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{23,50}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23&50'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; + count +------- + 10 +(1 row) + +SELECT count(*) from test__int WHERE a = '{73,23,20}'; + count +------- + 1 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '50&68'; + count +------- + 9 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '20 | !21'; + count +------- + 6567 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + count +------- + 6344 +(1 row) + +DROP INDEX text_idx; +CREATE INDEX text_idx on test__int using gist (a gist__intbig_ops(siglen = 0)); +ERROR: value 0 out of bounds for option "siglen" +DETAIL: Valid values are between "1" and "2024". +CREATE INDEX text_idx on test__int using gist (a gist__intbig_ops(siglen = 2025)); +ERROR: value 2025 out of bounds for option "siglen" +DETAIL: Valid values are between "1" and "2024". +CREATE INDEX text_idx on test__int using gist (a gist__intbig_ops(siglen = 2024)); +SELECT count(*) from test__int WHERE a && '{23,50}'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23|50'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{23,50}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23&50'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; + count +------- + 10 +(1 row) + +SELECT count(*) from test__int WHERE a = '{73,23,20}'; + count +------- + 1 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '50&68'; + count +------- + 9 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '20 | !21'; + count +------- + 6567 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + count +------- + 6344 +(1 row) + +DROP INDEX text_idx; +CREATE INDEX text_idx on test__int using gist ( a gist__intbig_ops ); +SELECT count(*) from test__int WHERE a && '{23,50}'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23|50'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{23,50}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23&50'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; + count +------- + 10 +(1 row) + +SELECT count(*) from test__int WHERE a = '{73,23,20}'; + count +------- + 1 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '50&68'; + count +------- + 9 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '20 | !21'; + count +------- + 6567 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + count +------- + 6344 +(1 row) + +DROP INDEX text_idx; +CREATE INDEX text_idx on test__int using gin ( a gin__int_ops ); +SELECT count(*) from test__int WHERE a && '{23,50}'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23|50'; + count +------- + 403 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{23,50}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '23&50'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}'; + count +------- + 12 +(1 row) + +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; + count +------- + 10 +(1 row) + +SELECT count(*) from test__int WHERE a = '{73,23,20}'; + count +------- + 1 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '50&68'; + count +------- + 9 +(1 row) + +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; + count +------- + 21 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '20 | !21'; + count +------- + 6567 +(1 row) + +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + count +------- + 6344 +(1 row) + +DROP INDEX text_idx; +-- Repeat the same queries with an extended data set. The data set is the +-- same that we used before, except that each element in the array is +-- repeated three times, offset by 1000 and 2000. For example, {1, 5} +-- becomes {1, 1001, 2001, 5, 1005, 2005}. +-- +-- That has proven to be unreasonably effective at exercising codepaths in +-- core GiST code related to splitting parent pages, which is not covered by +-- other tests. This is a bit out-of-place as the point is to test core GiST +-- code rather than this extension, but there is no suitable GiST opclass in +-- core that would reach the same codepaths. +CREATE TABLE more__int AS SELECT + -- Leave alone NULLs, empty arrays and the one row that we use to test + -- equality; also skip INT_MAX + CASE WHEN a IS NULL OR a = '{}' OR a = '{73,23,20}' THEN a ELSE + (select array_agg(u) || array_agg(u + 1000) || array_agg(u + 2000) + from unnest(a) u where u < 2000000000) + END AS a, a as b + FROM test__int; +CREATE INDEX ON more__int using gist (a gist__int_ops(numranges = 252)); +SELECT count(*) from more__int WHERE a && '{23,50}'; + count +------- + 403 +(1 row) + +SELECT count(*) from more__int WHERE a @@ '23|50'; + count +------- + 403 +(1 row) + +SELECT count(*) from more__int WHERE a @> '{23,50}'; + count +------- + 12 +(1 row) + +SELECT count(*) from more__int WHERE a @@ '23&50'; + count +------- + 12 +(1 row) + +SELECT count(*) from more__int WHERE a @> '{20,23}'; + count +------- + 12 +(1 row) + +SELECT count(*) from more__int WHERE a <@ '{73,23,20}'; + count +------- + 10 +(1 row) + +SELECT count(*) from more__int WHERE a = '{73,23,20}'; + count +------- + 1 +(1 row) + +SELECT count(*) from more__int WHERE a @@ '50&68'; + count +------- + 9 +(1 row) + +SELECT count(*) from more__int WHERE a @> '{20,23}' or a @> '{50,68}'; + count +------- + 21 +(1 row) + +SELECT count(*) from more__int WHERE a @@ '(20&23)|(50&68)'; + count +------- + 21 +(1 row) + +SELECT count(*) from more__int WHERE a @@ '20 | !21'; + count +------- + 6567 +(1 row) + +SELECT count(*) from more__int WHERE a @@ '!20 & !21'; + count +------- + 6344 +(1 row) + +RESET enable_seqscan; diff --git a/contrib/intarray/intarray--1.0--1.1.sql b/contrib/intarray/intarray--1.0--1.1.sql new file mode 100644 index 0000000..fecebdd --- /dev/null +++ b/contrib/intarray/intarray--1.0--1.1.sql @@ -0,0 +1,49 @@ +/* contrib/intarray/intarray--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION intarray UPDATE TO '1.1'" to load this file. \quit + +CREATE FUNCTION _int_matchsel(internal, oid, internal, integer) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE; + +ALTER OPERATOR @@ (_int4, query_int) SET (RESTRICT = _int_matchsel); +ALTER OPERATOR ~~ (query_int, _int4) SET (RESTRICT = _int_matchsel); + +CREATE FUNCTION _int_overlap_sel(internal, oid, internal, integer) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION _int_contains_sel(internal, oid, internal, integer) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION _int_contained_sel(internal, oid, internal, integer) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION _int_overlap_joinsel(internal, oid, internal, smallint, internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION _int_contains_joinsel(internal, oid, internal, smallint, internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE; + +CREATE FUNCTION _int_contained_joinsel(internal, oid, internal, smallint, internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE; + +ALTER OPERATOR && (_int4, _int4) SET (RESTRICT = _int_overlap_sel, JOIN = _int_overlap_joinsel); +ALTER OPERATOR @> (_int4, _int4) SET (RESTRICT = _int_contains_sel, JOIN = _int_contains_joinsel); +ALTER OPERATOR <@ (_int4, _int4) SET (RESTRICT = _int_contained_sel, JOIN = _int_contained_joinsel); + +ALTER OPERATOR @ (_int4, _int4) SET (RESTRICT = _int_contains_sel, JOIN = _int_contains_joinsel); +ALTER OPERATOR ~ (_int4, _int4) SET (RESTRICT = _int_contained_sel, JOIN = _int_contained_joinsel); diff --git a/contrib/intarray/intarray--1.1--1.2.sql b/contrib/intarray/intarray--1.1--1.2.sql new file mode 100644 index 0000000..919340e --- /dev/null +++ b/contrib/intarray/intarray--1.1--1.2.sql @@ -0,0 +1,94 @@ +/* contrib/intarray/intarray--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION intarray UPDATE TO '1.2'" to load this file. \quit + +-- Update procedure signatures the hard way. +-- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, +-- wherein the signatures have been updated already. In that case to_regprocedure() will +-- return NULL and no updates will happen. +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) +FROM (VALUES +(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types +('g_int_consistent(internal,_int4,int4,oid,internal)', '{internal,_int4,int2,oid,internal}'), +('g_intbig_consistent(internal,internal,int4,oid,internal)', '{internal,_int4,int2,oid,internal}'), +('g_intbig_same(internal,internal,internal)', '{SCH.intbig_gkey,SCH.intbig_gkey,internal}'), +('ginint4_queryextract(internal,internal,int2,internal,internal,internal,internal)', '{_int4,internal,int2,internal,internal,internal,internal}'), +('ginint4_consistent(internal,int2,internal,int4,internal,internal,internal,internal)', '{internal,int2,_int4,int4,internal,internal,internal,internal}') +) AS update_data (oldproc, newtypestext), +LATERAL ( + SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ +) ls +WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + +UPDATE pg_catalog.pg_proc SET + prorettype = (my_schema || '.intbig_gkey')::pg_catalog.regtype +WHERE oid = pg_catalog.to_regprocedure(my_schema || '.g_intbig_union(internal,internal)'); + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; + +ALTER FUNCTION bqarr_in(cstring) PARALLEL SAFE; +ALTER FUNCTION bqarr_out(query_int) PARALLEL SAFE; +ALTER FUNCTION querytree(query_int) PARALLEL SAFE; +ALTER FUNCTION boolop(_int4, query_int) PARALLEL SAFE; +ALTER FUNCTION rboolop(query_int, _int4) PARALLEL SAFE; +ALTER FUNCTION _int_matchsel(internal, oid, internal, integer) PARALLEL SAFE; +ALTER FUNCTION _int_contains(_int4, _int4) PARALLEL SAFE; +ALTER FUNCTION _int_contained(_int4, _int4) PARALLEL SAFE; +ALTER FUNCTION _int_overlap(_int4, _int4) PARALLEL SAFE; +ALTER FUNCTION _int_same(_int4, _int4) PARALLEL SAFE; +ALTER FUNCTION _int_different(_int4, _int4) PARALLEL SAFE; +ALTER FUNCTION _int_union(_int4, _int4) PARALLEL SAFE; +ALTER FUNCTION _int_inter(_int4, _int4) PARALLEL SAFE; +ALTER FUNCTION _int_overlap_sel(internal, oid, internal, integer) PARALLEL SAFE; +ALTER FUNCTION _int_contains_sel(internal, oid, internal, integer) PARALLEL SAFE; +ALTER FUNCTION _int_contained_sel(internal, oid, internal, integer) PARALLEL SAFE; +ALTER FUNCTION _int_overlap_joinsel(internal, oid, internal, smallint, internal) PARALLEL SAFE; +ALTER FUNCTION _int_contains_joinsel(internal, oid, internal, smallint, internal) PARALLEL SAFE; +ALTER FUNCTION _int_contained_joinsel(internal, oid, internal, smallint, internal) PARALLEL SAFE; +ALTER FUNCTION intset(int4) PARALLEL SAFE; +ALTER FUNCTION icount(_int4) PARALLEL SAFE; +ALTER FUNCTION sort(_int4, text) PARALLEL SAFE; +ALTER FUNCTION sort(_int4) PARALLEL SAFE; +ALTER FUNCTION sort_asc(_int4) PARALLEL SAFE; +ALTER FUNCTION sort_desc(_int4) PARALLEL SAFE; +ALTER FUNCTION uniq(_int4) PARALLEL SAFE; +ALTER FUNCTION idx(_int4, int4) PARALLEL SAFE; +ALTER FUNCTION subarray(_int4, int4, int4) PARALLEL SAFE; +ALTER FUNCTION subarray(_int4, int4) PARALLEL SAFE; +ALTER FUNCTION intarray_push_elem(_int4, int4) PARALLEL SAFE; +ALTER FUNCTION intarray_push_array(_int4, _int4) PARALLEL SAFE; +ALTER FUNCTION intarray_del_elem(_int4, int4) PARALLEL SAFE; +ALTER FUNCTION intset_union_elem(_int4, int4) PARALLEL SAFE; +ALTER FUNCTION intset_subtract(_int4, _int4) PARALLEL SAFE; +ALTER FUNCTION g_int_consistent(internal, _int4, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION g_int_compress(internal) PARALLEL SAFE; +ALTER FUNCTION g_int_decompress(internal) PARALLEL SAFE; +ALTER FUNCTION g_int_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION g_int_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION g_int_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION g_int_same(_int4, _int4, internal) PARALLEL SAFE; +ALTER FUNCTION _intbig_in(cstring) PARALLEL SAFE; +ALTER FUNCTION _intbig_out(intbig_gkey) PARALLEL SAFE; +ALTER FUNCTION g_intbig_consistent(internal, _int4, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION g_intbig_compress(internal) PARALLEL SAFE; +ALTER FUNCTION g_intbig_decompress(internal) PARALLEL SAFE; +ALTER FUNCTION g_intbig_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION g_intbig_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION g_intbig_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION g_intbig_same(intbig_gkey, intbig_gkey, internal) PARALLEL SAFE; +ALTER FUNCTION ginint4_queryextract(_int4, internal, int2, internal, internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION ginint4_consistent(internal, int2, _int4, int4, internal, internal, internal, internal) PARALLEL SAFE; diff --git a/contrib/intarray/intarray--1.2--1.3.sql b/contrib/intarray/intarray--1.2--1.3.sql new file mode 100644 index 0000000..790d159 --- /dev/null +++ b/contrib/intarray/intarray--1.2--1.3.sql @@ -0,0 +1,20 @@ +/* contrib/intarray/intarray--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION intarray UPDATE TO '1.3'" to load this file. \quit + +CREATE FUNCTION g_int_options(internal) +RETURNS void +AS 'MODULE_PATHNAME', 'g_int_options' +LANGUAGE C IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION g_intbig_options(internal) +RETURNS void +AS 'MODULE_PATHNAME', 'g_intbig_options' +LANGUAGE C IMMUTABLE PARALLEL SAFE; + +ALTER OPERATOR FAMILY gist__int_ops USING gist +ADD FUNCTION 10 (_int4) g_int_options (internal); + +ALTER OPERATOR FAMILY gist__intbig_ops USING gist +ADD FUNCTION 10 (_int4) g_intbig_options (internal); diff --git a/contrib/intarray/intarray--1.2.sql b/contrib/intarray/intarray--1.2.sql new file mode 100644 index 0000000..f10b53d --- /dev/null +++ b/contrib/intarray/intarray--1.2.sql @@ -0,0 +1,520 @@ +/* contrib/intarray/intarray--1.2.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION intarray" to load this file. \quit + +-- +-- Create the user-defined type for the 1-D integer arrays (_int4) +-- + +-- Query type +CREATE FUNCTION bqarr_in(cstring) +RETURNS query_int +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION bqarr_out(query_int) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE TYPE query_int ( + INTERNALLENGTH = -1, + INPUT = bqarr_in, + OUTPUT = bqarr_out +); + +--only for debug +CREATE FUNCTION querytree(query_int) +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + + +CREATE FUNCTION boolop(_int4, query_int) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION boolop(_int4, query_int) IS 'boolean operation with array'; + +CREATE FUNCTION rboolop(query_int, _int4) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION rboolop(query_int, _int4) IS 'boolean operation with array'; + +CREATE FUNCTION _int_matchsel(internal, oid, internal, integer) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; + +CREATE OPERATOR @@ ( + LEFTARG = _int4, + RIGHTARG = query_int, + PROCEDURE = boolop, + COMMUTATOR = '~~', + RESTRICT = _int_matchsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ~~ ( + LEFTARG = query_int, + RIGHTARG = _int4, + PROCEDURE = rboolop, + COMMUTATOR = '@@', + RESTRICT = _int_matchsel, + JOIN = contjoinsel +); + + +-- +-- External C-functions for R-tree methods +-- + +-- Comparison methods + +CREATE FUNCTION _int_contains(_int4, _int4) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION _int_contains(_int4, _int4) IS 'contains'; + +CREATE FUNCTION _int_contained(_int4, _int4) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION _int_contained(_int4, _int4) IS 'contained in'; + +CREATE FUNCTION _int_overlap(_int4, _int4) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION _int_overlap(_int4, _int4) IS 'overlaps'; + +CREATE FUNCTION _int_same(_int4, _int4) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION _int_same(_int4, _int4) IS 'same as'; + +CREATE FUNCTION _int_different(_int4, _int4) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION _int_different(_int4, _int4) IS 'different'; + +-- support routines for indexing + +CREATE FUNCTION _int_union(_int4, _int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _int_inter(_int4, _int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _int_overlap_sel(internal, oid, internal, integer) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; + +CREATE FUNCTION _int_contains_sel(internal, oid, internal, integer) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; + +CREATE FUNCTION _int_contained_sel(internal, oid, internal, integer) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; + +CREATE FUNCTION _int_overlap_joinsel(internal, oid, internal, smallint, internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; + +CREATE FUNCTION _int_contains_joinsel(internal, oid, internal, smallint, internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; + +CREATE FUNCTION _int_contained_joinsel(internal, oid, internal, smallint, internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; + +-- +-- OPERATORS +-- + +CREATE OPERATOR && ( + LEFTARG = _int4, + RIGHTARG = _int4, + PROCEDURE = _int_overlap, + COMMUTATOR = '&&', + RESTRICT = _int_overlap_sel, + JOIN = _int_overlap_joinsel +); + +--CREATE OPERATOR = ( +-- LEFTARG = _int4, +-- RIGHTARG = _int4, +-- PROCEDURE = _int_same, +-- COMMUTATOR = '=', +-- NEGATOR = '<>', +-- RESTRICT = eqsel, +-- JOIN = eqjoinsel, +-- SORT1 = '<', +-- SORT2 = '<' +--); + +--CREATE OPERATOR <> ( +-- LEFTARG = _int4, +-- RIGHTARG = _int4, +-- PROCEDURE = _int_different, +-- COMMUTATOR = '<>', +-- NEGATOR = '=', +-- RESTRICT = neqsel, +-- JOIN = neqjoinsel +--); + +CREATE OPERATOR @> ( + LEFTARG = _int4, + RIGHTARG = _int4, + PROCEDURE = _int_contains, + COMMUTATOR = '<@', + RESTRICT = _int_contains_sel, + JOIN = _int_contains_joinsel +); + +CREATE OPERATOR <@ ( + LEFTARG = _int4, + RIGHTARG = _int4, + PROCEDURE = _int_contained, + COMMUTATOR = '@>', + RESTRICT = _int_contained_sel, + JOIN = _int_contained_joinsel +); + +-- obsolete: +CREATE OPERATOR @ ( + LEFTARG = _int4, + RIGHTARG = _int4, + PROCEDURE = _int_contains, + COMMUTATOR = '~', + RESTRICT = _int_contains_sel, + JOIN = _int_contains_joinsel +); + +CREATE OPERATOR ~ ( + LEFTARG = _int4, + RIGHTARG = _int4, + PROCEDURE = _int_contained, + COMMUTATOR = '@', + RESTRICT = _int_contained_sel, + JOIN = _int_contained_joinsel +); + +-------------- +CREATE FUNCTION intset(int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION icount(_int4) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR # ( + RIGHTARG = _int4, + PROCEDURE = icount +); + +CREATE FUNCTION sort(_int4, text) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION sort(_int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION sort_asc(_int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION sort_desc(_int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION uniq(_int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION idx(_int4, int4) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR # ( + LEFTARG = _int4, + RIGHTARG = int4, + PROCEDURE = idx +); + +CREATE FUNCTION subarray(_int4, int4, int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION subarray(_int4, int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION intarray_push_elem(_int4, int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR + ( + LEFTARG = _int4, + RIGHTARG = int4, + PROCEDURE = intarray_push_elem +); + +CREATE FUNCTION intarray_push_array(_int4, _int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR + ( + LEFTARG = _int4, + RIGHTARG = _int4, + COMMUTATOR = +, + PROCEDURE = intarray_push_array +); + +CREATE FUNCTION intarray_del_elem(_int4, int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR - ( + LEFTARG = _int4, + RIGHTARG = int4, + PROCEDURE = intarray_del_elem +); + +CREATE FUNCTION intset_union_elem(_int4, int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR | ( + LEFTARG = _int4, + RIGHTARG = int4, + PROCEDURE = intset_union_elem +); + +CREATE OPERATOR | ( + LEFTARG = _int4, + RIGHTARG = _int4, + COMMUTATOR = |, + PROCEDURE = _int_union +); + +CREATE FUNCTION intset_subtract(_int4, _int4) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR - ( + LEFTARG = _int4, + RIGHTARG = _int4, + PROCEDURE = intset_subtract +); + +CREATE OPERATOR & ( + LEFTARG = _int4, + RIGHTARG = _int4, + COMMUTATOR = &, + PROCEDURE = _int_inter +); +-------------- + +-- define the GiST support methods +CREATE FUNCTION g_int_consistent(internal,_int4,smallint,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_int_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_int_decompress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_int_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_int_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_int_union(internal, internal) +RETURNS _int4 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_int_same(_int4, _int4, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + + +-- Create the operator class for indexing + +CREATE OPERATOR CLASS gist__int_ops +DEFAULT FOR TYPE _int4 USING gist AS + OPERATOR 3 &&, + OPERATOR 6 = (anyarray, anyarray), + OPERATOR 7 @>, + OPERATOR 8 <@, + OPERATOR 13 @, + OPERATOR 14 ~, + OPERATOR 20 @@ (_int4, query_int), + FUNCTION 1 g_int_consistent (internal, _int4, smallint, oid, internal), + FUNCTION 2 g_int_union (internal, internal), + FUNCTION 3 g_int_compress (internal), + FUNCTION 4 g_int_decompress (internal), + FUNCTION 5 g_int_penalty (internal, internal, internal), + FUNCTION 6 g_int_picksplit (internal, internal), + FUNCTION 7 g_int_same (_int4, _int4, internal); + + +--------------------------------------------- +-- intbig +--------------------------------------------- +-- define the GiST support methods + +CREATE FUNCTION _intbig_in(cstring) +RETURNS intbig_gkey +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _intbig_out(intbig_gkey) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE TYPE intbig_gkey ( + INTERNALLENGTH = -1, + INPUT = _intbig_in, + OUTPUT = _intbig_out +); + +CREATE FUNCTION g_intbig_consistent(internal,_int4,smallint,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_intbig_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_intbig_decompress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_intbig_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_intbig_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_intbig_union(internal, internal) +RETURNS intbig_gkey +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION g_intbig_same(intbig_gkey, intbig_gkey, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- register the opclass for indexing (not as default) + +CREATE OPERATOR CLASS gist__intbig_ops +FOR TYPE _int4 USING gist +AS + OPERATOR 3 &&, + OPERATOR 6 = (anyarray, anyarray), + OPERATOR 7 @>, + OPERATOR 8 <@, + OPERATOR 13 @, + OPERATOR 14 ~, + OPERATOR 20 @@ (_int4, query_int), + FUNCTION 1 g_intbig_consistent (internal, _int4, smallint, oid, internal), + FUNCTION 2 g_intbig_union (internal, internal), + FUNCTION 3 g_intbig_compress (internal), + FUNCTION 4 g_intbig_decompress (internal), + FUNCTION 5 g_intbig_penalty (internal, internal, internal), + FUNCTION 6 g_intbig_picksplit (internal, internal), + FUNCTION 7 g_intbig_same (intbig_gkey, intbig_gkey, internal), + STORAGE intbig_gkey; + +--GIN + +CREATE FUNCTION ginint4_queryextract(_int4, internal, int2, internal, internal, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ginint4_consistent(internal, int2, _int4, int4, internal, internal, internal, internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR CLASS gin__int_ops +FOR TYPE _int4 USING gin +AS + OPERATOR 3 &&, + OPERATOR 6 = (anyarray, anyarray), + OPERATOR 7 @>, + OPERATOR 8 <@, + OPERATOR 13 @, + OPERATOR 14 ~, + OPERATOR 20 @@ (_int4, query_int), + FUNCTION 1 btint4cmp (int4, int4), + FUNCTION 2 ginarrayextract (anyarray, internal, internal), + FUNCTION 3 ginint4_queryextract (_int4, internal, int2, internal, internal, internal, internal), + FUNCTION 4 ginint4_consistent (internal, int2, _int4, int4, internal, internal, internal, internal), + STORAGE int4; diff --git a/contrib/intarray/intarray--1.3--1.4.sql b/contrib/intarray/intarray--1.3--1.4.sql new file mode 100644 index 0000000..3fbebb5 --- /dev/null +++ b/contrib/intarray/intarray--1.3--1.4.sql @@ -0,0 +1,21 @@ +/* contrib/intarray/intarray--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION intarray UPDATE TO '1.4'" to load this file. \quit + +-- Remove <@ from the GiST opclasses, as it's not usefully indexable +-- due to mishandling of empty arrays. (It's OK in GIN.) + +ALTER OPERATOR FAMILY gist__int_ops USING gist +DROP OPERATOR 8 (_int4, _int4); + +ALTER OPERATOR FAMILY gist__intbig_ops USING gist +DROP OPERATOR 8 (_int4, _int4); + +-- Likewise for the old spelling ~. + +ALTER OPERATOR FAMILY gist__int_ops USING gist +DROP OPERATOR 14 (_int4, _int4); + +ALTER OPERATOR FAMILY gist__intbig_ops USING gist +DROP OPERATOR 14 (_int4, _int4); diff --git a/contrib/intarray/intarray--1.4--1.5.sql b/contrib/intarray/intarray--1.4--1.5.sql new file mode 100644 index 0000000..2454ebc --- /dev/null +++ b/contrib/intarray/intarray--1.4--1.5.sql @@ -0,0 +1,8 @@ +/* contrib/intarray/intarray--1.4--1.5.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION intarray UPDATE TO '1.5'" to load this file. \quit + +-- Remove @ and ~ +DROP OPERATOR @ (_int4, _int4); +DROP OPERATOR ~ (_int4, _int4); diff --git a/contrib/intarray/intarray.control b/contrib/intarray/intarray.control new file mode 100644 index 0000000..c3ff753 --- /dev/null +++ b/contrib/intarray/intarray.control @@ -0,0 +1,6 @@ +# intarray extension +comment = 'functions, operators, and index support for 1-D arrays of integers' +default_version = '1.5' +module_pathname = '$libdir/_int' +relocatable = true +trusted = true diff --git a/contrib/intarray/meson.build b/contrib/intarray/meson.build new file mode 100644 index 0000000..17459ae --- /dev/null +++ b/contrib/intarray/meson.build @@ -0,0 +1,45 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +intarray_sources = files( + '_int_bool.c', + '_int_gin.c', + '_int_gist.c', + '_int_op.c', + '_int_selfuncs.c', + '_int_tool.c', + '_intbig_gist.c', +) + +if host_system == 'windows' + intarray_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', '_int', + '--FILEDESC', 'intarray - functions and operators for arrays of integers',]) +endif + +intarray = shared_module('_int', + intarray_sources, + kwargs: contrib_mod_args, +) +contrib_targets += intarray + +install_data( + 'intarray.control', + 'intarray--1.0--1.1.sql', + 'intarray--1.1--1.2.sql', + 'intarray--1.2.sql', + 'intarray--1.2--1.3.sql', + 'intarray--1.3--1.4.sql', + 'intarray--1.4--1.5.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'intarray', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + '_int', + ], + }, +} diff --git a/contrib/intarray/sql/_int.sql b/contrib/intarray/sql/_int.sql new file mode 100644 index 0000000..2d4ed1c --- /dev/null +++ b/contrib/intarray/sql/_int.sql @@ -0,0 +1,234 @@ +CREATE EXTENSION intarray; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +SELECT intset(1234); +SELECT icount('{1234234,234234}'); +SELECT sort('{1234234,-30,234234}'); +SELECT sort('{1234234,-30,234234}','asc'); +SELECT sort('{1234234,-30,234234}','desc'); +SELECT sort_asc('{1234234,-30,234234}'); +SELECT sort_desc('{1234234,-30,234234}'); +SELECT uniq('{1234234,-30,-30,234234,-30}'); +SELECT uniq(sort_asc('{1234234,-30,-30,234234,-30}')); +SELECT idx('{1234234,-30,-30,234234,-30}',-30); +SELECT subarray('{1234234,-30,-30,234234,-30}',2,3); +SELECT subarray('{1234234,-30,-30,234234,-30}',-1,1); +SELECT subarray('{1234234,-30,-30,234234,-30}',0,-1); + +SELECT #'{1234234,234234}'::int[]; +SELECT '{123,623,445}'::int[] + 1245; +SELECT '{123,623,445}'::int[] + 445; +SELECT '{123,623,445}'::int[] + '{1245,87,445}'; +SELECT '{123,623,445}'::int[] - 623; +SELECT '{123,623,445}'::int[] - '{1623,623}'; +SELECT '{123,623,445}'::int[] | 623; +SELECT '{123,623,445}'::int[] | 1623; +SELECT '{123,623,445}'::int[] | '{1623,623}'; +SELECT '{123,623,445}'::int[] & '{1623,623}'; +SELECT '{-1,3,1}'::int[] & '{1,2}'; +SELECT '{1}'::int[] & '{2}'::int[]; +SELECT array_dims('{1}'::int[] & '{2}'::int[]); +SELECT ('{1}'::int[] & '{2}'::int[]) = '{}'::int[]; +SELECT ('{}'::int[] & '{}'::int[]) = '{}'::int[]; + + +--test query_int +SELECT '1'::query_int; +SELECT ' 1'::query_int; +SELECT '1 '::query_int; +SELECT ' 1 '::query_int; +SELECT ' ! 1 '::query_int; +SELECT '!1'::query_int; +SELECT '1|2'::query_int; +SELECT '1|!2'::query_int; +SELECT '!1|2'::query_int; +SELECT '!1|!2'::query_int; +SELECT '!(!1|!2)'::query_int; +SELECT '!(!1|2)'::query_int; +SELECT '!(1|!2)'::query_int; +SELECT '!(1|2)'::query_int; +SELECT '1&2'::query_int; +SELECT '!1&2'::query_int; +SELECT '1&!2'::query_int; +SELECT '!1&!2'::query_int; +SELECT '(1&2)'::query_int; +SELECT '1&(2)'::query_int; +SELECT '!(1)&2'::query_int; +SELECT '!(1&2)'::query_int; +SELECT '1|2&3'::query_int; +SELECT '1|(2&3)'::query_int; +SELECT '(1|2)&3'::query_int; +SELECT '1|2&!3'::query_int; +SELECT '1|!2&3'::query_int; +SELECT '!1|2&3'::query_int; +SELECT '!1|(2&3)'::query_int; +SELECT '!(1|2)&3'::query_int; +SELECT '(!1|2)&3'::query_int; +SELECT '1|(2|(4|(5|6)))'::query_int; +SELECT '1|2|4|5|6'::query_int; +SELECT '1&(2&(4&(5&6)))'::query_int; +SELECT '1&2&4&5&6'::query_int; +SELECT '1&(2&(4&(5|6)))'::query_int; +SELECT '1&(2&(4&(5|!6)))'::query_int; + +-- test non-error-throwing input + +SELECT str as "query_int", + pg_input_is_valid(str,'query_int') as ok, + errinfo.sql_error_code, + errinfo.message, + errinfo.detail, + errinfo.hint +FROM (VALUES ('1&(2&(4&(5|6)))'), + ('1#(2&(4&(5&6)))'), + ('foo')) + AS a(str), + LATERAL pg_input_error_info(a.str, 'query_int') as errinfo; + + + +CREATE TABLE test__int( a int[] ); +\copy test__int from 'data/test__int.data' +ANALYZE test__int; + +SELECT count(*) from test__int WHERE a && '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23|50'; +SELECT count(*) from test__int WHERE a @> '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23&50'; +SELECT count(*) from test__int WHERE a @> '{20,23}'; +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; +SELECT count(*) from test__int WHERE a = '{73,23,20}'; +SELECT count(*) from test__int WHERE a @@ '50&68'; +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; +SELECT count(*) from test__int WHERE a @@ '20 | !21'; +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + +SET enable_seqscan = off; -- not all of these would use index by default + +CREATE INDEX text_idx on test__int using gist ( a gist__int_ops ); + +SELECT count(*) from test__int WHERE a && '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23|50'; +SELECT count(*) from test__int WHERE a @> '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23&50'; +SELECT count(*) from test__int WHERE a @> '{20,23}'; +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; +SELECT count(*) from test__int WHERE a = '{73,23,20}'; +SELECT count(*) from test__int WHERE a @@ '50&68'; +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; +SELECT count(*) from test__int WHERE a @@ '20 | !21'; +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + +INSERT INTO test__int SELECT array(SELECT x FROM generate_series(1, 1001) x); -- should fail + +DROP INDEX text_idx; +CREATE INDEX text_idx on test__int using gist (a gist__int_ops(numranges = 0)); +CREATE INDEX text_idx on test__int using gist (a gist__int_ops(numranges = 253)); +CREATE INDEX text_idx on test__int using gist (a gist__int_ops(numranges = 252)); + +SELECT count(*) from test__int WHERE a && '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23|50'; +SELECT count(*) from test__int WHERE a @> '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23&50'; +SELECT count(*) from test__int WHERE a @> '{20,23}'; +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; +SELECT count(*) from test__int WHERE a = '{73,23,20}'; +SELECT count(*) from test__int WHERE a @@ '50&68'; +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; +SELECT count(*) from test__int WHERE a @@ '20 | !21'; +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + +DROP INDEX text_idx; +CREATE INDEX text_idx on test__int using gist (a gist__intbig_ops(siglen = 0)); +CREATE INDEX text_idx on test__int using gist (a gist__intbig_ops(siglen = 2025)); +CREATE INDEX text_idx on test__int using gist (a gist__intbig_ops(siglen = 2024)); + +SELECT count(*) from test__int WHERE a && '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23|50'; +SELECT count(*) from test__int WHERE a @> '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23&50'; +SELECT count(*) from test__int WHERE a @> '{20,23}'; +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; +SELECT count(*) from test__int WHERE a = '{73,23,20}'; +SELECT count(*) from test__int WHERE a @@ '50&68'; +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; +SELECT count(*) from test__int WHERE a @@ '20 | !21'; +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + +DROP INDEX text_idx; +CREATE INDEX text_idx on test__int using gist ( a gist__intbig_ops ); + +SELECT count(*) from test__int WHERE a && '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23|50'; +SELECT count(*) from test__int WHERE a @> '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23&50'; +SELECT count(*) from test__int WHERE a @> '{20,23}'; +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; +SELECT count(*) from test__int WHERE a = '{73,23,20}'; +SELECT count(*) from test__int WHERE a @@ '50&68'; +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; +SELECT count(*) from test__int WHERE a @@ '20 | !21'; +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + +DROP INDEX text_idx; +CREATE INDEX text_idx on test__int using gin ( a gin__int_ops ); + +SELECT count(*) from test__int WHERE a && '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23|50'; +SELECT count(*) from test__int WHERE a @> '{23,50}'; +SELECT count(*) from test__int WHERE a @@ '23&50'; +SELECT count(*) from test__int WHERE a @> '{20,23}'; +SELECT count(*) from test__int WHERE a <@ '{73,23,20}'; +SELECT count(*) from test__int WHERE a = '{73,23,20}'; +SELECT count(*) from test__int WHERE a @@ '50&68'; +SELECT count(*) from test__int WHERE a @> '{20,23}' or a @> '{50,68}'; +SELECT count(*) from test__int WHERE a @@ '(20&23)|(50&68)'; +SELECT count(*) from test__int WHERE a @@ '20 | !21'; +SELECT count(*) from test__int WHERE a @@ '!20 & !21'; + +DROP INDEX text_idx; + +-- Repeat the same queries with an extended data set. The data set is the +-- same that we used before, except that each element in the array is +-- repeated three times, offset by 1000 and 2000. For example, {1, 5} +-- becomes {1, 1001, 2001, 5, 1005, 2005}. +-- +-- That has proven to be unreasonably effective at exercising codepaths in +-- core GiST code related to splitting parent pages, which is not covered by +-- other tests. This is a bit out-of-place as the point is to test core GiST +-- code rather than this extension, but there is no suitable GiST opclass in +-- core that would reach the same codepaths. +CREATE TABLE more__int AS SELECT + -- Leave alone NULLs, empty arrays and the one row that we use to test + -- equality; also skip INT_MAX + CASE WHEN a IS NULL OR a = '{}' OR a = '{73,23,20}' THEN a ELSE + (select array_agg(u) || array_agg(u + 1000) || array_agg(u + 2000) + from unnest(a) u where u < 2000000000) + END AS a, a as b + FROM test__int; +CREATE INDEX ON more__int using gist (a gist__int_ops(numranges = 252)); + +SELECT count(*) from more__int WHERE a && '{23,50}'; +SELECT count(*) from more__int WHERE a @@ '23|50'; +SELECT count(*) from more__int WHERE a @> '{23,50}'; +SELECT count(*) from more__int WHERE a @@ '23&50'; +SELECT count(*) from more__int WHERE a @> '{20,23}'; +SELECT count(*) from more__int WHERE a <@ '{73,23,20}'; +SELECT count(*) from more__int WHERE a = '{73,23,20}'; +SELECT count(*) from more__int WHERE a @@ '50&68'; +SELECT count(*) from more__int WHERE a @> '{20,23}' or a @> '{50,68}'; +SELECT count(*) from more__int WHERE a @@ '(20&23)|(50&68)'; +SELECT count(*) from more__int WHERE a @@ '20 | !21'; +SELECT count(*) from more__int WHERE a @@ '!20 & !21'; + + +RESET enable_seqscan; diff --git a/contrib/isn/.gitignore b/contrib/isn/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/isn/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/isn/EAN13.h b/contrib/isn/EAN13.h new file mode 100644 index 0000000..7023ebd --- /dev/null +++ b/contrib/isn/EAN13.h @@ -0,0 +1,148 @@ +/* + * EAN13.h + * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC) + * + * Information recompiled by Kronuz on August 23, 2006 + * http://www.gs1.org/productssolutions/idkeys/support/prefix_list.html + * + * IDENTIFICATION + * contrib/isn/EAN13.h + * + */ + +/* where the digit set begins, and how many of them are in the table */ +const unsigned EAN13_index[10][2] = { + {0, 6}, + {6, 1}, + {7, 1}, + {8, 5}, + {13, 20}, + {33, 15}, + {48, 19}, + {67, 23}, + {90, 17}, + {107, 12}, +}; +const char *EAN13_range[][2] = { + {"000", "019"}, /* GS1 US */ + {"020", "029"}, /* Restricted distribution (MO defined) */ + {"030", "039"}, /* GS1 US */ + {"040", "049"}, /* Restricted distribution (MO defined) */ + {"050", "059"}, /* Coupons */ + {"060", "099"}, /* GS1 US */ + {"100", "139"}, /* GS1 US */ + {"200", "299"}, /* Restricted distribution (MO defined) */ + {"300", "379"}, /* GS1 France */ + {"380", "380"}, /* GS1 Bulgaria */ + {"383", "383"}, /* GS1 Slovenija */ + {"385", "385"}, /* GS1 Croatia */ + {"387", "387"}, /* GS1 BIH (Bosnia-Herzegovina) */ + {"400", "440"}, /* GS1 Germany */ + {"450", "459"}, /* GS1 Japan */ + {"460", "469"}, /* GS1 Russia */ + {"470", "470"}, /* GS1 Kyrgyzstan */ + {"471", "471"}, /* GS1 Taiwan */ + {"474", "474"}, /* GS1 Estonia */ + {"475", "475"}, /* GS1 Latvia */ + {"476", "476"}, /* GS1 Azerbaijan */ + {"477", "477"}, /* GS1 Lithuania */ + {"478", "478"}, /* GS1 Uzbekistan */ + {"479", "479"}, /* GS1 Sri Lanka */ + {"480", "480"}, /* GS1 Philippines */ + {"481", "481"}, /* GS1 Belarus */ + {"482", "482"}, /* GS1 Ukraine */ + {"484", "484"}, /* GS1 Moldova */ + {"485", "485"}, /* GS1 Armenia */ + {"486", "486"}, /* GS1 Georgia */ + {"487", "487"}, /* GS1 Kazakstan */ + {"489", "489"}, /* GS1 Hong Kong */ + {"490", "499"}, /* GS1 Japan */ + {"500", "509"}, /* GS1 UK */ + {"520", "520"}, /* GS1 Greece */ + {"528", "528"}, /* GS1 Lebanon */ + {"529", "529"}, /* GS1 Cyprus */ + {"530", "530"}, /* GS1 Albania */ + {"531", "531"}, /* GS1 MAC (FYR Macedonia) */ + {"535", "535"}, /* GS1 Malta */ + {"539", "539"}, /* GS1 Ireland */ + {"540", "549"}, /* GS1 Belgium & Luxembourg */ + {"560", "560"}, /* GS1 Portugal */ + {"569", "569"}, /* GS1 Iceland */ + {"570", "579"}, /* GS1 Denmark */ + {"590", "590"}, /* GS1 Poland */ + {"594", "594"}, /* GS1 Romania */ + {"599", "599"}, /* GS1 Hungary */ + {"600", "601"}, /* GS1 South Africa */ + {"603", "603"}, /* GS1 Ghana */ + {"608", "608"}, /* GS1 Bahrain */ + {"609", "609"}, /* GS1 Mauritius */ + {"611", "611"}, /* GS1 Morocco */ + {"613", "613"}, /* GS1 Algeria */ + {"616", "616"}, /* GS1 Kenya */ + {"618", "618"}, /* GS1 Ivory Coast */ + {"619", "619"}, /* GS1 Tunisia */ + {"621", "621"}, /* GS1 Syria */ + {"622", "622"}, /* GS1 Egypt */ + {"624", "624"}, /* GS1 Libya */ + {"625", "625"}, /* GS1 Jordan */ + {"626", "626"}, /* GS1 Iran */ + {"627", "627"}, /* GS1 Kuwait */ + {"628", "628"}, /* GS1 Saudi Arabia */ + {"629", "629"}, /* GS1 Emirates */ + {"640", "649"}, /* GS1 Finland */ + {"690", "695"}, /* GS1 China */ + {"700", "709"}, /* GS1 Norway */ + {"729", "729"}, /* GS1 Israel */ + {"730", "739"}, /* GS1 Sweden */ + {"740", "740"}, /* GS1 Guatemala */ + {"741", "741"}, /* GS1 El Salvador */ + {"742", "742"}, /* GS1 Honduras */ + {"743", "743"}, /* GS1 Nicaragua */ + {"744", "744"}, /* GS1 Costa Rica */ + {"745", "745"}, /* GS1 Panama */ + {"746", "746"}, /* GS1 Republica Dominicana */ + {"750", "750"}, /* GS1 Mexico */ + {"754", "755"}, /* GS1 Canada */ + {"759", "759"}, /* GS1 Venezuela */ + {"760", "769"}, /* GS1 Schweiz, Suisse, Svizzera */ + {"770", "770"}, /* GS1 Colombia */ + {"773", "773"}, /* GS1 Uruguay */ + {"775", "775"}, /* GS1 Peru */ + {"777", "777"}, /* GS1 Bolivia */ + {"779", "779"}, /* GS1 Argentina */ + {"780", "780"}, /* GS1 Chile */ + {"784", "784"}, /* GS1 Paraguay */ + {"786", "786"}, /* GS1 Ecuador */ + {"789", "790"}, /* GS1 Brasil */ + {"800", "839"}, /* GS1 Italy */ + {"840", "849"}, /* GS1 Spain */ + {"850", "850"}, /* GS1 Cuba */ + {"858", "858"}, /* GS1 Slovakia */ + {"859", "859"}, /* GS1 Czech */ + {"860", "860"}, /* GS1 YU (Serbia & Montenegro) */ + {"865", "865"}, /* GS1 Mongolia */ + {"867", "867"}, /* GS1 North Korea */ + {"869", "869"}, /* GS1 Turkey */ + {"870", "879"}, /* GS1 Netherlands */ + {"880", "880"}, /* GS1 South Korea */ + {"884", "884"}, /* GS1 Cambodia */ + {"885", "885"}, /* GS1 Thailand */ + {"888", "888"}, /* GS1 Singapore */ + {"890", "890"}, /* GS1 India */ + {"893", "893"}, /* GS1 Vietnam */ + {"899", "899"}, /* GS1 Indonesia */ + {"900", "919"}, /* GS1 Austria */ + {"930", "939"}, /* GS1 Australia */ + {"940", "949"}, /* GS1 New Zealand */ + {"950", "950"}, /* GS1 Head Office */ + {"955", "955"}, /* GS1 Malaysia */ + {"958", "958"}, /* GS1 Macau */ + {"977", "977"}, /* Serial publications (ISSN) */ + {"978", "978"}, /* Bookland (ISBN) */ + {"979", "979"}, /* International Standard Music Number (ISMN) + * and ISBN contingent */ + {"980", "980"}, /* Refund receipts */ + {"981", "982"}, /* Common Currency Coupons */ + {"990", "999"}, /* Coupons */ + {NULL, NULL} +}; diff --git a/contrib/isn/ISBN.h b/contrib/isn/ISBN.h new file mode 100644 index 0000000..dbda6fb --- /dev/null +++ b/contrib/isn/ISBN.h @@ -0,0 +1,990 @@ +/* + * ISBN.h + * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC) + * + * Information recompiled by Kronuz on June 20, 2006 + * http://www.isbn-international.org/ + * http://www.isbn.org/ + * + * IDENTIFICATION + * contrib/isn/ISBN.h + * + * 0-393-04002-X => 039304002(X) <=> 039304002 <=> (978)039304002 <=> 978039304002(9) <=> 978-0-393-04002-9 + * + * + * ISBN 0 3 9 3 0 4 0 0 2 + * Weight 10 9 8 7 6 5 4 3 2 + * Product 0 + 27 + 72 + 21 + 0 + 20 + 0 + 0 + 4 = 144 + * 144 / 11 = 13 remainder 1 + * Check digit 11 - 1 = 10 = X + * => 0-393-04002-X + * + * ISBN 9 7 8 0 3 9 3 0 4 0 0 2 + * Weight 1 3 1 3 1 3 1 3 1 3 1 3 + * Product 9 + 21 + 8 + 0 + 3 + 27 + 3 + 0 + 4 + 0 + 0 + 6 = 81 + * 81 / 10 = 8 remainder 1 + * Check digit 10 - 1 = 9 + * => 978-0-393-04002-9 + * + */ + +/* + * For ISBN with prefix 978 + * Range Table as of 2010-Jul-29 + */ + +/* where the digit set begins, and how many of them are in the table */ +const unsigned ISBN_index[10][2] = { + {0, 6}, + {6, 6}, + {12, 8}, + {20, 14}, + {34, 6}, + {40, 19}, + {59, 68}, + {127, 5}, + {132, 60}, + {192, 718}, +}; + +const char *ISBN_range[][2] = { + {"0-00", "0-19"}, + {"0-200", "0-699"}, + {"0-7000", "0-8499"}, + {"0-85000", "0-89999"}, + {"0-900000", "0-949999"}, + {"0-9500000", "0-9999999"}, + {"1-00", "1-09"}, + {"1-100", "1-399"}, + {"1-4000", "1-5499"}, + {"1-55000", "1-86979"}, + {"1-869800", "1-998999"}, + {"1-9990000", "1-9999999"}, + {"2-00", "2-19"}, + {"2-200", "2-349"}, + {"2-35000", "2-39999"}, + {"2-400", "2-699"}, + {"2-7000", "2-8399"}, + {"2-84000", "2-89999"}, + {"2-900000", "2-949999"}, + {"2-9500000", "2-9999999"}, + {"3-00", "3-02"}, + {"3-030", "3-033"}, + {"3-0340", "3-0369"}, + {"3-03700", "3-03999"}, + {"3-04", "3-19"}, + {"3-200", "3-699"}, + {"3-7000", "3-8499"}, + {"3-85000", "3-89999"}, + {"3-900000", "3-949999"}, + {"3-9500000", "3-9539999"}, + {"3-95400", "3-96999"}, + {"3-9700000", "3-9899999"}, + {"3-99000", "3-99499"}, + {"3-99500", "3-99999"}, + {"4-00", "4-19"}, + {"4-200", "4-699"}, + {"4-7000", "4-8499"}, + {"4-85000", "4-89999"}, + {"4-900000", "4-949999"}, + {"4-9500000", "4-9999999"}, + {"5-00", "5-19"}, + {"5-200", "5-420"}, + {"5-4210", "5-4299"}, + {"5-430", "5-430"}, + {"5-4310", "5-4399"}, + {"5-440", "5-440"}, + {"5-4410", "5-4499"}, + {"5-450", "5-699"}, + {"5-7000", "5-8499"}, + {"5-85000", "5-89999"}, + {"5-900000", "5-909999"}, + {"5-91000", "5-91999"}, + {"5-9200", "5-9299"}, + {"5-93000", "5-94999"}, + {"5-9500000", "5-9500999"}, + {"5-9501", "5-9799"}, + {"5-98000", "5-98999"}, + {"5-9900000", "5-9909999"}, + {"5-9910", "5-9999"}, + {"600-00", "600-09"}, + {"600-100", "600-499"}, + {"600-5000", "600-8999"}, + {"600-90000", "600-99999"}, + {"601-00", "601-19"}, + {"601-200", "601-699"}, + {"601-7000", "601-7999"}, + {"601-80000", "601-84999"}, + {"601-85", "601-99"}, + {"602-00", "602-19"}, + {"602-200", "602-799"}, + {"602-8000", "602-9499"}, + {"602-95000", "602-99999"}, + {"603-00", "603-04"}, + {"603-05", "603-49"}, + {"603-500", "603-799"}, + {"603-8000", "603-8999"}, + {"603-90000", "603-99999"}, + {"604-0", "604-4"}, + {"604-50", "604-89"}, + {"604-900", "604-979"}, + {"604-9800", "604-9999"}, + {"605-01", "605-09"}, + {"605-100", "605-399"}, + {"605-4000", "605-5999"}, + {"605-60000", "605-89999"}, + {"605-90", "605-99"}, + {"606-0", "606-0"}, + {"606-10", "606-49"}, + {"606-500", "606-799"}, + {"606-8000", "606-9199"}, + {"606-92000", "606-99999"}, + {"607-00", "607-39"}, + {"607-400", "607-749"}, + {"607-7500", "607-9499"}, + {"607-95000", "607-99999"}, + {"608-0", "608-0"}, + {"608-10", "608-19"}, + {"608-200", "608-449"}, + {"608-4500", "608-6499"}, + {"608-65000", "608-69999"}, + {"608-7", "608-9"}, + {"609-00", "609-39"}, + {"609-400", "609-799"}, + {"609-8000", "609-9499"}, + {"609-95000", "609-99999"}, + {"612-00", "612-29"}, + {"612-300", "612-399"}, + {"612-4000", "612-4499"}, + {"612-45000", "612-49999"}, + {"612-50", "612-99"}, + {"613-0", "613-9"}, + {"614-00", "614-39"}, + {"614-400", "614-799"}, + {"614-8000", "614-9499"}, + {"614-95000", "614-99999"}, + {"615-00", "615-09"}, + {"615-100", "615-499"}, + {"615-5000", "615-7999"}, + {"615-80000", "615-89999"}, + {"616-00", "616-19"}, + {"616-200", "616-699"}, + {"616-7000", "616-8999"}, + {"616-90000", "616-99999"}, + {"617-00", "617-49"}, + {"617-500", "617-699"}, + {"617-7000", "617-8999"}, + {"617-90000", "617-99999"}, + {"7-00", "7-09"}, + {"7-100", "7-499"}, + {"7-5000", "7-7999"}, + {"7-80000", "7-89999"}, + {"7-900000", "7-999999"}, + {"80-00", "80-19"}, + {"80-200", "80-699"}, + {"80-7000", "80-8499"}, + {"80-85000", "80-89999"}, + {"80-900000", "80-999999"}, + {"81-00", "81-19"}, + {"81-200", "81-699"}, + {"81-7000", "81-8499"}, + {"81-85000", "81-89999"}, + {"81-900000", "81-999999"}, + {"82-00", "82-19"}, + {"82-200", "82-699"}, + {"82-7000", "82-8999"}, + {"82-90000", "82-98999"}, + {"82-990000", "82-999999"}, + {"83-00", "83-19"}, + {"83-200", "83-599"}, + {"83-60000", "83-69999"}, + {"83-7000", "83-8499"}, + {"83-85000", "83-89999"}, + {"83-900000", "83-999999"}, + {"84-00", "84-14"}, + {"84-15000", "84-19999"}, + {"84-200", "84-699"}, + {"84-7000", "84-8499"}, + {"84-85000", "84-89999"}, + {"84-9000", "84-9199"}, + {"84-920000", "84-923999"}, + {"84-92400", "84-92999"}, + {"84-930000", "84-949999"}, + {"84-95000", "84-96999"}, + {"84-9700", "84-9999"}, + {"85-00", "85-19"}, + {"85-200", "85-599"}, + {"85-60000", "85-69999"}, + {"85-7000", "85-8499"}, + {"85-85000", "85-89999"}, + {"85-900000", "85-979999"}, + {"85-98000", "85-99999"}, + {"86-00", "86-29"}, + {"86-300", "86-599"}, + {"86-6000", "86-7999"}, + {"86-80000", "86-89999"}, + {"86-900000", "86-999999"}, + {"87-00", "87-29"}, + {"87-400", "87-649"}, + {"87-7000", "87-7999"}, + {"87-85000", "87-94999"}, + {"87-970000", "87-999999"}, + {"88-00", "88-19"}, + {"88-200", "88-599"}, + {"88-6000", "88-8499"}, + {"88-85000", "88-89999"}, + {"88-900000", "88-949999"}, + {"88-95000", "88-99999"}, + {"89-00", "89-24"}, + {"89-250", "89-549"}, + {"89-5500", "89-8499"}, + {"89-85000", "89-94999"}, + {"89-950000", "89-999999"}, + {"90-00", "90-19"}, + {"90-200", "90-499"}, + {"90-5000", "90-6999"}, + {"90-70000", "90-79999"}, + {"90-800000", "90-849999"}, + {"90-8500", "90-8999"}, + {"90-90", "90-90"}, + {"90-910000", "90-939999"}, + {"90-94", "90-94"}, + {"90-950000", "90-999999"}, + {"91-0", "91-1"}, + {"91-20", "91-49"}, + {"91-500", "91-649"}, + {"91-7000", "91-7999"}, + {"91-85000", "91-94999"}, + {"91-970000", "91-999999"}, + {"92-0", "92-5"}, + {"92-60", "92-79"}, + {"92-800", "92-899"}, + {"92-9000", "92-9499"}, + {"92-95000", "92-98999"}, + {"92-990000", "92-999999"}, + {"93-00", "93-09"}, + {"93-100", "93-499"}, + {"93-5000", "93-7999"}, + {"93-80000", "93-94999"}, + {"93-950000", "93-999999"}, + {"94-000", "94-599"}, + {"94-6000", "94-8999"}, + {"94-90000", "94-99999"}, + {"950-00", "950-49"}, + {"950-500", "950-899"}, + {"950-9000", "950-9899"}, + {"950-99000", "950-99999"}, + {"951-0", "951-1"}, + {"951-20", "951-54"}, + {"951-550", "951-889"}, + {"951-8900", "951-9499"}, + {"951-95000", "951-99999"}, + {"952-00", "952-19"}, + {"952-200", "952-499"}, + {"952-5000", "952-5999"}, + {"952-60", "952-65"}, + {"952-6600", "952-6699"}, + {"952-67000", "952-69999"}, + {"952-7000", "952-7999"}, + {"952-80", "952-94"}, + {"952-9500", "952-9899"}, + {"952-99000", "952-99999"}, + {"953-0", "953-0"}, + {"953-10", "953-14"}, + {"953-150", "953-549"}, + {"953-55000", "953-59999"}, + {"953-6000", "953-9499"}, + {"953-95000", "953-99999"}, + {"954-00", "954-28"}, + {"954-2900", "954-2999"}, + {"954-300", "954-799"}, + {"954-8000", "954-8999"}, + {"954-90000", "954-92999"}, + {"954-9300", "954-9999"}, + {"955-0000", "955-1999"}, + {"955-20", "955-49"}, + {"955-50000", "955-54999"}, + {"955-550", "955-799"}, + {"955-8000", "955-9499"}, + {"955-95000", "955-99999"}, + {"956-00", "956-19"}, + {"956-200", "956-699"}, + {"956-7000", "956-9999"}, + {"957-00", "957-02"}, + {"957-0300", "957-0499"}, + {"957-05", "957-19"}, + {"957-2000", "957-2099"}, + {"957-21", "957-27"}, + {"957-28000", "957-30999"}, + {"957-31", "957-43"}, + {"957-440", "957-819"}, + {"957-8200", "957-9699"}, + {"957-97000", "957-99999"}, + {"958-00", "958-56"}, + {"958-57000", "958-59999"}, + {"958-600", "958-799"}, + {"958-8000", "958-9499"}, + {"958-95000", "958-99999"}, + {"959-00", "959-19"}, + {"959-200", "959-699"}, + {"959-7000", "959-8499"}, + {"959-85000", "959-99999"}, + {"960-00", "960-19"}, + {"960-200", "960-659"}, + {"960-6600", "960-6899"}, + {"960-690", "960-699"}, + {"960-7000", "960-8499"}, + {"960-85000", "960-92999"}, + {"960-93", "960-93"}, + {"960-9400", "960-9799"}, + {"960-98000", "960-99999"}, + {"961-00", "961-19"}, + {"961-200", "961-599"}, + {"961-6000", "961-8999"}, + {"961-90000", "961-94999"}, + {"962-00", "962-19"}, + {"962-200", "962-699"}, + {"962-7000", "962-8499"}, + {"962-85000", "962-86999"}, + {"962-8700", "962-8999"}, + {"962-900", "962-999"}, + {"963-00", "963-19"}, + {"963-200", "963-699"}, + {"963-7000", "963-8499"}, + {"963-85000", "963-89999"}, + {"963-9000", "963-9999"}, + {"964-00", "964-14"}, + {"964-150", "964-249"}, + {"964-2500", "964-2999"}, + {"964-300", "964-549"}, + {"964-5500", "964-8999"}, + {"964-90000", "964-96999"}, + {"964-970", "964-989"}, + {"964-9900", "964-9999"}, + {"965-00", "965-19"}, + {"965-200", "965-599"}, + {"965-7000", "965-7999"}, + {"965-90000", "965-99999"}, + {"966-00", "966-14"}, + {"966-1500", "966-1699"}, + {"966-170", "966-199"}, + {"966-2000", "966-2999"}, + {"966-300", "966-699"}, + {"966-7000", "966-8999"}, + {"966-90000", "966-99999"}, + {"967-00", "967-29"}, + {"967-300", "967-499"}, + {"967-5000", "967-5999"}, + {"967-60", "967-89"}, + {"967-900", "967-989"}, + {"967-9900", "967-9989"}, + {"967-99900", "967-99999"}, + {"968-01", "968-39"}, + {"968-400", "968-499"}, + {"968-5000", "968-7999"}, + {"968-800", "968-899"}, + {"968-9000", "968-9999"}, + {"969-0", "969-1"}, + {"969-20", "969-39"}, + {"969-400", "969-799"}, + {"969-8000", "969-9999"}, + {"970-01", "970-59"}, + {"970-600", "970-899"}, + {"970-9000", "970-9099"}, + {"970-91000", "970-96999"}, + {"970-9700", "970-9999"}, + {"971-000", "971-015"}, + {"971-0160", "971-0199"}, + {"971-02", "971-02"}, + {"971-0300", "971-0599"}, + {"971-06", "971-09"}, + {"971-10", "971-49"}, + {"971-500", "971-849"}, + {"971-8500", "971-9099"}, + {"971-91000", "971-98999"}, + {"971-9900", "971-9999"}, + {"972-0", "972-1"}, + {"972-20", "972-54"}, + {"972-550", "972-799"}, + {"972-8000", "972-9499"}, + {"972-95000", "972-99999"}, + {"973-0", "973-0"}, + {"973-100", "973-169"}, + {"973-1700", "973-1999"}, + {"973-20", "973-54"}, + {"973-550", "973-759"}, + {"973-7600", "973-8499"}, + {"973-85000", "973-88999"}, + {"973-8900", "973-9499"}, + {"973-95000", "973-99999"}, + {"974-00", "974-19"}, + {"974-200", "974-699"}, + {"974-7000", "974-8499"}, + {"974-85000", "974-89999"}, + {"974-90000", "974-94999"}, + {"974-9500", "974-9999"}, + {"975-00000", "975-00999"}, + {"975-01", "975-01"}, + {"975-02", "975-24"}, + {"975-250", "975-599"}, + {"975-6000", "975-9199"}, + {"975-92000", "975-98999"}, + {"975-990", "975-999"}, + {"976-0", "976-3"}, + {"976-40", "976-59"}, + {"976-600", "976-799"}, + {"976-8000", "976-9499"}, + {"976-95000", "976-99999"}, + {"977-00", "977-19"}, + {"977-200", "977-499"}, + {"977-5000", "977-6999"}, + {"977-700", "977-999"}, + {"978-000", "978-199"}, + {"978-2000", "978-2999"}, + {"978-30000", "978-79999"}, + {"978-8000", "978-8999"}, + {"978-900", "978-999"}, + {"979-000", "979-099"}, + {"979-1000", "979-1499"}, + {"979-15000", "979-19999"}, + {"979-20", "979-29"}, + {"979-3000", "979-3999"}, + {"979-400", "979-799"}, + {"979-8000", "979-9499"}, + {"979-95000", "979-99999"}, + {"980-00", "980-19"}, + {"980-200", "980-599"}, + {"980-6000", "980-9999"}, + {"981-00", "981-11"}, + {"981-1200", "981-1999"}, + {"981-200", "981-289"}, + {"981-2900", "981-9999"}, + {"982-00", "982-09"}, + {"982-100", "982-699"}, + {"982-70", "982-89"}, + {"982-9000", "982-9799"}, + {"982-98000", "982-99999"}, + {"983-00", "983-01"}, + {"983-020", "983-199"}, + {"983-2000", "983-3999"}, + {"983-40000", "983-44999"}, + {"983-45", "983-49"}, + {"983-50", "983-79"}, + {"983-800", "983-899"}, + {"983-9000", "983-9899"}, + {"983-99000", "983-99999"}, + {"984-00", "984-39"}, + {"984-400", "984-799"}, + {"984-8000", "984-8999"}, + {"984-90000", "984-99999"}, + {"985-00", "985-39"}, + {"985-400", "985-599"}, + {"985-6000", "985-8999"}, + {"985-90000", "985-99999"}, + {"986-00", "986-11"}, + {"986-120", "986-559"}, + {"986-5600", "986-7999"}, + {"986-80000", "986-99999"}, + {"987-00", "987-09"}, + {"987-1000", "987-1999"}, + {"987-20000", "987-29999"}, + {"987-30", "987-49"}, + {"987-500", "987-899"}, + {"987-9000", "987-9499"}, + {"987-95000", "987-99999"}, + {"988-00", "988-16"}, + {"988-17000", "988-19999"}, + {"988-200", "988-799"}, + {"988-8000", "988-9699"}, + {"988-97000", "988-99999"}, + {"989-0", "989-1"}, + {"989-20", "989-54"}, + {"989-550", "989-799"}, + {"989-8000", "989-9499"}, + {"989-95000", "989-99999"}, + {"9927-00", "9927-09"}, + {"9927-100", "9927-399"}, + {"9927-4000", "9927-4999"}, + {"9928-00", "9928-09"}, + {"9928-100", "9928-399"}, + {"9928-4000", "9928-4999"}, + {"9929-0", "9929-3"}, + {"9929-40", "9929-54"}, + {"9929-550", "9929-799"}, + {"9929-8000", "9929-9999"}, + {"9930-00", "9930-49"}, + {"9930-500", "9930-939"}, + {"9930-9400", "9930-9999"}, + {"9931-00", "9931-29"}, + {"9931-300", "9931-899"}, + {"9931-9000", "9931-9999"}, + {"9932-00", "9932-39"}, + {"9932-400", "9932-849"}, + {"9932-8500", "9932-9999"}, + {"9933-0", "9933-0"}, + {"9933-10", "9933-39"}, + {"9933-400", "9933-899"}, + {"9933-9000", "9933-9999"}, + {"9934-0", "9934-0"}, + {"9934-10", "9934-49"}, + {"9934-500", "9934-799"}, + {"9934-8000", "9934-9999"}, + {"9935-0", "9935-0"}, + {"9935-10", "9935-39"}, + {"9935-400", "9935-899"}, + {"9935-9000", "9935-9999"}, + {"9936-0", "9936-1"}, + {"9936-20", "9936-39"}, + {"9936-400", "9936-799"}, + {"9936-8000", "9936-9999"}, + {"9937-0", "9937-2"}, + {"9937-30", "9937-49"}, + {"9937-500", "9937-799"}, + {"9937-8000", "9937-9999"}, + {"9938-00", "9938-79"}, + {"9938-800", "9938-949"}, + {"9938-9500", "9938-9999"}, + {"9939-0", "9939-4"}, + {"9939-50", "9939-79"}, + {"9939-800", "9939-899"}, + {"9939-9000", "9939-9999"}, + {"9940-0", "9940-1"}, + {"9940-20", "9940-49"}, + {"9940-500", "9940-899"}, + {"9940-9000", "9940-9999"}, + {"9941-0", "9941-0"}, + {"9941-10", "9941-39"}, + {"9941-400", "9941-899"}, + {"9941-9000", "9941-9999"}, + {"9942-00", "9942-89"}, + {"9942-900", "9942-994"}, + {"9942-9950", "9942-9999"}, + {"9943-00", "9943-29"}, + {"9943-300", "9943-399"}, + {"9943-4000", "9943-9999"}, + {"9944-0000", "9944-0999"}, + {"9944-100", "9944-499"}, + {"9944-5000", "9944-5999"}, + {"9944-60", "9944-69"}, + {"9944-700", "9944-799"}, + {"9944-80", "9944-89"}, + {"9944-900", "9944-999"}, + {"9945-00", "9945-00"}, + {"9945-010", "9945-079"}, + {"9945-08", "9945-39"}, + {"9945-400", "9945-569"}, + {"9945-57", "9945-57"}, + {"9945-580", "9945-849"}, + {"9945-8500", "9945-9999"}, + {"9946-0", "9946-1"}, + {"9946-20", "9946-39"}, + {"9946-400", "9946-899"}, + {"9946-9000", "9946-9999"}, + {"9947-0", "9947-1"}, + {"9947-20", "9947-79"}, + {"9947-800", "9947-999"}, + {"9948-00", "9948-39"}, + {"9948-400", "9948-849"}, + {"9948-8500", "9948-9999"}, + {"9949-0", "9949-0"}, + {"9949-10", "9949-39"}, + {"9949-400", "9949-899"}, + {"9949-9000", "9949-9999"}, + {"9950-00", "9950-29"}, + {"9950-300", "9950-849"}, + {"9950-8500", "9950-9999"}, + {"9951-00", "9951-39"}, + {"9951-400", "9951-849"}, + {"9951-8500", "9951-9999"}, + {"9952-0", "9952-1"}, + {"9952-20", "9952-39"}, + {"9952-400", "9952-799"}, + {"9952-8000", "9952-9999"}, + {"9953-0", "9953-0"}, + {"9953-10", "9953-39"}, + {"9953-400", "9953-599"}, + {"9953-60", "9953-89"}, + {"9953-9000", "9953-9999"}, + {"9954-0", "9954-1"}, + {"9954-20", "9954-39"}, + {"9954-400", "9954-799"}, + {"9954-8000", "9954-9999"}, + {"9955-00", "9955-39"}, + {"9955-400", "9955-929"}, + {"9955-9300", "9955-9999"}, + {"9956-0", "9956-0"}, + {"9956-10", "9956-39"}, + {"9956-400", "9956-899"}, + {"9956-9000", "9956-9999"}, + {"9957-00", "9957-39"}, + {"9957-400", "9957-699"}, + {"9957-70", "9957-84"}, + {"9957-8500", "9957-8799"}, + {"9957-88", "9957-99"}, + {"9958-0", "9958-0"}, + {"9958-10", "9958-49"}, + {"9958-500", "9958-899"}, + {"9958-9000", "9958-9999"}, + {"9959-0", "9959-1"}, + {"9959-20", "9959-79"}, + {"9959-800", "9959-949"}, + {"9959-9500", "9959-9999"}, + {"9960-00", "9960-59"}, + {"9960-600", "9960-899"}, + {"9960-9000", "9960-9999"}, + {"9961-0", "9961-2"}, + {"9961-30", "9961-69"}, + {"9961-700", "9961-949"}, + {"9961-9500", "9961-9999"}, + {"9962-00", "9962-54"}, + {"9962-5500", "9962-5599"}, + {"9962-56", "9962-59"}, + {"9962-600", "9962-849"}, + {"9962-8500", "9962-9999"}, + {"9963-0", "9963-2"}, + {"9963-30", "9963-54"}, + {"9963-550", "9963-734"}, + {"9963-7350", "9963-7499"}, + {"9963-7500", "9963-9999"}, + {"9964-0", "9964-6"}, + {"9964-70", "9964-94"}, + {"9964-950", "9964-999"}, + {"9965-00", "9965-39"}, + {"9965-400", "9965-899"}, + {"9965-9000", "9965-9999"}, + {"9966-000", "9966-199"}, + {"9966-20", "9966-69"}, + {"9966-7000", "9966-7499"}, + {"9966-750", "9966-959"}, + {"9966-9600", "9966-9999"}, + {"9967-00", "9967-39"}, + {"9967-400", "9967-899"}, + {"9967-9000", "9967-9999"}, + {"9968-00", "9968-49"}, + {"9968-500", "9968-939"}, + {"9968-9400", "9968-9999"}, + {"9970-00", "9970-39"}, + {"9970-400", "9970-899"}, + {"9970-9000", "9970-9999"}, + {"9971-0", "9971-5"}, + {"9971-60", "9971-89"}, + {"9971-900", "9971-989"}, + {"9971-9900", "9971-9999"}, + {"9972-00", "9972-09"}, + {"9972-1", "9972-1"}, + {"9972-200", "9972-249"}, + {"9972-2500", "9972-2999"}, + {"9972-30", "9972-59"}, + {"9972-600", "9972-899"}, + {"9972-9000", "9972-9999"}, + {"9973-00", "9973-05"}, + {"9973-060", "9973-089"}, + {"9973-0900", "9973-0999"}, + {"9973-10", "9973-69"}, + {"9973-700", "9973-969"}, + {"9973-9700", "9973-9999"}, + {"9974-0", "9974-2"}, + {"9974-30", "9974-54"}, + {"9974-550", "9974-749"}, + {"9974-7500", "9974-9499"}, + {"9974-95", "9974-99"}, + {"9975-0", "9975-0"}, + {"9975-100", "9975-399"}, + {"9975-4000", "9975-4499"}, + {"9975-45", "9975-89"}, + {"9975-900", "9975-949"}, + {"9975-9500", "9975-9999"}, + {"9976-0", "9976-5"}, + {"9976-60", "9976-89"}, + {"9976-900", "9976-989"}, + {"9976-9900", "9976-9999"}, + {"9977-00", "9977-89"}, + {"9977-900", "9977-989"}, + {"9977-9900", "9977-9999"}, + {"9978-00", "9978-29"}, + {"9978-300", "9978-399"}, + {"9978-40", "9978-94"}, + {"9978-950", "9978-989"}, + {"9978-9900", "9978-9999"}, + {"9979-0", "9979-4"}, + {"9979-50", "9979-64"}, + {"9979-650", "9979-659"}, + {"9979-66", "9979-75"}, + {"9979-760", "9979-899"}, + {"9979-9000", "9979-9999"}, + {"9980-0", "9980-3"}, + {"9980-40", "9980-89"}, + {"9980-900", "9980-989"}, + {"9980-9900", "9980-9999"}, + {"9981-00", "9981-09"}, + {"9981-100", "9981-159"}, + {"9981-1600", "9981-1999"}, + {"9981-20", "9981-79"}, + {"9981-800", "9981-949"}, + {"9981-9500", "9981-9999"}, + {"9982-00", "9982-79"}, + {"9982-800", "9982-989"}, + {"9982-9900", "9982-9999"}, + {"9983-80", "9983-94"}, + {"9983-950", "9983-989"}, + {"9983-9900", "9983-9999"}, + {"9984-00", "9984-49"}, + {"9984-500", "9984-899"}, + {"9984-9000", "9984-9999"}, + {"9985-0", "9985-4"}, + {"9985-50", "9985-79"}, + {"9985-800", "9985-899"}, + {"9985-9000", "9985-9999"}, + {"9986-00", "9986-39"}, + {"9986-400", "9986-899"}, + {"9986-9000", "9986-9399"}, + {"9986-940", "9986-969"}, + {"9986-97", "9986-99"}, + {"9987-00", "9987-39"}, + {"9987-400", "9987-879"}, + {"9987-8800", "9987-9999"}, + {"9988-0", "9988-2"}, + {"9988-30", "9988-54"}, + {"9988-550", "9988-749"}, + {"9988-7500", "9988-9999"}, + {"9989-0", "9989-0"}, + {"9989-100", "9989-199"}, + {"9989-2000", "9989-2999"}, + {"9989-30", "9989-59"}, + {"9989-600", "9989-949"}, + {"9989-9500", "9989-9999"}, + {"99901-00", "99901-49"}, + {"99901-500", "99901-799"}, + {"99901-80", "99901-99"}, + {"99903-0", "99903-1"}, + {"99903-20", "99903-89"}, + {"99903-900", "99903-999"}, + {"99904-0", "99904-5"}, + {"99904-60", "99904-89"}, + {"99904-900", "99904-999"}, + {"99905-0", "99905-3"}, + {"99905-40", "99905-79"}, + {"99905-800", "99905-999"}, + {"99906-0", "99906-2"}, + {"99906-30", "99906-59"}, + {"99906-600", "99906-699"}, + {"99906-70", "99906-89"}, + {"99906-90", "99906-94"}, + {"99906-950", "99906-999"}, + {"99908-0", "99908-0"}, + {"99908-10", "99908-89"}, + {"99908-900", "99908-999"}, + {"99909-0", "99909-3"}, + {"99909-40", "99909-94"}, + {"99909-950", "99909-999"}, + {"99910-0", "99910-2"}, + {"99910-30", "99910-89"}, + {"99910-900", "99910-999"}, + {"99911-00", "99911-59"}, + {"99911-600", "99911-999"}, + {"99912-0", "99912-3"}, + {"99912-400", "99912-599"}, + {"99912-60", "99912-89"}, + {"99912-900", "99912-999"}, + {"99913-0", "99913-2"}, + {"99913-30", "99913-35"}, + {"99913-600", "99913-604"}, + {"99914-0", "99914-4"}, + {"99914-50", "99914-89"}, + {"99914-900", "99914-999"}, + {"99915-0", "99915-4"}, + {"99915-50", "99915-79"}, + {"99915-800", "99915-999"}, + {"99916-0", "99916-2"}, + {"99916-30", "99916-69"}, + {"99916-700", "99916-999"}, + {"99917-0", "99917-2"}, + {"99917-30", "99917-89"}, + {"99917-900", "99917-999"}, + {"99918-0", "99918-3"}, + {"99918-40", "99918-79"}, + {"99918-800", "99918-999"}, + {"99919-0", "99919-2"}, + {"99919-300", "99919-399"}, + {"99919-40", "99919-69"}, + {"99919-900", "99919-999"}, + {"99920-0", "99920-4"}, + {"99920-50", "99920-89"}, + {"99920-900", "99920-999"}, + {"99921-0", "99921-1"}, + {"99921-20", "99921-69"}, + {"99921-700", "99921-799"}, + {"99921-8", "99921-8"}, + {"99921-90", "99921-99"}, + {"99922-0", "99922-3"}, + {"99922-40", "99922-69"}, + {"99922-700", "99922-999"}, + {"99923-0", "99923-1"}, + {"99923-20", "99923-79"}, + {"99923-800", "99923-999"}, + {"99924-0", "99924-1"}, + {"99924-20", "99924-79"}, + {"99924-800", "99924-999"}, + {"99925-0", "99925-3"}, + {"99925-40", "99925-79"}, + {"99925-800", "99925-999"}, + {"99926-0", "99926-0"}, + {"99926-10", "99926-59"}, + {"99926-600", "99926-999"}, + {"99927-0", "99927-2"}, + {"99927-30", "99927-59"}, + {"99927-600", "99927-999"}, + {"99928-0", "99928-0"}, + {"99928-10", "99928-79"}, + {"99928-800", "99928-999"}, + {"99929-0", "99929-4"}, + {"99929-50", "99929-79"}, + {"99929-800", "99929-999"}, + {"99930-0", "99930-4"}, + {"99930-50", "99930-79"}, + {"99930-800", "99930-999"}, + {"99931-0", "99931-4"}, + {"99931-50", "99931-79"}, + {"99931-800", "99931-999"}, + {"99932-0", "99932-0"}, + {"99932-10", "99932-59"}, + {"99932-600", "99932-699"}, + {"99932-7", "99932-7"}, + {"99932-80", "99932-99"}, + {"99933-0", "99933-2"}, + {"99933-30", "99933-59"}, + {"99933-600", "99933-999"}, + {"99934-0", "99934-1"}, + {"99934-20", "99934-79"}, + {"99934-800", "99934-999"}, + {"99935-0", "99935-2"}, + {"99935-30", "99935-59"}, + {"99935-600", "99935-699"}, + {"99935-7", "99935-8"}, + {"99935-90", "99935-99"}, + {"99936-0", "99936-0"}, + {"99936-10", "99936-59"}, + {"99936-600", "99936-999"}, + {"99937-0", "99937-1"}, + {"99937-20", "99937-59"}, + {"99937-600", "99937-999"}, + {"99938-0", "99938-1"}, + {"99938-20", "99938-59"}, + {"99938-600", "99938-899"}, + {"99938-90", "99938-99"}, + {"99939-0", "99939-5"}, + {"99939-60", "99939-89"}, + {"99939-900", "99939-999"}, + {"99940-0", "99940-0"}, + {"99940-10", "99940-69"}, + {"99940-700", "99940-999"}, + {"99941-0", "99941-2"}, + {"99941-30", "99941-79"}, + {"99941-800", "99941-999"}, + {"99942-0", "99942-4"}, + {"99942-50", "99942-79"}, + {"99942-800", "99942-999"}, + {"99943-0", "99943-2"}, + {"99943-30", "99943-59"}, + {"99943-600", "99943-999"}, + {"99944-0", "99944-4"}, + {"99944-50", "99944-79"}, + {"99944-800", "99944-999"}, + {"99945-0", "99945-5"}, + {"99945-60", "99945-89"}, + {"99945-900", "99945-999"}, + {"99946-0", "99946-2"}, + {"99946-30", "99946-59"}, + {"99946-600", "99946-999"}, + {"99947-0", "99947-2"}, + {"99947-30", "99947-69"}, + {"99947-700", "99947-999"}, + {"99948-0", "99948-4"}, + {"99948-50", "99948-79"}, + {"99948-800", "99948-999"}, + {"99949-0", "99949-1"}, + {"99949-20", "99949-89"}, + {"99949-900", "99949-999"}, + {"99950-0", "99950-4"}, + {"99950-50", "99950-79"}, + {"99950-800", "99950-999"}, + {"99952-0", "99952-4"}, + {"99952-50", "99952-79"}, + {"99952-800", "99952-999"}, + {"99953-0", "99953-2"}, + {"99953-30", "99953-79"}, + {"99953-800", "99953-939"}, + {"99953-94", "99953-99"}, + {"99954-0", "99954-2"}, + {"99954-30", "99954-69"}, + {"99954-700", "99954-999"}, + {"99955-0", "99955-1"}, + {"99955-20", "99955-59"}, + {"99955-600", "99955-799"}, + {"99955-80", "99955-89"}, + {"99955-90", "99955-99"}, + {"99956-00", "99956-59"}, + {"99956-600", "99956-859"}, + {"99956-86", "99956-99"}, + {"99957-0", "99957-1"}, + {"99957-20", "99957-79"}, + {"99957-800", "99957-999"}, + {"99958-0", "99958-4"}, + {"99958-50", "99958-94"}, + {"99958-950", "99958-999"}, + {"99959-0", "99959-2"}, + {"99959-30", "99959-59"}, + {"99959-600", "99959-999"}, + {"99960-0", "99960-0"}, + {"99960-10", "99960-94"}, + {"99960-950", "99960-999"}, + {"99961-0", "99961-3"}, + {"99961-40", "99961-89"}, + {"99961-900", "99961-999"}, + {"99962-0", "99962-4"}, + {"99962-50", "99962-79"}, + {"99962-800", "99962-999"}, + {"99963-00", "99963-49"}, + {"99963-500", "99963-999"}, + {"99964-0", "99964-1"}, + {"99964-20", "99964-79"}, + {"99964-800", "99964-999"}, + {"99965-0", "99965-3"}, + {"99965-40", "99965-79"}, + {"99965-800", "99965-999"}, + {"99966-0", "99966-2"}, + {"99966-30", "99966-69"}, + {"99966-700", "99966-799"}, + {"99967-0", "99967-1"}, + {"99967-20", "99967-59"}, + {"99967-600", "99967-899"}, + {NULL, NULL}, +}; + +/* + * For ISBN with prefix 979 + * Range Table as of 2010-Jul-29 + */ + +/* where the digit set begins, and how many of them are in the table */ +const unsigned ISBN_index_new[10][2] = { + {0, 0}, + {0, 5}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, +}; + +const char *ISBN_range_new[][2] = { + {"10-00", "10-19"}, + {"10-200", "10-699"}, + {"10-7000", "10-8999"}, + {"10-90000", "10-97599"}, + {"10-976000", "10-999999"}, + {NULL, NULL}, +}; diff --git a/contrib/isn/ISMN.h b/contrib/isn/ISMN.h new file mode 100644 index 0000000..281f2cd --- /dev/null +++ b/contrib/isn/ISMN.h @@ -0,0 +1,52 @@ +/* + * ISMN.h + * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC) + * + * Information recompiled by Kronuz on November 12, 2004 + * http://www.ismn-international.org + * + * IDENTIFICATION + * contrib/isn/ISMN.h + * + * M-3452-4680-5 <=> (0)-3452-4680-5 <=> 0345246805 <=> 9790345246805 <=> 979-0-3452-4680-5 + * + * (M counts as 3) + * ISMN M 3 4 5 2 4 6 8 0 + * Weight 3 1 3 1 3 1 3 1 3 + * Product 9 + 3 + 12 + 5 + 6 + 4 + 18 + 8 + 0 = 65 + * 65 / 10 = 6 remainder 5 + * Check digit 10 - 5 = 5 + * => M-3452-4680-5 + * + * ISMN 9 7 9 0 3 4 5 2 4 6 8 0 + * Weight 1 3 1 3 1 3 1 3 1 3 1 3 + * Product 9 + 21 + 9 + 0 + 3 + 12 + 5 + 6 + 4 + 18 + 8 + 0 = 95 + * 95 / 10 = 9 remainder 5 + * Check digit 10 - 5 = 5 + * => 979-0-3452-4680-5 + * + * Since mod10(9*1 + 7*3 + 9*1 + 0*3) = mod10(M*3) = mod10(3*3) = 9; the check digit remains the same. + * + */ + +/* where the digit set begins, and how many of them are in the table */ +const unsigned ISMN_index[10][2] = { + {0, 5}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, + {5, 0}, +}; +const char *ISMN_range[][2] = { + {"0-000", "0-099"}, + {"0-1000", "0-3999"}, + {"0-40000", "0-69999"}, + {"0-700000", "0-899999"}, + {"0-9000000", "0-9999999"}, + {NULL, NULL} +}; diff --git a/contrib/isn/ISSN.h b/contrib/isn/ISSN.h new file mode 100644 index 0000000..585f0e2 --- /dev/null +++ b/contrib/isn/ISSN.h @@ -0,0 +1,49 @@ +/* + * ISSN.h + * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC) + * + * Information recompiled by Kronuz on November 12, 2004 + * http://www.issn.org/ + * + * IDENTIFICATION + * contrib/isn/ISSN.h + * + * 1144-875X <=> 1144875(X) <=> 1144875 <=> (977)1144875 <=> 9771144875(00) <=> 977114487500(7) <=> 977-1144-875-00-7 + * + * + * ISSN 1 1 4 4 8 7 5 + * Weight 8 7 6 5 4 3 2 + * Product 8 + 7 + 24 + 20 + 32 + 21 + 10 = 122 + * 122 / 11 = 11 remainder 1 + * Check digit 11 - 1 = 10 = X + * => 1144-875X + * + * ISSN 9 7 7 1 1 4 4 8 7 5 0 0 + * Weight 1 3 1 3 1 3 1 3 1 3 1 3 + * Product 9 + 21 + 7 + 3 + 1 + 12 + 4 + 24 + 7 + 15 + 0 + 0 = 103 + * 103 / 10 = 10 remainder 3 + * Check digit 10 - 3 = 7 + * => 977-1144875-00-7 ?? <- supplemental number (number of the week, month, etc.) + * ^^ 00 for non-daily publications (01=Monday, 02=Tuesday, ...) + * + * The hyphenation is always in after the four digits of the ISSN code. + * + */ + +/* where the digit set begins, and how many of them are in the table */ +const unsigned ISSN_index[10][2] = { + {0, 1}, + {0, 1}, + {0, 1}, + {0, 1}, + {0, 1}, + {0, 1}, + {0, 1}, + {0, 1}, + {0, 1}, + {0, 1}, +}; +const char *ISSN_range[][2] = { + {"0000-000", "9999-999"}, + {NULL, NULL} +}; diff --git a/contrib/isn/Makefile b/contrib/isn/Makefile new file mode 100644 index 0000000..1037506 --- /dev/null +++ b/contrib/isn/Makefile @@ -0,0 +1,24 @@ +# contrib/isn/Makefile + +MODULES = isn + +EXTENSION = isn +DATA = isn--1.1.sql isn--1.1--1.2.sql \ + isn--1.0--1.1.sql +PGFILEDESC = "isn - data types for international product numbering standards" + +# the other .h files are data tables, we don't install those +HEADERS_isn = isn.h + +REGRESS = isn + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/isn +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/isn/UPC.h b/contrib/isn/UPC.h new file mode 100644 index 0000000..b95473e --- /dev/null +++ b/contrib/isn/UPC.h @@ -0,0 +1,28 @@ +/* + * ISSN.h + * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC) + * + * No information available for UPC prefixes + * + * + * IDENTIFICATION + * contrib/isn/UPC.h + * + */ + +/* where the digit set begins, and how many of them are in the table */ +const unsigned UPC_index[10][2] = { + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, + {0, 0}, +}; +const char *UPC_range[][2] = { + {NULL, NULL} +}; diff --git a/contrib/isn/expected/isn.out b/contrib/isn/expected/isn.out new file mode 100644 index 0000000..2f05b7e --- /dev/null +++ b/contrib/isn/expected/isn.out @@ -0,0 +1,285 @@ +-- +-- Test ISN extension +-- +CREATE EXTENSION isn; +-- Check whether any of our opclasses fail amvalidate +-- ... they will, because of missing cross-type operators +SELECT amname, opcname +FROM (SELECT amname, opcname, opc.oid + FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod + WHERE opc.oid >= 16384 + ORDER BY 1, 2 OFFSET 0) ss +WHERE NOT amvalidate(oid); +INFO: operator family "isn_ops" of access method btree is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method btree is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method btree is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method btree is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method btree is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method btree is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method btree is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method btree is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method hash is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method hash is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method hash is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method hash is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method hash is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method hash is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method hash is missing cross-type operator(s) +INFO: operator family "isn_ops" of access method hash is missing cross-type operator(s) + amname | opcname +--------+------------ + btree | ean13_ops + btree | isbn13_ops + btree | isbn_ops + btree | ismn13_ops + btree | ismn_ops + btree | issn13_ops + btree | issn_ops + btree | upc_ops + hash | ean13_ops + hash | isbn13_ops + hash | isbn_ops + hash | ismn13_ops + hash | ismn_ops + hash | issn13_ops + hash | issn_ops + hash | upc_ops +(16 rows) + +-- +-- test valid conversions +-- +SELECT '9780123456786'::EAN13, -- old book + '9790123456785'::EAN13, -- music + '9791234567896'::EAN13, -- new book + '9771234567898'::EAN13, -- serial + '0123456789012'::EAN13, -- upc + '1234567890128'::EAN13; + ean13 | ean13 | ean13 | ean13 | ean13 | ean13 +-------------------+-------------------+-----------------+-------------------+-----------------+----------------- + 978-0-12-345678-6 | 979-0-1234-5678-5 | 979-123456789-6 | 977-1234-567-89-8 | 012-345678901-2 | 123-456789012-8 +(1 row) + +SELECT '9780123456786'::ISBN, + '123456789X'::ISBN, + '9780123456786'::ISBN13::ISBN, + '9780123456786'::EAN13::ISBN; + isbn | isbn | isbn | isbn +---------------+---------------+---------------+--------------- + 0-12-345678-9 | 1-234-56789-X | 0-12-345678-9 | 0-12-345678-9 +(1 row) + +SELECT -- new books, shown as ISBN13 even for ISBN... + '9791234567896'::ISBN, + '9791234567896'::ISBN13::ISBN, + '9791234567896'::EAN13::ISBN; + isbn | isbn | isbn +-----------------+-----------------+----------------- + 979-123456789-6 | 979-123456789-6 | 979-123456789-6 +(1 row) + +SELECT '9780123456786'::ISBN13, + '123456789X'::ISBN13, + '9791234567896'::ISBN13, + '9791234567896'::EAN13::ISBN13; + isbn13 | isbn13 | isbn13 | isbn13 +-------------------+-------------------+-----------------+----------------- + 978-0-12-345678-6 | 978-1-234-56789-7 | 979-123456789-6 | 979-123456789-6 +(1 row) + +SELECT '9790123456785'::ISMN, + '9790123456785'::EAN13::ISMN, + 'M123456785'::ISMN, + 'M-1234-5678-5'::ISMN; + ismn | ismn | ismn | ismn +---------------+---------------+---------------+--------------- + M-1234-5678-5 | M-1234-5678-5 | M-1234-5678-5 | M-1234-5678-5 +(1 row) + +SELECT '9790123456785'::ISMN13, + 'M123456785'::ISMN13, + 'M-1234-5678-5'::ISMN13; + ismn13 | ismn13 | ismn13 +-------------------+-------------------+------------------- + 979-0-1234-5678-5 | 979-0-1234-5678-5 | 979-0-1234-5678-5 +(1 row) + +SELECT '9771234567003'::ISSN, + '12345679'::ISSN; + issn | issn +-----------+----------- + 1234-5679 | 1234-5679 +(1 row) + +SELECT '9771234567003'::ISSN13, + '12345679'::ISSN13, + '9771234567898'::ISSN13, + '9771234567898'::EAN13::ISSN13; + issn13 | issn13 | issn13 | issn13 +-------------------+-------------------+-------------------+------------------- + 977-1234-567-00-3 | 977-1234-567-00-3 | 977-1234-567-89-8 | 977-1234-567-89-8 +(1 row) + +SELECT '0123456789012'::UPC, + '0123456789012'::EAN13::UPC; + upc | upc +--------------+-------------- + 123456789012 | 123456789012 +(1 row) + +-- +-- test invalid checksums +-- +SELECT '1234567890'::ISBN; +ERROR: invalid check digit for ISBN number: "1234567890", should be X +LINE 1: SELECT '1234567890'::ISBN; + ^ +SELECT 'M123456780'::ISMN; +ERROR: invalid check digit for ISMN number: "M123456780", should be 5 +LINE 1: SELECT 'M123456780'::ISMN; + ^ +SELECT '12345670'::ISSN; +ERROR: invalid check digit for ISSN number: "12345670", should be 9 +LINE 1: SELECT '12345670'::ISSN; + ^ +SELECT '9780123456780'::ISBN; +ERROR: invalid check digit for ISBN number: "9780123456780", should be 6 +LINE 1: SELECT '9780123456780'::ISBN; + ^ +SELECT '9791234567890'::ISBN13; +ERROR: invalid check digit for ISBN number: "9791234567890", should be 6 +LINE 1: SELECT '9791234567890'::ISBN13; + ^ +SELECT '0123456789010'::UPC; +ERROR: invalid check digit for UPC number: "0123456789010", should be 2 +LINE 1: SELECT '0123456789010'::UPC; + ^ +SELECT '1234567890120'::EAN13; +ERROR: invalid check digit for EAN13 number: "1234567890120", should be 8 +LINE 1: SELECT '1234567890120'::EAN13; + ^ +-- +-- test invalid conversions +-- +SELECT '9790123456785'::ISBN; -- not a book +ERROR: cannot cast ISMN to ISBN for number: "9790123456785" +LINE 1: SELECT '9790123456785'::ISBN; + ^ +SELECT '9771234567898'::ISBN; -- not a book +ERROR: cannot cast ISSN to ISBN for number: "9771234567898" +LINE 1: SELECT '9771234567898'::ISBN; + ^ +SELECT '0123456789012'::ISBN; -- not a book +ERROR: cannot cast UPC to ISBN for number: "0123456789012" +LINE 1: SELECT '0123456789012'::ISBN; + ^ +SELECT '9790123456785'::ISBN13; -- not a book +ERROR: cannot cast ISMN to ISBN for number: "9790123456785" +LINE 1: SELECT '9790123456785'::ISBN13; + ^ +SELECT '9771234567898'::ISBN13; -- not a book +ERROR: cannot cast ISSN to ISBN for number: "9771234567898" +LINE 1: SELECT '9771234567898'::ISBN13; + ^ +SELECT '0123456789012'::ISBN13; -- not a book +ERROR: cannot cast UPC to ISBN for number: "0123456789012" +LINE 1: SELECT '0123456789012'::ISBN13; + ^ +SELECT '9780123456786'::ISMN; -- not music +ERROR: cannot cast ISBN to ISMN for number: "9780123456786" +LINE 1: SELECT '9780123456786'::ISMN; + ^ +SELECT '9771234567898'::ISMN; -- not music +ERROR: cannot cast ISSN to ISMN for number: "9771234567898" +LINE 1: SELECT '9771234567898'::ISMN; + ^ +SELECT '9791234567896'::ISMN; -- not music +ERROR: cannot cast ISBN to ISMN for number: "9791234567896" +LINE 1: SELECT '9791234567896'::ISMN; + ^ +SELECT '0123456789012'::ISMN; -- not music +ERROR: cannot cast UPC to ISMN for number: "0123456789012" +LINE 1: SELECT '0123456789012'::ISMN; + ^ +SELECT '9780123456786'::ISSN; -- not serial +ERROR: cannot cast ISBN to ISSN for number: "9780123456786" +LINE 1: SELECT '9780123456786'::ISSN; + ^ +SELECT '9790123456785'::ISSN; -- not serial +ERROR: cannot cast ISMN to ISSN for number: "9790123456785" +LINE 1: SELECT '9790123456785'::ISSN; + ^ +SELECT '9791234567896'::ISSN; -- not serial +ERROR: cannot cast ISBN to ISSN for number: "9791234567896" +LINE 1: SELECT '9791234567896'::ISSN; + ^ +SELECT '0123456789012'::ISSN; -- not serial +ERROR: cannot cast UPC to ISSN for number: "0123456789012" +LINE 1: SELECT '0123456789012'::ISSN; + ^ +SELECT '9780123456786'::UPC; -- not a product +ERROR: cannot cast ISBN to UPC for number: "9780123456786" +LINE 1: SELECT '9780123456786'::UPC; + ^ +SELECT '9771234567898'::UPC; -- not a product +ERROR: cannot cast ISSN to UPC for number: "9771234567898" +LINE 1: SELECT '9771234567898'::UPC; + ^ +SELECT '9790123456785'::UPC; -- not a product +ERROR: cannot cast ISMN to UPC for number: "9790123456785" +LINE 1: SELECT '9790123456785'::UPC; + ^ +SELECT '9791234567896'::UPC; -- not a product +ERROR: cannot cast ISBN to UPC for number: "9791234567896" +LINE 1: SELECT '9791234567896'::UPC; + ^ +SELECT 'postgresql...'::EAN13; +ERROR: invalid input syntax for EAN13 number: "postgresql..." +LINE 1: SELECT 'postgresql...'::EAN13; + ^ +SELECT 'postgresql...'::ISBN; +ERROR: invalid input syntax for ISBN number: "postgresql..." +LINE 1: SELECT 'postgresql...'::ISBN; + ^ +SELECT 9780123456786::EAN13; +ERROR: cannot cast type bigint to ean13 +LINE 1: SELECT 9780123456786::EAN13; + ^ +SELECT 9780123456786::ISBN; +ERROR: cannot cast type bigint to isbn +LINE 1: SELECT 9780123456786::ISBN; + ^ +-- +-- test some comparisons, must yield true +-- +SELECT '12345679'::ISSN = '9771234567003'::EAN13 AS "ok", + 'M-1234-5678-5'::ISMN = '9790123456785'::EAN13 AS "ok", + '9791234567896'::EAN13 != '123456789X'::ISBN AS "nope"; + ok | ok | nope +----+----+------ + t | t | t +(1 row) + +-- test non-error-throwing input API +SELECT str as isn, typ as "type", + pg_input_is_valid(str,typ) as ok, + errinfo.sql_error_code, + errinfo.message, + errinfo.detail, + errinfo.hint +FROM (VALUES ('9780123456786', 'UPC'), + ('postgresql...','EAN13'), + ('9771234567003','ISSN')) + AS a(str,typ), + LATERAL pg_input_error_info(a.str, a.typ) as errinfo; + isn | type | ok | sql_error_code | message | detail | hint +---------------+-------+----+----------------+--------------------------------------------------------+--------+------ + 9780123456786 | UPC | f | 22P02 | cannot cast ISBN to UPC for number: "9780123456786" | | + postgresql... | EAN13 | f | 22P02 | invalid input syntax for EAN13 number: "postgresql..." | | + 9771234567003 | ISSN | t | | | | +(3 rows) + +-- +-- cleanup +-- +DROP EXTENSION isn; diff --git a/contrib/isn/isn--1.0--1.1.sql b/contrib/isn/isn--1.0--1.1.sql new file mode 100644 index 0000000..6f1ccb0 --- /dev/null +++ b/contrib/isn/isn--1.0--1.1.sql @@ -0,0 +1,250 @@ +/* contrib/isn/isn--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION isn UPDATE TO '1.1'" to load this file. \quit + +ALTER FUNCTION ean13_in(cstring) PARALLEL SAFE; +ALTER FUNCTION ean13_out(ean13) PARALLEL SAFE; +ALTER FUNCTION isbn13_in(cstring) PARALLEL SAFE; +ALTER FUNCTION ean13_out(isbn13) PARALLEL SAFE; +ALTER FUNCTION ismn13_in(cstring) PARALLEL SAFE; +ALTER FUNCTION ean13_out(ismn13) PARALLEL SAFE; +ALTER FUNCTION issn13_in(cstring) PARALLEL SAFE; +ALTER FUNCTION ean13_out(issn13) PARALLEL SAFE; +ALTER FUNCTION isbn_in(cstring) PARALLEL SAFE; +ALTER FUNCTION isn_out(isbn) PARALLEL SAFE; +ALTER FUNCTION ismn_in(cstring) PARALLEL SAFE; +ALTER FUNCTION isn_out(ismn) PARALLEL SAFE; +ALTER FUNCTION issn_in(cstring) PARALLEL SAFE; +ALTER FUNCTION isn_out(issn) PARALLEL SAFE; +ALTER FUNCTION upc_in(cstring) PARALLEL SAFE; +ALTER FUNCTION isn_out(upc) PARALLEL SAFE; +ALTER FUNCTION isnlt(ean13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnle(ean13, ean13) PARALLEL SAFE; +ALTER FUNCTION isneq(ean13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnge(ean13, ean13) PARALLEL SAFE; +ALTER FUNCTION isngt(ean13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnne(ean13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnlt(ean13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnle(ean13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isneq(ean13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnge(ean13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isngt(ean13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnne(ean13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnlt(ean13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnle(ean13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isneq(ean13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnge(ean13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isngt(ean13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnne(ean13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnlt(ean13, issn13) PARALLEL SAFE; +ALTER FUNCTION isnle(ean13, issn13) PARALLEL SAFE; +ALTER FUNCTION isneq(ean13, issn13) PARALLEL SAFE; +ALTER FUNCTION isnge(ean13, issn13) PARALLEL SAFE; +ALTER FUNCTION isngt(ean13, issn13) PARALLEL SAFE; +ALTER FUNCTION isnne(ean13, issn13) PARALLEL SAFE; +ALTER FUNCTION isnlt(ean13, isbn) PARALLEL SAFE; +ALTER FUNCTION isnle(ean13, isbn) PARALLEL SAFE; +ALTER FUNCTION isneq(ean13, isbn) PARALLEL SAFE; +ALTER FUNCTION isnge(ean13, isbn) PARALLEL SAFE; +ALTER FUNCTION isngt(ean13, isbn) PARALLEL SAFE; +ALTER FUNCTION isnne(ean13, isbn) PARALLEL SAFE; +ALTER FUNCTION isnlt(ean13, ismn) PARALLEL SAFE; +ALTER FUNCTION isnle(ean13, ismn) PARALLEL SAFE; +ALTER FUNCTION isneq(ean13, ismn) PARALLEL SAFE; +ALTER FUNCTION isnge(ean13, ismn) PARALLEL SAFE; +ALTER FUNCTION isngt(ean13, ismn) PARALLEL SAFE; +ALTER FUNCTION isnne(ean13, ismn) PARALLEL SAFE; +ALTER FUNCTION isnlt(ean13, issn) PARALLEL SAFE; +ALTER FUNCTION isnle(ean13, issn) PARALLEL SAFE; +ALTER FUNCTION isneq(ean13, issn) PARALLEL SAFE; +ALTER FUNCTION isnge(ean13, issn) PARALLEL SAFE; +ALTER FUNCTION isngt(ean13, issn) PARALLEL SAFE; +ALTER FUNCTION isnne(ean13, issn) PARALLEL SAFE; +ALTER FUNCTION isnlt(ean13, upc) PARALLEL SAFE; +ALTER FUNCTION isnle(ean13, upc) PARALLEL SAFE; +ALTER FUNCTION isneq(ean13, upc) PARALLEL SAFE; +ALTER FUNCTION isnge(ean13, upc) PARALLEL SAFE; +ALTER FUNCTION isngt(ean13, upc) PARALLEL SAFE; +ALTER FUNCTION isnne(ean13, upc) PARALLEL SAFE; +ALTER FUNCTION isnlt(isbn13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnle(isbn13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isneq(isbn13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnge(isbn13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isngt(isbn13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnne(isbn13, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnlt(isbn13, isbn) PARALLEL SAFE; +ALTER FUNCTION isnle(isbn13, isbn) PARALLEL SAFE; +ALTER FUNCTION isneq(isbn13, isbn) PARALLEL SAFE; +ALTER FUNCTION isnge(isbn13, isbn) PARALLEL SAFE; +ALTER FUNCTION isngt(isbn13, isbn) PARALLEL SAFE; +ALTER FUNCTION isnne(isbn13, isbn) PARALLEL SAFE; +ALTER FUNCTION isnlt(isbn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnle(isbn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isneq(isbn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnge(isbn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isngt(isbn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnne(isbn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnlt(isbn, isbn) PARALLEL SAFE; +ALTER FUNCTION isnle(isbn, isbn) PARALLEL SAFE; +ALTER FUNCTION isneq(isbn, isbn) PARALLEL SAFE; +ALTER FUNCTION isnge(isbn, isbn) PARALLEL SAFE; +ALTER FUNCTION isngt(isbn, isbn) PARALLEL SAFE; +ALTER FUNCTION isnne(isbn, isbn) PARALLEL SAFE; +ALTER FUNCTION isnlt(isbn, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnle(isbn, isbn13) PARALLEL SAFE; +ALTER FUNCTION isneq(isbn, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnge(isbn, isbn13) PARALLEL SAFE; +ALTER FUNCTION isngt(isbn, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnne(isbn, isbn13) PARALLEL SAFE; +ALTER FUNCTION isnlt(isbn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnle(isbn, ean13) PARALLEL SAFE; +ALTER FUNCTION isneq(isbn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnge(isbn, ean13) PARALLEL SAFE; +ALTER FUNCTION isngt(isbn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnne(isbn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnlt(ismn13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnle(ismn13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isneq(ismn13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnge(ismn13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isngt(ismn13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnne(ismn13, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnlt(ismn13, ismn) PARALLEL SAFE; +ALTER FUNCTION isnle(ismn13, ismn) PARALLEL SAFE; +ALTER FUNCTION isneq(ismn13, ismn) PARALLEL SAFE; +ALTER FUNCTION isnge(ismn13, ismn) PARALLEL SAFE; +ALTER FUNCTION isngt(ismn13, ismn) PARALLEL SAFE; +ALTER FUNCTION isnne(ismn13, ismn) PARALLEL SAFE; +ALTER FUNCTION isnlt(ismn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnle(ismn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isneq(ismn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnge(ismn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isngt(ismn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnne(ismn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnlt(ismn, ismn) PARALLEL SAFE; +ALTER FUNCTION isnle(ismn, ismn) PARALLEL SAFE; +ALTER FUNCTION isneq(ismn, ismn) PARALLEL SAFE; +ALTER FUNCTION isnge(ismn, ismn) PARALLEL SAFE; +ALTER FUNCTION isngt(ismn, ismn) PARALLEL SAFE; +ALTER FUNCTION isnne(ismn, ismn) PARALLEL SAFE; +ALTER FUNCTION isnlt(ismn, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnle(ismn, ismn13) PARALLEL SAFE; +ALTER FUNCTION isneq(ismn, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnge(ismn, ismn13) PARALLEL SAFE; +ALTER FUNCTION isngt(ismn, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnne(ismn, ismn13) PARALLEL SAFE; +ALTER FUNCTION isnlt(ismn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnle(ismn, ean13) PARALLEL SAFE; +ALTER FUNCTION isneq(ismn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnge(ismn, ean13) PARALLEL SAFE; +ALTER FUNCTION isngt(ismn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnne(ismn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnlt(issn13, issn13) PARALLEL SAFE; +ALTER FUNCTION isnle(issn13, issn13) PARALLEL SAFE; +ALTER FUNCTION isneq(issn13, issn13) PARALLEL SAFE; +ALTER FUNCTION isnge(issn13, issn13) PARALLEL SAFE; +ALTER FUNCTION isngt(issn13, issn13) PARALLEL SAFE; +ALTER FUNCTION isnne(issn13, issn13) PARALLEL SAFE; +ALTER FUNCTION isnlt(issn13, issn) PARALLEL SAFE; +ALTER FUNCTION isnle(issn13, issn) PARALLEL SAFE; +ALTER FUNCTION isneq(issn13, issn) PARALLEL SAFE; +ALTER FUNCTION isnge(issn13, issn) PARALLEL SAFE; +ALTER FUNCTION isngt(issn13, issn) PARALLEL SAFE; +ALTER FUNCTION isnne(issn13, issn) PARALLEL SAFE; +ALTER FUNCTION isnlt(issn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnle(issn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isneq(issn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnge(issn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isngt(issn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnne(issn13, ean13) PARALLEL SAFE; +ALTER FUNCTION isnlt(issn, issn) PARALLEL SAFE; +ALTER FUNCTION isnle(issn, issn) PARALLEL SAFE; +ALTER FUNCTION isneq(issn, issn) PARALLEL SAFE; +ALTER FUNCTION isnge(issn, issn) PARALLEL SAFE; +ALTER FUNCTION isngt(issn, issn) PARALLEL SAFE; +ALTER FUNCTION isnne(issn, issn) PARALLEL SAFE; +ALTER FUNCTION isnlt(issn, issn13) PARALLEL SAFE; +ALTER FUNCTION isnle(issn, issn13) PARALLEL SAFE; +ALTER FUNCTION isneq(issn, issn13) PARALLEL SAFE; +ALTER FUNCTION isnge(issn, issn13) PARALLEL SAFE; +ALTER FUNCTION isngt(issn, issn13) PARALLEL SAFE; +ALTER FUNCTION isnne(issn, issn13) PARALLEL SAFE; +ALTER FUNCTION isnlt(issn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnle(issn, ean13) PARALLEL SAFE; +ALTER FUNCTION isneq(issn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnge(issn, ean13) PARALLEL SAFE; +ALTER FUNCTION isngt(issn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnne(issn, ean13) PARALLEL SAFE; +ALTER FUNCTION isnlt(upc, upc) PARALLEL SAFE; +ALTER FUNCTION isnle(upc, upc) PARALLEL SAFE; +ALTER FUNCTION isneq(upc, upc) PARALLEL SAFE; +ALTER FUNCTION isnge(upc, upc) PARALLEL SAFE; +ALTER FUNCTION isngt(upc, upc) PARALLEL SAFE; +ALTER FUNCTION isnne(upc, upc) PARALLEL SAFE; +ALTER FUNCTION isnlt(upc, ean13) PARALLEL SAFE; +ALTER FUNCTION isnle(upc, ean13) PARALLEL SAFE; +ALTER FUNCTION isneq(upc, ean13) PARALLEL SAFE; +ALTER FUNCTION isnge(upc, ean13) PARALLEL SAFE; +ALTER FUNCTION isngt(upc, ean13) PARALLEL SAFE; +ALTER FUNCTION isnne(upc, ean13) PARALLEL SAFE; +ALTER FUNCTION btean13cmp(ean13, ean13) PARALLEL SAFE; +ALTER FUNCTION hashean13(ean13) PARALLEL SAFE; +ALTER FUNCTION btean13cmp(ean13, isbn13) PARALLEL SAFE; +ALTER FUNCTION btean13cmp(ean13, ismn13) PARALLEL SAFE; +ALTER FUNCTION btean13cmp(ean13, issn13) PARALLEL SAFE; +ALTER FUNCTION btean13cmp(ean13, isbn) PARALLEL SAFE; +ALTER FUNCTION btean13cmp(ean13, ismn) PARALLEL SAFE; +ALTER FUNCTION btean13cmp(ean13, issn) PARALLEL SAFE; +ALTER FUNCTION btean13cmp(ean13, upc) PARALLEL SAFE; +ALTER FUNCTION btisbn13cmp(isbn13, isbn13) PARALLEL SAFE; +ALTER FUNCTION hashisbn13(isbn13) PARALLEL SAFE; +ALTER FUNCTION btisbn13cmp(isbn13, ean13) PARALLEL SAFE; +ALTER FUNCTION btisbn13cmp(isbn13, isbn) PARALLEL SAFE; +ALTER FUNCTION btisbncmp(isbn, isbn) PARALLEL SAFE; +ALTER FUNCTION hashisbn(isbn) PARALLEL SAFE; +ALTER FUNCTION btisbncmp(isbn, ean13) PARALLEL SAFE; +ALTER FUNCTION btisbncmp(isbn, isbn13) PARALLEL SAFE; +ALTER FUNCTION btismn13cmp(ismn13, ismn13) PARALLEL SAFE; +ALTER FUNCTION hashismn13(ismn13) PARALLEL SAFE; +ALTER FUNCTION btismn13cmp(ismn13, ean13) PARALLEL SAFE; +ALTER FUNCTION btismn13cmp(ismn13, ismn) PARALLEL SAFE; +ALTER FUNCTION btismncmp(ismn, ismn) PARALLEL SAFE; +ALTER FUNCTION hashismn(ismn) PARALLEL SAFE; +ALTER FUNCTION btismncmp(ismn, ean13) PARALLEL SAFE; +ALTER FUNCTION btismncmp(ismn, ismn13) PARALLEL SAFE; +ALTER FUNCTION btissn13cmp(issn13, issn13) PARALLEL SAFE; +ALTER FUNCTION hashissn13(issn13) PARALLEL SAFE; +ALTER FUNCTION btissn13cmp(issn13, ean13) PARALLEL SAFE; +ALTER FUNCTION btissn13cmp(issn13, issn) PARALLEL SAFE; +ALTER FUNCTION btissncmp(issn, issn) PARALLEL SAFE; +ALTER FUNCTION hashissn(issn) PARALLEL SAFE; +ALTER FUNCTION btissncmp(issn, ean13) PARALLEL SAFE; +ALTER FUNCTION btissncmp(issn, issn13) PARALLEL SAFE; +ALTER FUNCTION btupccmp(upc, upc) PARALLEL SAFE; +ALTER FUNCTION hashupc(upc) PARALLEL SAFE; +ALTER FUNCTION btupccmp(upc, ean13) PARALLEL SAFE; +ALTER FUNCTION isbn13(ean13) PARALLEL SAFE; +ALTER FUNCTION ismn13(ean13) PARALLEL SAFE; +ALTER FUNCTION issn13(ean13) PARALLEL SAFE; +ALTER FUNCTION isbn(ean13) PARALLEL SAFE; +ALTER FUNCTION ismn(ean13) PARALLEL SAFE; +ALTER FUNCTION issn(ean13) PARALLEL SAFE; +ALTER FUNCTION upc(ean13) PARALLEL SAFE; +ALTER FUNCTION make_valid(ean13) PARALLEL SAFE; +ALTER FUNCTION make_valid(isbn13) PARALLEL SAFE; +ALTER FUNCTION make_valid(ismn13) PARALLEL SAFE; +ALTER FUNCTION make_valid(issn13) PARALLEL SAFE; +ALTER FUNCTION make_valid(isbn) PARALLEL SAFE; +ALTER FUNCTION make_valid(ismn) PARALLEL SAFE; +ALTER FUNCTION make_valid(issn) PARALLEL SAFE; +ALTER FUNCTION make_valid(upc) PARALLEL SAFE; +ALTER FUNCTION is_valid(ean13) PARALLEL SAFE; +ALTER FUNCTION is_valid(isbn13) PARALLEL SAFE; +ALTER FUNCTION is_valid(ismn13) PARALLEL SAFE; +ALTER FUNCTION is_valid(issn13) PARALLEL SAFE; +ALTER FUNCTION is_valid(isbn) PARALLEL SAFE; +ALTER FUNCTION is_valid(ismn) PARALLEL SAFE; +ALTER FUNCTION is_valid(issn) PARALLEL SAFE; +ALTER FUNCTION is_valid(upc) PARALLEL SAFE; +ALTER FUNCTION isn_weak(boolean) PARALLEL RESTRICTED; +ALTER FUNCTION isn_weak() PARALLEL RESTRICTED; diff --git a/contrib/isn/isn--1.1--1.2.sql b/contrib/isn/isn--1.1--1.2.sql new file mode 100644 index 0000000..d626a5f --- /dev/null +++ b/contrib/isn/isn--1.1--1.2.sql @@ -0,0 +1,228 @@ +/* contrib/isn/isn--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION isn UPDATE TO '1.2'" to load this file. \quit + +ALTER OPERATOR <= (ean13, ean13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ean13, ean13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ean13, isbn13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ean13, isbn13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (isbn13, ean13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (isbn13, ean13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ean13, ismn13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ean13, ismn13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ismn13, ean13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ismn13, ean13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ean13, issn13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ean13, issn13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ean13, isbn) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ean13, isbn) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ean13, ismn) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ean13, ismn) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ean13, issn) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ean13, issn) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ean13, upc) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ean13, upc) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (isbn13, isbn13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (isbn13, isbn13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (isbn13, isbn) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (isbn13, isbn) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (isbn, isbn) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (isbn, isbn) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (isbn, isbn13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (isbn, isbn13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (isbn, ean13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (isbn, ean13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ismn13, ismn13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ismn13, ismn13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ismn13, ismn) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ismn13, ismn) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ismn, ismn) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ismn, ismn) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ismn, ismn13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ismn, ismn13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (ismn, ean13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (ismn, ean13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (issn13, issn13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (issn13, issn13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (issn13, issn) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (issn13, issn) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (issn13, ean13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (issn13, ean13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (issn, issn) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (issn, issn) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (issn, issn13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (issn, issn13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (issn, ean13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (issn, ean13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (upc, upc) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (upc, upc) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); + +ALTER OPERATOR <= (upc, ean13) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel); + +ALTER OPERATOR >= (upc, ean13) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel); diff --git a/contrib/isn/isn--1.1.sql b/contrib/isn/isn--1.1.sql new file mode 100644 index 0000000..5206961 --- /dev/null +++ b/contrib/isn/isn--1.1.sql @@ -0,0 +1,3434 @@ +/* contrib/isn/isn--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION isn" to load this file. \quit + +-- Example: +-- create table test ( id isbn ); +-- insert into test values('978-0-393-04002-9'); +-- +-- select isbn('978-0-393-04002-9'); +-- select isbn13('0-901690-54-6'); +-- + +-- +-- Input and output functions and data types: +-- +--------------------------------------------------- +CREATE FUNCTION ean13_in(cstring) + RETURNS ean13 + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION ean13_out(ean13) + RETURNS cstring + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE TYPE ean13 ( + INPUT = ean13_in, + OUTPUT = ean13_out, + LIKE = pg_catalog.int8 +); +COMMENT ON TYPE ean13 + IS 'International European Article Number (EAN13)'; + +CREATE FUNCTION isbn13_in(cstring) + RETURNS isbn13 + AS 'MODULE_PATHNAME', 'isbn_in' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION ean13_out(isbn13) + RETURNS cstring + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE TYPE isbn13 ( + INPUT = isbn13_in, + OUTPUT = ean13_out, + LIKE = pg_catalog.int8 +); +COMMENT ON TYPE isbn13 + IS 'International Standard Book Number 13 (ISBN13)'; + +CREATE FUNCTION ismn13_in(cstring) + RETURNS ismn13 + AS 'MODULE_PATHNAME', 'ismn_in' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION ean13_out(ismn13) + RETURNS cstring + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE TYPE ismn13 ( + INPUT = ismn13_in, + OUTPUT = ean13_out, + LIKE = pg_catalog.int8 +); +COMMENT ON TYPE ismn13 + IS 'International Standard Music Number 13 (ISMN13)'; + +CREATE FUNCTION issn13_in(cstring) + RETURNS issn13 + AS 'MODULE_PATHNAME', 'issn_in' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION ean13_out(issn13) + RETURNS cstring + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE TYPE issn13 ( + INPUT = issn13_in, + OUTPUT = ean13_out, + LIKE = pg_catalog.int8 +); +COMMENT ON TYPE issn13 + IS 'International Standard Serial Number 13 (ISSN13)'; + +-- Short format: + +CREATE FUNCTION isbn_in(cstring) + RETURNS isbn + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isn_out(isbn) + RETURNS cstring + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE TYPE isbn ( + INPUT = isbn_in, + OUTPUT = isn_out, + LIKE = pg_catalog.int8 +); +COMMENT ON TYPE isbn + IS 'International Standard Book Number (ISBN)'; + +CREATE FUNCTION ismn_in(cstring) + RETURNS ismn + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isn_out(ismn) + RETURNS cstring + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE TYPE ismn ( + INPUT = ismn_in, + OUTPUT = isn_out, + LIKE = pg_catalog.int8 +); +COMMENT ON TYPE ismn + IS 'International Standard Music Number (ISMN)'; + +CREATE FUNCTION issn_in(cstring) + RETURNS issn + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isn_out(issn) + RETURNS cstring + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE TYPE issn ( + INPUT = issn_in, + OUTPUT = isn_out, + LIKE = pg_catalog.int8 +); +COMMENT ON TYPE issn + IS 'International Standard Serial Number (ISSN)'; + +CREATE FUNCTION upc_in(cstring) + RETURNS upc + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isn_out(upc) + RETURNS cstring + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE TYPE upc ( + INPUT = upc_in, + OUTPUT = isn_out, + LIKE = pg_catalog.int8 +); +COMMENT ON TYPE upc + IS 'Universal Product Code (UPC)'; + +-- +-- Operator functions: +-- +--------------------------------------------------- +-- EAN13: +CREATE FUNCTION isnlt(ean13, ean13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ean13, ean13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ean13, ean13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ean13, ean13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ean13, ean13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ean13, ean13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ean13, isbn13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ean13, isbn13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ean13, isbn13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ean13, isbn13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ean13, isbn13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ean13, isbn13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ean13, ismn13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ean13, ismn13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ean13, ismn13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ean13, ismn13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ean13, ismn13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ean13, ismn13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ean13, issn13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ean13, issn13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ean13, issn13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ean13, issn13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ean13, issn13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ean13, issn13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ean13, isbn) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ean13, isbn) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ean13, isbn) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ean13, isbn) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ean13, isbn) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ean13, isbn) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ean13, ismn) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ean13, ismn) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ean13, ismn) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ean13, ismn) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ean13, ismn) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ean13, ismn) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ean13, issn) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ean13, issn) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ean13, issn) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ean13, issn) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ean13, issn) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ean13, issn) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ean13, upc) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ean13, upc) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ean13, upc) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ean13, upc) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ean13, upc) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ean13, upc) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +--------------------------------------------------- +-- ISBN13: +CREATE FUNCTION isnlt(isbn13, isbn13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(isbn13, isbn13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(isbn13, isbn13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(isbn13, isbn13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(isbn13, isbn13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(isbn13, isbn13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(isbn13, isbn) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(isbn13, isbn) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(isbn13, isbn) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(isbn13, isbn) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(isbn13, isbn) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(isbn13, isbn) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(isbn13, ean13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(isbn13, ean13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(isbn13, ean13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(isbn13, ean13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(isbn13, ean13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(isbn13, ean13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +--------------------------------------------------- +-- ISBN: +CREATE FUNCTION isnlt(isbn, isbn) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(isbn, isbn) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(isbn, isbn) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(isbn, isbn) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(isbn, isbn) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(isbn, isbn) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(isbn, isbn13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(isbn, isbn13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(isbn, isbn13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(isbn, isbn13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(isbn, isbn13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(isbn, isbn13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(isbn, ean13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(isbn, ean13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(isbn, ean13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(isbn, ean13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(isbn, ean13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(isbn, ean13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +--------------------------------------------------- +-- ISMN13: +CREATE FUNCTION isnlt(ismn13, ismn13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ismn13, ismn13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ismn13, ismn13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ismn13, ismn13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ismn13, ismn13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ismn13, ismn13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ismn13, ismn) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ismn13, ismn) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ismn13, ismn) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ismn13, ismn) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ismn13, ismn) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ismn13, ismn) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ismn13, ean13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ismn13, ean13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ismn13, ean13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ismn13, ean13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ismn13, ean13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ismn13, ean13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +--------------------------------------------------- +-- ISMN: +CREATE FUNCTION isnlt(ismn, ismn) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ismn, ismn) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ismn, ismn) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ismn, ismn) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ismn, ismn) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ismn, ismn) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ismn, ismn13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ismn, ismn13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ismn, ismn13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ismn, ismn13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ismn, ismn13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ismn, ismn13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(ismn, ean13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(ismn, ean13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(ismn, ean13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(ismn, ean13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(ismn, ean13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(ismn, ean13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +--------------------------------------------------- +-- ISSN13: +CREATE FUNCTION isnlt(issn13, issn13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(issn13, issn13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(issn13, issn13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(issn13, issn13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(issn13, issn13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(issn13, issn13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(issn13, issn) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(issn13, issn) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(issn13, issn) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(issn13, issn) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(issn13, issn) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(issn13, issn) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(issn13, ean13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(issn13, ean13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(issn13, ean13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(issn13, ean13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(issn13, ean13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(issn13, ean13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +--------------------------------------------------- +-- ISSN: +CREATE FUNCTION isnlt(issn, issn) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(issn, issn) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(issn, issn) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(issn, issn) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(issn, issn) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(issn, issn) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(issn, issn13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(issn, issn13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(issn, issn13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(issn, issn13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(issn, issn13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(issn, issn13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(issn, ean13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(issn, ean13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(issn, ean13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(issn, ean13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(issn, ean13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(issn, ean13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +--------------------------------------------------- +-- UPC: +CREATE FUNCTION isnlt(upc, upc) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(upc, upc) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(upc, upc) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(upc, upc) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(upc, upc) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(upc, upc) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION isnlt(upc, ean13) + RETURNS boolean + AS 'int8lt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnle(upc, ean13) + RETURNS boolean + AS 'int8le' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isneq(upc, ean13) + RETURNS boolean + AS 'int8eq' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnge(upc, ean13) + RETURNS boolean + AS 'int8ge' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isngt(upc, ean13) + RETURNS boolean + AS 'int8gt' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION isnne(upc, ean13) + RETURNS boolean + AS 'int8ne' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +-- +-- Now the operators: +-- + +-- +-- EAN13 operators: +-- +--------------------------------------------------- +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ean13, + RIGHTARG = ean13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ean13, + RIGHTARG = ean13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ean13, + RIGHTARG = ean13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ean13, + RIGHTARG = ean13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ean13, + RIGHTARG = ean13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ean13, + RIGHTARG = ean13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ean13, + RIGHTARG = isbn13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ean13, + RIGHTARG = isbn13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ean13, + RIGHTARG = isbn13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ean13, + RIGHTARG = isbn13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ean13, + RIGHTARG = isbn13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ean13, + RIGHTARG = isbn13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = isbn13, + RIGHTARG = ean13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = isbn13, + RIGHTARG = ean13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = isbn13, + RIGHTARG = ean13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = isbn13, + RIGHTARG = ean13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = isbn13, + RIGHTARG = ean13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = isbn13, + RIGHTARG = ean13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ean13, + RIGHTARG = ismn13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ean13, + RIGHTARG = ismn13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ean13, + RIGHTARG = ismn13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ean13, + RIGHTARG = ismn13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ean13, + RIGHTARG = ismn13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ean13, + RIGHTARG = ismn13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ismn13, + RIGHTARG = ean13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ismn13, + RIGHTARG = ean13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ismn13, + RIGHTARG = ean13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ismn13, + RIGHTARG = ean13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ismn13, + RIGHTARG = ean13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ismn13, + RIGHTARG = ean13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ean13, + RIGHTARG = issn13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ean13, + RIGHTARG = issn13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ean13, + RIGHTARG = issn13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ean13, + RIGHTARG = issn13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ean13, + RIGHTARG = issn13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ean13, + RIGHTARG = issn13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ean13, + RIGHTARG = isbn, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ean13, + RIGHTARG = isbn, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ean13, + RIGHTARG = isbn, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ean13, + RIGHTARG = isbn, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ean13, + RIGHTARG = isbn, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ean13, + RIGHTARG = isbn, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ean13, + RIGHTARG = ismn, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ean13, + RIGHTARG = ismn, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ean13, + RIGHTARG = ismn, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ean13, + RIGHTARG = ismn, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ean13, + RIGHTARG = ismn, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ean13, + RIGHTARG = ismn, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ean13, + RIGHTARG = issn, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ean13, + RIGHTARG = issn, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ean13, + RIGHTARG = issn, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ean13, + RIGHTARG = issn, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ean13, + RIGHTARG = issn, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ean13, + RIGHTARG = issn, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ean13, + RIGHTARG = upc, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ean13, + RIGHTARG = upc, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ean13, + RIGHTARG = upc, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ean13, + RIGHTARG = upc, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ean13, + RIGHTARG = upc, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ean13, + RIGHTARG = upc, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +-- +-- ISBN13 operators: +-- +--------------------------------------------------- +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = isbn13, + RIGHTARG = isbn13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = isbn13, + RIGHTARG = isbn13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = isbn13, + RIGHTARG = isbn13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = isbn13, + RIGHTARG = isbn13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = isbn13, + RIGHTARG = isbn13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = isbn13, + RIGHTARG = isbn13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = isbn13, + RIGHTARG = isbn, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = isbn13, + RIGHTARG = isbn, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = isbn13, + RIGHTARG = isbn, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = isbn13, + RIGHTARG = isbn, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = isbn13, + RIGHTARG = isbn, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = isbn13, + RIGHTARG = isbn, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +-- +-- ISBN operators: +-- +--------------------------------------------------- +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = isbn, + RIGHTARG = isbn, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = isbn, + RIGHTARG = isbn, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = isbn, + RIGHTARG = isbn, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = isbn, + RIGHTARG = isbn, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = isbn, + RIGHTARG = isbn, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = isbn, + RIGHTARG = isbn, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = isbn, + RIGHTARG = isbn13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = isbn, + RIGHTARG = isbn13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = isbn, + RIGHTARG = isbn13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = isbn, + RIGHTARG = isbn13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = isbn, + RIGHTARG = isbn13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = isbn, + RIGHTARG = isbn13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = isbn, + RIGHTARG = ean13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = isbn, + RIGHTARG = ean13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = isbn, + RIGHTARG = ean13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = isbn, + RIGHTARG = ean13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = isbn, + RIGHTARG = ean13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = isbn, + RIGHTARG = ean13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +-- +-- ISMN13 operators: +-- +--------------------------------------------------- +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ismn13, + RIGHTARG = ismn13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ismn13, + RIGHTARG = ismn13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ismn13, + RIGHTARG = ismn13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ismn13, + RIGHTARG = ismn13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ismn13, + RIGHTARG = ismn13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ismn13, + RIGHTARG = ismn13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ismn13, + RIGHTARG = ismn, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ismn13, + RIGHTARG = ismn, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ismn13, + RIGHTARG = ismn, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ismn13, + RIGHTARG = ismn, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ismn13, + RIGHTARG = ismn, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ismn13, + RIGHTARG = ismn, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +-- +-- ISMN operators: +-- +--------------------------------------------------- +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ismn, + RIGHTARG = ismn, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ismn, + RIGHTARG = ismn, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ismn, + RIGHTARG = ismn, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ismn, + RIGHTARG = ismn, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ismn, + RIGHTARG = ismn, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ismn, + RIGHTARG = ismn, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ismn, + RIGHTARG = ismn13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ismn, + RIGHTARG = ismn13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ismn, + RIGHTARG = ismn13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ismn, + RIGHTARG = ismn13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ismn, + RIGHTARG = ismn13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ismn, + RIGHTARG = ismn13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = ismn, + RIGHTARG = ean13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = ismn, + RIGHTARG = ean13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = ismn, + RIGHTARG = ean13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = ismn, + RIGHTARG = ean13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = ismn, + RIGHTARG = ean13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = ismn, + RIGHTARG = ean13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +-- +-- ISSN13 operators: +-- +--------------------------------------------------- +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = issn13, + RIGHTARG = issn13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = issn13, + RIGHTARG = issn13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = issn13, + RIGHTARG = issn13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = issn13, + RIGHTARG = issn13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = issn13, + RIGHTARG = issn13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = issn13, + RIGHTARG = issn13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = issn13, + RIGHTARG = issn, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = issn13, + RIGHTARG = issn, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = issn13, + RIGHTARG = issn, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = issn13, + RIGHTARG = issn, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = issn13, + RIGHTARG = issn, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = issn13, + RIGHTARG = issn, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = issn13, + RIGHTARG = ean13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = issn13, + RIGHTARG = ean13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = issn13, + RIGHTARG = ean13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = issn13, + RIGHTARG = ean13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = issn13, + RIGHTARG = ean13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = issn13, + RIGHTARG = ean13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +-- +-- ISSN operators: +-- +--------------------------------------------------- +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = issn, + RIGHTARG = issn, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = issn, + RIGHTARG = issn, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = issn, + RIGHTARG = issn, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = issn, + RIGHTARG = issn, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = issn, + RIGHTARG = issn, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = issn, + RIGHTARG = issn, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = issn, + RIGHTARG = issn13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = issn, + RIGHTARG = issn13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = issn, + RIGHTARG = issn13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = issn, + RIGHTARG = issn13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = issn, + RIGHTARG = issn13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = issn, + RIGHTARG = issn13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = issn, + RIGHTARG = ean13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = issn, + RIGHTARG = ean13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = issn, + RIGHTARG = ean13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = issn, + RIGHTARG = ean13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = issn, + RIGHTARG = ean13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = issn, + RIGHTARG = ean13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +-- +-- UPC operators: +-- +--------------------------------------------------- +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = upc, + RIGHTARG = upc, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = upc, + RIGHTARG = upc, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = upc, + RIGHTARG = upc, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = upc, + RIGHTARG = upc, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = upc, + RIGHTARG = upc, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = upc, + RIGHTARG = upc, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR < ( + PROCEDURE = isnlt, + LEFTARG = upc, + RIGHTARG = ean13, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR <= ( + PROCEDURE = isnle, + LEFTARG = upc, + RIGHTARG = ean13, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); +CREATE OPERATOR = ( + PROCEDURE = isneq, + LEFTARG = upc, + RIGHTARG = ean13, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES, + HASHES); +CREATE OPERATOR >= ( + PROCEDURE = isnge, + LEFTARG = upc, + RIGHTARG = ean13, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR > ( + PROCEDURE = isngt, + LEFTARG = upc, + RIGHTARG = ean13, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); +CREATE OPERATOR <> ( + PROCEDURE = isnne, + LEFTARG = upc, + RIGHTARG = ean13, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +-- +-- Operator families for the various operator classes: +-- +--------------------------------------------------- + +CREATE OPERATOR FAMILY isn_ops USING btree; +CREATE OPERATOR FAMILY isn_ops USING hash; + +-- +-- Operator classes: +-- +--------------------------------------------------- +-- EAN13: +CREATE FUNCTION btean13cmp(ean13, ean13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS ean13_ops DEFAULT + FOR TYPE ean13 USING btree FAMILY isn_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btean13cmp(ean13, ean13); + +CREATE FUNCTION hashean13(ean13) + RETURNS int4 + AS 'hashint8' + LANGUAGE 'internal' IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS ean13_ops DEFAULT + FOR TYPE ean13 USING hash FAMILY isn_ops AS + OPERATOR 1 =, + FUNCTION 1 hashean13(ean13); + +-- EAN13 vs other types: +CREATE FUNCTION btean13cmp(ean13, isbn13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btean13cmp(ean13, ismn13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btean13cmp(ean13, issn13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btean13cmp(ean13, isbn) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btean13cmp(ean13, ismn) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btean13cmp(ean13, issn) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btean13cmp(ean13, upc) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +ALTER OPERATOR FAMILY isn_ops USING btree ADD + OPERATOR 1 < (ean13, isbn13), + OPERATOR 1 < (ean13, ismn13), + OPERATOR 1 < (ean13, issn13), + OPERATOR 1 < (ean13, isbn), + OPERATOR 1 < (ean13, ismn), + OPERATOR 1 < (ean13, issn), + OPERATOR 1 < (ean13, upc), + OPERATOR 2 <= (ean13, isbn13), + OPERATOR 2 <= (ean13, ismn13), + OPERATOR 2 <= (ean13, issn13), + OPERATOR 2 <= (ean13, isbn), + OPERATOR 2 <= (ean13, ismn), + OPERATOR 2 <= (ean13, issn), + OPERATOR 2 <= (ean13, upc), + OPERATOR 3 = (ean13, isbn13), + OPERATOR 3 = (ean13, ismn13), + OPERATOR 3 = (ean13, issn13), + OPERATOR 3 = (ean13, isbn), + OPERATOR 3 = (ean13, ismn), + OPERATOR 3 = (ean13, issn), + OPERATOR 3 = (ean13, upc), + OPERATOR 4 >= (ean13, isbn13), + OPERATOR 4 >= (ean13, ismn13), + OPERATOR 4 >= (ean13, issn13), + OPERATOR 4 >= (ean13, isbn), + OPERATOR 4 >= (ean13, ismn), + OPERATOR 4 >= (ean13, issn), + OPERATOR 4 >= (ean13, upc), + OPERATOR 5 > (ean13, isbn13), + OPERATOR 5 > (ean13, ismn13), + OPERATOR 5 > (ean13, issn13), + OPERATOR 5 > (ean13, isbn), + OPERATOR 5 > (ean13, ismn), + OPERATOR 5 > (ean13, issn), + OPERATOR 5 > (ean13, upc), + FUNCTION 1 btean13cmp(ean13, isbn13), + FUNCTION 1 btean13cmp(ean13, ismn13), + FUNCTION 1 btean13cmp(ean13, issn13), + FUNCTION 1 btean13cmp(ean13, isbn), + FUNCTION 1 btean13cmp(ean13, ismn), + FUNCTION 1 btean13cmp(ean13, issn), + FUNCTION 1 btean13cmp(ean13, upc); + +ALTER OPERATOR FAMILY isn_ops USING hash ADD + OPERATOR 1 = (ean13, isbn13), + OPERATOR 1 = (ean13, ismn13), + OPERATOR 1 = (ean13, issn13), + OPERATOR 1 = (ean13, isbn), + OPERATOR 1 = (ean13, ismn), + OPERATOR 1 = (ean13, issn), + OPERATOR 1 = (ean13, upc); + +--------------------------------------------------- +-- ISBN13: +CREATE FUNCTION btisbn13cmp(isbn13, isbn13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS isbn13_ops DEFAULT + FOR TYPE isbn13 USING btree FAMILY isn_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btisbn13cmp(isbn13, isbn13); + +CREATE FUNCTION hashisbn13(isbn13) + RETURNS int4 + AS 'hashint8' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS isbn13_ops DEFAULT + FOR TYPE isbn13 USING hash FAMILY isn_ops AS + OPERATOR 1 =, + FUNCTION 1 hashisbn13(isbn13); + +-- ISBN13 vs other types: +CREATE FUNCTION btisbn13cmp(isbn13, ean13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btisbn13cmp(isbn13, isbn) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +ALTER OPERATOR FAMILY isn_ops USING btree ADD + OPERATOR 1 < (isbn13, ean13), + OPERATOR 1 < (isbn13, isbn), + OPERATOR 2 <= (isbn13, ean13), + OPERATOR 2 <= (isbn13, isbn), + OPERATOR 3 = (isbn13, ean13), + OPERATOR 3 = (isbn13, isbn), + OPERATOR 4 >= (isbn13, ean13), + OPERATOR 4 >= (isbn13, isbn), + OPERATOR 5 > (isbn13, ean13), + OPERATOR 5 > (isbn13, isbn), + FUNCTION 1 btisbn13cmp(isbn13, ean13), + FUNCTION 1 btisbn13cmp(isbn13, isbn); + +ALTER OPERATOR FAMILY isn_ops USING hash ADD + OPERATOR 1 = (isbn13, ean13), + OPERATOR 1 = (isbn13, isbn); + +--------------------------------------------------- +-- ISBN: +CREATE FUNCTION btisbncmp(isbn, isbn) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS isbn_ops DEFAULT + FOR TYPE isbn USING btree FAMILY isn_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btisbncmp(isbn, isbn); + +CREATE FUNCTION hashisbn(isbn) + RETURNS int4 + AS 'hashint8' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS isbn_ops DEFAULT + FOR TYPE isbn USING hash FAMILY isn_ops AS + OPERATOR 1 =, + FUNCTION 1 hashisbn(isbn); + +-- ISBN vs other types: +CREATE FUNCTION btisbncmp(isbn, ean13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btisbncmp(isbn, isbn13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +ALTER OPERATOR FAMILY isn_ops USING btree ADD + OPERATOR 1 < (isbn, ean13), + OPERATOR 1 < (isbn, isbn13), + OPERATOR 2 <= (isbn, ean13), + OPERATOR 2 <= (isbn, isbn13), + OPERATOR 3 = (isbn, ean13), + OPERATOR 3 = (isbn, isbn13), + OPERATOR 4 >= (isbn, ean13), + OPERATOR 4 >= (isbn, isbn13), + OPERATOR 5 > (isbn, ean13), + OPERATOR 5 > (isbn, isbn13), + FUNCTION 1 btisbncmp(isbn, ean13), + FUNCTION 1 btisbncmp(isbn, isbn13); + +ALTER OPERATOR FAMILY isn_ops USING hash ADD + OPERATOR 1 = (isbn, ean13), + OPERATOR 1 = (isbn, isbn13); + +--------------------------------------------------- +-- ISMN13: +CREATE FUNCTION btismn13cmp(ismn13, ismn13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS ismn13_ops DEFAULT + FOR TYPE ismn13 USING btree FAMILY isn_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btismn13cmp(ismn13, ismn13); + +CREATE FUNCTION hashismn13(ismn13) + RETURNS int4 + AS 'hashint8' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS ismn13_ops DEFAULT + FOR TYPE ismn13 USING hash FAMILY isn_ops AS + OPERATOR 1 =, + FUNCTION 1 hashismn13(ismn13); + +-- ISMN13 vs other types: +CREATE FUNCTION btismn13cmp(ismn13, ean13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btismn13cmp(ismn13, ismn) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +ALTER OPERATOR FAMILY isn_ops USING btree ADD + OPERATOR 1 < (ismn13, ean13), + OPERATOR 1 < (ismn13, ismn), + OPERATOR 2 <= (ismn13, ean13), + OPERATOR 2 <= (ismn13, ismn), + OPERATOR 3 = (ismn13, ean13), + OPERATOR 3 = (ismn13, ismn), + OPERATOR 4 >= (ismn13, ean13), + OPERATOR 4 >= (ismn13, ismn), + OPERATOR 5 > (ismn13, ean13), + OPERATOR 5 > (ismn13, ismn), + FUNCTION 1 btismn13cmp(ismn13, ean13), + FUNCTION 1 btismn13cmp(ismn13, ismn); + +ALTER OPERATOR FAMILY isn_ops USING hash ADD + OPERATOR 1 = (ismn13, ean13), + OPERATOR 1 = (ismn13, ismn); + +--------------------------------------------------- +-- ISMN: +CREATE FUNCTION btismncmp(ismn, ismn) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS ismn_ops DEFAULT + FOR TYPE ismn USING btree FAMILY isn_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btismncmp(ismn, ismn); + +CREATE FUNCTION hashismn(ismn) + RETURNS int4 + AS 'hashint8' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS ismn_ops DEFAULT + FOR TYPE ismn USING hash FAMILY isn_ops AS + OPERATOR 1 =, + FUNCTION 1 hashismn(ismn); + +-- ISMN vs other types: +CREATE FUNCTION btismncmp(ismn, ean13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btismncmp(ismn, ismn13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +ALTER OPERATOR FAMILY isn_ops USING btree ADD + OPERATOR 1 < (ismn, ean13), + OPERATOR 1 < (ismn, ismn13), + OPERATOR 2 <= (ismn, ean13), + OPERATOR 2 <= (ismn, ismn13), + OPERATOR 3 = (ismn, ean13), + OPERATOR 3 = (ismn, ismn13), + OPERATOR 4 >= (ismn, ean13), + OPERATOR 4 >= (ismn, ismn13), + OPERATOR 5 > (ismn, ean13), + OPERATOR 5 > (ismn, ismn13), + FUNCTION 1 btismncmp(ismn, ean13), + FUNCTION 1 btismncmp(ismn, ismn13); + +ALTER OPERATOR FAMILY isn_ops USING hash ADD + OPERATOR 1 = (ismn, ean13), + OPERATOR 1 = (ismn, ismn13); + +--------------------------------------------------- +-- ISSN13: +CREATE FUNCTION btissn13cmp(issn13, issn13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS issn13_ops DEFAULT + FOR TYPE issn13 USING btree FAMILY isn_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btissn13cmp(issn13, issn13); + +CREATE FUNCTION hashissn13(issn13) + RETURNS int4 + AS 'hashint8' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS issn13_ops DEFAULT + FOR TYPE issn13 USING hash FAMILY isn_ops AS + OPERATOR 1 =, + FUNCTION 1 hashissn13(issn13); + +-- ISSN13 vs other types: +CREATE FUNCTION btissn13cmp(issn13, ean13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btissn13cmp(issn13, issn) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +ALTER OPERATOR FAMILY isn_ops USING btree ADD + OPERATOR 1 < (issn13, ean13), + OPERATOR 1 < (issn13, issn), + OPERATOR 2 <= (issn13, ean13), + OPERATOR 2 <= (issn13, issn), + OPERATOR 3 = (issn13, ean13), + OPERATOR 3 = (issn13, issn), + OPERATOR 4 >= (issn13, ean13), + OPERATOR 4 >= (issn13, issn), + OPERATOR 5 > (issn13, ean13), + OPERATOR 5 > (issn13, issn), + FUNCTION 1 btissn13cmp(issn13, ean13), + FUNCTION 1 btissn13cmp(issn13, issn); + +ALTER OPERATOR FAMILY isn_ops USING hash ADD + OPERATOR 1 = (issn13, ean13), + OPERATOR 1 = (issn13, issn); + +--------------------------------------------------- +-- ISSN: +CREATE FUNCTION btissncmp(issn, issn) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS issn_ops DEFAULT + FOR TYPE issn USING btree FAMILY isn_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btissncmp(issn, issn); + +CREATE FUNCTION hashissn(issn) + RETURNS int4 + AS 'hashint8' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS issn_ops DEFAULT + FOR TYPE issn USING hash FAMILY isn_ops AS + OPERATOR 1 =, + FUNCTION 1 hashissn(issn); + +-- ISSN vs other types: +CREATE FUNCTION btissncmp(issn, ean13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION btissncmp(issn, issn13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +ALTER OPERATOR FAMILY isn_ops USING btree ADD + OPERATOR 1 < (issn, ean13), + OPERATOR 1 < (issn, issn13), + OPERATOR 2 <= (issn, ean13), + OPERATOR 2 <= (issn, issn13), + OPERATOR 3 = (issn, ean13), + OPERATOR 3 = (issn, issn13), + OPERATOR 4 >= (issn, ean13), + OPERATOR 4 >= (issn, issn13), + OPERATOR 5 > (issn, ean13), + OPERATOR 5 > (issn, issn13), + FUNCTION 1 btissncmp(issn, ean13), + FUNCTION 1 btissncmp(issn, issn13); + +ALTER OPERATOR FAMILY isn_ops USING hash ADD + OPERATOR 1 = (issn, ean13), + OPERATOR 1 = (issn, issn13); + +--------------------------------------------------- +-- UPC: +CREATE FUNCTION btupccmp(upc, upc) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS upc_ops DEFAULT + FOR TYPE upc USING btree FAMILY isn_ops AS + OPERATOR 1 <, + OPERATOR 2 <=, + OPERATOR 3 =, + OPERATOR 4 >=, + OPERATOR 5 >, + FUNCTION 1 btupccmp(upc, upc); + +CREATE FUNCTION hashupc(upc) + RETURNS int4 + AS 'hashint8' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE OPERATOR CLASS upc_ops DEFAULT + FOR TYPE upc USING hash FAMILY isn_ops AS + OPERATOR 1 =, + FUNCTION 1 hashupc(upc); + +-- UPC vs other types: +CREATE FUNCTION btupccmp(upc, ean13) + RETURNS int4 + AS 'btint8cmp' + LANGUAGE 'internal' + IMMUTABLE STRICT + PARALLEL SAFE; + +ALTER OPERATOR FAMILY isn_ops USING btree ADD + OPERATOR 1 < (upc, ean13), + OPERATOR 2 <= (upc, ean13), + OPERATOR 3 = (upc, ean13), + OPERATOR 4 >= (upc, ean13), + OPERATOR 5 > (upc, ean13), + FUNCTION 1 btupccmp(upc, ean13); + +ALTER OPERATOR FAMILY isn_ops USING hash ADD + OPERATOR 1 = (upc, ean13); + +-- +-- Type casts: +-- +--------------------------------------------------- +CREATE FUNCTION isbn13(ean13) +RETURNS isbn13 +AS 'MODULE_PATHNAME', 'isbn_cast_from_ean13' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION ismn13(ean13) +RETURNS ismn13 +AS 'MODULE_PATHNAME', 'ismn_cast_from_ean13' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION issn13(ean13) +RETURNS issn13 +AS 'MODULE_PATHNAME', 'issn_cast_from_ean13' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION isbn(ean13) +RETURNS isbn +AS 'MODULE_PATHNAME', 'isbn_cast_from_ean13' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION ismn(ean13) +RETURNS ismn +AS 'MODULE_PATHNAME', 'ismn_cast_from_ean13' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION issn(ean13) +RETURNS issn +AS 'MODULE_PATHNAME', 'issn_cast_from_ean13' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; +CREATE FUNCTION upc(ean13) +RETURNS upc +AS 'MODULE_PATHNAME', 'upc_cast_from_ean13' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + + +CREATE CAST (ean13 AS isbn13) WITH FUNCTION isbn13(ean13); +CREATE CAST (ean13 AS isbn) WITH FUNCTION isbn(ean13); +CREATE CAST (ean13 AS ismn13) WITH FUNCTION ismn13(ean13); +CREATE CAST (ean13 AS ismn) WITH FUNCTION ismn(ean13); +CREATE CAST (ean13 AS issn13) WITH FUNCTION issn13(ean13); +CREATE CAST (ean13 AS issn) WITH FUNCTION issn(ean13); +CREATE CAST (ean13 AS upc) WITH FUNCTION upc(ean13); + +CREATE CAST (isbn13 AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (isbn AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (ismn13 AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (ismn AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (issn13 AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (issn AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (upc AS ean13) WITHOUT FUNCTION AS ASSIGNMENT; + +CREATE CAST (isbn AS isbn13) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (isbn13 AS isbn) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (ismn AS ismn13) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (ismn13 AS ismn) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (issn AS issn13) WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (issn13 AS issn) WITHOUT FUNCTION AS ASSIGNMENT; + +-- +-- Validation stuff for lose types: +-- +CREATE FUNCTION make_valid(ean13) + RETURNS ean13 + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION make_valid(isbn13) + RETURNS isbn13 + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION make_valid(ismn13) + RETURNS ismn13 + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION make_valid(issn13) + RETURNS issn13 + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION make_valid(isbn) + RETURNS isbn + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION make_valid(ismn) + RETURNS ismn + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION make_valid(issn) + RETURNS issn + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION make_valid(upc) + RETURNS upc + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; + +CREATE FUNCTION is_valid(ean13) + RETURNS boolean + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION is_valid(isbn13) + RETURNS boolean + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION is_valid(ismn13) + RETURNS boolean + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION is_valid(issn13) + RETURNS boolean + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION is_valid(isbn) + RETURNS boolean + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION is_valid(ismn) + RETURNS boolean + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION is_valid(issn) + RETURNS boolean + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; +CREATE FUNCTION is_valid(upc) + RETURNS boolean + AS 'MODULE_PATHNAME' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL SAFE; + +-- +-- isn_weak(boolean) - Sets the weak input mode. +-- This function is intended for testing use only! +-- +CREATE FUNCTION isn_weak(boolean) + RETURNS boolean + AS 'MODULE_PATHNAME', 'accept_weak_input' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL RESTRICTED; + +-- +-- isn_weak() - Gets the weak input mode status +-- +CREATE FUNCTION isn_weak() + RETURNS boolean + AS 'MODULE_PATHNAME', 'weak_input_status' + LANGUAGE C + IMMUTABLE STRICT + PARALLEL RESTRICTED; diff --git a/contrib/isn/isn.c b/contrib/isn/isn.c new file mode 100644 index 0000000..00bd9cd --- /dev/null +++ b/contrib/isn/isn.c @@ -0,0 +1,1132 @@ +/*------------------------------------------------------------------------- + * + * isn.c + * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC) + * + * Author: German Mendez Bravo (Kronuz) + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/isn/isn.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "EAN13.h" +#include "ISBN.h" +#include "ISMN.h" +#include "ISSN.h" +#include "UPC.h" +#include "fmgr.h" +#include "isn.h" +#include "utils/builtins.h" + +PG_MODULE_MAGIC; + +#ifdef USE_ASSERT_CHECKING +#define ISN_DEBUG 1 +#else +#define ISN_DEBUG 0 +#endif + +#define MAXEAN13LEN 18 + +enum isn_type +{ + INVALID, ANY, EAN13, ISBN, ISMN, ISSN, UPC +}; + +static const char *const isn_names[] = {"EAN13/UPC/ISxN", "EAN13/UPC/ISxN", "EAN13", "ISBN", "ISMN", "ISSN", "UPC"}; + +static bool g_weak = false; + + +/*********************************************************************** + ** + ** Routines for EAN13/UPC/ISxNs. + ** + ** Note: + ** In this code, a normalized string is one that is known to be a valid + ** ISxN number containing only digits and hyphens and with enough space + ** to hold the full 13 digits plus the maximum of four hyphens. + ***********************************************************************/ + +/*---------------------------------------------------------- + * Debugging routines. + *---------------------------------------------------------*/ + +/* + * Check if the table and its index is correct (just for debugging) + */ +pg_attribute_unused() +static bool +check_table(const char *(*TABLE)[2], const unsigned TABLE_index[10][2]) +{ + const char *aux1, + *aux2; + int a, + b, + x = 0, + y = -1, + i = 0, + j, + init = 0; + + if (TABLE == NULL || TABLE_index == NULL) + return true; + + while (TABLE[i][0] && TABLE[i][1]) + { + aux1 = TABLE[i][0]; + aux2 = TABLE[i][1]; + + /* must always start with a digit: */ + if (!isdigit((unsigned char) *aux1) || !isdigit((unsigned char) *aux2)) + goto invalidtable; + a = *aux1 - '0'; + b = *aux2 - '0'; + + /* must always have the same format and length: */ + while (*aux1 && *aux2) + { + if (!(isdigit((unsigned char) *aux1) && + isdigit((unsigned char) *aux2)) && + (*aux1 != *aux2 || *aux1 != '-')) + goto invalidtable; + aux1++; + aux2++; + } + if (*aux1 != *aux2) + goto invalidtable; + + /* found a new range */ + if (a > y) + { + /* check current range in the index: */ + for (j = x; j <= y; j++) + { + if (TABLE_index[j][0] != init) + goto invalidindex; + if (TABLE_index[j][1] != i - init) + goto invalidindex; + } + init = i; + x = a; + } + + /* Always get the new limit */ + y = b; + if (y < x) + goto invalidtable; + i++; + } + + return true; + +invalidtable: + elog(DEBUG1, "invalid table near {\"%s\", \"%s\"} (pos: %d)", + TABLE[i][0], TABLE[i][1], i); + return false; + +invalidindex: + elog(DEBUG1, "index %d is invalid", j); + return false; +} + +/*---------------------------------------------------------- + * Formatting and conversion routines. + *---------------------------------------------------------*/ + +static unsigned +dehyphenate(char *bufO, char *bufI) +{ + unsigned ret = 0; + + while (*bufI) + { + if (isdigit((unsigned char) *bufI)) + { + *bufO++ = *bufI; + ret++; + } + bufI++; + } + *bufO = '\0'; + return ret; +} + +/* + * hyphenate --- Try to hyphenate, in-place, the string starting at bufI + * into bufO using the given hyphenation range TABLE. + * Assumes the input string to be used is of only digits. + * + * Returns the number of characters actually hyphenated. + */ +static unsigned +hyphenate(char *bufO, char *bufI, const char *(*TABLE)[2], const unsigned TABLE_index[10][2]) +{ + unsigned ret = 0; + const char *ean_aux1, + *ean_aux2, + *ean_p; + char *firstdig, + *aux1, + *aux2; + unsigned search, + upper, + lower, + step; + bool ean_in1, + ean_in2; + + /* just compress the string if no further hyphenation is required */ + if (TABLE == NULL || TABLE_index == NULL) + { + while (*bufI) + { + *bufO++ = *bufI++; + ret++; + } + *bufO = '\0'; + return (ret + 1); + } + + /* add remaining hyphenations */ + + search = *bufI - '0'; + upper = lower = TABLE_index[search][0]; + upper += TABLE_index[search][1]; + lower--; + + step = (upper - lower) / 2; + if (step == 0) + return 0; + search = lower + step; + + firstdig = bufI; + ean_in1 = ean_in2 = false; + ean_aux1 = TABLE[search][0]; + ean_aux2 = TABLE[search][1]; + do + { + if ((ean_in1 || *firstdig >= *ean_aux1) && (ean_in2 || *firstdig <= *ean_aux2)) + { + if (*firstdig > *ean_aux1) + ean_in1 = true; + if (*firstdig < *ean_aux2) + ean_in2 = true; + if (ean_in1 && ean_in2) + break; + + firstdig++, ean_aux1++, ean_aux2++; + if (!(*ean_aux1 && *ean_aux2 && *firstdig)) + break; + if (!isdigit((unsigned char) *ean_aux1)) + ean_aux1++, ean_aux2++; + } + else + { + /* + * check in what direction we should go and move the pointer + * accordingly + */ + if (*firstdig < *ean_aux1 && !ean_in1) + upper = search; + else + lower = search; + + step = (upper - lower) / 2; + search = lower + step; + + /* Initialize stuff again: */ + firstdig = bufI; + ean_in1 = ean_in2 = false; + ean_aux1 = TABLE[search][0]; + ean_aux2 = TABLE[search][1]; + } + } while (step); + + if (step) + { + aux1 = bufO; + aux2 = bufI; + ean_p = TABLE[search][0]; + while (*ean_p && *aux2) + { + if (*ean_p++ != '-') + *aux1++ = *aux2++; + else + *aux1++ = '-'; + ret++; + } + *aux1++ = '-'; + *aux1 = *aux2; /* add a lookahead char */ + return (ret + 1); + } + return ret; +} + +/* + * weight_checkdig -- Receives a buffer with a normalized ISxN string number, + * and the length to weight. + * + * Returns the weight of the number (the check digit value, 0-10) + */ +static unsigned +weight_checkdig(char *isn, unsigned size) +{ + unsigned weight = 0; + + while (*isn && size > 1) + { + if (isdigit((unsigned char) *isn)) + { + weight += size-- * (*isn - '0'); + } + isn++; + } + weight = weight % 11; + if (weight != 0) + weight = 11 - weight; + return weight; +} + + +/* + * checkdig --- Receives a buffer with a normalized ISxN string number, + * and the length to check. + * + * Returns the check digit value (0-9) + */ +static unsigned +checkdig(char *num, unsigned size) +{ + unsigned check = 0, + check3 = 0; + unsigned pos = 0; + + if (*num == 'M') + { /* ISMN start with 'M' */ + check3 = 3; + pos = 1; + } + while (*num && size > 1) + { + if (isdigit((unsigned char) *num)) + { + if (pos++ % 2) + check3 += *num - '0'; + else + check += *num - '0'; + size--; + } + num++; + } + check = (check + 3 * check3) % 10; + if (check != 0) + check = 10 - check; + return check; +} + +/* + * ean2isn --- Try to convert an ean13 number to a UPC/ISxN number. + * This doesn't verify for a valid check digit. + * + * If errorOK is false, ereport a useful error message if the ean13 is bad. + * If errorOK is true, just return "false" for bad input. + */ +static bool +ean2isn(ean13 ean, bool errorOK, ean13 *result, enum isn_type accept) +{ + enum isn_type type = INVALID; + + char buf[MAXEAN13LEN + 1]; + char *aux; + unsigned digval; + unsigned search; + ean13 ret = ean; + + ean >>= 1; + /* verify it's in the EAN13 range */ + if (ean > UINT64CONST(9999999999999)) + goto eantoobig; + + /* convert the number */ + search = 0; + aux = buf + 13; + *aux = '\0'; /* terminate string; aux points to last digit */ + do + { + digval = (unsigned) (ean % 10); /* get the decimal value */ + ean /= 10; /* get next digit */ + *--aux = (char) (digval + '0'); /* convert to ascii and store */ + } while (ean && search++ < 12); + while (search++ < 12) + *--aux = '0'; /* fill the remaining EAN13 with '0' */ + + /* find out the data type: */ + if (strncmp("978", buf, 3) == 0) + { /* ISBN */ + type = ISBN; + } + else if (strncmp("977", buf, 3) == 0) + { /* ISSN */ + type = ISSN; + } + else if (strncmp("9790", buf, 4) == 0) + { /* ISMN */ + type = ISMN; + } + else if (strncmp("979", buf, 3) == 0) + { /* ISBN-13 */ + type = ISBN; + } + else if (*buf == '0') + { /* UPC */ + type = UPC; + } + else + { + type = EAN13; + } + if (accept != ANY && accept != EAN13 && accept != type) + goto eanwrongtype; + + *result = ret; + return true; + +eanwrongtype: + if (!errorOK) + { + if (type != EAN13) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("cannot cast EAN13(%s) to %s for number: \"%s\"", + isn_names[type], isn_names[accept], buf))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("cannot cast %s to %s for number: \"%s\"", + isn_names[type], isn_names[accept], buf))); + } + } + return false; + +eantoobig: + if (!errorOK) + { + char eanbuf[64]; + + /* + * Format the number separately to keep the machine-dependent format + * code out of the translatable message text + */ + snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean); + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for %s type", + eanbuf, isn_names[type]))); + } + return false; +} + +/* + * ean2UPC/ISxN --- Convert in-place a normalized EAN13 string to the corresponding + * UPC/ISxN string number. Assumes the input string is normalized. + */ +static inline void +ean2ISBN(char *isn) +{ + char *aux; + unsigned check; + + /* + * The number should come in this format: 978-0-000-00000-0 or may be an + * ISBN-13 number, 979-..., which does not have a short representation. Do + * the short output version if possible. + */ + if (strncmp("978-", isn, 4) == 0) + { + /* Strip the first part and calculate the new check digit */ + hyphenate(isn, isn + 4, NULL, NULL); + check = weight_checkdig(isn, 10); + aux = strchr(isn, '\0'); + while (!isdigit((unsigned char) *--aux)); + if (check == 10) + *aux = 'X'; + else + *aux = check + '0'; + } +} + +static inline void +ean2ISMN(char *isn) +{ + /* the number should come in this format: 979-0-000-00000-0 */ + /* Just strip the first part and change the first digit ('0') to 'M' */ + hyphenate(isn, isn + 4, NULL, NULL); + isn[0] = 'M'; +} + +static inline void +ean2ISSN(char *isn) +{ + unsigned check; + + /* the number should come in this format: 977-0000-000-00-0 */ + /* Strip the first part, crop, and calculate the new check digit */ + hyphenate(isn, isn + 4, NULL, NULL); + check = weight_checkdig(isn, 8); + if (check == 10) + isn[8] = 'X'; + else + isn[8] = check + '0'; + isn[9] = '\0'; +} + +static inline void +ean2UPC(char *isn) +{ + /* the number should come in this format: 000-000000000-0 */ + /* Strip the first part, crop, and dehyphenate */ + dehyphenate(isn, isn + 1); + isn[12] = '\0'; +} + +/* + * ean2* --- Converts a string of digits into an ean13 number. + * Assumes the input string is a string with only digits + * on it, and that it's within the range of ean13. + * + * Returns the ean13 value of the string. + */ +static ean13 +str2ean(const char *num) +{ + ean13 ean = 0; /* current ean */ + + while (*num) + { + if (isdigit((unsigned char) *num)) + ean = 10 * ean + (*num - '0'); + num++; + } + return (ean << 1); /* also give room to a flag */ +} + +/* + * ean2string --- Try to convert an ean13 number to a hyphenated string. + * Assumes there's enough space in result to hold + * the string (maximum MAXEAN13LEN+1 bytes) + * This doesn't verify for a valid check digit. + * + * If shortType is true, the returned string is in the old ISxN short format. + * If errorOK is false, ereport a useful error message if the string is bad. + * If errorOK is true, just return "false" for bad input. + */ +static bool +ean2string(ean13 ean, bool errorOK, char *result, bool shortType) +{ + const char *(*TABLE)[2]; + const unsigned (*TABLE_index)[2]; + enum isn_type type = INVALID; + + char *aux; + unsigned digval; + unsigned search; + char valid = '\0'; /* was the number initially written with a + * valid check digit? */ + + TABLE_index = ISBN_index; + + if ((ean & 1) != 0) + valid = '!'; + ean >>= 1; + /* verify it's in the EAN13 range */ + if (ean > UINT64CONST(9999999999999)) + goto eantoobig; + + /* convert the number */ + search = 0; + aux = result + MAXEAN13LEN; + *aux = '\0'; /* terminate string; aux points to last digit */ + *--aux = valid; /* append '!' for numbers with invalid but + * corrected check digit */ + do + { + digval = (unsigned) (ean % 10); /* get the decimal value */ + ean /= 10; /* get next digit */ + *--aux = (char) (digval + '0'); /* convert to ascii and store */ + if (search == 0) + *--aux = '-'; /* the check digit is always there */ + } while (ean && search++ < 13); + while (search++ < 13) + *--aux = '0'; /* fill the remaining EAN13 with '0' */ + + /* The string should be in this form: ???DDDDDDDDDDDD-D" */ + search = hyphenate(result, result + 3, EAN13_range, EAN13_index); + + /* verify it's a logically valid EAN13 */ + if (search == 0) + { + search = hyphenate(result, result + 3, NULL, NULL); + goto okay; + } + + /* find out what type of hyphenation is needed: */ + if (strncmp("978-", result, search) == 0) + { /* ISBN -13 978-range */ + /* The string should be in this form: 978-??000000000-0" */ + type = ISBN; + TABLE = ISBN_range; + TABLE_index = ISBN_index; + } + else if (strncmp("977-", result, search) == 0) + { /* ISSN */ + /* The string should be in this form: 977-??000000000-0" */ + type = ISSN; + TABLE = ISSN_range; + TABLE_index = ISSN_index; + } + else if (strncmp("979-0", result, search + 1) == 0) + { /* ISMN */ + /* The string should be in this form: 979-0?000000000-0" */ + type = ISMN; + TABLE = ISMN_range; + TABLE_index = ISMN_index; + } + else if (strncmp("979-", result, search) == 0) + { /* ISBN-13 979-range */ + /* The string should be in this form: 979-??000000000-0" */ + type = ISBN; + TABLE = ISBN_range_new; + TABLE_index = ISBN_index_new; + } + else if (*result == '0') + { /* UPC */ + /* The string should be in this form: 000-00000000000-0" */ + type = UPC; + TABLE = UPC_range; + TABLE_index = UPC_index; + } + else + { + type = EAN13; + TABLE = NULL; + TABLE_index = NULL; + } + + /* verify it's a logically valid EAN13/UPC/ISxN */ + digval = search; + search = hyphenate(result + digval, result + digval + 2, TABLE, TABLE_index); + + /* verify it's a valid EAN13 */ + if (search == 0) + { + search = hyphenate(result + digval, result + digval + 2, NULL, NULL); + goto okay; + } + +okay: + /* convert to the old short type: */ + if (shortType) + switch (type) + { + case ISBN: + ean2ISBN(result); + break; + case ISMN: + ean2ISMN(result); + break; + case ISSN: + ean2ISSN(result); + break; + case UPC: + ean2UPC(result); + break; + default: + break; + } + return true; + +eantoobig: + if (!errorOK) + { + char eanbuf[64]; + + /* + * Format the number separately to keep the machine-dependent format + * code out of the translatable message text + */ + snprintf(eanbuf, sizeof(eanbuf), EAN13_FORMAT, ean); + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for %s type", + eanbuf, isn_names[type]))); + } + return false; +} + +/* + * string2ean --- try to parse a string into an ean13. + * + * ereturn false with a useful error message if the string is bad. + * Otherwise return true. + * + * if the input string ends with '!' it will always be treated as invalid + * (even if the check digit is valid) + */ +static bool +string2ean(const char *str, struct Node *escontext, ean13 *result, + enum isn_type accept) +{ + bool digit, + last; + char buf[17] = " "; + char *aux1 = buf + 3; /* leave space for the first part, in case + * it's needed */ + const char *aux2 = str; + enum isn_type type = INVALID; + unsigned check = 0, + rcheck = (unsigned) -1; + unsigned length = 0; + bool magic = false, + valid = true; + + /* recognize and validate the number: */ + while (*aux2 && length <= 13) + { + last = (*(aux2 + 1) == '!' || *(aux2 + 1) == '\0'); /* is the last character */ + digit = (isdigit((unsigned char) *aux2) != 0); /* is current character + * a digit? */ + if (*aux2 == '?' && last) /* automagically calculate check digit if + * it's '?' */ + magic = digit = true; + if (length == 0 && (*aux2 == 'M' || *aux2 == 'm')) + { + /* only ISMN can be here */ + if (type != INVALID) + goto eaninvalid; + type = ISMN; + *aux1++ = 'M'; + length++; + } + else if (length == 7 && (digit || *aux2 == 'X' || *aux2 == 'x') && last) + { + /* only ISSN can be here */ + if (type != INVALID) + goto eaninvalid; + type = ISSN; + *aux1++ = toupper((unsigned char) *aux2); + length++; + } + else if (length == 9 && (digit || *aux2 == 'X' || *aux2 == 'x') && last) + { + /* only ISBN and ISMN can be here */ + if (type != INVALID && type != ISMN) + goto eaninvalid; + if (type == INVALID) + type = ISBN; /* ISMN must start with 'M' */ + *aux1++ = toupper((unsigned char) *aux2); + length++; + } + else if (length == 11 && digit && last) + { + /* only UPC can be here */ + if (type != INVALID) + goto eaninvalid; + type = UPC; + *aux1++ = *aux2; + length++; + } + else if (*aux2 == '-' || *aux2 == ' ') + { + /* skip, we could validate but I think it's worthless */ + } + else if (*aux2 == '!' && *(aux2 + 1) == '\0') + { + /* the invalid check digit suffix was found, set it */ + if (!magic) + valid = false; + magic = true; + } + else if (!digit) + { + goto eaninvalid; + } + else + { + *aux1++ = *aux2; + if (++length > 13) + goto eantoobig; + } + aux2++; + } + *aux1 = '\0'; /* terminate the string */ + + /* find the current check digit value */ + if (length == 13) + { + /* only EAN13 can be here */ + if (type != INVALID) + goto eaninvalid; + type = EAN13; + check = buf[15] - '0'; + } + else if (length == 12) + { + /* only UPC can be here */ + if (type != UPC) + goto eaninvalid; + check = buf[14] - '0'; + } + else if (length == 10) + { + if (type != ISBN && type != ISMN) + goto eaninvalid; + if (buf[12] == 'X') + check = 10; + else + check = buf[12] - '0'; + } + else if (length == 8) + { + if (type != INVALID && type != ISSN) + goto eaninvalid; + type = ISSN; + if (buf[10] == 'X') + check = 10; + else + check = buf[10] - '0'; + } + else + goto eaninvalid; + + if (type == INVALID) + goto eaninvalid; + + /* obtain the real check digit value, validate, and convert to ean13: */ + if (accept == EAN13 && type != accept) + goto eanwrongtype; + if (accept != ANY && type != EAN13 && type != accept) + goto eanwrongtype; + switch (type) + { + case EAN13: + valid = (valid && ((rcheck = checkdig(buf + 3, 13)) == check || magic)); + /* now get the subtype of EAN13: */ + if (buf[3] == '0') + type = UPC; + else if (strncmp("977", buf + 3, 3) == 0) + type = ISSN; + else if (strncmp("978", buf + 3, 3) == 0) + type = ISBN; + else if (strncmp("9790", buf + 3, 4) == 0) + type = ISMN; + else if (strncmp("979", buf + 3, 3) == 0) + type = ISBN; + if (accept != EAN13 && accept != ANY && type != accept) + goto eanwrongtype; + break; + case ISMN: + memcpy(buf, "9790", 4); /* this isn't for sure yet, for now ISMN + * it's only 9790 */ + valid = (valid && ((rcheck = checkdig(buf, 13)) == check || magic)); + break; + case ISBN: + memcpy(buf, "978", 3); + valid = (valid && ((rcheck = weight_checkdig(buf + 3, 10)) == check || magic)); + break; + case ISSN: + memcpy(buf + 10, "00", 2); /* append 00 as the normal issue + * publication code */ + memcpy(buf, "977", 3); + valid = (valid && ((rcheck = weight_checkdig(buf + 3, 8)) == check || magic)); + break; + case UPC: + buf[2] = '0'; + valid = (valid && ((rcheck = checkdig(buf + 2, 13)) == check || magic)); + default: + break; + } + + /* fix the check digit: */ + for (aux1 = buf; *aux1 && *aux1 <= ' '; aux1++); + aux1[12] = checkdig(aux1, 13) + '0'; + aux1[13] = '\0'; + + if (!valid && !magic) + goto eanbadcheck; + + *result = str2ean(aux1); + *result |= valid ? 0 : 1; + return true; + +eanbadcheck: + if (g_weak) + { /* weak input mode is activated: */ + /* set the "invalid-check-digit-on-input" flag */ + *result = str2ean(aux1); + *result |= 1; + return true; + } + + if (rcheck == (unsigned) -1) + { + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid %s number: \"%s\"", + isn_names[accept], str))); + } + else + { + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid check digit for %s number: \"%s\", should be %c", + isn_names[accept], str, (rcheck == 10) ? ('X') : (rcheck + '0')))); + } + +eaninvalid: + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("invalid input syntax for %s number: \"%s\"", + isn_names[accept], str))); + +eanwrongtype: + ereturn(escontext, false, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("cannot cast %s to %s for number: \"%s\"", + isn_names[type], isn_names[accept], str))); + +eantoobig: + ereturn(escontext, false, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("value \"%s\" is out of range for %s type", + str, isn_names[accept]))); +} + +/*---------------------------------------------------------- + * Exported routines. + *---------------------------------------------------------*/ + +void +_PG_init(void) +{ + if (ISN_DEBUG) + { + if (!check_table(EAN13_range, EAN13_index)) + elog(ERROR, "EAN13 failed check"); + if (!check_table(ISBN_range, ISBN_index)) + elog(ERROR, "ISBN failed check"); + if (!check_table(ISMN_range, ISMN_index)) + elog(ERROR, "ISMN failed check"); + if (!check_table(ISSN_range, ISSN_index)) + elog(ERROR, "ISSN failed check"); + if (!check_table(UPC_range, UPC_index)) + elog(ERROR, "UPC failed check"); + } +} + +/* isn_out + */ +PG_FUNCTION_INFO_V1(isn_out); +Datum +isn_out(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + char *result; + char buf[MAXEAN13LEN + 1]; + + (void) ean2string(val, false, buf, true); + + result = pstrdup(buf); + PG_RETURN_CSTRING(result); +} + +/* ean13_out + */ +PG_FUNCTION_INFO_V1(ean13_out); +Datum +ean13_out(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + char *result; + char buf[MAXEAN13LEN + 1]; + + (void) ean2string(val, false, buf, false); + + result = pstrdup(buf); + PG_RETURN_CSTRING(result); +} + +/* ean13_in + */ +PG_FUNCTION_INFO_V1(ean13_in); +Datum +ean13_in(PG_FUNCTION_ARGS) +{ + const char *str = PG_GETARG_CSTRING(0); + ean13 result; + + if (!string2ean(str, fcinfo->context, &result, EAN13)) + PG_RETURN_NULL(); + PG_RETURN_EAN13(result); +} + +/* isbn_in + */ +PG_FUNCTION_INFO_V1(isbn_in); +Datum +isbn_in(PG_FUNCTION_ARGS) +{ + const char *str = PG_GETARG_CSTRING(0); + ean13 result; + + if (!string2ean(str, fcinfo->context, &result, ISBN)) + PG_RETURN_NULL(); + PG_RETURN_EAN13(result); +} + +/* ismn_in + */ +PG_FUNCTION_INFO_V1(ismn_in); +Datum +ismn_in(PG_FUNCTION_ARGS) +{ + const char *str = PG_GETARG_CSTRING(0); + ean13 result; + + if (!string2ean(str, fcinfo->context, &result, ISMN)) + PG_RETURN_NULL(); + PG_RETURN_EAN13(result); +} + +/* issn_in + */ +PG_FUNCTION_INFO_V1(issn_in); +Datum +issn_in(PG_FUNCTION_ARGS) +{ + const char *str = PG_GETARG_CSTRING(0); + ean13 result; + + if (!string2ean(str, fcinfo->context, &result, ISSN)) + PG_RETURN_NULL(); + PG_RETURN_EAN13(result); +} + +/* upc_in + */ +PG_FUNCTION_INFO_V1(upc_in); +Datum +upc_in(PG_FUNCTION_ARGS) +{ + const char *str = PG_GETARG_CSTRING(0); + ean13 result; + + if (!string2ean(str, fcinfo->context, &result, UPC)) + PG_RETURN_NULL(); + PG_RETURN_EAN13(result); +} + +/* casting functions +*/ +PG_FUNCTION_INFO_V1(isbn_cast_from_ean13); +Datum +isbn_cast_from_ean13(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + ean13 result; + + (void) ean2isn(val, false, &result, ISBN); + + PG_RETURN_EAN13(result); +} + +PG_FUNCTION_INFO_V1(ismn_cast_from_ean13); +Datum +ismn_cast_from_ean13(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + ean13 result; + + (void) ean2isn(val, false, &result, ISMN); + + PG_RETURN_EAN13(result); +} + +PG_FUNCTION_INFO_V1(issn_cast_from_ean13); +Datum +issn_cast_from_ean13(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + ean13 result; + + (void) ean2isn(val, false, &result, ISSN); + + PG_RETURN_EAN13(result); +} + +PG_FUNCTION_INFO_V1(upc_cast_from_ean13); +Datum +upc_cast_from_ean13(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + ean13 result; + + (void) ean2isn(val, false, &result, UPC); + + PG_RETURN_EAN13(result); +} + + +/* is_valid - returns false if the "invalid-check-digit-on-input" is set + */ +PG_FUNCTION_INFO_V1(is_valid); +Datum +is_valid(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + + PG_RETURN_BOOL((val & 1) == 0); +} + +/* make_valid - unsets the "invalid-check-digit-on-input" flag + */ +PG_FUNCTION_INFO_V1(make_valid); +Datum +make_valid(PG_FUNCTION_ARGS) +{ + ean13 val = PG_GETARG_EAN13(0); + + val &= ~((ean13) 1); + PG_RETURN_EAN13(val); +} + +/* this function temporarily sets weak input flag + * (to lose the strictness of check digit acceptance) + * It's a helper function, not intended to be used!! + */ +PG_FUNCTION_INFO_V1(accept_weak_input); +Datum +accept_weak_input(PG_FUNCTION_ARGS) +{ +#ifdef ISN_WEAK_MODE + g_weak = PG_GETARG_BOOL(0); +#else + /* function has no effect */ +#endif /* ISN_WEAK_MODE */ + PG_RETURN_BOOL(g_weak); +} + +PG_FUNCTION_INFO_V1(weak_input_status); +Datum +weak_input_status(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(g_weak); +} diff --git a/contrib/isn/isn.control b/contrib/isn/isn.control new file mode 100644 index 0000000..1cb5e2b --- /dev/null +++ b/contrib/isn/isn.control @@ -0,0 +1,6 @@ +# isn extension +comment = 'data types for international product numbering standards' +default_version = '1.2' +module_pathname = '$libdir/isn' +relocatable = true +trusted = true diff --git a/contrib/isn/isn.h b/contrib/isn/isn.h new file mode 100644 index 0000000..f3f8ee0 --- /dev/null +++ b/contrib/isn/isn.h @@ -0,0 +1,35 @@ +/*------------------------------------------------------------------------- + * + * isn.h + * PostgreSQL type definitions for ISNs (ISBN, ISMN, ISSN, EAN13, UPC) + * + * Author: German Mendez Bravo (Kronuz) + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/isn/isn.h + * + *------------------------------------------------------------------------- + */ + +#ifndef ISN_H +#define ISN_H + +#include "fmgr.h" + +#undef ISN_DEBUG +#define ISN_WEAK_MODE + +/* + * uint64 is the internal storage format for ISNs. + */ +typedef uint64 ean13; + +#define EAN13_FORMAT UINT64_FORMAT + +#define PG_GETARG_EAN13(n) PG_GETARG_INT64(n) +#define PG_RETURN_EAN13(x) PG_RETURN_INT64(x) + +extern void initialize(void); + +#endif /* ISN_H */ diff --git a/contrib/isn/meson.build b/contrib/isn/meson.build new file mode 100644 index 0000000..a6a87f8 --- /dev/null +++ b/contrib/isn/meson.build @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +isn_sources = files( + 'isn.c', +) + +if host_system == 'windows' + isn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'isn', + '--FILEDESC', 'isn - data types for international product numbering standards',]) +endif + +isn = shared_module('isn', + isn_sources, + kwargs: contrib_mod_args, +) +contrib_targets += isn + +install_data( + 'isn.control', + 'isn--1.0--1.1.sql', + 'isn--1.1--1.2.sql', + 'isn--1.1.sql', + kwargs: contrib_data_args, +) + +install_headers( + 'isn.h', + install_dir: dir_include_extension / 'isn', +) + +tests += { + 'name': 'isn', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'isn', + ], + }, +} diff --git a/contrib/isn/sql/isn.sql b/contrib/isn/sql/isn.sql new file mode 100644 index 0000000..2c2ea07 --- /dev/null +++ b/contrib/isn/sql/isn.sql @@ -0,0 +1,126 @@ +-- +-- Test ISN extension +-- + +CREATE EXTENSION isn; + +-- Check whether any of our opclasses fail amvalidate +-- ... they will, because of missing cross-type operators +SELECT amname, opcname +FROM (SELECT amname, opcname, opc.oid + FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod + WHERE opc.oid >= 16384 + ORDER BY 1, 2 OFFSET 0) ss +WHERE NOT amvalidate(oid); + +-- +-- test valid conversions +-- +SELECT '9780123456786'::EAN13, -- old book + '9790123456785'::EAN13, -- music + '9791234567896'::EAN13, -- new book + '9771234567898'::EAN13, -- serial + '0123456789012'::EAN13, -- upc + '1234567890128'::EAN13; + +SELECT '9780123456786'::ISBN, + '123456789X'::ISBN, + '9780123456786'::ISBN13::ISBN, + '9780123456786'::EAN13::ISBN; + +SELECT -- new books, shown as ISBN13 even for ISBN... + '9791234567896'::ISBN, + '9791234567896'::ISBN13::ISBN, + '9791234567896'::EAN13::ISBN; + +SELECT '9780123456786'::ISBN13, + '123456789X'::ISBN13, + '9791234567896'::ISBN13, + '9791234567896'::EAN13::ISBN13; + +SELECT '9790123456785'::ISMN, + '9790123456785'::EAN13::ISMN, + 'M123456785'::ISMN, + 'M-1234-5678-5'::ISMN; + +SELECT '9790123456785'::ISMN13, + 'M123456785'::ISMN13, + 'M-1234-5678-5'::ISMN13; + +SELECT '9771234567003'::ISSN, + '12345679'::ISSN; + +SELECT '9771234567003'::ISSN13, + '12345679'::ISSN13, + '9771234567898'::ISSN13, + '9771234567898'::EAN13::ISSN13; + +SELECT '0123456789012'::UPC, + '0123456789012'::EAN13::UPC; + +-- +-- test invalid checksums +-- +SELECT '1234567890'::ISBN; +SELECT 'M123456780'::ISMN; +SELECT '12345670'::ISSN; +SELECT '9780123456780'::ISBN; +SELECT '9791234567890'::ISBN13; +SELECT '0123456789010'::UPC; +SELECT '1234567890120'::EAN13; + +-- +-- test invalid conversions +-- +SELECT '9790123456785'::ISBN; -- not a book +SELECT '9771234567898'::ISBN; -- not a book +SELECT '0123456789012'::ISBN; -- not a book + +SELECT '9790123456785'::ISBN13; -- not a book +SELECT '9771234567898'::ISBN13; -- not a book +SELECT '0123456789012'::ISBN13; -- not a book + +SELECT '9780123456786'::ISMN; -- not music +SELECT '9771234567898'::ISMN; -- not music +SELECT '9791234567896'::ISMN; -- not music +SELECT '0123456789012'::ISMN; -- not music + +SELECT '9780123456786'::ISSN; -- not serial +SELECT '9790123456785'::ISSN; -- not serial +SELECT '9791234567896'::ISSN; -- not serial +SELECT '0123456789012'::ISSN; -- not serial + +SELECT '9780123456786'::UPC; -- not a product +SELECT '9771234567898'::UPC; -- not a product +SELECT '9790123456785'::UPC; -- not a product +SELECT '9791234567896'::UPC; -- not a product + +SELECT 'postgresql...'::EAN13; +SELECT 'postgresql...'::ISBN; +SELECT 9780123456786::EAN13; +SELECT 9780123456786::ISBN; + +-- +-- test some comparisons, must yield true +-- +SELECT '12345679'::ISSN = '9771234567003'::EAN13 AS "ok", + 'M-1234-5678-5'::ISMN = '9790123456785'::EAN13 AS "ok", + '9791234567896'::EAN13 != '123456789X'::ISBN AS "nope"; + +-- test non-error-throwing input API +SELECT str as isn, typ as "type", + pg_input_is_valid(str,typ) as ok, + errinfo.sql_error_code, + errinfo.message, + errinfo.detail, + errinfo.hint +FROM (VALUES ('9780123456786', 'UPC'), + ('postgresql...','EAN13'), + ('9771234567003','ISSN')) + AS a(str,typ), + LATERAL pg_input_error_info(a.str, a.typ) as errinfo; + +-- +-- cleanup +-- +DROP EXTENSION isn; diff --git a/contrib/jsonb_plperl/.gitignore b/contrib/jsonb_plperl/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/jsonb_plperl/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/jsonb_plperl/Makefile b/contrib/jsonb_plperl/Makefile new file mode 100644 index 0000000..ba9480e --- /dev/null +++ b/contrib/jsonb_plperl/Makefile @@ -0,0 +1,41 @@ +# contrib/jsonb_plperl/Makefile + +MODULE_big = jsonb_plperl +OBJS = \ + $(WIN32RES) \ + jsonb_plperl.o +PGFILEDESC = "jsonb_plperl - jsonb transform for plperl" + +PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plperl + +EXTENSION = jsonb_plperlu jsonb_plperl +DATA = jsonb_plperlu--1.0.sql jsonb_plperl--1.0.sql + +REGRESS = jsonb_plperl jsonb_plperlu + +SHLIB_LINK += $(filter -lm, $(LIBS)) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/jsonb_plperl +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +# We must link libperl explicitly +ifeq ($(PORTNAME), win32) +# these settings are the same as for plperl +override CPPFLAGS += -DPLPERL_HAVE_UID_GID -Wno-comment +# ... see silliness in plperl Makefile ... +SHLIB_LINK_INTERNAL += $(sort $(wildcard ../../src/pl/plperl/libperl*.a)) +else +rpathdir = $(perl_archlibexp)/CORE +SHLIB_LINK += $(perl_embed_ldflags) +endif + +# As with plperl we need to include the perl_includespec directory last. +override CPPFLAGS := $(CPPFLAGS) $(perl_embed_ccflags) $(perl_includespec) diff --git a/contrib/jsonb_plperl/expected/jsonb_plperl.out b/contrib/jsonb_plperl/expected/jsonb_plperl.out new file mode 100644 index 0000000..5a73485 --- /dev/null +++ b/contrib/jsonb_plperl/expected/jsonb_plperl.out @@ -0,0 +1,253 @@ +CREATE EXTENSION jsonb_plperl CASCADE; +NOTICE: installing required extension "plperl" +CREATE FUNCTION testHVToJsonb() RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +$val = {a => 1, b => 'boo', c => undef}; +return $val; +$$; +SELECT testHVToJsonb(); + testhvtojsonb +--------------------------------- + {"a": 1, "b": "boo", "c": null} +(1 row) + +CREATE FUNCTION testAVToJsonb() RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; +return $val; +$$; +SELECT testAVToJsonb(); + testavtojsonb +--------------------------------------------- + [{"a": 1, "b": "boo", "c": null}, {"d": 2}] +(1 row) + +CREATE FUNCTION testSVToJsonb() RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +$val = 1; +return $val; +$$; +SELECT testSVToJsonb(); + testsvtojsonb +--------------- + 1 +(1 row) + +CREATE FUNCTION testUVToJsonb() RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +as $$ +$val = ~0; +return $val; +$$; +-- this might produce either 18446744073709551615 or 4294967295 +SELECT testUVToJsonb() IN ('18446744073709551615'::jsonb, '4294967295'::jsonb); + ?column? +---------- + t +(1 row) + +-- this revealed a bug in the original implementation +CREATE FUNCTION testRegexpResultToJsonb() RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +return ('1' =~ m(0\t2)); +$$; +SELECT testRegexpResultToJsonb(); + testregexpresulttojsonb +------------------------- + 0 +(1 row) + +-- this revealed a different bug +CREATE FUNCTION testTextToJsonbObject(text) RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +my $x = shift; +return {a => $x}; +$$; +SELECT testTextToJsonbObject('abc'); + testtexttojsonbobject +----------------------- + {"a": "abc"} +(1 row) + +SELECT testTextToJsonbObject(NULL); + testtexttojsonbobject +----------------------- + {"a": null} +(1 row) + +CREATE FUNCTION roundtrip(val jsonb, ref text = '') RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +# can't use Data::Dumper, but let's at least check for unexpected ref type +die 'unexpected '.(ref($_[0]) || 'not a').' reference' + if ref($_[0]) ne $_[1]; +return $_[0]; +$$; +SELECT roundtrip('null') is null; + ?column? +---------- + t +(1 row) + +SELECT roundtrip('1'); + roundtrip +----------- + 1 +(1 row) + +SELECT roundtrip('1E+131071'); +ERROR: cannot convert infinity to jsonb +CONTEXT: PL/Perl function "roundtrip" +SELECT roundtrip('-1'); + roundtrip +----------- + -1 +(1 row) + +SELECT roundtrip('1.2'); + roundtrip +----------- + 1.2 +(1 row) + +SELECT roundtrip('-1.2'); + roundtrip +----------- + -1.2 +(1 row) + +SELECT roundtrip('"string"'); + roundtrip +----------- + "string" +(1 row) + +SELECT roundtrip('"NaN"'); + roundtrip +----------- + "NaN" +(1 row) + +SELECT roundtrip('true'); + roundtrip +----------- + 1 +(1 row) + +SELECT roundtrip('false'); + roundtrip +----------- + 0 +(1 row) + +SELECT roundtrip('[]', 'ARRAY'); + roundtrip +----------- + [] +(1 row) + +SELECT roundtrip('[null, null]', 'ARRAY'); + roundtrip +-------------- + [null, null] +(1 row) + +SELECT roundtrip('[1, 2, 3]', 'ARRAY'); + roundtrip +----------- + [1, 2, 3] +(1 row) + +SELECT roundtrip('[-1, 2, -3]', 'ARRAY'); + roundtrip +------------- + [-1, 2, -3] +(1 row) + +SELECT roundtrip('[1.2, 2.3, 3.4]', 'ARRAY'); + roundtrip +----------------- + [1.2, 2.3, 3.4] +(1 row) + +SELECT roundtrip('[-1.2, 2.3, -3.4]', 'ARRAY'); + roundtrip +------------------- + [-1.2, 2.3, -3.4] +(1 row) + +SELECT roundtrip('["string1", "string2"]', 'ARRAY'); + roundtrip +------------------------ + ["string1", "string2"] +(1 row) + +SELECT roundtrip('[["string1", "string2"]]', 'ARRAY'); + roundtrip +-------------------------- + [["string1", "string2"]] +(1 row) + +SELECT roundtrip('{}', 'HASH'); + roundtrip +----------- + {} +(1 row) + +SELECT roundtrip('{"1": null}', 'HASH'); + roundtrip +------------- + {"1": null} +(1 row) + +SELECT roundtrip('{"1": 1}', 'HASH'); + roundtrip +----------- + {"1": 1} +(1 row) + +SELECT roundtrip('{"1": -1}', 'HASH'); + roundtrip +----------- + {"1": -1} +(1 row) + +SELECT roundtrip('{"1": 1.1}', 'HASH'); + roundtrip +------------ + {"1": 1.1} +(1 row) + +SELECT roundtrip('{"1": -1.1}', 'HASH'); + roundtrip +------------- + {"1": -1.1} +(1 row) + +SELECT roundtrip('{"1": "string1"}', 'HASH'); + roundtrip +------------------ + {"1": "string1"} +(1 row) + +SELECT roundtrip('{"1": {"2": [3, 4, 5]}, "2": 3}', 'HASH'); + roundtrip +--------------------------------- + {"1": {"2": [3, 4, 5]}, "2": 3} +(1 row) + +\set VERBOSITY terse \\ -- suppress cascade details +DROP EXTENSION plperl CASCADE; +NOTICE: drop cascades to 8 other objects diff --git a/contrib/jsonb_plperl/expected/jsonb_plperlu.out b/contrib/jsonb_plperl/expected/jsonb_plperlu.out new file mode 100644 index 0000000..dff316c --- /dev/null +++ b/contrib/jsonb_plperl/expected/jsonb_plperlu.out @@ -0,0 +1,280 @@ +CREATE EXTENSION jsonb_plperlu CASCADE; +NOTICE: installing required extension "plperlu" +CREATE FUNCTION testHVToJsonb() RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +$val = {a => 1, b => 'boo', c => undef}; +return $val; +$$; +SELECT testHVToJsonb(); + testhvtojsonb +--------------------------------- + {"a": 1, "b": "boo", "c": null} +(1 row) + +CREATE FUNCTION testAVToJsonb() RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; +return $val; +$$; +SELECT testAVToJsonb(); + testavtojsonb +--------------------------------------------- + [{"a": 1, "b": "boo", "c": null}, {"d": 2}] +(1 row) + +CREATE FUNCTION testSVToJsonb() RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +$val = 1; +return $val; +$$; +SELECT testSVToJsonb(); + testsvtojsonb +--------------- + 1 +(1 row) + +CREATE FUNCTION testUVToJsonb() RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +as $$ +$val = ~0; +return $val; +$$; +-- this might produce either 18446744073709551615 or 4294967295 +SELECT testUVToJsonb() IN ('18446744073709551615'::jsonb, '4294967295'::jsonb); + ?column? +---------- + t +(1 row) + +-- this revealed a bug in the original implementation +CREATE FUNCTION testRegexpResultToJsonb() RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +return ('1' =~ m(0\t2)); +$$; +SELECT testRegexpResultToJsonb(); + testregexpresulttojsonb +------------------------- + 0 +(1 row) + +-- this revealed a different bug +CREATE FUNCTION testTextToJsonbObject(text) RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +my $x = shift; +return {a => $x}; +$$; +SELECT testTextToJsonbObject('abc'); + testtexttojsonbobject +----------------------- + {"a": "abc"} +(1 row) + +SELECT testTextToJsonbObject(NULL); + testtexttojsonbobject +----------------------- + {"a": null} +(1 row) + +CREATE FUNCTION roundtrip(val jsonb, ref text = '') RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_[0])); +die 'unexpected '.(ref($_[0]) || 'not a').' reference' + if ref($_[0]) ne $_[1]; +return $_[0]; +$$; +SELECT roundtrip('null') is null; +INFO: $VAR1 = undef; + ?column? +---------- + t +(1 row) + +SELECT roundtrip('1'); +INFO: $VAR1 = '1'; + roundtrip +----------- + 1 +(1 row) + +-- skip because Data::Dumper produces a platform-dependent spelling of infinity +-- SELECT roundtrip('1E+131071'); +SELECT roundtrip('-1'); +INFO: $VAR1 = '-1'; + roundtrip +----------- + -1 +(1 row) + +SELECT roundtrip('1.2'); +INFO: $VAR1 = '1.2'; + roundtrip +----------- + 1.2 +(1 row) + +SELECT roundtrip('-1.2'); +INFO: $VAR1 = '-1.2'; + roundtrip +----------- + -1.2 +(1 row) + +SELECT roundtrip('"string"'); +INFO: $VAR1 = 'string'; + roundtrip +----------- + "string" +(1 row) + +SELECT roundtrip('"NaN"'); +INFO: $VAR1 = 'NaN'; + roundtrip +----------- + "NaN" +(1 row) + +SELECT roundtrip('true'); +INFO: $VAR1 = '1'; + roundtrip +----------- + 1 +(1 row) + +SELECT roundtrip('false'); +INFO: $VAR1 = '0'; + roundtrip +----------- + 0 +(1 row) + +SELECT roundtrip('[]', 'ARRAY'); +INFO: $VAR1 = []; + roundtrip +----------- + [] +(1 row) + +SELECT roundtrip('[null, null]', 'ARRAY'); +INFO: $VAR1 = [undef,undef]; + roundtrip +-------------- + [null, null] +(1 row) + +SELECT roundtrip('[1, 2, 3]', 'ARRAY'); +INFO: $VAR1 = ['1','2','3']; + roundtrip +----------- + [1, 2, 3] +(1 row) + +SELECT roundtrip('[-1, 2, -3]', 'ARRAY'); +INFO: $VAR1 = ['-1','2','-3']; + roundtrip +------------- + [-1, 2, -3] +(1 row) + +SELECT roundtrip('[1.2, 2.3, 3.4]', 'ARRAY'); +INFO: $VAR1 = ['1.2','2.3','3.4']; + roundtrip +----------------- + [1.2, 2.3, 3.4] +(1 row) + +SELECT roundtrip('[-1.2, 2.3, -3.4]', 'ARRAY'); +INFO: $VAR1 = ['-1.2','2.3','-3.4']; + roundtrip +------------------- + [-1.2, 2.3, -3.4] +(1 row) + +SELECT roundtrip('["string1", "string2"]', 'ARRAY'); +INFO: $VAR1 = ['string1','string2']; + roundtrip +------------------------ + ["string1", "string2"] +(1 row) + +SELECT roundtrip('[["string1", "string2"]]', 'ARRAY'); +INFO: $VAR1 = [['string1','string2']]; + roundtrip +-------------------------- + [["string1", "string2"]] +(1 row) + +SELECT roundtrip('{}', 'HASH'); +INFO: $VAR1 = {}; + roundtrip +----------- + {} +(1 row) + +SELECT roundtrip('{"1": null}', 'HASH'); +INFO: $VAR1 = {'1' => undef}; + roundtrip +------------- + {"1": null} +(1 row) + +SELECT roundtrip('{"1": 1}', 'HASH'); +INFO: $VAR1 = {'1' => '1'}; + roundtrip +----------- + {"1": 1} +(1 row) + +SELECT roundtrip('{"1": -1}', 'HASH'); +INFO: $VAR1 = {'1' => '-1'}; + roundtrip +----------- + {"1": -1} +(1 row) + +SELECT roundtrip('{"1": 1.1}', 'HASH'); +INFO: $VAR1 = {'1' => '1.1'}; + roundtrip +------------ + {"1": 1.1} +(1 row) + +SELECT roundtrip('{"1": -1.1}', 'HASH'); +INFO: $VAR1 = {'1' => '-1.1'}; + roundtrip +------------- + {"1": -1.1} +(1 row) + +SELECT roundtrip('{"1": "string1"}', 'HASH'); +INFO: $VAR1 = {'1' => 'string1'}; + roundtrip +------------------ + {"1": "string1"} +(1 row) + +SELECT roundtrip('{"1": {"2": [3, 4, 5]}, "2": 3}', 'HASH'); +INFO: $VAR1 = {'1' => {'2' => ['3','4','5']},'2' => '3'}; + roundtrip +--------------------------------- + {"1": {"2": [3, 4, 5]}, "2": 3} +(1 row) + +\set VERBOSITY terse \\ -- suppress cascade details +DROP EXTENSION plperlu CASCADE; +NOTICE: drop cascades to 8 other objects diff --git a/contrib/jsonb_plperl/jsonb_plperl--1.0.sql b/contrib/jsonb_plperl/jsonb_plperl--1.0.sql new file mode 100644 index 0000000..c7964ba --- /dev/null +++ b/contrib/jsonb_plperl/jsonb_plperl--1.0.sql @@ -0,0 +1,19 @@ +/* contrib/jsonb_plperl/jsonb_plperl--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION jsonb_plperl" to load this file. \quit + +CREATE FUNCTION jsonb_to_plperl(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE FUNCTION plperl_to_jsonb(val internal) RETURNS jsonb +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME'; + +CREATE TRANSFORM FOR jsonb LANGUAGE plperl ( + FROM SQL WITH FUNCTION jsonb_to_plperl(internal), + TO SQL WITH FUNCTION plperl_to_jsonb(internal) +); + +COMMENT ON TRANSFORM FOR jsonb LANGUAGE plperl IS 'transform between jsonb and Perl'; diff --git a/contrib/jsonb_plperl/jsonb_plperl.c b/contrib/jsonb_plperl/jsonb_plperl.c new file mode 100644 index 0000000..2af1e0c --- /dev/null +++ b/contrib/jsonb_plperl/jsonb_plperl.c @@ -0,0 +1,295 @@ +#include "postgres.h" + +#include + +#include "fmgr.h" +#include "plperl.h" +#include "utils/fmgrprotos.h" +#include "utils/jsonb.h" + +PG_MODULE_MAGIC; + +static SV *Jsonb_to_SV(JsonbContainer *jsonb); +static JsonbValue *SV_to_JsonbValue(SV *obj, JsonbParseState **ps, bool is_elem); + + +static SV * +JsonbValue_to_SV(JsonbValue *jbv) +{ + dTHX; + + switch (jbv->type) + { + case jbvBinary: + return Jsonb_to_SV(jbv->val.binary.data); + + case jbvNumeric: + { + char *str = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(jbv->val.numeric))); + SV *result = newSVnv(SvNV(cstr2sv(str))); + + pfree(str); + return result; + } + + case jbvString: + { + char *str = pnstrdup(jbv->val.string.val, + jbv->val.string.len); + SV *result = cstr2sv(str); + + pfree(str); + return result; + } + + case jbvBool: + return newSVnv(SvNV(jbv->val.boolean ? &PL_sv_yes : &PL_sv_no)); + + case jbvNull: + return newSV(0); + + default: + elog(ERROR, "unexpected jsonb value type: %d", jbv->type); + return NULL; + } +} + +static SV * +Jsonb_to_SV(JsonbContainer *jsonb) +{ + dTHX; + JsonbValue v; + JsonbIterator *it; + JsonbIteratorToken r; + + it = JsonbIteratorInit(jsonb); + r = JsonbIteratorNext(&it, &v, true); + + switch (r) + { + case WJB_BEGIN_ARRAY: + if (v.val.array.rawScalar) + { + JsonbValue tmp; + + if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM || + (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY || + (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE) + elog(ERROR, "unexpected jsonb token: %d", r); + + return JsonbValue_to_SV(&v); + } + else + { + AV *av = newAV(); + + while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r == WJB_ELEM) + av_push(av, JsonbValue_to_SV(&v)); + } + + return newRV((SV *) av); + } + + case WJB_BEGIN_OBJECT: + { + HV *hv = newHV(); + + while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r == WJB_KEY) + { + /* json key in v, json value in val */ + JsonbValue val; + + if (JsonbIteratorNext(&it, &val, true) == WJB_VALUE) + { + SV *value = JsonbValue_to_SV(&val); + + (void) hv_store(hv, + v.val.string.val, v.val.string.len, + value, 0); + } + } + } + + return newRV((SV *) hv); + } + + default: + elog(ERROR, "unexpected jsonb token: %d", r); + return NULL; + } +} + +static JsonbValue * +AV_to_JsonbValue(AV *in, JsonbParseState **jsonb_state) +{ + dTHX; + SSize_t pcount = av_len(in) + 1; + SSize_t i; + + pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL); + + for (i = 0; i < pcount; i++) + { + SV **value = av_fetch(in, i, FALSE); + + if (value) + (void) SV_to_JsonbValue(*value, jsonb_state, true); + } + + return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL); +} + +static JsonbValue * +HV_to_JsonbValue(HV *obj, JsonbParseState **jsonb_state) +{ + dTHX; + JsonbValue key; + SV *val; + char *kstr; + I32 klen; + + key.type = jbvString; + + pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL); + + (void) hv_iterinit(obj); + + while ((val = hv_iternextsv(obj, &kstr, &klen))) + { + key.val.string.val = pnstrdup(kstr, klen); + key.val.string.len = klen; + pushJsonbValue(jsonb_state, WJB_KEY, &key); + (void) SV_to_JsonbValue(val, jsonb_state, false); + } + + return pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL); +} + +static JsonbValue * +SV_to_JsonbValue(SV *in, JsonbParseState **jsonb_state, bool is_elem) +{ + dTHX; + JsonbValue out; /* result */ + + /* Dereference references recursively. */ + while (SvROK(in)) + in = SvRV(in); + + switch (SvTYPE(in)) + { + case SVt_PVAV: + return AV_to_JsonbValue((AV *) in, jsonb_state); + + case SVt_PVHV: + return HV_to_JsonbValue((HV *) in, jsonb_state); + + default: + if (!SvOK(in)) + { + out.type = jbvNull; + } + else if (SvUOK(in)) + { + /* + * If UV is >=64 bits, we have no better way to make this + * happen than converting to text and back. Given the low + * usage of UV in Perl code, it's not clear it's worth working + * hard to provide alternate code paths. + */ + const char *strval = SvPV_nolen(in); + + out.type = jbvNumeric; + out.val.numeric = + DatumGetNumeric(DirectFunctionCall3(numeric_in, + CStringGetDatum(strval), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1))); + } + else if (SvIOK(in)) + { + IV ival = SvIV(in); + + out.type = jbvNumeric; + out.val.numeric = int64_to_numeric(ival); + } + else if (SvNOK(in)) + { + double nval = SvNV(in); + + /* + * jsonb doesn't allow infinity or NaN (per JSON + * specification), but the numeric type that is used for the + * storage accepts those, so we have to reject them here + * explicitly. + */ + if (isinf(nval)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("cannot convert infinity to jsonb"))); + if (isnan(nval)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("cannot convert NaN to jsonb"))); + + out.type = jbvNumeric; + out.val.numeric = + DatumGetNumeric(DirectFunctionCall1(float8_numeric, + Float8GetDatum(nval))); + } + else if (SvPOK(in)) + { + out.type = jbvString; + out.val.string.val = sv2cstr(in); + out.val.string.len = strlen(out.val.string.val); + } + else + { + /* + * XXX It might be nice if we could include the Perl type in + * the error message. + */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot transform this Perl type to jsonb"))); + return NULL; + } + } + + /* Push result into 'jsonb_state' unless it is a raw scalar. */ + return *jsonb_state + ? pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, &out) + : memcpy(palloc(sizeof(JsonbValue)), &out, sizeof(JsonbValue)); +} + + +PG_FUNCTION_INFO_V1(jsonb_to_plperl); + +Datum +jsonb_to_plperl(PG_FUNCTION_ARGS) +{ + dTHX; + Jsonb *in = PG_GETARG_JSONB_P(0); + SV *sv = Jsonb_to_SV(&in->root); + + return PointerGetDatum(sv); +} + + +PG_FUNCTION_INFO_V1(plperl_to_jsonb); + +Datum +plperl_to_jsonb(PG_FUNCTION_ARGS) +{ + dTHX; + JsonbParseState *jsonb_state = NULL; + SV *in = (SV *) PG_GETARG_POINTER(0); + JsonbValue *out = SV_to_JsonbValue(in, &jsonb_state, true); + Jsonb *result = JsonbValueToJsonb(out); + + PG_RETURN_JSONB_P(result); +} diff --git a/contrib/jsonb_plperl/jsonb_plperl.control b/contrib/jsonb_plperl/jsonb_plperl.control new file mode 100644 index 0000000..4acee93 --- /dev/null +++ b/contrib/jsonb_plperl/jsonb_plperl.control @@ -0,0 +1,7 @@ +# jsonb_plperl extension +comment = 'transform between jsonb and plperl' +default_version = '1.0' +module_pathname = '$libdir/jsonb_plperl' +relocatable = true +trusted = true +requires = 'plperl' diff --git a/contrib/jsonb_plperl/jsonb_plperlu--1.0.sql b/contrib/jsonb_plperl/jsonb_plperlu--1.0.sql new file mode 100644 index 0000000..aa84b37 --- /dev/null +++ b/contrib/jsonb_plperl/jsonb_plperlu--1.0.sql @@ -0,0 +1,19 @@ +/* contrib/jsonb_plperl/jsonb_plperlu--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION jsonb_plperlu" to load this file. \quit + +CREATE FUNCTION jsonb_to_plperlu(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'jsonb_to_plperl'; + +CREATE FUNCTION plperlu_to_jsonb(val internal) RETURNS jsonb +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'plperl_to_jsonb'; + +CREATE TRANSFORM FOR jsonb LANGUAGE plperlu ( + FROM SQL WITH FUNCTION jsonb_to_plperlu(internal), + TO SQL WITH FUNCTION plperlu_to_jsonb(internal) +); + +COMMENT ON TRANSFORM FOR jsonb LANGUAGE plperlu IS 'transform between jsonb and Perl'; diff --git a/contrib/jsonb_plperl/jsonb_plperlu.control b/contrib/jsonb_plperl/jsonb_plperlu.control new file mode 100644 index 0000000..946fc51 --- /dev/null +++ b/contrib/jsonb_plperl/jsonb_plperlu.control @@ -0,0 +1,6 @@ +# jsonb_plperl extension +comment = 'transform between jsonb and plperlu' +default_version = '1.0' +module_pathname = '$libdir/jsonb_plperl' +relocatable = true +requires = 'plperlu' diff --git a/contrib/jsonb_plperl/meson.build b/contrib/jsonb_plperl/meson.build new file mode 100644 index 0000000..8bf0a8d --- /dev/null +++ b/contrib/jsonb_plperl/meson.build @@ -0,0 +1,51 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not perl_dep.found() + subdir_done() +endif + +jsonb_plperl_sources = files( + 'jsonb_plperl.c', +) + +if host_system == 'windows' + jsonb_plperl_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'jsonb_plperl', + '--FILEDESC', 'jsonb_plperl - jsonb transform for plperl',]) +endif + +jsonb_plperl = shared_module('jsonb_plperl', + jsonb_plperl_sources, + include_directories: [plperl_inc], + kwargs: contrib_mod_args + { + 'dependencies': [perl_dep, contrib_mod_args['dependencies']], + 'install_rpath': ':'.join(mod_install_rpaths + ['@0@/CORE'.format(archlibexp)]), + 'build_rpath': '@0@/CORE'.format(archlibexp), + }, +) +contrib_targets += jsonb_plperl + +install_data( + 'jsonb_plperl.control', + 'jsonb_plperl--1.0.sql', + kwargs: contrib_data_args, +) + +install_data( + 'jsonb_plperlu.control', + 'jsonb_plperlu--1.0.sql', + kwargs: contrib_data_args, +) + + +tests += { + 'name': 'jsonb_plperl', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'jsonb_plperl', + 'jsonb_plperlu', + ], + }, +} diff --git a/contrib/jsonb_plperl/sql/jsonb_plperl.sql b/contrib/jsonb_plperl/sql/jsonb_plperl.sql new file mode 100644 index 0000000..a5b2cff --- /dev/null +++ b/contrib/jsonb_plperl/sql/jsonb_plperl.sql @@ -0,0 +1,117 @@ +CREATE EXTENSION jsonb_plperl CASCADE; + + +CREATE FUNCTION testHVToJsonb() RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +$val = {a => 1, b => 'boo', c => undef}; +return $val; +$$; + +SELECT testHVToJsonb(); + + +CREATE FUNCTION testAVToJsonb() RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; +return $val; +$$; + +SELECT testAVToJsonb(); + + +CREATE FUNCTION testSVToJsonb() RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +$val = 1; +return $val; +$$; + +SELECT testSVToJsonb(); + + +CREATE FUNCTION testUVToJsonb() RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +as $$ +$val = ~0; +return $val; +$$; + +-- this might produce either 18446744073709551615 or 4294967295 +SELECT testUVToJsonb() IN ('18446744073709551615'::jsonb, '4294967295'::jsonb); + + +-- this revealed a bug in the original implementation +CREATE FUNCTION testRegexpResultToJsonb() RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +return ('1' =~ m(0\t2)); +$$; + +SELECT testRegexpResultToJsonb(); + + +-- this revealed a different bug +CREATE FUNCTION testTextToJsonbObject(text) RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +my $x = shift; +return {a => $x}; +$$; + +SELECT testTextToJsonbObject('abc'); +SELECT testTextToJsonbObject(NULL); + + +CREATE FUNCTION roundtrip(val jsonb, ref text = '') RETURNS jsonb +LANGUAGE plperl +TRANSFORM FOR TYPE jsonb +AS $$ +# can't use Data::Dumper, but let's at least check for unexpected ref type +die 'unexpected '.(ref($_[0]) || 'not a').' reference' + if ref($_[0]) ne $_[1]; +return $_[0]; +$$; + + +SELECT roundtrip('null') is null; +SELECT roundtrip('1'); +SELECT roundtrip('1E+131071'); +SELECT roundtrip('-1'); +SELECT roundtrip('1.2'); +SELECT roundtrip('-1.2'); +SELECT roundtrip('"string"'); +SELECT roundtrip('"NaN"'); + +SELECT roundtrip('true'); +SELECT roundtrip('false'); + +SELECT roundtrip('[]', 'ARRAY'); +SELECT roundtrip('[null, null]', 'ARRAY'); +SELECT roundtrip('[1, 2, 3]', 'ARRAY'); +SELECT roundtrip('[-1, 2, -3]', 'ARRAY'); +SELECT roundtrip('[1.2, 2.3, 3.4]', 'ARRAY'); +SELECT roundtrip('[-1.2, 2.3, -3.4]', 'ARRAY'); +SELECT roundtrip('["string1", "string2"]', 'ARRAY'); +SELECT roundtrip('[["string1", "string2"]]', 'ARRAY'); + +SELECT roundtrip('{}', 'HASH'); +SELECT roundtrip('{"1": null}', 'HASH'); +SELECT roundtrip('{"1": 1}', 'HASH'); +SELECT roundtrip('{"1": -1}', 'HASH'); +SELECT roundtrip('{"1": 1.1}', 'HASH'); +SELECT roundtrip('{"1": -1.1}', 'HASH'); +SELECT roundtrip('{"1": "string1"}', 'HASH'); + +SELECT roundtrip('{"1": {"2": [3, 4, 5]}, "2": 3}', 'HASH'); + + +\set VERBOSITY terse \\ -- suppress cascade details +DROP EXTENSION plperl CASCADE; diff --git a/contrib/jsonb_plperl/sql/jsonb_plperlu.sql b/contrib/jsonb_plperl/sql/jsonb_plperlu.sql new file mode 100644 index 0000000..c68ef73 --- /dev/null +++ b/contrib/jsonb_plperl/sql/jsonb_plperlu.sql @@ -0,0 +1,121 @@ +CREATE EXTENSION jsonb_plperlu CASCADE; + + +CREATE FUNCTION testHVToJsonb() RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +$val = {a => 1, b => 'boo', c => undef}; +return $val; +$$; + +SELECT testHVToJsonb(); + + +CREATE FUNCTION testAVToJsonb() RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +$val = [{a => 1, b => 'boo', c => undef}, {d => 2}]; +return $val; +$$; + +SELECT testAVToJsonb(); + + +CREATE FUNCTION testSVToJsonb() RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +$val = 1; +return $val; +$$; + +SELECT testSVToJsonb(); + + +CREATE FUNCTION testUVToJsonb() RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +as $$ +$val = ~0; +return $val; +$$; + +-- this might produce either 18446744073709551615 or 4294967295 +SELECT testUVToJsonb() IN ('18446744073709551615'::jsonb, '4294967295'::jsonb); + + +-- this revealed a bug in the original implementation +CREATE FUNCTION testRegexpResultToJsonb() RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +return ('1' =~ m(0\t2)); +$$; + +SELECT testRegexpResultToJsonb(); + + +-- this revealed a different bug +CREATE FUNCTION testTextToJsonbObject(text) RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +my $x = shift; +return {a => $x}; +$$; + +SELECT testTextToJsonbObject('abc'); +SELECT testTextToJsonbObject(NULL); + + +CREATE FUNCTION roundtrip(val jsonb, ref text = '') RETURNS jsonb +LANGUAGE plperlu +TRANSFORM FOR TYPE jsonb +AS $$ +use Data::Dumper; +$Data::Dumper::Sortkeys = 1; +$Data::Dumper::Indent = 0; +elog(INFO, Dumper($_[0])); +die 'unexpected '.(ref($_[0]) || 'not a').' reference' + if ref($_[0]) ne $_[1]; +return $_[0]; +$$; + + +SELECT roundtrip('null') is null; +SELECT roundtrip('1'); +-- skip because Data::Dumper produces a platform-dependent spelling of infinity +-- SELECT roundtrip('1E+131071'); +SELECT roundtrip('-1'); +SELECT roundtrip('1.2'); +SELECT roundtrip('-1.2'); +SELECT roundtrip('"string"'); +SELECT roundtrip('"NaN"'); + +SELECT roundtrip('true'); +SELECT roundtrip('false'); + +SELECT roundtrip('[]', 'ARRAY'); +SELECT roundtrip('[null, null]', 'ARRAY'); +SELECT roundtrip('[1, 2, 3]', 'ARRAY'); +SELECT roundtrip('[-1, 2, -3]', 'ARRAY'); +SELECT roundtrip('[1.2, 2.3, 3.4]', 'ARRAY'); +SELECT roundtrip('[-1.2, 2.3, -3.4]', 'ARRAY'); +SELECT roundtrip('["string1", "string2"]', 'ARRAY'); +SELECT roundtrip('[["string1", "string2"]]', 'ARRAY'); + +SELECT roundtrip('{}', 'HASH'); +SELECT roundtrip('{"1": null}', 'HASH'); +SELECT roundtrip('{"1": 1}', 'HASH'); +SELECT roundtrip('{"1": -1}', 'HASH'); +SELECT roundtrip('{"1": 1.1}', 'HASH'); +SELECT roundtrip('{"1": -1.1}', 'HASH'); +SELECT roundtrip('{"1": "string1"}', 'HASH'); + +SELECT roundtrip('{"1": {"2": [3, 4, 5]}, "2": 3}', 'HASH'); + + +\set VERBOSITY terse \\ -- suppress cascade details +DROP EXTENSION plperlu CASCADE; diff --git a/contrib/jsonb_plpython/.gitignore b/contrib/jsonb_plpython/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/jsonb_plpython/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/jsonb_plpython/Makefile b/contrib/jsonb_plpython/Makefile new file mode 100644 index 0000000..fea7bdf --- /dev/null +++ b/contrib/jsonb_plpython/Makefile @@ -0,0 +1,34 @@ +# contrib/jsonb_plpython/Makefile + +MODULE_big = jsonb_plpython$(python_majorversion) +OBJS = \ + $(WIN32RES) \ + jsonb_plpython.o +PGFILEDESC = "jsonb_plpython - jsonb transform for plpython" + +PG_CPPFLAGS = -I$(top_srcdir)/src/pl/plpython $(python_includespec) -DPLPYTHON_LIBNAME='"plpython$(python_majorversion)"' + +EXTENSION = jsonb_plpython3u +DATA = jsonb_plpython3u--1.0.sql + +REGRESS = jsonb_plpython + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/jsonb_plpython +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +# We must link libpython explicitly +ifeq ($(PORTNAME), win32) +# ... see silliness in plpython Makefile ... +SHLIB_LINK_INTERNAL += $(sort $(wildcard ../../src/pl/plpython/libpython*.a)) +else +rpathdir = $(python_libdir) +SHLIB_LINK += $(python_libspec) $(python_additional_libs) +endif diff --git a/contrib/jsonb_plpython/expected/jsonb_plpython.out b/contrib/jsonb_plpython/expected/jsonb_plpython.out new file mode 100644 index 0000000..cac963d --- /dev/null +++ b/contrib/jsonb_plpython/expected/jsonb_plpython.out @@ -0,0 +1,306 @@ +CREATE EXTENSION jsonb_plpython3u CASCADE; +NOTICE: installing required extension "plpython3u" +-- test jsonb -> python dict +CREATE FUNCTION test1(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert isinstance(val, dict) +assert(val == {'a': 1, 'c': 'NULL'}) +return len(val) +$$; +SELECT test1('{"a": 1, "c": "NULL"}'::jsonb); + test1 +------- + 2 +(1 row) + +-- test jsonb -> python dict +-- complex dict with dicts as value +CREATE FUNCTION test1complex(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert isinstance(val, dict) +assert(val == {"d": {"d": 1}}) +return len(val) +$$; +SELECT test1complex('{"d": {"d": 1}}'::jsonb); + test1complex +-------------- + 1 +(1 row) + +-- test jsonb[] -> python dict +-- dict with array as value +CREATE FUNCTION test1arr(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert isinstance(val, dict) +assert(val == {"d": [12, 1]}) +return len(val) +$$; +SELECT test1arr('{"d":[12, 1]}'::jsonb); + test1arr +---------- + 1 +(1 row) + +-- test jsonb[] -> python list +-- simple list +CREATE FUNCTION test2arr(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert isinstance(val, list) +assert(val == [12, 1]) +return len(val) +$$; +SELECT test2arr('[12, 1]'::jsonb); + test2arr +---------- + 2 +(1 row) + +-- test jsonb[] -> python list +-- array of dicts +CREATE FUNCTION test3arr(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert isinstance(val, list) +assert(val == [{"a": 1,"b": 2}, {"c": 3,"d": 4}]) +return len(val) +$$; +SELECT test3arr('[{"a": 1, "b": 2}, {"c": 3,"d": 4}]'::jsonb); + test3arr +---------- + 2 +(1 row) + +-- test jsonb int -> python int +CREATE FUNCTION test1int(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert(val == 1) +return val +$$; +SELECT test1int('1'::jsonb); + test1int +---------- + 1 +(1 row) + +-- test jsonb string -> python string +CREATE FUNCTION test1string(val jsonb) RETURNS text +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert(val == "a") +return val +$$; +SELECT test1string('"a"'::jsonb); + test1string +------------- + a +(1 row) + +-- test jsonb null -> python None +CREATE FUNCTION test1null(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert(val == None) +return 1 +$$; +SELECT test1null('null'::jsonb); + test1null +----------- + 1 +(1 row) + +-- test python -> jsonb +CREATE FUNCTION roundtrip(val jsonb) RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +as $$ +return val +$$; +SELECT roundtrip('null'::jsonb); + roundtrip +----------- + +(1 row) + +SELECT roundtrip('1'::jsonb); + roundtrip +----------- + 1 +(1 row) + +SELECT roundtrip('1234567890.0987654321'::jsonb); + roundtrip +----------------------- + 1234567890.0987654321 +(1 row) + +SELECT roundtrip('-1234567890.0987654321'::jsonb); + roundtrip +------------------------ + -1234567890.0987654321 +(1 row) + +SELECT roundtrip('true'::jsonb); + roundtrip +----------- + true +(1 row) + +SELECT roundtrip('"string"'::jsonb); + roundtrip +----------- + "string" +(1 row) + +SELECT roundtrip('{"1": null}'::jsonb); + roundtrip +------------- + {"1": null} +(1 row) + +SELECT roundtrip('{"1": 1}'::jsonb); + roundtrip +----------- + {"1": 1} +(1 row) + +SELECT roundtrip('{"1": true}'::jsonb); + roundtrip +------------- + {"1": true} +(1 row) + +SELECT roundtrip('{"1": "string"}'::jsonb); + roundtrip +----------------- + {"1": "string"} +(1 row) + +SELECT roundtrip('[null]'::jsonb); + roundtrip +----------- + [null] +(1 row) + +SELECT roundtrip('[1]'::jsonb); + roundtrip +----------- + [1] +(1 row) + +SELECT roundtrip('[true]'::jsonb); + roundtrip +----------- + [true] +(1 row) + +SELECT roundtrip('["string"]'::jsonb); + roundtrip +------------ + ["string"] +(1 row) + +SELECT roundtrip('[null, 1]'::jsonb); + roundtrip +----------- + [null, 1] +(1 row) + +SELECT roundtrip('[1, true]'::jsonb); + roundtrip +----------- + [1, true] +(1 row) + +SELECT roundtrip('[true, "string"]'::jsonb); + roundtrip +------------------ + [true, "string"] +(1 row) + +SELECT roundtrip('["string", "string2"]'::jsonb); + roundtrip +----------------------- + ["string", "string2"] +(1 row) + +-- complex numbers -> jsonb +CREATE FUNCTION testComplexNumbers() RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +x = 1 + 2j +return x +$$; +SELECT testComplexNumbers(); +ERROR: could not convert value "(1+2j)" to jsonb +CONTEXT: while creating return value +PL/Python function "testcomplexnumbers" +-- range -> jsonb +CREATE FUNCTION testRange() RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +x = range(3) +return x +$$; +SELECT testRange(); + testrange +----------- + [0, 1, 2] +(1 row) + +-- 0xff -> jsonb +CREATE FUNCTION testDecimal() RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +x = 0xff +return x +$$; +SELECT testDecimal(); + testdecimal +------------- + 255 +(1 row) + +-- tuple -> jsonb +CREATE FUNCTION testTuple() RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +x = (1, 'String', None) +return x +$$; +SELECT testTuple(); + testtuple +--------------------- + [1, "String", null] +(1 row) + +-- interesting dict -> jsonb +CREATE FUNCTION test_dict1() RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +x = {"a": 1, None: 2, 33: 3} +return x +$$; +SELECT test_dict1(); + test_dict1 +-------------------------- + {"": 2, "a": 1, "33": 3} +(1 row) + diff --git a/contrib/jsonb_plpython/jsonb_plpython.c b/contrib/jsonb_plpython/jsonb_plpython.c new file mode 100644 index 0000000..a625727 --- /dev/null +++ b/contrib/jsonb_plpython/jsonb_plpython.c @@ -0,0 +1,502 @@ +#include "postgres.h" + +#include "plpy_elog.h" +#include "plpy_typeio.h" +#include "plpython.h" +#include "utils/fmgrprotos.h" +#include "utils/jsonb.h" +#include "utils/numeric.h" + +PG_MODULE_MAGIC; + +/* for PLyObject_AsString in plpy_typeio.c */ +typedef char *(*PLyObject_AsString_t) (PyObject *plrv); +static PLyObject_AsString_t PLyObject_AsString_p; + +typedef void (*PLy_elog_impl_t) (int elevel, const char *fmt,...); +static PLy_elog_impl_t PLy_elog_impl_p; + +/* + * decimal_constructor is a function from python library and used + * for transforming strings into python decimal type + */ +static PyObject *decimal_constructor; + +static PyObject *PLyObject_FromJsonbContainer(JsonbContainer *jsonb); +static JsonbValue *PLyObject_ToJsonbValue(PyObject *obj, + JsonbParseState **jsonb_state, bool is_elem); + +typedef PyObject *(*PLyUnicode_FromStringAndSize_t) + (const char *s, Py_ssize_t size); +static PLyUnicode_FromStringAndSize_t PLyUnicode_FromStringAndSize_p; + +/* + * Module initialize function: fetch function pointers for cross-module calls. + */ +void +_PG_init(void) +{ + /* Asserts verify that typedefs above match original declarations */ + AssertVariableIsOfType(&PLyObject_AsString, PLyObject_AsString_t); + PLyObject_AsString_p = (PLyObject_AsString_t) + load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyObject_AsString", + true, NULL); + AssertVariableIsOfType(&PLyUnicode_FromStringAndSize, PLyUnicode_FromStringAndSize_t); + PLyUnicode_FromStringAndSize_p = (PLyUnicode_FromStringAndSize_t) + load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyUnicode_FromStringAndSize", + true, NULL); + AssertVariableIsOfType(&PLy_elog_impl, PLy_elog_impl_t); + PLy_elog_impl_p = (PLy_elog_impl_t) + load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLy_elog_impl", + true, NULL); +} + +/* These defines must be after the _PG_init */ +#define PLyObject_AsString (PLyObject_AsString_p) +#define PLyUnicode_FromStringAndSize (PLyUnicode_FromStringAndSize_p) +#undef PLy_elog +#define PLy_elog (PLy_elog_impl_p) + +/* + * PLyUnicode_FromJsonbValue + * + * Transform string JsonbValue to Python string. + */ +static PyObject * +PLyUnicode_FromJsonbValue(JsonbValue *jbv) +{ + Assert(jbv->type == jbvString); + + return PLyUnicode_FromStringAndSize(jbv->val.string.val, jbv->val.string.len); +} + +/* + * PLyUnicode_ToJsonbValue + * + * Transform Python string to JsonbValue. + */ +static void +PLyUnicode_ToJsonbValue(PyObject *obj, JsonbValue *jbvElem) +{ + jbvElem->type = jbvString; + jbvElem->val.string.val = PLyObject_AsString(obj); + jbvElem->val.string.len = strlen(jbvElem->val.string.val); +} + +/* + * PLyObject_FromJsonbValue + * + * Transform JsonbValue to PyObject. + */ +static PyObject * +PLyObject_FromJsonbValue(JsonbValue *jsonbValue) +{ + switch (jsonbValue->type) + { + case jbvNull: + Py_RETURN_NONE; + + case jbvBinary: + return PLyObject_FromJsonbContainer(jsonbValue->val.binary.data); + + case jbvNumeric: + { + Datum num; + char *str; + + num = NumericGetDatum(jsonbValue->val.numeric); + str = DatumGetCString(DirectFunctionCall1(numeric_out, num)); + + return PyObject_CallFunction(decimal_constructor, "s", str); + } + + case jbvString: + return PLyUnicode_FromJsonbValue(jsonbValue); + + case jbvBool: + if (jsonbValue->val.boolean) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; + + default: + elog(ERROR, "unexpected jsonb value type: %d", jsonbValue->type); + return NULL; + } +} + +/* + * PLyObject_FromJsonbContainer + * + * Transform JsonbContainer to PyObject. + */ +static PyObject * +PLyObject_FromJsonbContainer(JsonbContainer *jsonb) +{ + JsonbIteratorToken r; + JsonbValue v; + JsonbIterator *it; + PyObject *result; + + it = JsonbIteratorInit(jsonb); + r = JsonbIteratorNext(&it, &v, true); + + switch (r) + { + case WJB_BEGIN_ARRAY: + if (v.val.array.rawScalar) + { + JsonbValue tmp; + + if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_ELEM || + (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_END_ARRAY || + (r = JsonbIteratorNext(&it, &tmp, true)) != WJB_DONE) + elog(ERROR, "unexpected jsonb token: %d", r); + + result = PLyObject_FromJsonbValue(&v); + } + else + { + PyObject *volatile elem = NULL; + + result = PyList_New(0); + if (!result) + return NULL; + + PG_TRY(); + { + while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r != WJB_ELEM) + continue; + + elem = PLyObject_FromJsonbValue(&v); + + PyList_Append(result, elem); + Py_XDECREF(elem); + elem = NULL; + } + } + PG_CATCH(); + { + Py_XDECREF(elem); + Py_XDECREF(result); + PG_RE_THROW(); + } + PG_END_TRY(); + } + break; + + case WJB_BEGIN_OBJECT: + { + PyObject *volatile result_v = PyDict_New(); + PyObject *volatile key = NULL; + PyObject *volatile val = NULL; + + if (!result_v) + return NULL; + + PG_TRY(); + { + while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + { + if (r != WJB_KEY) + continue; + + key = PLyUnicode_FromJsonbValue(&v); + if (!key) + { + Py_XDECREF(result_v); + result_v = NULL; + break; + } + + if ((r = JsonbIteratorNext(&it, &v, true)) != WJB_VALUE) + elog(ERROR, "unexpected jsonb token: %d", r); + + val = PLyObject_FromJsonbValue(&v); + if (!val) + { + Py_XDECREF(key); + key = NULL; + Py_XDECREF(result_v); + result_v = NULL; + break; + } + + PyDict_SetItem(result_v, key, val); + + Py_XDECREF(key); + key = NULL; + Py_XDECREF(val); + val = NULL; + } + } + PG_CATCH(); + { + Py_XDECREF(result_v); + Py_XDECREF(key); + Py_XDECREF(val); + PG_RE_THROW(); + } + PG_END_TRY(); + + result = result_v; + } + break; + + default: + elog(ERROR, "unexpected jsonb token: %d", r); + return NULL; + } + + return result; +} + +/* + * PLyMapping_ToJsonbValue + * + * Transform Python dict to JsonbValue. + */ +static JsonbValue * +PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state) +{ + Py_ssize_t pcount; + PyObject *volatile items; + JsonbValue *volatile out; + + pcount = PyMapping_Size(obj); + items = PyMapping_Items(obj); + + PG_TRY(); + { + Py_ssize_t i; + + pushJsonbValue(jsonb_state, WJB_BEGIN_OBJECT, NULL); + + for (i = 0; i < pcount; i++) + { + JsonbValue jbvKey; + PyObject *item = PyList_GetItem(items, i); + PyObject *key = PyTuple_GetItem(item, 0); + PyObject *value = PyTuple_GetItem(item, 1); + + /* Python dictionary can have None as key */ + if (key == Py_None) + { + jbvKey.type = jbvString; + jbvKey.val.string.len = 0; + jbvKey.val.string.val = ""; + } + else + { + /* All others types of keys we serialize to string */ + PLyUnicode_ToJsonbValue(key, &jbvKey); + } + + (void) pushJsonbValue(jsonb_state, WJB_KEY, &jbvKey); + (void) PLyObject_ToJsonbValue(value, jsonb_state, false); + } + + out = pushJsonbValue(jsonb_state, WJB_END_OBJECT, NULL); + } + PG_FINALLY(); + { + Py_DECREF(items); + } + PG_END_TRY(); + + return out; +} + +/* + * PLySequence_ToJsonbValue + * + * Transform python list to JsonbValue. Expects transformed PyObject and + * a state required for jsonb construction. + */ +static JsonbValue * +PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state) +{ + Py_ssize_t i; + Py_ssize_t pcount; + PyObject *volatile value = NULL; + + pcount = PySequence_Size(obj); + + pushJsonbValue(jsonb_state, WJB_BEGIN_ARRAY, NULL); + + PG_TRY(); + { + for (i = 0; i < pcount; i++) + { + value = PySequence_GetItem(obj, i); + Assert(value); + + (void) PLyObject_ToJsonbValue(value, jsonb_state, true); + Py_XDECREF(value); + value = NULL; + } + } + PG_CATCH(); + { + Py_XDECREF(value); + PG_RE_THROW(); + } + PG_END_TRY(); + + return pushJsonbValue(jsonb_state, WJB_END_ARRAY, NULL); +} + +/* + * PLyNumber_ToJsonbValue(PyObject *obj) + * + * Transform python number to JsonbValue. + */ +static JsonbValue * +PLyNumber_ToJsonbValue(PyObject *obj, JsonbValue *jbvNum) +{ + Numeric num; + char *str = PLyObject_AsString(obj); + + PG_TRY(); + { + Datum numd; + + numd = DirectFunctionCall3(numeric_in, + CStringGetDatum(str), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + num = DatumGetNumeric(numd); + } + PG_CATCH(); + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not convert value \"%s\" to jsonb", str))); + } + PG_END_TRY(); + + pfree(str); + + /* + * jsonb doesn't allow NaN or infinity (per JSON specification), so we + * have to reject those here explicitly. + */ + if (numeric_is_nan(num)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("cannot convert NaN to jsonb"))); + if (numeric_is_inf(num)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("cannot convert infinity to jsonb"))); + + jbvNum->type = jbvNumeric; + jbvNum->val.numeric = num; + + return jbvNum; +} + +/* + * PLyObject_ToJsonbValue(PyObject *obj) + * + * Transform python object to JsonbValue. + */ +static JsonbValue * +PLyObject_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state, bool is_elem) +{ + JsonbValue *out; + + if (!PyUnicode_Check(obj)) + { + if (PySequence_Check(obj)) + return PLySequence_ToJsonbValue(obj, jsonb_state); + else if (PyMapping_Check(obj)) + return PLyMapping_ToJsonbValue(obj, jsonb_state); + } + + out = palloc(sizeof(JsonbValue)); + + if (obj == Py_None) + out->type = jbvNull; + else if (PyUnicode_Check(obj)) + PLyUnicode_ToJsonbValue(obj, out); + + /* + * PyNumber_Check() returns true for booleans, so boolean check should + * come first. + */ + else if (PyBool_Check(obj)) + { + out->type = jbvBool; + out->val.boolean = (obj == Py_True); + } + else if (PyNumber_Check(obj)) + out = PLyNumber_ToJsonbValue(obj, out); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Python type \"%s\" cannot be transformed to jsonb", + PLyObject_AsString((PyObject *) obj->ob_type)))); + + /* Push result into 'jsonb_state' unless it is raw scalar value. */ + return (*jsonb_state ? + pushJsonbValue(jsonb_state, is_elem ? WJB_ELEM : WJB_VALUE, out) : + out); +} + +/* + * plpython_to_jsonb + * + * Transform python object to Jsonb datum + */ +PG_FUNCTION_INFO_V1(plpython_to_jsonb); +Datum +plpython_to_jsonb(PG_FUNCTION_ARGS) +{ + PyObject *obj; + JsonbValue *out; + JsonbParseState *jsonb_state = NULL; + + obj = (PyObject *) PG_GETARG_POINTER(0); + out = PLyObject_ToJsonbValue(obj, &jsonb_state, true); + PG_RETURN_POINTER(JsonbValueToJsonb(out)); +} + +/* + * jsonb_to_plpython + * + * Transform Jsonb datum to PyObject and return it as internal. + */ +PG_FUNCTION_INFO_V1(jsonb_to_plpython); +Datum +jsonb_to_plpython(PG_FUNCTION_ARGS) +{ + PyObject *result; + Jsonb *in = PG_GETARG_JSONB_P(0); + + /* + * Initialize pointer to Decimal constructor. First we try "cdecimal", C + * version of decimal library. In case of failure we use slower "decimal" + * module. + */ + if (!decimal_constructor) + { + PyObject *decimal_module = PyImport_ImportModule("cdecimal"); + + if (!decimal_module) + { + PyErr_Clear(); + decimal_module = PyImport_ImportModule("decimal"); + } + Assert(decimal_module); + decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal"); + } + + result = PLyObject_FromJsonbContainer(&in->root); + if (!result) + PLy_elog(ERROR, "transformation from jsonb to Python failed"); + + return PointerGetDatum(result); +} diff --git a/contrib/jsonb_plpython/jsonb_plpython3u--1.0.sql b/contrib/jsonb_plpython/jsonb_plpython3u--1.0.sql new file mode 100644 index 0000000..60c34c0 --- /dev/null +++ b/contrib/jsonb_plpython/jsonb_plpython3u--1.0.sql @@ -0,0 +1,19 @@ +/* contrib/jsonb_plpython/jsonb_plpython3u--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION jsonb_plpython3u" to load this file. \quit + +CREATE FUNCTION jsonb_to_plpython3(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'jsonb_to_plpython'; + +CREATE FUNCTION plpython3_to_jsonb(val internal) RETURNS jsonb +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'plpython_to_jsonb'; + +CREATE TRANSFORM FOR jsonb LANGUAGE plpython3u ( + FROM SQL WITH FUNCTION jsonb_to_plpython3(internal), + TO SQL WITH FUNCTION plpython3_to_jsonb(internal) +); + +COMMENT ON TRANSFORM FOR jsonb LANGUAGE plpython3u IS 'transform between jsonb and Python'; diff --git a/contrib/jsonb_plpython/jsonb_plpython3u.control b/contrib/jsonb_plpython/jsonb_plpython3u.control new file mode 100644 index 0000000..f701e80 --- /dev/null +++ b/contrib/jsonb_plpython/jsonb_plpython3u.control @@ -0,0 +1,6 @@ +# jsonb_plpython3u extension +comment = 'transform between jsonb and plpython3u' +default_version = '1.0' +module_pathname = '$libdir/jsonb_plpython3' +relocatable = true +requires = 'plpython3u' diff --git a/contrib/jsonb_plpython/meson.build b/contrib/jsonb_plpython/meson.build new file mode 100644 index 0000000..35e745e --- /dev/null +++ b/contrib/jsonb_plpython/meson.build @@ -0,0 +1,44 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not python3_dep.found() + subdir_done() +endif + +jsonb_plpython_sources = files( + 'jsonb_plpython.c', +) + +if host_system == 'windows' + jsonb_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'jsonb_plpython3', + '--FILEDESC', 'jsonb_plpython - jsonb transform for plpython',]) +endif + +jsonb_plpython = shared_module('jsonb_plpython3', + jsonb_plpython_sources, + include_directories: [plpython_inc], + c_args: ['-DPLPYTHON_LIBNAME="plpython3"'], + kwargs: contrib_mod_args + { + 'dependencies': [python3_dep, contrib_mod_args['dependencies']], + }, +) +contrib_targets += jsonb_plpython + +install_data( + 'jsonb_plpython3u.control', + 'jsonb_plpython3u--1.0.sql', + kwargs: contrib_data_args, +) + +jsonb_plpython_regress = [ + 'jsonb_plpython' +] + +tests += { + 'name': 'jsonb_plpython', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': jsonb_plpython_regress, + }, +} diff --git a/contrib/jsonb_plpython/sql/jsonb_plpython.sql b/contrib/jsonb_plpython/sql/jsonb_plpython.sql new file mode 100644 index 0000000..29dc332 --- /dev/null +++ b/contrib/jsonb_plpython/sql/jsonb_plpython.sql @@ -0,0 +1,183 @@ +CREATE EXTENSION jsonb_plpython3u CASCADE; + +-- test jsonb -> python dict +CREATE FUNCTION test1(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert isinstance(val, dict) +assert(val == {'a': 1, 'c': 'NULL'}) +return len(val) +$$; + +SELECT test1('{"a": 1, "c": "NULL"}'::jsonb); + +-- test jsonb -> python dict +-- complex dict with dicts as value +CREATE FUNCTION test1complex(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert isinstance(val, dict) +assert(val == {"d": {"d": 1}}) +return len(val) +$$; + +SELECT test1complex('{"d": {"d": 1}}'::jsonb); + + +-- test jsonb[] -> python dict +-- dict with array as value +CREATE FUNCTION test1arr(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert isinstance(val, dict) +assert(val == {"d": [12, 1]}) +return len(val) +$$; + +SELECT test1arr('{"d":[12, 1]}'::jsonb); + +-- test jsonb[] -> python list +-- simple list +CREATE FUNCTION test2arr(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert isinstance(val, list) +assert(val == [12, 1]) +return len(val) +$$; + +SELECT test2arr('[12, 1]'::jsonb); + +-- test jsonb[] -> python list +-- array of dicts +CREATE FUNCTION test3arr(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert isinstance(val, list) +assert(val == [{"a": 1,"b": 2}, {"c": 3,"d": 4}]) +return len(val) +$$; + +SELECT test3arr('[{"a": 1, "b": 2}, {"c": 3,"d": 4}]'::jsonb); + +-- test jsonb int -> python int +CREATE FUNCTION test1int(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert(val == 1) +return val +$$; + +SELECT test1int('1'::jsonb); + +-- test jsonb string -> python string +CREATE FUNCTION test1string(val jsonb) RETURNS text +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert(val == "a") +return val +$$; + +SELECT test1string('"a"'::jsonb); + +-- test jsonb null -> python None +CREATE FUNCTION test1null(val jsonb) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +assert(val == None) +return 1 +$$; + +SELECT test1null('null'::jsonb); + +-- test python -> jsonb +CREATE FUNCTION roundtrip(val jsonb) RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +as $$ +return val +$$; + +SELECT roundtrip('null'::jsonb); +SELECT roundtrip('1'::jsonb); +SELECT roundtrip('1234567890.0987654321'::jsonb); +SELECT roundtrip('-1234567890.0987654321'::jsonb); +SELECT roundtrip('true'::jsonb); +SELECT roundtrip('"string"'::jsonb); + +SELECT roundtrip('{"1": null}'::jsonb); +SELECT roundtrip('{"1": 1}'::jsonb); +SELECT roundtrip('{"1": true}'::jsonb); +SELECT roundtrip('{"1": "string"}'::jsonb); + +SELECT roundtrip('[null]'::jsonb); +SELECT roundtrip('[1]'::jsonb); +SELECT roundtrip('[true]'::jsonb); +SELECT roundtrip('["string"]'::jsonb); +SELECT roundtrip('[null, 1]'::jsonb); +SELECT roundtrip('[1, true]'::jsonb); +SELECT roundtrip('[true, "string"]'::jsonb); +SELECT roundtrip('["string", "string2"]'::jsonb); + +-- complex numbers -> jsonb +CREATE FUNCTION testComplexNumbers() RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +x = 1 + 2j +return x +$$; + +SELECT testComplexNumbers(); + +-- range -> jsonb +CREATE FUNCTION testRange() RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +x = range(3) +return x +$$; + +SELECT testRange(); + +-- 0xff -> jsonb +CREATE FUNCTION testDecimal() RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +x = 0xff +return x +$$; + +SELECT testDecimal(); + +-- tuple -> jsonb +CREATE FUNCTION testTuple() RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +x = (1, 'String', None) +return x +$$; + +SELECT testTuple(); + +-- interesting dict -> jsonb +CREATE FUNCTION test_dict1() RETURNS jsonb +LANGUAGE plpython3u +TRANSFORM FOR TYPE jsonb +AS $$ +x = {"a": 1, None: 2, 33: 3} +return x +$$; + +SELECT test_dict1(); diff --git a/contrib/lo/.gitignore b/contrib/lo/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/lo/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/lo/Makefile b/contrib/lo/Makefile new file mode 100644 index 0000000..7168938 --- /dev/null +++ b/contrib/lo/Makefile @@ -0,0 +1,20 @@ +# contrib/lo/Makefile + +MODULES = lo + +EXTENSION = lo +DATA = lo--1.1.sql lo--1.0--1.1.sql +PGFILEDESC = "lo - management for large objects" + +REGRESS = lo + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/lo +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/lo/expected/lo.out b/contrib/lo/expected/lo.out new file mode 100644 index 0000000..c63e4b1 --- /dev/null +++ b/contrib/lo/expected/lo.out @@ -0,0 +1,50 @@ +CREATE EXTENSION lo; +CREATE TABLE image (title text, raster lo); +CREATE TRIGGER t_raster BEFORE UPDATE OR DELETE ON image + FOR EACH ROW EXECUTE PROCEDURE lo_manage(raster); +SELECT lo_create(43213); + lo_create +----------- + 43213 +(1 row) + +SELECT lo_create(43214); + lo_create +----------- + 43214 +(1 row) + +INSERT INTO image (title, raster) VALUES ('beautiful image', 43213); +SELECT lo_get(43213); + lo_get +-------- + \x +(1 row) + +SELECT lo_get(43214); + lo_get +-------- + \x +(1 row) + +UPDATE image SET raster = 43214 WHERE title = 'beautiful image'; +SELECT lo_get(43213); +ERROR: large object 43213 does not exist +SELECT lo_get(43214); + lo_get +-------- + \x +(1 row) + +-- test updating of unrelated column +UPDATE image SET title = 'beautiful picture' WHERE title = 'beautiful image'; +SELECT lo_get(43214); + lo_get +-------- + \x +(1 row) + +DELETE FROM image; +SELECT lo_get(43214); +ERROR: large object 43214 does not exist +DROP TABLE image; diff --git a/contrib/lo/lo--1.0--1.1.sql b/contrib/lo/lo--1.0--1.1.sql new file mode 100644 index 0000000..10a4ea2 --- /dev/null +++ b/contrib/lo/lo--1.0--1.1.sql @@ -0,0 +1,6 @@ +/* contrib/lo/lo--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION lo UPDATE TO '1.1'" to load this file. \quit + +ALTER FUNCTION lo_oid(lo) PARALLEL SAFE; diff --git a/contrib/lo/lo--1.1.sql b/contrib/lo/lo--1.1.sql new file mode 100644 index 0000000..c817cb4 --- /dev/null +++ b/contrib/lo/lo--1.1.sql @@ -0,0 +1,25 @@ +/* contrib/lo/lo--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION lo" to load this file. \quit + +-- +-- Create the data type ... now just a domain over OID +-- + +CREATE DOMAIN lo AS pg_catalog.oid; + +-- +-- For backwards compatibility, define a function named lo_oid. +-- +-- The other functions that formerly existed are not needed because +-- the implicit casts between a domain and its underlying type handle them. +-- +CREATE FUNCTION lo_oid(lo) RETURNS pg_catalog.oid AS +'SELECT $1::pg_catalog.oid' LANGUAGE SQL STRICT IMMUTABLE PARALLEL SAFE; + +-- This is used in triggers +CREATE FUNCTION lo_manage() +RETURNS pg_catalog.trigger +AS 'MODULE_PATHNAME' +LANGUAGE C; diff --git a/contrib/lo/lo.c b/contrib/lo/lo.c new file mode 100644 index 0000000..457be26 --- /dev/null +++ b/contrib/lo/lo.c @@ -0,0 +1,111 @@ +/* + * PostgreSQL definitions for managed Large Objects. + * + * contrib/lo/lo.c + * + */ + +#include "postgres.h" + +#include "commands/trigger.h" +#include "executor/spi.h" +#include "utils/builtins.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + + +/* + * This is the trigger that protects us from orphaned large objects + */ +PG_FUNCTION_INFO_V1(lo_manage); + +Datum +lo_manage(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + int attnum; /* attribute number to monitor */ + char **args; /* Args containing attr name */ + TupleDesc tupdesc; /* Tuple Descriptor */ + HeapTuple rettuple; /* Tuple to be returned */ + bool isdelete; /* are we deleting? */ + HeapTuple newtuple; /* The new value for tuple */ + HeapTuple trigtuple; /* The original value of tuple */ + + if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */ + elog(ERROR, "lo_manage: not fired by trigger manager"); + + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) /* internal error */ + elog(ERROR, "%s: must be fired for row", + trigdata->tg_trigger->tgname); + + /* + * Fetch some values from trigdata + */ + newtuple = trigdata->tg_newtuple; + trigtuple = trigdata->tg_trigtuple; + tupdesc = trigdata->tg_relation->rd_att; + args = trigdata->tg_trigger->tgargs; + + if (args == NULL) /* internal error */ + elog(ERROR, "%s: no column name provided in the trigger definition", + trigdata->tg_trigger->tgname); + + /* tuple to return to Executor */ + if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + rettuple = newtuple; + else + rettuple = trigtuple; + + /* Are we deleting the row? */ + isdelete = TRIGGER_FIRED_BY_DELETE(trigdata->tg_event); + + /* Get the column we're interested in */ + attnum = SPI_fnumber(tupdesc, args[0]); + + if (attnum <= 0) + elog(ERROR, "%s: column \"%s\" does not exist", + trigdata->tg_trigger->tgname, args[0]); + + /* + * Handle updates + * + * Here, if the value of the monitored attribute changes, then the large + * object associated with the original value is unlinked. + */ + if (newtuple != NULL && + bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, trigdata->tg_updatedcols)) + { + char *orig = SPI_getvalue(trigtuple, tupdesc, attnum); + char *newv = SPI_getvalue(newtuple, tupdesc, attnum); + + if (orig != NULL && (newv == NULL || strcmp(orig, newv) != 0)) + DirectFunctionCall1(be_lo_unlink, + ObjectIdGetDatum(atooid(orig))); + + if (newv) + pfree(newv); + if (orig) + pfree(orig); + } + + /* + * Handle deleting of rows + * + * Here, we unlink the large object associated with the managed attribute + */ + if (isdelete) + { + char *orig = SPI_getvalue(trigtuple, tupdesc, attnum); + + if (orig != NULL) + { + DirectFunctionCall1(be_lo_unlink, + ObjectIdGetDatum(atooid(orig))); + + pfree(orig); + } + } + + return PointerGetDatum(rettuple); +} diff --git a/contrib/lo/lo.control b/contrib/lo/lo.control new file mode 100644 index 0000000..f73f8b5 --- /dev/null +++ b/contrib/lo/lo.control @@ -0,0 +1,6 @@ +# lo extension +comment = 'Large Object maintenance' +default_version = '1.1' +module_pathname = '$libdir/lo' +relocatable = true +trusted = true diff --git a/contrib/lo/lo_test.sql b/contrib/lo/lo_test.sql new file mode 100644 index 0000000..7e52362 --- /dev/null +++ b/contrib/lo/lo_test.sql @@ -0,0 +1,79 @@ +/* contrib/lo/lo_test.sql */ + +-- Adjust this setting to control where the objects get created. +SET search_path = public; + +-- +-- This runs some common tests against the type +-- +-- It's used just for development +-- +-- XXX would be nice to turn this into a proper regression test +-- + +-- Check what is in pg_largeobject +SELECT count(oid) FROM pg_largeobject_metadata; + +-- ignore any errors here - simply drop the table if it already exists +DROP TABLE a; + +-- create the test table +CREATE TABLE a (fname name,image lo); + +-- insert a null object +INSERT INTO a VALUES ('empty'); + +-- insert a large object based on a file +INSERT INTO a VALUES ('/etc/group', lo_import('/etc/group')::lo); + +-- now select the table +SELECT * FROM a; + +-- check that coercion to plain oid works +SELECT *,image::oid from a; + +-- now test the trigger +CREATE TRIGGER t_a +BEFORE UPDATE OR DELETE ON a +FOR EACH ROW +EXECUTE PROCEDURE lo_manage(image); + +-- insert +INSERT INTO a VALUES ('aa', lo_import('/etc/hosts')); +SELECT * FROM a +WHERE fname LIKE 'aa%'; + +-- update +UPDATE a SET image=lo_import('/etc/group')::lo +WHERE fname='aa'; +SELECT * FROM a +WHERE fname LIKE 'aa%'; + +-- update the 'empty' row which should be null +UPDATE a SET image=lo_import('/etc/hosts') +WHERE fname='empty'; +SELECT * FROM a +WHERE fname LIKE 'empty%'; +UPDATE a SET image=null +WHERE fname='empty'; +SELECT * FROM a +WHERE fname LIKE 'empty%'; + +-- delete the entry +DELETE FROM a +WHERE fname='aa'; +SELECT * FROM a +WHERE fname LIKE 'aa%'; + +-- This deletes the table contents. Note, if you comment this out, and +-- expect the drop table to remove the objects, think again. The trigger +-- doesn't get fired by drop table. +DELETE FROM a; + +-- finally drop the table +DROP TABLE a; + +-- Check what is in pg_largeobject ... if different from original, trouble +SELECT count(oid) FROM pg_largeobject_metadata; + +-- end of tests diff --git a/contrib/lo/meson.build b/contrib/lo/meson.build new file mode 100644 index 0000000..06a811b --- /dev/null +++ b/contrib/lo/meson.build @@ -0,0 +1,35 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +lo_sources = files( + 'lo.c', +) + +if host_system == 'windows' + lo_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'lo', + '--FILEDESC', 'lo - management for large objects',]) +endif + +lo = shared_module('lo', + lo_sources, + kwargs: contrib_mod_args, +) +contrib_targets += lo + +install_data( + 'lo.control', + 'lo--1.0--1.1.sql', + 'lo--1.1.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'lo', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'lo', + ], + }, +} diff --git a/contrib/lo/sql/lo.sql b/contrib/lo/sql/lo.sql new file mode 100644 index 0000000..7703950 --- /dev/null +++ b/contrib/lo/sql/lo.sql @@ -0,0 +1,30 @@ +CREATE EXTENSION lo; + +CREATE TABLE image (title text, raster lo); + +CREATE TRIGGER t_raster BEFORE UPDATE OR DELETE ON image + FOR EACH ROW EXECUTE PROCEDURE lo_manage(raster); + +SELECT lo_create(43213); +SELECT lo_create(43214); + +INSERT INTO image (title, raster) VALUES ('beautiful image', 43213); + +SELECT lo_get(43213); +SELECT lo_get(43214); + +UPDATE image SET raster = 43214 WHERE title = 'beautiful image'; + +SELECT lo_get(43213); +SELECT lo_get(43214); + +-- test updating of unrelated column +UPDATE image SET title = 'beautiful picture' WHERE title = 'beautiful image'; + +SELECT lo_get(43214); + +DELETE FROM image; + +SELECT lo_get(43214); + +DROP TABLE image; diff --git a/contrib/ltree/.gitignore b/contrib/ltree/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/ltree/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/ltree/Makefile b/contrib/ltree/Makefile new file mode 100644 index 0000000..770769a --- /dev/null +++ b/contrib/ltree/Makefile @@ -0,0 +1,33 @@ +# contrib/ltree/Makefile + +MODULE_big = ltree +OBJS = \ + $(WIN32RES) \ + _ltree_gist.o \ + _ltree_op.o \ + crc32.o \ + lquery_op.o \ + ltree_gist.o \ + ltree_io.o \ + ltree_op.o \ + ltxtquery_io.o \ + ltxtquery_op.o + +EXTENSION = ltree +DATA = ltree--1.1--1.2.sql ltree--1.1.sql ltree--1.0--1.1.sql +PGFILEDESC = "ltree - hierarchical label data type" + +HEADERS = ltree.h + +REGRESS = ltree + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/ltree +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/ltree/_ltree_gist.c b/contrib/ltree/_ltree_gist.c new file mode 100644 index 0000000..e89a39a --- /dev/null +++ b/contrib/ltree/_ltree_gist.c @@ -0,0 +1,558 @@ +/* + * contrib/ltree/_ltree_gist.c + * + * + * GiST support for ltree[] + * Teodor Sigaev + */ +#include "postgres.h" + +#include + +#include "access/gist.h" +#include "access/reloptions.h" +#include "access/stratnum.h" +#include "crc32.h" +#include "ltree.h" +#include "port/pg_bitutils.h" +#include "utils/array.h" + +PG_FUNCTION_INFO_V1(_ltree_compress); +PG_FUNCTION_INFO_V1(_ltree_same); +PG_FUNCTION_INFO_V1(_ltree_union); +PG_FUNCTION_INFO_V1(_ltree_penalty); +PG_FUNCTION_INFO_V1(_ltree_picksplit); +PG_FUNCTION_INFO_V1(_ltree_consistent); +PG_FUNCTION_INFO_V1(_ltree_gist_options); + +#define GETENTRY(vec,pos) ((ltree_gist *) DatumGetPointer((vec)->vector[(pos)].key)) +#define NEXTVAL(x) ( (ltree*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) ) + +#define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) ) + + +static void +hashing(BITVECP sign, ltree *t, int siglen) +{ + int tlen = t->numlevel; + ltree_level *cur = LTREE_FIRST(t); + int hash; + + while (tlen > 0) + { + hash = ltree_crc32_sz(cur->name, cur->len); + AHASH(sign, hash, siglen); + cur = LEVEL_NEXT(cur); + tlen--; + } +} + +Datum +_ltree_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval = entry; + int siglen = LTREE_GET_ASIGLEN(); + + if (entry->leafkey) + { /* ltree */ + ltree_gist *key; + ArrayType *val = DatumGetArrayTypeP(entry->key); + int num = ArrayGetNItems(ARR_NDIM(val), ARR_DIMS(val)); + ltree *item = (ltree *) ARR_DATA_PTR(val); + + if (ARR_NDIM(val) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must be one-dimensional"))); + if (array_contains_nulls(val)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array must not contain nulls"))); + + key = ltree_gist_alloc(false, NULL, siglen, NULL, NULL); + + while (num > 0) + { + hashing(LTG_SIGN(key), item, siglen); + num--; + item = NEXTVAL(item); + } + + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(key), + entry->rel, entry->page, + entry->offset, false); + } + else if (!LTG_ISALLTRUE(entry->key)) + { + int32 i; + ltree_gist *key; + BITVECP sign = LTG_SIGN(DatumGetPointer(entry->key)); + + ALOOPBYTE(siglen) + { + if ((sign[i] & 0xff) != 0xff) + PG_RETURN_POINTER(retval); + } + + key = ltree_gist_alloc(true, sign, siglen, NULL, NULL); + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(key), + entry->rel, entry->page, + entry->offset, false); + } + PG_RETURN_POINTER(retval); +} + +Datum +_ltree_same(PG_FUNCTION_ARGS) +{ + ltree_gist *a = (ltree_gist *) PG_GETARG_POINTER(0); + ltree_gist *b = (ltree_gist *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + int siglen = LTREE_GET_ASIGLEN(); + + if (LTG_ISALLTRUE(a) && LTG_ISALLTRUE(b)) + *result = true; + else if (LTG_ISALLTRUE(a)) + *result = false; + else if (LTG_ISALLTRUE(b)) + *result = false; + else + { + int32 i; + BITVECP sa = LTG_SIGN(a), + sb = LTG_SIGN(b); + + *result = true; + ALOOPBYTE(siglen) + { + if (sa[i] != sb[i]) + { + *result = false; + break; + } + } + } + PG_RETURN_POINTER(result); +} + +static int32 +unionkey(BITVECP sbase, ltree_gist *add, int siglen) +{ + int32 i; + BITVECP sadd = LTG_SIGN(add); + + if (LTG_ISALLTRUE(add)) + return 1; + + ALOOPBYTE(siglen) + sbase[i] |= sadd[i]; + return 0; +} + +Datum +_ltree_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int *size = (int *) PG_GETARG_POINTER(1); + int siglen = LTREE_GET_ASIGLEN(); + int32 i; + ltree_gist *result = ltree_gist_alloc(false, NULL, siglen, NULL, NULL); + BITVECP base = LTG_SIGN(result); + + for (i = 0; i < entryvec->n; i++) + { + if (unionkey(base, GETENTRY(entryvec, i), siglen)) + { + result->flag |= LTG_ALLTRUE; + SET_VARSIZE(result, LTG_HDRSIZE); + break; + } + } + + *size = VARSIZE(result); + + PG_RETURN_POINTER(result); +} + +static int32 +sizebitvec(BITVECP sign, int siglen) +{ + return pg_popcount((const char *) sign, siglen); +} + +static int +hemdistsign(BITVECP a, BITVECP b, int siglen) +{ + int i, + diff, + dist = 0; + + ALOOPBYTE(siglen) + { + diff = (unsigned char) (a[i] ^ b[i]); + /* Using the popcount functions here isn't likely to win */ + dist += pg_number_of_ones[diff]; + } + return dist; +} + +static int +hemdist(ltree_gist *a, ltree_gist *b, int siglen) +{ + if (LTG_ISALLTRUE(a)) + { + if (LTG_ISALLTRUE(b)) + return 0; + else + return ASIGLENBIT(siglen) - sizebitvec(LTG_SIGN(b), siglen); + } + else if (LTG_ISALLTRUE(b)) + return ASIGLENBIT(siglen) - sizebitvec(LTG_SIGN(a), siglen); + + return hemdistsign(LTG_SIGN(a), LTG_SIGN(b), siglen); +} + + +Datum +_ltree_penalty(PG_FUNCTION_ARGS) +{ + ltree_gist *origval = (ltree_gist *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + ltree_gist *newval = (ltree_gist *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *penalty = (float *) PG_GETARG_POINTER(2); + int siglen = LTREE_GET_ASIGLEN(); + + *penalty = hemdist(origval, newval, siglen); + PG_RETURN_POINTER(penalty); +} + +typedef struct +{ + OffsetNumber pos; + int32 cost; +} SPLITCOST; + +static int +comparecost(const void *a, const void *b) +{ + return ((const SPLITCOST *) a)->cost - ((const SPLITCOST *) b)->cost; +} + +Datum +_ltree_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + int siglen = LTREE_GET_ASIGLEN(); + OffsetNumber k, + j; + ltree_gist *datum_l, + *datum_r; + BITVECP union_l, + union_r; + int32 size_alpha, + size_beta; + int32 size_waste, + waste = -1; + int32 nbytes; + OffsetNumber seed_1 = 0, + seed_2 = 0; + OffsetNumber *left, + *right; + OffsetNumber maxoff; + BITVECP ptr; + int i; + SPLITCOST *costvector; + ltree_gist *_k, + *_j; + + maxoff = entryvec->n - 2; + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + + for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k)) + { + _k = GETENTRY(entryvec, k); + for (j = OffsetNumberNext(k); j <= maxoff; j = OffsetNumberNext(j)) + { + size_waste = hemdist(_k, GETENTRY(entryvec, j), siglen); + if (size_waste > waste) + { + waste = size_waste; + seed_1 = k; + seed_2 = j; + } + } + } + + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + + if (seed_1 == 0 || seed_2 == 0) + { + seed_1 = 1; + seed_2 = 2; + } + + /* form initial .. */ + datum_l = ltree_gist_alloc(LTG_ISALLTRUE(GETENTRY(entryvec, seed_1)), + LTG_SIGN(GETENTRY(entryvec, seed_1)), + siglen, NULL, NULL); + + datum_r = ltree_gist_alloc(LTG_ISALLTRUE(GETENTRY(entryvec, seed_2)), + LTG_SIGN(GETENTRY(entryvec, seed_2)), + siglen, NULL, NULL); + + maxoff = OffsetNumberNext(maxoff); + /* sort before ... */ + costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff); + for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) + { + costvector[j - 1].pos = j; + _j = GETENTRY(entryvec, j); + size_alpha = hemdist(datum_l, _j, siglen); + size_beta = hemdist(datum_r, _j, siglen); + costvector[j - 1].cost = abs(size_alpha - size_beta); + } + qsort(costvector, maxoff, sizeof(SPLITCOST), comparecost); + + union_l = LTG_SIGN(datum_l); + union_r = LTG_SIGN(datum_r); + + for (k = 0; k < maxoff; k++) + { + j = costvector[k].pos; + if (j == seed_1) + { + *left++ = j; + v->spl_nleft++; + continue; + } + else if (j == seed_2) + { + *right++ = j; + v->spl_nright++; + continue; + } + _j = GETENTRY(entryvec, j); + size_alpha = hemdist(datum_l, _j, siglen); + size_beta = hemdist(datum_r, _j, siglen); + + if (size_alpha < size_beta + WISH_F(v->spl_nleft, v->spl_nright, 0.00001)) + { + if (LTG_ISALLTRUE(datum_l) || LTG_ISALLTRUE(_j)) + { + if (!LTG_ISALLTRUE(datum_l)) + memset(union_l, 0xff, siglen); + } + else + { + ptr = LTG_SIGN(_j); + ALOOPBYTE(siglen) + union_l[i] |= ptr[i]; + } + *left++ = j; + v->spl_nleft++; + } + else + { + if (LTG_ISALLTRUE(datum_r) || LTG_ISALLTRUE(_j)) + { + if (!LTG_ISALLTRUE(datum_r)) + memset(union_r, 0xff, siglen); + } + else + { + ptr = LTG_SIGN(_j); + ALOOPBYTE(siglen) + union_r[i] |= ptr[i]; + } + *right++ = j; + v->spl_nright++; + } + } + + *right = *left = FirstOffsetNumber; + + v->spl_ldatum = PointerGetDatum(datum_l); + v->spl_rdatum = PointerGetDatum(datum_r); + + PG_RETURN_POINTER(v); +} + +static bool +gist_te(ltree_gist *key, ltree *query, int siglen) +{ + ltree_level *curq = LTREE_FIRST(query); + BITVECP sign = LTG_SIGN(key); + int qlen = query->numlevel; + unsigned int hv; + + if (LTG_ISALLTRUE(key)) + return true; + + while (qlen > 0) + { + hv = ltree_crc32_sz(curq->name, curq->len); + if (!GETBIT(sign, AHASHVAL(hv, siglen))) + return false; + curq = LEVEL_NEXT(curq); + qlen--; + } + + return true; +} + +typedef struct LtreeSignature +{ + BITVECP sign; + int siglen; +} LtreeSignature; + +static bool +checkcondition_bit(void *cxt, ITEM *val) +{ + LtreeSignature *sig = cxt; + + return (FLG_CANLOOKSIGN(val->flag)) ? GETBIT(sig->sign, AHASHVAL(val->val, sig->siglen)) : true; +} + +static bool +gist_qtxt(ltree_gist *key, ltxtquery *query, int siglen) +{ + LtreeSignature sig; + + if (LTG_ISALLTRUE(key)) + return true; + + sig.sign = LTG_SIGN(key); + sig.siglen = siglen; + + return ltree_execute(GETQUERY(query), + &sig, false, + checkcondition_bit); +} + +static bool +gist_qe(ltree_gist *key, lquery *query, int siglen) +{ + lquery_level *curq = LQUERY_FIRST(query); + BITVECP sign = LTG_SIGN(key); + int qlen = query->numlevel; + + if (LTG_ISALLTRUE(key)) + return true; + + while (qlen > 0) + { + if (curq->numvar && LQL_CANLOOKSIGN(curq)) + { + bool isexist = false; + int vlen = curq->numvar; + lquery_variant *curv = LQL_FIRST(curq); + + while (vlen > 0) + { + if (GETBIT(sign, AHASHVAL(curv->val, siglen))) + { + isexist = true; + break; + } + curv = LVAR_NEXT(curv); + vlen--; + } + if (!isexist) + return false; + } + + curq = LQL_NEXT(curq); + qlen--; + } + + return true; +} + +static bool +_arrq_cons(ltree_gist *key, ArrayType *_query, int siglen) +{ + lquery *query = (lquery *) ARR_DATA_PTR(_query); + int num = ArrayGetNItems(ARR_NDIM(_query), ARR_DIMS(_query)); + + if (ARR_NDIM(_query) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must be one-dimensional"))); + if (array_contains_nulls(_query)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array must not contain nulls"))); + + while (num > 0) + { + if (gist_qe(key, query, siglen)) + return true; + num--; + query = (lquery *) NEXTVAL(query); + } + return false; +} + +Datum +_ltree_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + void *query = (void *) PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + int siglen = LTREE_GET_ASIGLEN(); + ltree_gist *key = (ltree_gist *) DatumGetPointer(entry->key); + bool res = false; + + /* All cases served by this function are inexact */ + *recheck = true; + + switch (strategy) + { + case 10: + case 11: + res = gist_te(key, (ltree *) query, siglen); + break; + case 12: + case 13: + res = gist_qe(key, (lquery *) query, siglen); + break; + case 14: + case 15: + res = gist_qtxt(key, (ltxtquery *) query, siglen); + break; + case 16: + case 17: + res = _arrq_cons(key, (ArrayType *) query, siglen); + break; + default: + /* internal error */ + elog(ERROR, "unrecognized StrategyNumber: %d", strategy); + } + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(res); +} + +Datum +_ltree_gist_options(PG_FUNCTION_ARGS) +{ + local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0); + + init_local_reloptions(relopts, sizeof(LtreeGistOptions)); + add_local_int_reloption(relopts, "siglen", "signature length", + LTREE_ASIGLEN_DEFAULT, 1, LTREE_ASIGLEN_MAX, + offsetof(LtreeGistOptions, siglen)); + + PG_RETURN_VOID(); +} diff --git a/contrib/ltree/_ltree_op.c b/contrib/ltree/_ltree_op.c new file mode 100644 index 0000000..2fdb5ea --- /dev/null +++ b/contrib/ltree/_ltree_op.c @@ -0,0 +1,326 @@ +/* + * contrib/ltree/_ltree_op.c + * + * + * op function for ltree[] + * Teodor Sigaev + */ +#include "postgres.h" + +#include + +#include "ltree.h" +#include "utils/array.h" + +PG_FUNCTION_INFO_V1(_ltree_isparent); +PG_FUNCTION_INFO_V1(_ltree_r_isparent); +PG_FUNCTION_INFO_V1(_ltree_risparent); +PG_FUNCTION_INFO_V1(_ltree_r_risparent); +PG_FUNCTION_INFO_V1(_ltq_regex); +PG_FUNCTION_INFO_V1(_ltq_rregex); +PG_FUNCTION_INFO_V1(_lt_q_regex); +PG_FUNCTION_INFO_V1(_lt_q_rregex); +PG_FUNCTION_INFO_V1(_ltxtq_exec); +PG_FUNCTION_INFO_V1(_ltxtq_rexec); + +PG_FUNCTION_INFO_V1(_ltree_extract_isparent); +PG_FUNCTION_INFO_V1(_ltree_extract_risparent); +PG_FUNCTION_INFO_V1(_ltq_extract_regex); +PG_FUNCTION_INFO_V1(_ltxtq_extract_exec); + +PG_FUNCTION_INFO_V1(_lca); + +typedef Datum (*PGCALL2) (PG_FUNCTION_ARGS); + +#define NEXTVAL(x) ( (ltree*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) ) + +static bool +array_iterator(ArrayType *la, PGCALL2 callback, void *param, ltree **found) +{ + int num = ArrayGetNItems(ARR_NDIM(la), ARR_DIMS(la)); + ltree *item = (ltree *) ARR_DATA_PTR(la); + + if (ARR_NDIM(la) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must be one-dimensional"))); + if (array_contains_nulls(la)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array must not contain nulls"))); + + if (found) + *found = NULL; + while (num > 0) + { + if (DatumGetBool(DirectFunctionCall2(callback, + PointerGetDatum(item), PointerGetDatum(param)))) + { + + if (found) + *found = item; + return true; + } + num--; + item = NEXTVAL(item); + } + + return false; +} + +Datum +_ltree_isparent(PG_FUNCTION_ARGS) +{ + ArrayType *la = PG_GETARG_ARRAYTYPE_P(0); + ltree *query = PG_GETARG_LTREE_P(1); + bool res = array_iterator(la, ltree_isparent, (void *) query, NULL); + + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(res); +} + +Datum +_ltree_r_isparent(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(_ltree_isparent, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); +} + +Datum +_ltree_risparent(PG_FUNCTION_ARGS) +{ + ArrayType *la = PG_GETARG_ARRAYTYPE_P(0); + ltree *query = PG_GETARG_LTREE_P(1); + bool res = array_iterator(la, ltree_risparent, (void *) query, NULL); + + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(res); +} + +Datum +_ltree_r_risparent(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(_ltree_risparent, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); +} + +Datum +_ltq_regex(PG_FUNCTION_ARGS) +{ + ArrayType *la = PG_GETARG_ARRAYTYPE_P(0); + lquery *query = PG_GETARG_LQUERY_P(1); + bool res = array_iterator(la, ltq_regex, (void *) query, NULL); + + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(res); +} + +Datum +_ltq_rregex(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(_ltq_regex, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); +} + +Datum +_lt_q_regex(PG_FUNCTION_ARGS) +{ + ArrayType *_tree = PG_GETARG_ARRAYTYPE_P(0); + ArrayType *_query = PG_GETARG_ARRAYTYPE_P(1); + lquery *query = (lquery *) ARR_DATA_PTR(_query); + bool res = false; + int num = ArrayGetNItems(ARR_NDIM(_query), ARR_DIMS(_query)); + + if (ARR_NDIM(_query) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must be one-dimensional"))); + if (array_contains_nulls(_query)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array must not contain nulls"))); + + while (num > 0) + { + if (array_iterator(_tree, ltq_regex, (void *) query, NULL)) + { + res = true; + break; + } + num--; + query = (lquery *) NEXTVAL(query); + } + + PG_FREE_IF_COPY(_tree, 0); + PG_FREE_IF_COPY(_query, 1); + PG_RETURN_BOOL(res); +} + +Datum +_lt_q_rregex(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(_lt_q_regex, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); +} + + +Datum +_ltxtq_exec(PG_FUNCTION_ARGS) +{ + ArrayType *la = PG_GETARG_ARRAYTYPE_P(0); + ltxtquery *query = PG_GETARG_LTXTQUERY_P(1); + bool res = array_iterator(la, ltxtq_exec, (void *) query, NULL); + + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(res); +} + +Datum +_ltxtq_rexec(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(_ltxtq_exec, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); +} + + +Datum +_ltree_extract_isparent(PG_FUNCTION_ARGS) +{ + ArrayType *la = PG_GETARG_ARRAYTYPE_P(0); + ltree *query = PG_GETARG_LTREE_P(1); + ltree *found, + *item; + + if (!array_iterator(la, ltree_isparent, (void *) query, &found)) + { + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_NULL(); + } + + item = (ltree *) palloc0(VARSIZE(found)); + memcpy(item, found, VARSIZE(found)); + + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_POINTER(item); +} + +Datum +_ltree_extract_risparent(PG_FUNCTION_ARGS) +{ + ArrayType *la = PG_GETARG_ARRAYTYPE_P(0); + ltree *query = PG_GETARG_LTREE_P(1); + ltree *found, + *item; + + if (!array_iterator(la, ltree_risparent, (void *) query, &found)) + { + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_NULL(); + } + + item = (ltree *) palloc0(VARSIZE(found)); + memcpy(item, found, VARSIZE(found)); + + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_POINTER(item); +} + +Datum +_ltq_extract_regex(PG_FUNCTION_ARGS) +{ + ArrayType *la = PG_GETARG_ARRAYTYPE_P(0); + lquery *query = PG_GETARG_LQUERY_P(1); + ltree *found, + *item; + + if (!array_iterator(la, ltq_regex, (void *) query, &found)) + { + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_NULL(); + } + + item = (ltree *) palloc0(VARSIZE(found)); + memcpy(item, found, VARSIZE(found)); + + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_POINTER(item); +} + +Datum +_ltxtq_extract_exec(PG_FUNCTION_ARGS) +{ + ArrayType *la = PG_GETARG_ARRAYTYPE_P(0); + ltxtquery *query = PG_GETARG_LTXTQUERY_P(1); + ltree *found, + *item; + + if (!array_iterator(la, ltxtq_exec, (void *) query, &found)) + { + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_NULL(); + } + + item = (ltree *) palloc0(VARSIZE(found)); + memcpy(item, found, VARSIZE(found)); + + PG_FREE_IF_COPY(la, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_POINTER(item); +} + +Datum +_lca(PG_FUNCTION_ARGS) +{ + ArrayType *la = PG_GETARG_ARRAYTYPE_P(0); + int num = ArrayGetNItems(ARR_NDIM(la), ARR_DIMS(la)); + ltree *item = (ltree *) ARR_DATA_PTR(la); + ltree **a, + *res; + + if (ARR_NDIM(la) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must be one-dimensional"))); + if (array_contains_nulls(la)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array must not contain nulls"))); + + a = (ltree **) palloc(sizeof(ltree *) * num); + while (num > 0) + { + num--; + a[num] = item; + item = NEXTVAL(item); + } + res = lca_inner(a, ArrayGetNItems(ARR_NDIM(la), ARR_DIMS(la))); + pfree(a); + + PG_FREE_IF_COPY(la, 0); + + if (res) + PG_RETURN_POINTER(res); + else + PG_RETURN_NULL(); +} diff --git a/contrib/ltree/crc32.c b/contrib/ltree/crc32.c new file mode 100644 index 0000000..134f46a --- /dev/null +++ b/contrib/ltree/crc32.c @@ -0,0 +1,40 @@ +/* contrib/ltree/crc32.c */ + +/* + * Implements CRC-32, as used in ltree. + * + * Note that the CRC is used in the on-disk format of GiST indexes, so we + * must stay backwards-compatible! + */ + +#include "postgres.h" +#include "ltree.h" + +#ifdef LOWER_NODE +#include +#define TOLOWER(x) tolower((unsigned char) (x)) +#else +#define TOLOWER(x) (x) +#endif + +#include "crc32.h" +#include "utils/pg_crc.h" + +unsigned int +ltree_crc32_sz(const char *buf, int size) +{ + pg_crc32 crc; + const char *p = buf; + + INIT_TRADITIONAL_CRC32(crc); + while (size > 0) + { + char c = (char) TOLOWER(*p); + + COMP_TRADITIONAL_CRC32(crc, &c, 1); + size--; + p++; + } + FIN_TRADITIONAL_CRC32(crc); + return (unsigned int) crc; +} diff --git a/contrib/ltree/crc32.h b/contrib/ltree/crc32.h new file mode 100644 index 0000000..9588122 --- /dev/null +++ b/contrib/ltree/crc32.h @@ -0,0 +1,12 @@ +#ifndef _CRC32_H +#define _CRC32_H + +/* contrib/ltree/crc32.h */ + +/* Returns crc32 of data block */ +extern unsigned int ltree_crc32_sz(const char *buf, int size); + +/* Returns crc32 of null-terminated string */ +#define crc32(buf) ltree_crc32_sz((buf),strlen(buf)) + +#endif diff --git a/contrib/ltree/data/_ltree.data b/contrib/ltree/data/_ltree.data new file mode 100644 index 0000000..9ba2787 --- /dev/null +++ b/contrib/ltree/data/_ltree.data @@ -0,0 +1,1000 @@ +{14.30.13.5.26.9.22.23.14.10, 22.8.20.1.10.28.6.27, 16.30.10.7.29.4.9.21.22.13.26} +{5.8.17.30.15.8.19.29.30.11.6, 3.13, 15.8.10, 23.17.28.31.28} +{22.23.22.30, 4.31, 7.13, 32.6.31.31} +{19.10.26.19.5.21.30.23, 24.1.10.20.28.18.6.27.20.30.26, 5.4.8.25.12.27.2.29.28.3, 1.1.1.2} +{11.14.21.24.10.7.29.23.24.28, 31.13.9.1.5.12, 23.12.32.22.19.1.22.4} +{8.32.30.1, 13.8.20.9.21, 8.29.6.3, 30.8.9.14.25.30, 24.2.26.24.14.15.31.23.17.26} +{28.5.12.9.2.27.11.11.2, 24.9.27.16.20.21, 31.30.12.20} +{29.9.25.27.15.16.32.26.6.32, 5.24.25.15.27.30.20} +{12.21.20.20, 4.14.10.19.16, 2.15.14.20.30.26, 19.26.24.27.6.24.16.27.32.29, 29.10.12.17.12.16} +{22.23.22.30, 25.19.27.2.9.20} +{5.3.29.9.22, 28.14.32.29.2.3.4, 21.14.25.20.13.31.14.20, 22.25.4.28.9.20.12.13} +{19.16.31.31.29.12, 1.30.18.31.12.25.4.19.28.12.15, 25.24.29} +{27.27.25.10.31.10.21.22.21.16.12, 7.30.5.10.10.5.30.14.9.18, 3.6.24.21.20.32.3.4.26.5} +{24.9.27.16.20.21, 12.11.20.20.29, 12.29.17.2.20.29.1.11.19.8.12} +{25.2.11.20.8.6.22, 22.24.22.25.15.23.13} +{16.14.3.17.17.26.12.19.19.30, 28.20.8.9.9.28.30.29, 14.9.15.21.21.31.1.29} +{6.26.29.10.21.28.20.19, 23.24.11.31.10.31.18.28.13.18.6, 13.17.7, 29.23.1.21.31.8, 7.27.20} +{22.17.4.2.22.17, 19.9.32.23.13.24.1} +{26.31.7, 16.21.13.1.4} +{22.23.25.28.5.27.9.9.24.31.10, 18.9.26.7, 23.14.12.30.18.4.16.18.7.7, 27.29.1.5.30.6.22.16.23.2.28, 10.29} +{30.27.8.6.11.19, 3.15.2.23.22.2.16.14} +{25.17.18.30, 18.29.5.1.10.21.2, 8.21.17.3.6.3.18, 26.31.7, 26.11} +{5.27.32.21.5.1.11.14, 27.3.3.11.21.4.25, 4.2.16.13.16.11.19.10.10.25, 32.3.23.7.2} +{3.21.6.13.12.18.25, 1.27.22.23.2.26.32.17.7.9, 9.23.21.22.5.29.15.21, 15.5.1.31.28.10.8, 27.5.15.1.15.16.21} +{30.25.17.17.10.29, 31.29.18.26.1.26.17, 2.2.18.18.3.3.18.8.10.8, 8.31.22.27, 18.9.21.2.31.8.32} +{14.6.10.29.25.26.20.24.24, 24.25.7.27.30.8.26.17, 3.4.22.19, 31.29.4.29.24.30.30.32.10.23, 14.5.13.19.25.12.32.9.13.16.12} +{8.10, 20.6.3.26.7.29.28.4, 31.29.4.29.24.30.30.32.10.23, 30.18.30.16.29} +{14.21.6.5.26.9.32.16.25, 29.20.1.11.21.16.1.2.14.28} +{12.7.16.8.21.22.2.16.18, 6.7.25.16.13.21.7.20.25.12.4, 18.29.13.24.18.3.12.18.12.12, 26.7.5.8.11.9.22.1.6, 32.30.18.17.1.14.12.18} +{21.15.18.18.30.3.20, 22.10.16.8, 26.18.32.20} +{5.27.28.26.14.15.6.20.1.31.13, 30.23.10.1.10.7.22.28.18.11.17, 5.27.28.26.14.15.6.20.1.31.13} +{9.6.9.21.6.11.29.13.29.20.32, 27.21.28.24.7.2.24.23.8, 26.25.10.10.13, 14.17.7.30.8.25.26.4} +{10.3.19, 10.16.19.7.15, 1.1, 14.21.6.5.26.9.32.16.25, 24.3.23.25} +{15.10.30.1.4.12.8.20, 22.25.4.28.9.20.12.13} +{15.9.8.20.27, 8.13.9.31.20.20.24.7.23.31.28, 15.1.8, 28.25.29.4.13.5.6} +{24.24, 20.3.1.8.8.30.20, 6.19.6.4.9.11.32.17.17.3.15, 9.31.4.14.31.10.17.5.2} +{21.9.32.1.27, 29.14.31.25.7.32.23, 25.16.9.6} +{1, 16.13.2.19.14.29.31.30.23.15.12, 26.19.3.14.8.28.31.10, 31.5.6.4.8.29.3, 1.11.10.19.6.1.26.17.2.22} +{29.3.15.17.12.29, 19.9.32.23.13.24.1, 2.32.8.28.24.20.9.24.25.8.9, 30.12.9.25.24.6.7.24.29} +{9.22.10.15.5.15, 29.30.7.31.22} +{16.8.29.7.21.2.3, 23.10.5.26.12.4.20.4, 1.12.25.26.22.8.15.23, 4.9, 14.1.15.25.27.23.25.26.28.10} +{11.17.10, 10.22.30.16.2.21.17.13, 29.27.7.7.3.11.14.26.21.11} +{11.14.21.24.10.7.29.23.24.28, 27.22.11.13.21.25.5.1.27.21.27} +{5.2.32.19.13.29.12.13.31.29, 8.6.6.5.8.8.12, 23.20.8, 5.27.21.1.29.29.28} +{13.30.24, 17.11.17.4.8.26.26.20.6, 12.1.28.22.25} +{5.14.27.15.11.17.3.10.27.25, 20.23.29.5.7.30.13.14.22, 24.13.1.8, 27.26.29, 27.18.10.4.22} +{19.16.26.2, 25.3, 10.5.5.15.29.2} +{32.6.3.2.12.5.28.1.25, 5.18.9.25.31.21.22} +{13.8.20.9.21, 24.9.15.1.14.29.6.4} +{1.15.17.6.28.25.24.31.27.9, 2.1.12.19.29.28.3.31.28.28.10, 25.16.9.6, 32.4.19} +{30.12.6.30, 30.31.13.9, 23.12.19.25.16.23.22.6.29.4, 21.18.30.19.24.24} +{26.26.22.21.14.11.29.19.14.24, 7.31.4.20.17, 14.30.13.5.26.9.22.23.14.10, 8.1.29.18.22} +{12.23.3.19.29.15.12.6, 2.4.25.32.16.22.26.13.17.18, 32.6.13.8.32, 31.18.32.11.7.25.20.5, 10.31} +{7.23.15.32.28.27.2.2.26, 8.32.30.1, 31.28.32.4.31.4.7, 2.31.25, 11.2.27.3} +{11.12.6.21, 22.28.20.6.32.32} +{21.14.22.29, 20.30.28.15.17, 4.22.17.10.19.9.8.19.28.3.9} +{31.18.25.1.14.29.25.5.22.30, 24.32.27, 8.21.8.23.4.18} +{28.15.25.7.13.6.19.2, 32.8.5, 25.17.18.17.27} +{6.25.17.32, 22.17.7.30.13.24, 3.9.25.26.7, 14.23.31.5.5.15.17.12.17.7.3, 18.15.14} +{13.12, 16.5.14.21.32.17.23.3.4.26} +{1.19.22.11.14.7.32.23.19.14, 12.1.1, 19.26.24.27.6.24.16.27.32.29, 24.15.15.17.22} +{10.27.7.24.26.11.31.20.29, 3.15.2.23.22.2.16.14, 26.32.8.12.30.19.24.8.6.1.10, 10.3.19} +{28.26.25.7, 13.28.14.2.8.18, 7.31.2.28.15.11.17.18.19.23.6} +{25.24.29, 21.18.2.1, 13.3.20} +{32.25.16, 22.19.5.22.20.31.23.24.14.24.4, 5.27.16.3.30, 12.25.32.2.27.3.3.16, 30.25.8.24.6.29.31} +{30.24.23.25.32.18.22.12.29.9.22, 11.30} +{9.22.10.15.5.15, 25.16.9.6, 23.10.13.32.14.20.16.11.14} +{22.10.27.19.29.20.29.3.12.14.25, 26.32.21.31.27.12, 3.26.32, 17.26.18} +{5.14.29.2.23.16.20.22, 11.6.11.29.4.5.24.6.26.12, 8.21.17.3.6.3.18, 26.26.22.21.14.11.29.19.14.24} +{28.8.21.15.16.28.4.16.26.8, 19.6.13.14.22.13.9.29, 11.17.10} +{25.28.30.24, 26.25.24, 25.10.29.3.6.21.3.31.13} +{32.24.29.6, 10.18.12.27.24.30.32.7.11.5.13, 20.6.26.3.30} +{19.26.24.27.6.24.16.27.32.29, 1.31.3} +{19.10.4.30.32.4.12, 20.25.22.19.22, 17.13.14.29.27.27.13.12.15, 5.1.5.31, 4.15.20.23.12.16.2.16.17} +{16.13.19.11.18.13.17.17, 17.25.26.23.32, 31.18.27.15.20.29.29} +{24.15.15.17.22, 24.2.26.24.14.15.31.23.17.26, 12.22.20.4.12, 3.13, 25.32.24.24.28.15.16.10} +{4.11.19.17.2.22.20.18.13.32.15, 14.1.11, 14.8.15.30.7.29.27.31.4} +{3.29.19.2.24, 24.23.29.8.24.11.21.10.28.14.27, 32.31.11.22.1, 31.13, 12.21.20.20} +{28.4, 14.30.13.5.26.9.22.23.14.10, 19.26.32.13.1.12.30.26.22.25} +{4.26.2.2, 9.30, 18.13.6.12.26.26.26.29.18.20.1, 22.23.22.30} +{26.11, 7.19.10.12.31.1.27.13.19, 15.17} +{12.16.13, 14.30.13.5.26.9.22.23.14.10, 21.5.17.19.15.25.18.21.24.9, 18.9.26.7} +{30.27.8.6.11.19, 22.18.20.23.15.9.12} +{31.18.32.11.7.25.20.5, 3.15.2.23.22.2.16.14} +{22.20.30, 23.28.3.30.15.31.32.3.21.9.19, 6.18.1.4.18.23, 1.30.18.31.12.25.4.19.28.12.15, 17.13.19.31.12.18.10.15.14} +{32.2.11, 22.24.22.25.15.23.13, 25.10.29.3.6.21.3.31.13, 20.32.9} +{16.8.29.7.21.2.3, 6.27.29.14.8.12.26.3.21.4.1, 21.14.22.29} +{15.8.10, 23.22.10.1.14.24, 23.5.7.12.11.23.10} +{6.29.32.13.30.3.16, 24.23.29.8.24.11.21.10.28.14.27} +{18.17.6.16.6.10, 14.19.30.6.4.10.10.10.22.25.11} +{14.17.7.30.8.25.26.4, 16.13.19.11.18.13.17.17} +{2.4.25.32.16.22.26.13.17.18, 7.16.20.17, 22.30} +{9.2.4.27.26, 15.3.31.9.27.14.9.8.14.6.32, 10.5.5.15.29.2, 16.14.3.17.17.26.12.19.19.30} +{16.9.32.14.3.7.8.7.21.22, 4.13, 31.18.27.15.20.29.29, 13.14.13.10.28.26.9.18.27.21} +{2.28.5.17.6.32, 18.27.11.27.9.16.7.6.22.26.27, 29.5.18.27.3.21.18.6.14} +{25.14.5.32.25, 30.24.32.15.14.10.11} +{27.2.10.4.25.14.2.15.4, 1.22.19.24.8.11, 4.1.24.24.28.24.18} +{5.15.16, 22.18.20.23.15.9.12, 8.11.20, 12.16.2.4.15, 21.17.27.23.15} +{8.9.25.25.26.30.31.31.2.32.7, 1.1, 23.28.20.25.30.24.15, 26.5.29.7.28, 25.17.18.30} +{32.6.8, 22.13.22.21.25.17.8, 6.29.32.13.30.3.16, 2.2.18.18.3.3.18.8.10.8, 6.7.7} +{8.13.14.11.11.29.22.4.4.10, 26.28.14, 32.24.29.6, 25.10, 15.26.24.31.16.15.17.22.8.30.3} +{14.16.6.29.26.13.14.16.25.26.8, 4.16.7.25.21.7, 13.32.15.32.26.14.32} +{25.9.1.5.9.11.25.4.11.27.32, 25.17.2.20.20.3.29.21.3.12, 8.32.30.1} +{19.15.26.19, 6.7.25.16.13.21.7.20.25.12.4, 7.5.28.8.17.26.31.10.15} +{6.19.3, 17.25.10.13.21.5.7.22.2, 11.11.9.30.15.29.15.18, 21.18, 18.9.21.2.31.8.32} +{23.27.27.16, 11.7.31.15.22, 25.17.2.20.20.3.29.21.3.12, 6.13.31.5.7.26} +{30.8.18.5.20.6.15, 21.15.18.18.30.3.20} +{2.13.9.28, 8.17.25.26.15.25} +{25.22.2.25.6, 3.20.19.10.17.27.3.6.22.23, 11.7.31.15.22} +{18.6.26.2.13.9.6.11.10.11.16, 8.10, 29.3.15.17.12.29, 19.31.14.25.5.8.21.11.13.20, 4.22} +{14.21.5.28.3.32.24.14.25.31, 28.18.6.22.13.8.25, 5.19.1.26.20.6.20, 20.30.17, 29.25.29.16.32.11.15.25.5.22.3} +{20.20.32.29.24.5.5.26.22.32, 29.23.1.21.31.8, 27.27.30.11.15.24.9.7.4.30} +{22.28.20.6.32.32, 1.15.17.6.28.25.24.31.27.9, 2.10.10.4.20.1.12.13} +{28.2.27.1.20, 8.1.29.18.22, 1, 28.26.25.7, 23.23} +{8.9.22, 1.10.21, 27.27.30.11.15.24.9.7.4.30, 24.28.13.26.8.8.31, 27.26.29} +{15.28.24, 20.4.1.16.31.3, 25.18.8.3.23.23.5.9.6} +{28.30.24.16.17.28.2.13.10, 3.32.2.29.3.32.28.11.29.30, 6.14, 30.24} +{24.1.10.20.28.18.6.27.20.30.26, 1.1, 4.3.20.27.9.1.18.30.12.5.19, 16.24.7.25, 1.4.14.32.14} +{29.25.29.16.32.11.15.25.5.22.3, 29.25.29.16.32.11.15.25.5.22.3} +{17.5.3.15.17.13.5, 5.19.1.26.20.6.20, 28.14.32.29.2.3.4, 26.18, 9.26.1.16} +{21.23.13, 15.30.17.5.32.28.2.18.27} +{11.21.16.27.16, 17.11.17.4.8.26.26.20.6, 13.28.12.6, 12.14.20.8.28.4} +{26.31.11.23.3, 23.28.3.30.15.31.32.3.21.9.19} +{22.23.18.18.9.8.23.7.23.23.16, 1.29.18.1.21.12.13.27.32.15} +{30.23.2.13.14.15.29.19.4.12.24, 19.31.14.25.5.8.21.11.13.20, 10.31, 25.17.18.30, 21.18.2.1} +{1.1.7.32.11.22, 6.25.17.32, 18.17.6.16.6.10, 6.6.22.8} +{31.21.14.20.1.22.2.5.3.27.12, 20.23.7.11.11.31.18.16.3, 1.21.28.4.23, 15.17.2.32.7} +{10.11.25.2.24.18.18.21.6.26.21, 5.10} +{4.2.2.32.24.25.31.3, 14.30.13.5.26.9.22.23.14.10, 8.21.17.3.6.3.18} +{24.10.10.31.4.29.9, 18.6.2.2.24, 24.17.24, 2.28.5.17.6.32} +{2.31.25, 22.10.27.19.29.20.29.3.12.14.25, 21.14, 27.26.29, 18.6.26.2.13.9.6.11.10.11.16} +{2.13.9.28, 8.13.1} +{11.10.22.18, 29.27, 21.28.24.23.3.11.7.12.22.32, 25.28.30.24, 4.2.6.20.7.8} +{20.1.24.3.30.31, 24.9.27.16.20.21, 25.5.30.7.16.12.21.12.11.16} +{16.23.30.12.31.31.19.14, 1.16.8.18.14.16.21.25.6, 5.12.2.20.1.24.25, 23.27.6.26.22} +{7.30.5.10.10.5.30.14.9.18, 19.30.18.11.32.14} +{9.22.10.15.5.15, 21.20.28.19.27.9, 3.21.6.13.12.18.25} +{11.22.28.8.12.23.25.15.21.28, 6.27.26.1.20.24.6} +{32.17.8.24.2.14.5.4.22, 16.13.2.19.14.29.31.30.23.15.12, 5.10.3.9.23.30.23, 16.28} +{14.9.15.21.21.31.1.29, 7.12, 28.14.32.29.2.3.4} +{3.18.8.22.7.28.32.31.3, 28.1.3} +{17.14.7.3.2.18.20.23.18.5, 27.5.15.1.15.16.21} +{13.19.2.6.23.19.9.7.21.8.16, 5.21.27.13.14.11.2.16.20, 2.9, 23.20.24} +{19.3.12.12, 5.3.29.9.22, 23.20.12.16.15.2} +{22.10.18, 32.6.9.26.16.4.4.29.7.11} +{23.3.32.21.5.14.10.17.1, 14.4.19.27.28.24.19, 14.12.31, 19.17.13.12.32.16.3} +{1.20.18.25.3.24.25.10.9, 28.14.32.29.2.3.4, 22.26.32, 22.23.22.30, 20.30.17} +{26.16.12.3.27.9.28, 9.22.10.15.5.15, 10.3} +{18.7.3.17.13.5.31.6.31.25.29, 12.24.29.32.32.29.2} +{13.28.12.6, 9.6.9.21.6.11.29.13.29.20.32} +{3.3, 16.13.2.19.14.29.31.30.23.15.12, 6.18.1.4.18.23, 8.14.19.18, 14.8.15.30.7.29.27.31.4} +{27.1.11.3.25.9.6.6, 19.26.24.27.6.24.16.27.32.29, 24.24, 26.32.21.31.27.12, 24.9} +{9.10.19.18.15.11.22.32.32.14.9, 7.10.17.21.11.29.17.25.19.4.29, 19.11.10.18.14.13.7.7, 8.16.6} +{4.26.2.2, 13.28.12.6} +{5.9.19.6, 29.26.25.14.24.18.2.13.23.29, 18.27.11.27.9.16.7.6.22.26.27} +{32.3.23.7.2, 21.14.22.29, 22.32.6.6.3.8.24.6.25.29} +{30.31.13.9, 5.13.23.19.28.26.27.6.1.22, 21.18, 8.13.9.31.20.20.24.7.23.31.28} +{11.10.22.18, 27.6.13.24.21.27.28.22.3.7.4} +{23.3.20.24, 27.24.11.31.21.6.29.17.24.18} +{22.10.18, 8.27.3.4.12.26.16, 3.10.27.4.5.6.19.12.28.12} +{6.13.31.5.7.26, 16.13.2.19.14.29.31.30.23.15.12, 29.23.1.21.31.8} +{22.12.22.28, 26.18.32.20, 1.10.23.25.5.11, 3.11.18.21.5.20.30, 18.31.26.18.6.15.18.11} +{7.7.22.24.17.32.17.25.28, 30.23.10.1.10.7.22.28.18.11.17, 14.19.30.6.4.10.10.10.22.25.11, 8.17.9.15.21.28.1.7.1.3.6} +{25.10.29.3.6.21.3.31.13, 9.3.31.18.12.3.9.29.10, 5.27.32.21.5.1.11.14} +{8.5.30.29.9.31, 6.2.32, 18.27.11.27.9.16.7.6.22.26.27, 22.29.29.11} +{32.16, 18.19.11.20.13.13.11, 20.25.22.19.22} +{1.27.22.23.2.26.32.17.7.9, 1.14.3.7.3.17.2.29, 13.24, 13.30.24, 31.18.27.15.20.29.29} +{19.26.32.13.1.12.30.26.22.25, 1.3.15.11.11.25.24.21.19, 20.32.5.1.3.20.3.30.27, 23.12.32.22.19.1.22.4, 12.4.24.6.1.13.5.20} +{4.26.5.26.21.28.17.24.25.23, 30.8.9.14.25.30, 31.18, 1.14.3.7.3.17.2.29, 11.11.9.30.15.29.15.18} +{17.14.7.3.2.18.20.23.18.5, 29.3.15.17.12.29, 17.1.12.20, 14.14.25, 6.17.26.25.27.11.10.9} +{9.9.13.9.14.27, 29.3.17.17.18.32} +{32.1.23.20.14.12.23.5.32.15, 23.1.23.18.12.29, 1.1.1, 6.7.7} +{29.5.18.27.3.21.18.6.14, 15.7.5.12.7.9.3.28.26, 11.17.10, 19.26.32.13.1.12.30.26.22.25} +{5.13.23.19.28.26.27.6.1.22, 21.10.20.9.3.16.9.10.20, 1.21.28.4.23, 22.19.5.22.20.31.23.24.14.24.4} +{2.22.19, 3.29.32.26.8.10.25, 25.11.24, 10.3} +{16.14.3.17.17.26.12.19.19.30, 16.13.2.19.14.29.31.30.23.15.12} +{10.31, 18.19.11.20.13.13.11} +{19.16.31.31.29.12, 30.2.17.8.14, 23.14.30.27.28.26.26.23.8.32, 19.3.12.12} +{8.16.6, 26.14.5.32.10, 24.31.2.13.5.23.18.16, 24.27.18.32.14.9.11.28.9, 3.29.19.2.24} +{24.15.15.17.22, 4.14} +{29.20.1.11.21.16.1.2.14.28, 14.27.29.23.4.1.17.32.6.25.22, 3.27.18.8.4.21.6.32.30.7.5, 4.26.23.6.19.31.10.4.22} +{22.13.22.8.30.32.10.24, 26.28.14, 12.4.26.23.25.5.15.7.16} +{21.14.25.20.13.31.14.20, 5.4.8.25.12.27.2.29.28.3, 23.24.11.31.10.31.18.28.13.18.6, 6.18.1.4.18.23, 19.17.12.15} +{10.12.23.22.23.22.20.17.17.9, 17.14.7.3.2.18.20.23.18.5, 3.10.4.5.28.11, 12.2.4.28.21.30.24} +{12.4.24.6.1.13.5.20, 24.25.7.27.30.8.26.17} +{20.4.1.16.31.3, 22.18.20.23.15.9.12, 25.10.4.28.3.31.19, 2.28.5.17.6.32} +{17.3, 31.7.14.2} +{19.3.23.4.4.21.23, 32.28.1.32.28.10, 25.4.32} +{31.28.32.4.31.4.7, 23.8.13.22.21, 5.21.27.13.14.11.2.16.20} +{21.17.18.32.7.8, 20.32.9, 19.26.32.13.1.12.30.26.22.25, 16.14.3.17.17.26.12.19.19.30, 8.31.22.27} +{27.4.15.14.19.6.12, 30.30.17.5.30.21.19.5.22.22.14, 26.17.9.13.4.25.32.2.24.9, 8.10, 12.24.29.32.32.29.2} +{5.21.27.13.14.11.2.16.20, 5.31.8.1.5.13.21.28.29.19.2, 25.2.3.15.11.19.5.28.25.14} +{8.5.24.9.29.32.31.30.13.9.7, 1.29.18.1.21.12.13.27.32.15, 32.15.20.28.5.1.23.4, 32.6.31.31} +{6.29.32.13.30.3.16, 24.17.24} +{5.10.2.11.21.9.19, 7.31, 10.20, 29.9.25.27.15.16.32.26.6.32} +{12.13.5.31, 7.19.12.3.21.19.18.5.2.14.10} +{20.9.29.32.13.7.23, 23.24.16.32.13.29, 9.7.31.11.8.23, 26.5.29.7.28, 1.1.1} +{9.6.9.21.6.11.29.13.29.20.32, 1.20.22.26.2.6.11} +{7.7.22.24.17.32.17.25.28, 13.7, 1.20.18.25.3.24.25.10.9, 9.5} +{9.8.23.2.20.16, 30.17.25.3.31.11.3.4.1.10, 6.19.29.11.2.32.21.15.32.9, 18.29.5.1.10.21.2} +{25.6.12.16.1, 11.10, 22.17.30, 1.28.19.8.25.6.20.27.29.27, 9.3.3} +{9.3.3, 15.7.3.14.23.19.26} +{27.21.28.24.7.2.24.23.8, 12.4.12.13.25.30.30.8.9.12, 9.18.23, 19.12.26.24.29.3, 26.14.5.32.10} +{25.24.2.32.14.18.16, 23.27.6.26.22, 32.30.18.17.1.14.12.18, 3.18.18} +{28.30.24.16.17.28.2.13.10, 11.17.10, 16.30.10.7.29.4.9.21.22.13.26} +{3.26, 22.17.24.14.21.15.12.18.17.25.11, 10.32.14, 23.12.19.25.16.23.22.6.29.4} +{14.24, 2.19.4.1.15.7.8.9.17.29, 13.24, 18.13.9.3.18.15.2, 10.28.7.16.31} +{26.32.8.12.30.19.24.8.6.1.10, 23.28.20.25.30.24.15, 6.11.31.23.12.8.30.14.27, 3.9.11.23.32.26.24.28, 6.11.11.5.16.8.14.12.9} +{8.3.3.25.25.15.7.13.21.18, 27.32.26.21.31.17.32.32, 13.3.8} +{15.7.3.14.23.19.26, 26.26.22.21.14.11.29.19.14.24, 8.3.18.13.30.20.27.26.17.28, 4.11.22.4.19.24.4.28.6.8.22} +{25.32.24.24.28.15.16.10, 12.21.20.20, 20.8.19.14.16.7, 18.13.6.12.26.26.26.29.18.20.1} +{8.1.29.18.22, 31.18.32.11.7.25.20.5, 5.15.10.3.23.13.32.23, 26.31.7} +{22.10.18, 3.21.16.24.23.12.16.32.3, 24.16.27.10.9} +{8.27.3.4.12.26.16, 9.14.27.31.26.21.25.3.20, 27.18} +{4.21.9.1.2.14.8.17.13.26, 17.11.17.4.8.26.26.20.6, 30.25.24.22} +{9.7.31.11.8.23, 15.5.1.31.28.10.8, 19.15.26.19, 20.22.10, 8.2.18.23.5.16.17.1} +{15.5.1.31.28.10.8, 12.11.20.20.29} +{32.25.16, 29.20.1.11.21.16.1.2.14.28, 3.29.19.2.24, 5.5.12.31.23.13.17.22.20} +{23.5.5.17, 23.12.11.11.15.16.22.31.32.5.8} +{26.24.9.12.11.15.31.2, 18.13.9.3.18.15.2, 28.4, 27.30.12.11.20.15.11.13, 8.3.3.25.25.15.7.13.21.18} +{16.5.23.17, 13.25.10.25.8.16, 1.16.8.18.14.16.21.25.6, 5.14.29.2.23.16.20.22, 8.13.14.11.11.29.22.4.4.10} +{9.8.23.2.20.16, 16.24.7.25, 3.9.11.23.32.26.24.28, 30.17.2.25} +{32.1.31, 17.17.14.28.6.30} +{3.3, 31.5.6.4.8.29.3, 28.26.25.7, 1.26.15.23.5.31.29.11.19.28.1} +{24.9.27.16.20.21, 28.2.27.1.20, 32.17.8.24.2.14.5.4.22} +{12.16.2.4.15, 16.13.19.11.18.13.17.17, 25.2.3.15.11.19.5.28.25.14} +{8.22.32.17.16.28.31.23.22.9, 18.31.32.28.1.4.24.24.12.25, 23.10.5.26.12.4.20.4, 28.17.26.9} +{24.2.6.7.16.7.28, 10.16.19.7.15, 3.14.1.14.17.28.29.16, 12.17.10.7.17.16} +{19.31.14.25.5.8.21.11.13.20, 21.23.13, 2.30.26.10.14.31.18.2} +{21.4.11.18, 13.7, 30.8.18.5.20.6.15} +{28.15.18.27, 11.10, 7.13} +{11.29, 29.9.25.27.15.16.32.26.6.32, 7.23.1.24.29.13.31.19.23.17.7, 7.30.19.25.23.15.14.29, 23.12.32.22.19.1.22.4} +{22.11, 21.32.13.21} +{22.31.2.32.32.11.26.23.19, 1.20.22.26.2.6.11, 32.24.11.8.12.23.22.19.11.17.18, 3.10, 18.21} +{12.7.16.8.21.22.2.16.18, 1.30.18.31.12.25.4.19.28.12.15} +{19.26.32.13.1.12.30.26.22.25, 29.3.17.17.18.32, 12.1.28.22.25, 18.4, 14.8.15.30.7.29.27.31.4} +{11.17.10, 32.17.8.24.2.14.5.4.22, 22.29.18.32.13.12.22.31.17.22} +{2.15.18.21.5.21.4.7.30, 4.14.32} +{15.17.2.32.7, 25.24.2.32.14.18.16, 28.9.3.16.17.21.23.30} +{3.5, 1.10.5.22.13, 4.15.20.23.12.16.2.16.17} +{24.9.8.12.29, 27.5.22, 15.3.31.9.27.14.9.8.14.6.32, 10.32.14, 23.24.11.31.10.31.18.28.13.18.6} +{10.22.30.16.2.21.17.13, 32.1.24.29.22.5.9.24.18.3.13, 32.31.26.19.13.29.4.25, 27.3.3.11.21.4.25} +{21.6.22.28.12.23.11.22, 22.11} +{8.5.24.9.29.32.31.30.13.9.7, 19.20.25.7.27.28.27.17.9.3.1} +{9.7.31.11.8.23, 7.13} +{12.23.3.19.29.15.12.6, 7.12.23, 30.12.6.30, 19.16.26.2} +{15.11.26.1.30.6.23.5, 31.21.22.14.8.21} +{2.32.8.28.24.20.9.24.25.8.9, 22.23.18.18.9.8.23.7.23.23.16, 26.12.27.2, 22.23.25.28.5.27.9.9.24.31.10} +{2.24.5.3.4.10.27.26.17.28.16, 4.19.16.15.5.2.25.8.28.14.2, 32.31.26.19.13.29.4.25, 18.31.32.28.1.4.24.24.12.25, 30.24.23.25.32.18.22.12.29.9.22} +{21.28.24.23.3.11.7.12.22.32, 16.8.29.7.21.2.3} +{23.12.19.25.16.23.22.6.29.4, 19.6.24.32.30.13.6.25.8.28, 24.1.29.32.14.15.32.6.15.22, 23.27.27.16, 28.5.12.9.2.27.11.11.2} +{24.9.27.16.20.21, 25.17.18.30, 15.28.24, 24.20.23, 17.29.21.10.18.8.16.26.18.21.26} +{1.4.14.32.14, 17.17.14.28.6.30} +{4.11.22.4.19.24.4.28.6.8.22, 6.19.6.4.9.11.32.17.17.3.15, 15.8.3.15.27.14.29.28.6.5.25, 28.14.32.29.2.3.4, 21.18} +{18.30.18.31, 23.27.6.26.22, 12.4.26.23.25.5.15.7.16, 25.4.4.1.13.32.26.20.20.3, 6.1.8.6.30.29.30} +{20.30.9.9.14.12.29, 29.28.9.15.8.27.31} +{27.23.20.30.7, 18.24.21.17.11.26.28.22.21.18.10, 8.21.17.3.6.3.18, 4.13, 22.19.20.5.2.20} +{27.12.4.2.29.22.15, 4.13} +{32.6.13.8.32, 23.12.1.5.32.25.8.24.1.25, 1.8} +{18.9.21.2.31.8.32, 10.22.30.16.2.21.17.13} +{1.21.28.4.23, 4.14.10.19.16, 1.29.18.1.21.12.13.27.32.15, 26.14} +{10.29.26.4.27.17.11, 10.28.22.29.13.19.6.7.6.14} +{25.28.3, 24.17.24, 2.13.9.28, 4.22.17.10.19.9.8.19.28.3.9} +{3.25, 14.4.23.4.23.22.11.6.26.5, 19.3.12.12, 15.7.3.14.23.19.26, 28.14.32.29.2.3.4} +{25.15.11, 5.20} +{15.28.30.19.31.6.2.2.31, 5.10, 32.1.23.20.14.12.23.5.32.15, 31.30.12.20} +{13.26.17.3.2.19, 6.7.25.16.13.21.7.20.25.12.4, 3.11.32.11.22.3.7.17.8.13.23, 30.20.3.2.5.15.8.7.17, 25.11.24} +{1.27.22.23.2.26.32.17.7.9, 8.6.6.5.8.8.12, 9.16.2.16.22.24.17.31.14.21.17, 17.13.8} +{6.20, 19.16.26.2} +{27.29.1.5.30.6.22.16.23.2.28, 8.5.30.29.9.31, 31.24.26.18} +{18.18.5.11.7.4.25, 17.29.21.10.18.8.16.26.18.21.26} +{25.17.2.20.20.3.29.21.3.12, 7.21.8} +{8.25.20.3.15.24.7.4.24.5.30, 31.17, 2.31.25} +{26.9.17.1.18.19.1.11.18.29.3, 4.3.20.27.9.1.18.30.12.5.19, 29.27.13.9.28.29.19.13.29.31.27} +{15.1.8, 9.28.10.26.14.26.15.14} +{9.10.32, 21.28.17.22.10.27.4.20.2.32} +{25.15.11, 29.9.25.27.15.16.32.26.6.32, 20.8.19.14.16.7, 24.32.17.23.24.19.23.9.20.18, 9.7.31.11.8.23} +{16.5, 14.23.31.5.5.15.17.12.17.7.3, 5.24.25.15.27.30.20, 1.22.29.5.16, 28.25.29.4.13.5.6} +{8.16, 32.6.15.26.14.15.3.19, 8.26.29.13.7.25.31.28.3.32, 12.21.15.27.24.15.8.24.24.26} +{10.11.25.2.24.18.18.21.6.26.21, 19.31.14.25.5.8.21.11.13.20} +{4.14, 1.18.29.30.22.14.3.20.15.21.20, 20.17.14.7, 1.26, 22.17.9.11.25.15.3.9} +{7.16.20.17, 27.11.15.9.24.31.18.4.1.30.20, 29.10.17.11.28.12.18.5.19.15.21} +{16.5, 8.5.30.29.9.31, 12.4.24.6.1.13.5.20, 25.11.24} +{22.28.20.6.32.32, 24.31.2.13.5.23.18.16, 1.30.18.31.12.25.4.19.28.12.15, 23.17.22.1.23.4.29.32.4.1} +{26.18.32.20, 27.17.3.18.2.13.18, 1.26.15.23.5.31.29.11.19.28.1, 26.12.27.2, 12.18} +{14.16.6.29.26.13.14.16.25.26.8, 30.25.24.22, 25.18.8.3.23.23.5.9.6, 10.29.26.4.27.17.11} +{3.25, 10.3.19, 4.14.32, 30.16.3.21.10} +{24.32.27, 3.1.14.8.9.16.30.22.20} +{1.18.29.30.22.14.3.20.15.21.20, 8.3.18.13.30.20.27.26.17.28, 17.25.10.13.21.5.7.22.2} +{31.24.26.18, 9.30} +{1.29.18.1.21.12.13.27.32.15, 5.14.27.15.11.17.3.10.27.25, 16.5, 28.14.24.26.6.15.16.32.25.13.8, 15.7.3.14.23.19.26} +{5.24.24.9.32.26.31, 4.21.28.5.16.29.5.21, 26.24, 1.19.22.11.14.7.32.23.19.14, 11.17.17.24.11.23.17.17.18.10.22} +{24.9.15.1.14.29.6.4, 21.23.17.8.23.11.8.1, 18.6.2.2.24, 25.32.24.24.28.15.16.10, 8.10} +{3.14.11.15.21.32.2.15.13, 4.1.24.24.28.24.18} +{6.29.6.13.14.24.10.4.14.28, 19.12.30.2.21, 26.24.9.12.11.15.31.2} +{4.13, 6.9.29.17.4.32, 1.22.29.5.16, 28.15.25.7.13.6.19.2} +{16.20.29.26, 11.22.28.8.12.23.25.15.21.28, 7.5.28.8.17.26.31.10.15, 27.17.17.19.24.9.14.20} +{20.17.18.21.1, 14.15.31.29, 6.19.3} +{5.24.4.31.3.16.25.17.13.26.11, 23.24.16.32.13.29, 23.24.16.32.13.29, 23.23, 24.28.13.26.8.8.31} +{1.27.22.23.2.26.32.17.7.9, 25.16.9.6, 11.6.11.29.4.5.24.6.26.12, 23.24.11.31.10.31.18.28.13.18.6} +{31.17.2.30.11, 7.7, 9.30, 7.31, 9.31.23.19.5.10.16.4.30.24.5} +{23.5.5.17, 15.31.11.27.19.19.20.5.5, 15.9.11.20.22.15.11.13} +{9.22.10.15.5.15, 4.15.20.23.12.16.2.16.17, 16.14.3.17.17.26.12.19.19.30, 9.3.3} +{18.31.26.18.6.15.18.11, 1.26} +{16.13.19.11.18.13.17.17, 18.7.10.27.17.24, 14.14.25, 31.18.32.11.7.25.20.5, 6.17.10.10.7.9.27.8.29} +{22.29.18.32.13.12.22.31.17.22, 16.24.7.25} +{14.24, 12.3, 31.4.7, 14.6.10.29.25.26.20.24.24, 21.6.22.28.12.23.11.22} +{12.4.10.17.4.10.23.3, 21.17.18.32.7.8, 4.16.22.19.24.21, 27.23.2.32.11.21, 2.1.3.30.24.17.9} +{10.15.16.3, 3.18.18, 30.27.8.6.11.19, 1.1.1.2} +{15.7.3.14.23.19.26, 24.27.14} +{4.22.17.10.19.9.8.19.28.3.9, 30.27.8.6.11.19, 18.13.9.3.18.15.2} +{31.18.25.1.14.29.25.5.22.30, 8.26.29.13.7.25.31.28.3.32} +{4.2.6.20.7.8, 7.30.5.10.10.5.30.14.9.18, 20.30.9.9.14.12.29, 18.13.9.3.18.15.2, 24.31.2.13.5.23.18.16} +{31.30.23.7.7.24.32.10.11.1.31, 2.32.10.13.12, 13.8.15.3.7.31.5.10.15.30, 13.16.4.28, 16.5.12.5.15.12.24.25.3} +{28.23.2.30.3.8.1.15.15.14.13, 7.11, 31.17, 26.14, 28.4} +{16.13.19.11.18.13.17.17, 29.5.32.20.11.7.13.24.17, 4.21.28.5.16.29.5.21} +{11.16.16.28.14, 25.16.9.6, 5.10.2.11.21.9.19, 32.16, 16.13.19.11.18.13.17.17} +{1.26, 26.13.4.7.13.11.3} +{12.11.20.20.29, 21.32.13.21, 12.3} +{20.6.3.26.7.29.28.4, 1.1.3, 14.21.5.28.3.32.24.14.25.31} +{3.10, 22.10.16.8, 28.1.3} +{15.1.8, 29.27.7.7.3.11.14.26.21.11, 7.31.2.28.15.11.17.18.19.23.6, 12.17.10.7.17.16} +{8.29.6.3, 8.14.19.18, 6.10.25.12, 1.1.2.1} +{31.13.9.1.5.12, 27.22.11.13.21.25.5.1.27.21.27} +{20.6.3.26.7.29.28.4, 6.13.31.5.7.26} +{29.6.12.31.20.23.32.20, 17.14.7.3.2.18.20.23.18.5, 20.8.19.14.16.7, 22.17.7.30.13.24} +{32.3.23.7.2, 3.26, 17.8, 8.13.1, 17.13.8} +{27.18, 29.32.13.4.1.16.20} +{14.30.23.3, 2.24.5.3.4.10.27.26.17.28.16, 6.29.32.13.30.3.16} +{22.17.9.11.25.15.3.9, 17.11.17.4.8.26.26.20.6, 4.2.2.32.24.25.31.3, 8.25.20.3.15.24.7.4.24.5.30, 4.3.6.27.22.23.10} +{27.5.15.1.15.16.21, 25.9.1.5.9.11.25.4.11.27.32, 21.7.7.11} +{2.32.8.28.24.20.9.24.25.8.9, 30.15, 13.28.12.6, 29.27} +{22.19.5.22.20.31.23.24.14.24.4, 8.9.25.25.26.30.31.31.2.32.7, 16.5.10.2.18.8.15.12.32.25.10, 9.9.13.9.14.27, 11.1.3.28.30.21.24.14} +{7.11, 28.1.3, 4.2.2.32.24.25.31.3} +{5.27.21.1.29.29.28, 30.23.10.1.10.7.22.28.18.11.17, 17.3, 1.12.25.26.22.8.15.23} +{4.15.20.23.12.16.2.16.17, 32.16, 29.5.18.27.3.21.18.6.14, 9.31.4.14.31.10.17.5.2, 17.8} +{9.30, 4.30.8.20.19.9.30.24.11, 13.3.8} +{23.5.7.12.11.23.10, 10.29} +{20.32.9, 20.20.7, 19.7.29.31.3.20.7.21.25.27.29, 24.1.10.20.28.18.6.27.20.30.26} +{22.23.22.30, 19.17.13.12.32.16.3, 6.1.8.6.30.29.30, 1} +{30.25, 8.2.18.23.5.16.17.1, 28.30.24.16.17.28.2.13.10} +{32.1.24.29.22.5.9.24.18.3.13, 13.14.13.10.28.26.9.18.27.21, 25.21.8.17, 27.27.25.10.31.10.21.22.21.16.12} +{6.9.29.17.4.32, 7.32.10.3.30.12.14, 25.22.2.25.6, 25.9, 17.11.17.4.8.26.26.20.6} +{21.7.7.11, 4.2.2.32.24.25.31.3, 12.4.12.13.25.30.30.8.9.12, 21.23.13, 19.16.31.31.29.12} +{1.8, 31.13, 19.6.24.32.30.13.6.25.8.28} +{26.16.12.3.27.9.28, 24.2.6.7.16.7.28, 19.6.24.32.30.13.6.25.8.28} +{27.17.17.19.24.9.14.20, 4.14.32} +{16.9.32.14.3.7.8.7.21.22, 14.21.6.5.26.9.32.16.25, 25.32.24.24.28.15.16.10, 19.22.29.32.1.21.26.24.23.17, 12.27.23.32.1.1.9.29.13} +{1.1.1.1, 14.21.5.28.3.32.24.14.25.31, 30.23.2.13.14.15.29.19.4.12.24} +{5.24.4.31.3.16.25.17.13.26.11, 4.16.7.25.21.7, 17.5.3.15.17.13.5} +{20.20.7, 1.12.25.26.22.8.15.23, 5.14.29.2.23.16.20.22, 4.19.16.15.5.2.25.8.28.14.2, 11.30} +{28.23.2.30.3.8.1.15.15.14.13, 5.12.2.20.1.24.25, 28.14.24.26.6.15.16.32.25.13.8} +{8.9.21.16.29, 23.3.32.21.5.14.10.17.1, 24.1.29.32.14.15.32.6.15.22, 32.16, 3.18.18} +{19.2.9.29.6, 31.30.12.20, 31.21.14.20.1.22.2.5.3.27.12, 1.30.31.31.20.16.7, 24.16.27.10.9} +{7.19.10.12.31.1.27.13.19, 25.10.29.3.6.21.3.31.13, 20.13} +{23.24.16.32.13.29, 12.22.20.4.12, 25.28.3, 18.19.11.20.13.13.11, 32.27.18.7.3.4.2} +{27.25, 29.23.15.25.1.6.6.10, 6.21.30.7, 32.6.8, 2.27.15.14} +{5.21.27.13.14.11.2.16.20, 12.21.15.27.24.15.8.24.24.26, 25.7.3.21.31.12.28} +{27.25, 18.19.11.20.13.13.11, 27.6.13.24.21.27.28.22.3.7.4, 1.27.22.23.2.26.32.17.7.9} +{24.12, 28.15.25.7.13.6.19.2} +{17.3, 13.32.15.32.26.14.32, 22.17.9.11.25.15.3.9, 3.27.18.8.4.21.6.32.30.7.5, 3.18.8.22.7.28.32.31.3} +{6.8.7.20.2, 16.13.26.18.9.29.11.17.1.24.26, 31.13.9.1.5.12, 22.17.9.11.25.15.3.9} +{13.9.9.27.31.11.25.9.27.22.13, 32.15.20.28.5.1.23.4, 15.23.26.20.27.7} +{23.17.28.31.28, 14.1.11, 4.22.7.19.25, 31.5.6.4.8.29.3} +{10.12.9.6.6.26.14.8.23.1.25, 19.11.10.18.14.13.7.7} +{3.4.22.19, 26.24, 14.4.23.4.23.22.11.6.26.5} +{2.1.12.19.29.28.3.31.28.28.10, 15.29.25, 8.16.30.29.19.22.28.24.2, 23.27.27.16} +{27.4, 23.10.5.26.12.4.20.4, 25.7.3.21.31.12.28, 14.19.30.6.4.10.10.10.22.25.11} +{25.10.4.28.3.31.19, 31.30.12.20, 27.3.3.11.21.4.25, 6.17.26.25.27.11.10.9, 29.29.18} +{8.9.22, 21.23.17.8.23.11.8.1, 24.1.10.20.28.18.6.27.20.30.26, 31.18.32.11.7.25.20.5, 22.31.2.32.32.11.26.23.19} +{27.5.15.1.15.16.21, 16.2.14.3.26.11, 9.6.9.21.6.11.29.13.29.20.32} +{21.1.4.9.9.31.24.21.3.29, 18.19.12.20.18.17.15.32.18.5} +{29.23.1.21.31.8, 21.18, 8.5.24.9.29.32.31.30.13.9.7, 16.28, 21.5.11.18} +{19.16.31.31.29.12, 2.15.14.20.30.26, 26.19.3.14.8.28.31.10, 5.24.25.15.27.30.20, 24.31} +{26.11, 5.19.1.26.20.6.20, 23.22.10.1.14.24, 11.17.17.24.11.23.17.17.18.10.22} +{28.6.8.22.25, 22.19.5.22.20.31.23.24.14.24.4} +{14.10.11.30.5.7.6.24.9.30.26, 3.18, 4.21.9.1.2.14.8.17.13.26, 3.29.19.2.24, 24.9.15.1.14.29.6.4} +{10.11.25.2.24.18.18.21.6.26.21, 9.28.24, 23.28.3.30.15.31.32.3.21.9.19, 18.29.13.24.18.3.12.18.12.12} +{25.24.2.32.14.18.16, 21.32.13.22.3.13.31.23.14.12.9} +{11.10.22.18, 4.2.16.13.16.11.19.10.10.25, 7.23.15.32.28.27.2.2.26, 6.10.25.12} +{12.23.3.19.29.15.12.6, 7.31} +{13.3.20, 32.8.29.18.31, 30.3.16.26.7.27.26.9.27.21.18, 8.9.21.16.29, 22.30.31.24.23.22.5.20.28.1} +{23.22.23.14.31.32, 3.5} +{7.19.10.12.31.1.27.13.19, 20.17.18.21.1} +{12.2.4.28.21.30.24, 23.25.23.11.7.23, 16.27.8.17.14.17.21.29.14, 7.19.12.3.21.19.18.5.2.14.10, 27.30.12.11.20.15.11.13} +{16.29.6.23.13.28.31.6.19.26.15, 20.30.9.9.14.12.29, 15.4.15, 9.6.9.21.6.11.29.13.29.20.32} +{16.19.17.30.30.5.17.24.27, 2.8.13.12.17.23.16.7.11.23, 6.17.26.25.27.11.10.9, 23.28.1, 21.21.10.27} +{6.27.26.1.20.24.6, 7.11, 9.14.27.31.26.21.25.3.20, 24.23.24.4.15.25.17, 21.8.9} +{28.6.11.6.15.22.12.6, 15.29.25, 12.15.10.17.18.13, 9.7.31.11.8.23} +{12.14.20.8.28.4, 20.4.27.31.1, 8.13.6.12.18.7, 29.10.12.17.12.16} +{29.5.18.27.3.21.18.6.14, 23.20.24, 17.7.26.30.18.23.4, 1.1.1.2.1} +{3.14.1.14.17.28.29.16, 1.13.16.27.11.16.30.2.9.18.4} +{22.23.22.30, 13.9.9.27.31.11.25.9.27.22.13, 19.2.26.21.16.11.2.2, 16.5.23.17} +{30.24.23.25.32.18.22.12.29.9.22, 22.19.21.11.6.8.29.24} +{25.30.1.4.24.11, 16.2.14.3.26.11, 12.6.14.23.19.21.9.12, 17.24.30.6.32} +{19.26.24.27.6.24.16.27.32.29, 13.32.15.32.26.14.32, 8.2, 12.15.10.17.18.13, 13.16.1.27.18.18.19.6.14.4} +{16.29.6.23.13.28.31.6.19.26.15, 21.32.13.21, 30.32, 18.6.26.2.13.9.6.11.10.11.16} +{14.5.13.19.25.12.32.9.13.16.12, 22.23.25.28.5.27.9.9.24.31.10, 26.14, 23.19.17.31.29.13.1.12.5.25, 18.6.26.2.13.9.6.11.10.11.16} +{21.15.31.24.29.24.26.12.20, 15.1.6.31.30.13.32.9.10, 9.9.13.9.14.27, 19.10.26.19.5.21.30.23, 2.9} +{28.6, 24.11.5, 18.18.19.16.14.16.21.10.25} +{5.3.29.9.22, 27.3.3.11.21.4.25} +{20.22.10, 25.15.11} +{5.15.10.3.23.13.32.23, 11.12.6.21} +{1.16.8.18.14.16.21.25.6, 2.4.25.32.16.22.26.13.17.18} +{10.7.9, 2.32.10.13.12, 17.13.19.31.12.18.10.15.14, 4.3.20.27.9.1.18.30.12.5.19, 3.26} +{17.1.12.20, 18.31.32.29.22.1.31.11.28} +{25.17.2.20.20.3.29.21.3.12, 29.28.9.15.8.27.31, 15.3.31.9.27.14.9.8.14.6.32, 1.22.29.5.16} +{28.26.25.7, 23.5.5.17, 7.19.10.12.31.1.27.13.19} +{8.16.6, 25.2.11.20.8.6.22, 23.12.11.11.15.16.22.31.32.5.8, 26.31.6.8.29.8.24, 15.1.8} +{3.26.32, 28.6.8.22.25, 28.31.10.28.22.26.16.15, 5.15.16, 8.5.30.29.9.31} +{30.12.9.25.24.6.7.24.29, 20.24.14.15.4.21.12.27.4.12} +{24.28.13.26.8.8.31, 23.31.27.16.8.30.20.27, 32.3.12.2, 32.6.8} +{26.9.20.12.22.22.32, 9.10.32, 13.32.15.32.26.14.32} +{30.18.30.16.29, 1.26} +{26.32.21.31.27.12, 27.17.17.19.24.9.14.20, 11.6.11.29.4.5.24.6.26.12, 12.24.29.32.32.29.2, 29.10.17.11.28.12.18.5.19.15.21} +{14.5.13.19.25.12.32.9.13.16.12, 3.21.16.24.23.12.16.32.3, 28.17.26.9, 9.31.4.14.31.10.17.5.2} +{23.20.12.16.15.2, 14.19.26.15.22.23} +{18.15.14, 7.26.18} +{28.15.18.27, 13.8.23.13.11.18.24.21.11.24.10, 8.14.19.18, 8.32.30.1, 15.25.31.11.4.22.16.7.11} +{24.3.23.25, 12.6.14.23.19.21.9.12, 27.11.14.17.24, 23.19.17.31.29.13.1.12.5.25, 6.10.25.12} +{15.17.2.32.7, 12.22.20.4.12, 30.32, 28.11.11.30.20.11.32} +{17.27, 14.12.31, 30.23.10.1.10.7.22.28.18.11.17} +{30.15, 22.24.22.25.15.23.13, 9.3.31.18.12.3.9.29.10, 20.4.27.31.1} +{24.28.32.21, 25.16.9.6, 24.9.27.16.20.21, 5.15.16} +{22.26.32, 16.14.3.17.17.26.12.19.19.30, 16.5} +{29.1.7.26.25.11.22, 6.17.10.10.7.9.27.8.29, 30.12.6.30, 4.26.5.26.21.28.17.24.25.23} +{10.29.26.4.27.17.11, 29.11.20.22.27} +{18.9.21.2.31.8.32, 24.17.31.20.12.9.19.29.18, 19.19.25.22.11.6.15.3.2.19, 30.8.18.5.20.6.15} +{27.21.27.5.13.30.17, 5.18.9.25.31.21.22} +{11.32.18.31, 6.14, 1.13.16.27.11.16.30.2.9.18.4, 31.9.3.5} +{29.5.18.27.3.21.18.6.14, 23.24.16.32.13.29, 29.30.7.31.22, 12.27.30.12.24.2.20, 7.30.5.10.10.5.30.14.9.18} +{26.5.29.7.28, 2.31.25, 12.16.2.4.15, 4.11.22.4.19.24.4.28.6.8.22} +{8.16.6, 12.29.26.18.4.21.28.8.13.3, 22.10.16.8, 27.2.10.4.25.14.2.15.4, 30.32} +{12.29.17.2.20.29.1.11.19.8.12, 31.9.3.5, 24.9.15.1.14.29.6.4} +{14.21.6.5.26.9.32.16.25, 9.8.23.2.20.16, 29.10.12.17.12.16, 4.2.2.32.24.25.31.3, 32.15.20.28.5.1.23.4} +{8.16, 30.23.2.13.14.15.29.19.4.12.24, 4.26.23.6.19.31.10.4.22, 10.11.25.2.24.18.18.21.6.26.21} +{14.4.19.27.28.24.19, 9.3.3} +{24.3.23.25, 21.7.7.11, 14.14.25} +{1.22.29.5.16, 29.28.9.15.8.27.31, 22.11} +{23.28.3.30.15.31.32.3.21.9.19, 12.14.20.8.28.4, 2.30.26.10.14.31.18.2, 24.9} +{10.12.23.22.23.22.20.17.17.9, 7.5.28.8.17.26.31.10.15, 29.15.29.8.31.26.1, 9.5.9.3.23.9.25.14.1.29.28} +{4.21.28.5.16.29.5.21, 1.30.18.31.12.25.4.19.28.12.15} +{3.25, 7.30.5.10.10.5.30.14.9.18, 16.20.29.26} +{9.22.10.15.5.15, 24.25.7.27.30.8.26.17, 14.10.11.30.5.7.6.24.9.30.26, 2.15.18.21.5.21.4.7.30, 15.1.6.31.30.13.32.9.10} +{14.19.20.13.27.2.2, 1.3.15.11.11.25.24.21.19, 7.30.5.10.10.5.30.14.9.18, 23.24.11.31.10.31.18.28.13.18.6} +{26.24.9.12.11.15.31.2, 15.7.3.14.23.19.26, 4.11.22.4.19.24.4.28.6.8.22} +{17.29.21.10.18.8.16.26.18.21.26, 32.3.12.2, 21.28.24.23.3.11.7.12.22.32, 10.8.20.11.12.23.22} +{23.19.17.31.29.13.1.12.5.25, 19.11.10.18.14.13.7.7, 24.15.15.17.22} +{5.15.10.3.23.13.32.23, 18.31.32.28.1.4.24.24.12.25, 25.28.3} +{30.30.17.5.30.21.19.5.22.22.14, 1.3.15.11.11.25.24.21.19, 5.8.17.30.15.8.19.29.30.11.6, 13.28.9.3, 3.25} +{1.22.19.24.8.11, 7.30.5.10.10.5.30.14.9.18, 10.3.19, 23.8.13.22.21} +{10.7.9, 7.32.10.3.30.12.14, 27.22.11.13.21.25.5.1.27.21.27} +{30.17.2.25, 1.20.22.26.2.6.11, 14.19.30.6.4.10.10.10.22.25.11} +{22.23.25.28.5.27.9.9.24.31.10, 23.22.10.1.14.24, 15.23.26.20.27.7, 5.3.17.29, 1.10.23.25.5.11} +{12.10, 18.24.21.17.11.26.28.22.21.18.10} +{25.10.29.3.6.21.3.31.13, 7.7.25.22.22.26, 1.27.22.23.2.26.32.17.7.9, 1.31.3} +{30.22.29.21.19.14.3.2.6, 27.25, 6.25.17.32} +{1.30.31.31.20.16.7, 26.5.29.7.28, 23.17.28.31.28, 11.6.11.29.4.5.24.6.26.12} +{10.3, 16.9.14.28.6.21.31.31.26, 23.19.17.31.29.13.1.12.5.25, 24.16.27.10.9} +{17.1.12.20, 14.3.17.1.14.15.21.4.26, 13.1.6.17.28.9.15.30.1.27.14} +{19.22.21.13.27.13.15, 24.2.6.7.16.7.28, 13.9.9.27.31.11.25.9.27.22.13, 31.21.22.14.8.21, 7.27.20} +{9.28.10.26.14.26.15.14, 30.12.6.30, 23.31.27.16.8.30.20.27, 18.18.19.16.14.16.21.10.25} +{3.14.11.15.21.32.2.15.13, 30.25, 7.14.22.29.30.14.25.1.9.26.25, 3.5} +{22.10.18, 31.17.2.30.11, 28.14.32.29.2.3.4, 11.21.13.9.19, 21.30.19.6.28.1.32.2.14.14} +{26.31.6.8.29.8.24, 25.28.3, 15.30.17.5.32.28.2.18.27, 1.1.1.2} +{12.15.10.17.18.13, 22.19.20.5.2.20, 30.2.17.8.14, 30.12.9.25.24.6.7.24.29} +{32.1.21.1.16.29.21, 14.2.14.11.12, 12.17.10.7.17.16} +{29.5.18.27.3.21.18.6.14, 3.27.18.8.4.21.6.32.30.7.5} +{29.15.29.8.31.26.1, 16.31.12.27.25.9.32.29, 2.13.9.28, 9.5} +{15.10.30.1.4.12.8.20, 2.16.3.7.22.18.29.20, 26.31.7} +{12.29.17.2.20.29.1.11.19.8.12, 11.14.21.24.10.7.29.23.24.28, 31.21.22.14.8.21, 31.18, 25.9.10} +{32.3.23.7.2, 5.27.21.1.29.29.28, 30.3.16.26.7.27.26.9.27.21.18, 22.28.20.6.32.32} +{5.19.1.26.20.6.20, 19.26.32.13.1.12.30.26.22.25, 21.8.9, 8.13.1, 15.26.24.31.16.15.17.22.8.30.3} +{14.14.25, 32.6.31.31, 29.1.7.26.25.11.22} +{24.17.24, 1.4.14.32.14, 14.21.22} +{22.17.4.2.22.17, 21.9.32.1.27} +{8.3.18.13.30.20.27.26.17.28, 25.21.8.17, 19.10.4.30.32.4.12} +{16.20.29.26, 21.18.30.19.24.24} +{32.15.20.28.5.1.23.4, 7.13.15, 9.16.2.16.22.24.17.31.14.21.17} +{23.28.1, 6.20.14, 26.9.20.12.22.22.32, 2.4.25.32.16.22.26.13.17.18} +{26.18, 12.10.11.9.10.31.4.16.31, 24.17.31.20.12.9.19.29.18, 4.22} +{28.15.18.27, 3.9.25.26.7, 2.1.12.19.29.28.3.31.28.28.10, 12.15.10.17.18.13, 29.32.13.4.1.16.20} +{8.17.9.15.21.28.1.7.1.3.6, 32.1.31, 23.17.25.4.1.16.29.10, 9.5} +{27.29.1.5.30.6.22.16.23.2.28, 20.30.17, 22.13.22.8.30.32.10.24} +{26.31.6.8.29.8.24, 7.19.10.12.31.1.27.13.19, 27.18.10.4.22} +{29.1, 24.15.15.17.22, 21.4.22.20.24.28.6} +{21.20.24.25.6.26.23, 29.32.13.4.1.16.20, 13.9.9.27.31.11.25.9.27.22.13} +{8.24.11.13.25.19, 29.1.2.14.14, 21.32.13.22.3.13.31.23.14.12.9, 2.10.28.1.17.19.32.28, 1.10.23.25.5.11} +{11.14.21.24.10.7.29.23.24.28, 3.13, 23.25.23.11.7.23} +{18.18.5.11.7.4.25, 7.31, 18.7.10.27.17.24, 19.2.9.29.6} +{15.6.19.3, 1.27.22.23.2.26.32.17.7.9} +{32.6.8, 23.12.19.25.16.23.22.6.29.4, 9.7.31.11.8.23, 28.4} +{1.29.18.1.21.12.13.27.32.15, 14.21.6.5.26.9.32.16.25, 30.25.8.24.6.29.31, 26.18.32.20, 17.5.3.15.17.13.5} +{1.3.15.11.11.25.24.21.19, 4.14.10.19.16, 32.3.23.7.2} +{28.5.13, 26.25.10.10.13} +{30.23.10.1.10.7.22.28.18.11.17, 32.16} +{8.17.9.15.21.28.1.7.1.3.6, 6.7.25.16.13.21.7.20.25.12.4, 26.28.14, 5.13.23.19.28.26.27.6.1.22, 32.1.21.1.16.29.21} +{6.26, 29.29.17.31, 4.11.19.17.2.22.20.18.13.32.15, 29.23.15.25.1.6.6.10} +{1.25.7.9.26.17.31.20.13, 23.3.32.21.5.14.10.17.1, 30.23.10.1.10.7.22.28.18.11.17, 23.5.7.12.11.23.10, 14.19.20.13.27.2.2} +{15.30.17.5.32.28.2.18.27, 29.1.7.26.25.11.22, 15.8.10, 15.8.3.15.27.14.29.28.6.5.25} +{19.30.27.26.21.7.18, 4.13, 16.5.14.21.32.17.23.3.4.26, 5.3.17.29} +{18.21, 21.9.27.22.32, 30.24.23.25.32.18.22.12.29.9.22, 5.9.19.6} +{30.18.30.16.29, 16.19.17.30.30.5.17.24.27, 15.7.5.12.7.9.3.28.26, 4.11.22.4.19.24.4.28.6.8.22, 25.24.29} +{24.16.27.10.9, 10.28.7.16.31, 24.12, 31.24.26.18} +{18.24.21.17.11.26.28.22.21.18.10, 10.29.26.4.27.17.11, 32.6.15.26.14.15.3.19, 15.28.24} +{8.14.19.18, 1.3.15.11.11.25.24.21.19} +{10.22.30.16.2.21.17.13, 30.5, 32.17.8.24.2.14.5.4.22, 1.10.5.22.13} +{32.1.21.1.16.29.21, 26.9.17.1.18.19.1.11.18.29.3, 32.1.31, 28.1.3, 14.21.6.5.26.9.32.16.25} +{25.29, 31.30.12.20} +{29.27, 30.3.16.26.7.27.26.9.27.21.18, 32.6.8, 24.28.32.21, 25.17.18.30} +{16.5.12.5.15.12.24.25.3, 23.22.10.1.14.24} +{26.25.24, 9.18.30.11.29.32.7.19.2, 8.16.30.29.19.22.28.24.2, 1.31.3} +{3.9.25.26.7, 21.5.11.18, 11.1.3.28.30.21.24.14, 23.32.5.25.19.9.15.17.15.11, 7.31.4.20.17} +{28.6, 21.20.24.25.6.26.23, 10.18.12.27.24.30.32.7.11.5.13} +{8.16, 2.2.18.18.3.3.18.8.10.8} +{27.27.30.11.15.24.9.7.4.30, 4.13} +{10.18.12.27.24.30.32.7.11.5.13, 4.22, 25.9, 26.8.28, 25.3} +{18.18.5.11.7.4.25, 19.26.32.13.1.12.30.26.22.25, 29.26.25.14.24.18.2.13.23.29} +{23.28.1, 23.20.12.16.15.2} +{20.32.5.1.3.20.3.30.27, 15.5.1.31.28.10.8, 24.23.24.4.15.25.17, 22.10.16.8, 27.31.2.16.29.6} +{17.8, 11.17.10, 19.16.31.31.29.12} +{19.3.23.4.4.21.23, 29.14.12.9.17.5.32, 27.27.30.11.15.24.9.7.4.30, 24.17.24} +{21.5.11.18, 23.28.1} +{2.8.13.12.17.23.16.7.11.23, 10.12.9.6.6.26.14.8.23.1.25, 9.17.13.31.7, 1.30.18.31.12.25.4.19.28.12.15, 15.29.25} +{15.7.3.14.23.19.26, 12.4.12.13.25.30.30.8.9.12} +{20.23.29.5.7.30.13.14.22, 23.12.1.5.32.25.8.24.1.25, 8.29.6.3, 22.19.20.5.2.20, 8.9.22} +{12.6.14.23.19.21.9.12, 21.7.7.11, 1.30.18.31.12.25.4.19.28.12.15} +{27.18, 14.6.10.29.25.26.20.24.24, 6.29.6.13.14.24.10.4.14.28} +{31.9.3.5, 18.29.13.24.18.3.12.18.12.12} +{5.4.8.25.12.27.2.29.28.3, 10.12.9.6.6.26.14.8.23.1.25, 32.24.11.8.12.23.22.19.11.17.18} +{27.15.15.15, 17.26.18, 1.10.21, 4.31} +{23.14.12.30.18.4.16.18.7.7, 8.17.9.15.21.28.1.7.1.3.6, 9.5} +{8.13.9.31.20.20.24.7.23.31.28, 17.29.21.10.18.8.16.26.18.21.26, 19.9.32.23.13.24.1} +{24.2.6.7.16.7.28, 32.6.8, 24.23.29.8.24.11.21.10.28.14.27} +{25.22.2.25.6, 32.19.20.24.23.31.8.32.16.29, 29.11.20.22.27, 14.4.23.4.23.22.11.6.26.5, 23.12.32.22.19.1.22.4} +{13.9.9.27.31.11.25.9.27.22.13, 13.16.4.28, 10.28.22.29.13.19.6.7.6.14, 28.15.25.7.13.6.19.2, 30.9.24} +{31.28.32.4.31.4.7, 28.15.18.27, 29.26.25.14.24.18.2.13.23.29} +{8.11.20, 13.32.15.32.26.14.32} +{13.16.1.27.18.18.19.6.14.4, 3.29.32.26.8.10.25, 4.27.32.18, 12.6.14.23.19.21.9.12} +{28.6.11.6.15.22.12.6, 6.1.8.6.30.29.30, 4.22.17.10.19.9.8.19.28.3.9, 6.14} +{5.8.17.30.15.8.19.29.30.11.6, 32.1.23.20.14.12.23.5.32.15, 2.10.10.4.20.1.12.13, 11.21.13.9.19, 12.23.3.19.29.15.12.6} +{10.31.25.31.24.16.17, 32.24.29.6, 19.22.21.13.27.13.15, 29.15.29.8.31.26.1, 2.16.3.7.22.18.29.20} +{6.19.3, 18.24.21.17.11.26.28.22.21.18.10, 11.1.3.28.30.21.24.14} +{1.19.22.11.14.7.32.23.19.14, 2.30.26.10.14.31.18.2} +{23.17.28.31.28, 5.15.16, 23.10.13.32.14.20.16.11.14} +{6.11.31.23.12.8.30.14.27, 4.15.20.23.12.16.2.16.17, 23.12.32.22.19.1.22.4} +{8.32.30.1, 17.22.12.10.30.11, 14.5.13.19.25.12.32.9.13.16.12} +{4.11.19.17.2.22.20.18.13.32.15, 8.27.3.4.12.26.16, 7.30.19.25.23.15.14.29} +{15.8.3.15.27.14.29.28.6.5.25, 30.31.13.9, 15.31.11.27.19.19.20.5.5} +{28.25.29.4.13.5.6, 21.7.7.11, 8.3.18.13.30.20.27.26.17.28, 12.15.10.17.18.13} +{24.1.10.20.28.18.6.27.20.30.26, 23.2.22.7.32.3.27.6} +{24.1.29.32.14.15.32.6.15.22, 28.5.12.9.2.27.11.11.2, 3.29.19.2.24, 28.23.2.30.3.8.1.15.15.14.13, 24.27.14} +{10.16.19.7.15, 31.9.3.5, 31.18.25.1.14.29.25.5.22.30} +{27.3.3.11.21.4.25, 4.31, 2.24.4.5.24.32, 30.24.23.25.32.18.22.12.29.9.22} +{30.25, 8.10, 5.23.31.18.24.32, 22.19.5.22.20.31.23.24.14.24.4} +{32.1.23.20.14.12.23.5.32.15, 31.18.25.1.14.29.25.5.22.30, 9.26.1.16, 24.23.24.4.15.25.17} +{16.8.29.7.21.2.3, 13.8.20.9.21, 22.19.5.22.20.31.23.24.14.24.4, 4.9} +{3.26, 2.32.8.28.24.20.9.24.25.8.9, 14.1.11, 19.31.14.25.5.8.21.11.13.20} +{8.27.3.4.12.26.16, 27.27.30.11.15.24.9.7.4.30, 2.30.26.10.14.31.18.2, 3.10} +{21.9.32.1.27, 5.15.10.3.23.13.32.23, 23.27.6.26.22, 30.25.8.24.6.29.31} +{32.1.24.29.22.5.9.24.18.3.13, 29.3.15.17.12.29, 5.15.16} +{1.10.5.22.13, 15.28.30.19.31.6.2.2.31, 28.14.24.26.6.15.16.32.25.13.8} +{14.2.14.11.12, 12.10.11.9.10.31.4.16.31, 1.18.29.30.22.14.3.20.15.21.20, 5.10.3.9.23.30.23, 26.8.28} +{6.22.12, 12.16.13} +{4.14, 11.3.15.28.22.8.14, 2.6.15.26.23.26.24, 11.17.17.24.11.23.17.17.18.10.22} +{9.30, 19.10.4.30.32.4.12, 21.28.17.22.10.27.4.20.2.32, 19.26.24.27.6.24.16.27.32.29, 26.14.5.32.10} +{20.3.1.8.8.30.20, 1.18.29.30.22.14.3.20.15.21.20, 1.18.29.30.22.14.3.20.15.21.20, 7.23.1.24.29.13.31.19.23.17.7, 31.7.14.2} +{14.2.14.11.12, 18.13.9.3.18.15.2, 28.11.11.30.20.11.32, 1.1.1.2} +{3.26.32, 25.9.1.5.9.11.25.4.11.27.32} +{12.17.10.7.17.16, 9.30, 4.30.8.20.19.9.30.24.11, 1.12.25.26.22.8.15.23, 12.11.20.20.29} +{30.20.3.2.5.15.8.7.17, 5.24.25.15.27.30.20, 9.28.24, 7.12.1.10.6.17.29.24.24.4} +{4.21.28.5.16.29.5.21, 32.6.9.26.16.4.4.29.7.11} +{6.18.1.4.18.23, 13.32.15.32.26.14.32, 10.5.23.5.32.9.18.5.30} +{27.17.3.18.2.13.18, 24.12, 6.11.11.5.16.8.14.12.9} +{6.11.31.23.12.8.30.14.27, 10.22.1, 28.6.8.22.25} +{7.30.19.25.23.15.14.29, 3.21.16.24.23.12.16.32.3, 4.25.12.10.15.9.18.9, 28.4} +{14.19.26.15.22.23, 24.21.14.25.11.3.20.6.6.16, 20.9.29.32.13.7.23, 12.4.12.13.25.30.30.8.9.12, 15.31.11.27.19.19.20.5.5} +{1.14.3.7.3.17.2.29, 29.26.25.14.24.18.2.13.23.29, 25.2.3.15.11.19.5.28.25.14} +{26.24, 22.10.16.8} +{12.29.17.2.20.29.1.11.19.8.12, 19.2.9.29.6, 14.5.13.19.25.12.32.9.13.16.12, 10.18.12.27.24.30.32.7.11.5.13, 31.4.7} +{22.30, 23.17.25.4.1.16.29.10, 4.26.5.26.21.28.17.24.25.23, 24.10.8.25.16} +{18.18.19.16.14.16.21.10.25, 4.10.28} +{22.19.21.11.6.8.29.24, 6.25.17.32, 17.13.19.31.12.18.10.15.14, 9.31.4.14.31.10.17.5.2, 9.9.13.9.14.27} +{26.18, 5.19.1.26.20.6.20, 23.5.7.12.11.23.10} +{22.17.7.30.13.24, 5.14.29.2.23.16.20.22, 4.5.9.4.15.19.8.26.17.26.3} +{24.9.15.1.14.29.6.4, 21.9.32.1.27} +{1.28.3.22, 19.9.32.23.13.24.1, 6.25.17.32, 15.28.30.19.31.6.2.2.31, 18.15.14} +{20.32.5.1.3.20.3.30.27, 1.13.16.27.11.16.30.2.9.18.4} +{15.29.32.16.29.12.20.32.13.20, 28.26.25.7, 12.29.26.18.4.21.28.8.13.3, 12.4.10.17.4.10.23.3, 15.1.8} +{14.21.6.5.26.9.32.16.25, 20.4.1.16.31.3, 17.11.17.4.8.26.26.20.6} +{1.1.1, 10.12.23.22.23.22.20.17.17.9} +{21.23.13, 9.5.9.3.23.9.25.14.1.29.28, 28.8.21.15.16.28.4.16.26.8, 25.15.11, 23.23} +{16.5.14.21.32.17.23.3.4.26, 24.9.15.1.14.29.6.4, 20.5.4.9.31.14.26.6} +{22.19.21.11.6.8.29.24, 4.11.19.17.2.22.20.18.13.32.15, 17.25.26.23.32, 27.3.3.11.21.4.25, 3.14.30.5.32.22.29} +{19.22.21.13.27.13.15, 19.17.13.12.32.16.3, 10.12.23.22.23.22.20.17.17.9, 7.19.6.17.15.26.21.9} +{2.13.9.28, 11.11.9.30.15.29.15.18, 20.14.11.2.10.14} +{1.1.7.32.11.22, 9.21.20.29.1, 32.31.11.22.1, 14.15.31.29} +{27.18, 27.26.29, 28.30.24.16.17.28.2.13.10} +{5.15.16, 30.17.2.25, 18.29.5.1.10.21.2, 15.3.31.9.27.14.9.8.14.6.32} +{9.28.24, 26.7.22.3.18.21.11} +{4.19.16.15.5.2.25.8.28.14.2, 9.31.4.14.31.10.17.5.2, 22.30} +{22.13.22.21.25.17.8, 31.13, 9.7.31.11.8.23} +{1.1.2.1, 6.11.31.23.12.8.30.14.27, 3.18, 24.27.14} +{30.8.9.14.25.30, 25.17.9.16.17.31.23.29.24, 29.26.25.14.24.18.2.13.23.29, 4.26.23.6.19.31.10.4.22, 23.27.27.16} +{11.19.23.3.6.11, 21.17.31.10.31.13.9.26.6.14} +{4.2.2.32.24.25.31.3, 8.1.29.18.22, 22.23.22.30, 15.1.8} +{20.29.18.16.2.21.23.11, 32.8.29.18.31, 28.25.29.4.13.5.6} +{32.30.18.17.1.14.12.18, 28.6.11.6.15.22.12.6, 9.30} +{3.27.18.8.4.21.6.32.30.7.5, 6.22.12, 12.11.17.1.2} +{26.14, 11.1.3.28.30.21.24.14} +{20.13, 29.30.7.31.22, 26.9.17.1.18.19.1.11.18.29.3, 15.9.11.20.22.15.11.13} +{6.2.32, 32.24.11.8.12.23.22.19.11.17.18, 23.20.12.16.15.2, 11.32.18.31} +{7.32.10.3.30.12.14, 27.4} +{32.3.23.7.2, 25.17.18.30} +{14.27.29.23.4.1.17.32.6.25.22, 11.7.31.15.22, 26.32.21.31.27.12} +{26.7.5.8.11.9.22.1.6, 7.27.20, 16.28, 1.18.29.30.22.14.3.20.15.21.20, 30.17.4.5.13.6} +{4.2.2.32.24.25.31.3, 2.1.12.19.29.28.3.31.28.28.10, 22.9.15.19.12, 19.6.13.14.22.13.9.29, 28.8.21.15.16.28.4.16.26.8} +{21.23.13, 11.17.17.24.11.23.17.17.18.10.22, 3.29.32.26.8.10.25, 31.18.25.1.14.29.25.5.22.30} +{1.1.1, 16.29.6.23.13.28.31.6.19.26.15, 9.5, 9.5, 25.19.27.2.9.20} +{8.13.9.31.20.20.24.7.23.31.28, 9.7.31.11.8.23, 6.11.11.5.16.8.14.12.9, 1.25.7.9.26.17.31.20.13, 11.2.27.3} +{31.28.32.4.31.4.7, 10.28.7.16.31} +{10.18.12.27.24.30.32.7.11.5.13, 3.3, 6.29.6.13.14.24.10.4.14.28, 1.9.18.10.1.26.22.16.17, 11.18.4.8.3.13.14.28.18.31} +{8.12.4, 10.31} +{20.30.28.15.17, 23.17.28.31.28} +{16.9.29, 31.9.3.5} +{30.12.28.2, 13.25.10.25.8.16} +{10.28.7.16.31, 23.8.13.22.21, 26.9.17.1.18.19.1.11.18.29.3} +{27.4, 24.27.18.32.14.9.11.28.9} +{13.14.13.10.28.26.9.18.27.21, 13.8.15.3.7.31.5.10.15.30, 31.18.32.11.7.25.20.5, 30.6.4, 24.27.18.32.14.9.11.28.9} +{25.2.11.20.8.6.22, 16.29.6.23.13.28.31.6.19.26.15, 5.10.3.9.23.30.23} +{32.25.3.6, 26.32.8.12.30.19.24.8.6.1.10, 11.32.18.31, 2.9} +{25.3, 32.6.15.26.14.15.3.19} +{11.16.16.28.14, 16.19.17.30.30.5.17.24.27} +{16.27.8.17.14.17.21.29.14, 12.18, 24.9, 12.16.2.4.15, 22.10.16.8} +{4.2.6.20.7.8, 25.24.2.32.14.18.16, 28.6.11.6.15.22.12.6, 4.22.17.10.19.9.8.19.28.3.9, 19.2.26.21.16.11.2.2} +{11.8.18, 22.8.20.1.10.28.6.27, 11.18.4.8.3.13.14.28.18.31, 27.15.15.15, 14.6.10.29.25.26.20.24.24} +{25.11.24, 25.6, 1.1.2} +{15.7.3.14.23.19.26, 7.19.6.17.15.26.21.9, 10.3.19} +{11.6.11.29.4.5.24.6.26.12, 6.29.6.13.14.24.10.4.14.28} +{26.32.21.31.27.12, 12.25.32.2.27.3.3.16, 11.10, 24.9.15.1.14.29.6.4} +{21.6.22.28.12.23.11.22, 27.11.15.9.24.31.18.4.1.30.20, 17.13.14.29.27.27.13.12.15, 21.14.13, 30.31.13.9} +{26.28.14, 9.21.20.29.1, 25.3, 9.14.27.31.26.21.25.3.20, 16.28} +{22.32.6.6.3.8.24.6.25.29, 12.18, 8.16, 15.10.30.1.4.12.8.20, 31.9.3.5} +{19.7.29.31.3.20.7.21.25.27.29, 20.29.18.16.2.21.23.11, 4.21.9.1.2.14.8.17.13.26} +{19.3.23.4.4.21.23, 20.5.4.9.31.14.26.6, 15.31.11.27.19.19.20.5.5, 7.26.18, 26.31.16.18.22.13.32.23.9.20} +{29.5.18.27.3.21.18.6.14, 12.27.30.12.24.2.20, 2.15.14.20.30.26, 21.14.13, 1.9.18.10.1.26.22.16.17} +{27.3.3.11.21.4.25, 3.18.18, 17.8, 8.13.9.31.20.20.24.7.23.31.28} +{21.14.13, 9.7.31.11.8.23, 14.13.9.13.11.5.5.2.2.32.12, 23.12.11.11.15.16.22.31.32.5.8} +{17.1.12.20, 21.31.31.25.5.30.26} +{22.10.27.19.29.20.29.3.12.14.25, 31.21.22.14.8.21} +{19.7, 9.17.13.31.7, 29.1.7.26.25.11.22, 8.16.1.16.28.6.3.22.6.23} +{23.20.8, 13.28.9.3, 3.29.19.2.24, 4.13.22.11.9.13.27.15.7, 9.8.23.2.20.16} +{12.16.13, 17.14.7.3.2.18.20.23.18.5, 13.3.20, 3.4.22.19} +{16.9.14.28.6.21.31.31.26, 20.4.1.16.31.3} +{1.1.3, 5.19.1.26.20.6.20, 18.31.32.29.22.1.31.11.28} +{24.32.17.23.24.19.23.9.20.18, 10.12.9.6.6.26.14.8.23.1.25, 26.25.24, 18.9.26.7, 3.9.11.23.32.26.24.28} +{32.27.13.6.7, 9.17.13.31.7, 29.27.13.29.10.2} +{31.29.18.26.1.26.17, 11.11.11.4.23.21.25, 25.28.30.24} +{23.22.10.1.14.24, 17.10.17.22.20.25.14.13, 7.14.22.29.30.14.25.1.9.26.25, 20.24.14.15.4.21.12.27.4.12, 30.16.3.21.10} +{16.19.17.30.30.5.17.24.27, 16.21.13.1.4, 7.30.5.10.10.5.30.14.9.18} +{10.28.22.29.13.19.6.7.6.14, 4.14.32, 24.2.6.7.16.7.28} +{16.14.3.17.17.26.12.19.19.30, 6.2.32, 32.28.1.32.28.10} +{28.17.26.9, 28.2.27.1.20, 16.31.12.27.25.9.32.29, 22.17.30, 2.24.5.3.4.10.27.26.17.28.16} +{18.31.32.28.1.4.24.24.12.25, 15.21.23.30.9.25, 22.20.30, 23.3.32.21.5.14.10.17.1} +{4.21.9.1.2.14.8.17.13.26, 11.32.18.31, 3.11.32.11.22.3.7.17.8.13.23} +{17.25.2.13.10.27.13.1, 30.25.8.24.6.29.31} +{12.13.16.17.29.27.16.14.9.19.9, 32.3.12.2} +{22.17.24.14.21.15.12.18.17.25.11, 18.19.12.20.18.17.15.32.18.5} +{21.17.18.32.7.8, 30.32} +{30.24.32.15.14.10.11, 2.15.18.21.5.21.4.7.30, 8.16.20.24.20.6.10.21, 29.1.7.26.25.11.22} +{2.19.4.1.15.7.8.9.17.29, 16.5, 4.30.8.20.19.9.30.24.11, 2.11.32.25.23, 7.23.15.32.28.27.2.2.26} +{11.22.28.8.12.23.25.15.21.28, 25.29, 23.5.5.17, 25.5.30.7.16.12.21.12.11.16, 18.30.11.17} +{26.31.6.8.29.8.24, 3.21.16.24.23.12.16.32.3, 32.3.5.9.17.15, 31.17, 23.19.17.31.29.13.1.12.5.25} +{12.13.5.31, 2.24.4.5.24.32, 31.5.6.4.8.29.3} +{23.10.13.32.14.20.16.11.14, 13.17.7, 19.12.20.24.32.13.11.23.26} +{1.13.16.27.11.16.30.2.9.18.4, 24.32.17.23.24.19.23.9.20.18} +{29.27.13.29.10.2, 20.17.18.21.1} +{19.31.14.25.5.8.21.11.13.20, 29.25.29.16.32.11.15.25.5.22.3} +{18.7.10.27.17.24, 23.10.5.26.12.4.20.4, 13.26.17.3.2.19, 17.10.17.22.20.25.14.13} +{15.17.2.32.7, 9.23.21.22.5.29.15.21, 29.1.2.14.14} +{30.32, 3.29.32.26.8.10.25, 10.29.26.4.27.17.11, 28.2.27.1.20, 31.17} +{28.6.8.22.25, 11.30.20.15.18.32.1.18.25.26.8, 27.4.15.14.19.6.12, 19.3.23.4.4.21.23, 30.15} +{8.26.29.13.7.25.31.28.3.32, 1.1.1} +{14.1.11, 4.26.5.26.21.28.17.24.25.23} +{30.9.24, 24.3.23.25, 4.26.5.26.21.28.17.24.25.23, 28.26.25.7} +{12.17.10.7.17.16, 19.15.26.19, 12.16.2.4.15} +{1.1.1.1, 4.26.23.6.19.31.10.4.22} +{2.32.10.13.12, 18.27.11.27.9.16.7.6.22.26.27, 22.11} +{17.8, 4.10.28} +{10.2.17.26.16.7.19.6.23.3, 14.10.11.30.5.7.6.24.9.30.26, 18.7.10.27.17.24, 10.26.27.23.4.31.11.25.29, 13.26.17.3.2.19} +{1.10.23.25.5.11, 18.24.21.17.11.26.28.22.21.18.10, 14.30.2.21.15.16.13} +{13.7, 22.9.15.19.12, 22.21.32.15.8.29.5.12.10.29, 32.1.24.29.22.5.9.24.18.3.13, 7.31.2.28.15.11.17.18.19.23.6} +{31.28.32.4.31.4.7, 9.5.9.3.23.9.25.14.1.29.28, 27.31.2.16.29.6, 31.30.23.7.7.24.32.10.11.1.31} +{20.14.11.2.10.14, 31.4.7, 22.24.22.25.15.23.13} +{13.3.20, 24.31, 24.9.15.1.14.29.6.4, 2.15.18.21.5.21.4.7.30} +{20.20.32.29.24.5.5.26.22.32, 20.18.24.14.12.13.9, 15.9.11.20.22.15.11.13, 21.7.23.9.16.5.18.14} +{4.14.32, 21.9.32.1.27, 9.21.28.8.12.15.3.13.10.11, 8.16.20.24.20.6.10.21, 6.9.1.10.10.22.6} +{7.11, 15.21.22} +{19.7.29.31.3.20.7.21.25.27.29, 7.12.23, 21.4.22.20.24.28.6, 30.25.24.22, 9.14.27.31.26.21.25.3.20} +{13.26.17.3.2.19, 27.4} +{11.7.31.15.22, 20.6.26.3.30, 27.32.26.21.31.17.32.32} +{2.22.19, 8.16.20.24.20.6.10.21, 4.10.28, 20.4.1.16.31.3, 28.14.32.29.2.3.4} +{6.17.10.10.7.9.27.8.29, 10.26.27.23.4.31.11.25.29} +{3.4.22.19, 15.31.11.27.19.19.20.5.5, 25.16.9.6, 14.6.10.29.25.26.20.24.24, 32.27.13.6.7} +{28.9.3.16.17.21.23.30, 13.3.20} +{8.25.20.3.15.24.7.4.24.5.30, 17.25.2.13.10.27.13.1, 29.10.12.17.12.16} +{22.16, 25.4.32, 28.15.25.7.13.6.19.2} +{3.1.13.22.24.14.12.31.3.4, 5.10.3.9.23.30.23, 28.2.27.1.20} +{26.16.12.3.27.9.28, 9.19.7.13.13.25, 9.18.23} +{23.20.24, 24.3.23.25, 22.15, 20.29.18.16.2.21.23.11} +{27.27.30.11.15.24.9.7.4.30, 10.31, 24.1.10.20.28.18.6.27.20.30.26, 3.29.19.2.24} +{20.30.28.15.17, 14.6.10.29.25.26.20.24.24, 12.10, 23.20.8} +{19.10.4.30.32.4.12, 28.6.8.22.25, 9.22.10.15.5.15} +{22.3.6, 12.4.24.6.1.13.5.20, 23.5.7.12.11.23.10, 20.23.7.11.11.31.18.16.3} +{24.2.26.24.14.15.31.23.17.26, 32.6.13.8.32, 22.17.4.2.22.17, 4.10.28, 17.1.12.20} +{1.27.22.23.2.26.32.17.7.9, 17.25.2.13.10.27.13.1, 2.12.30.22.12, 23.20.8, 27.27.25.10.31.10.21.22.21.16.12} +{24.27.18.32.14.9.11.28.9, 27.3.3.11.21.4.25, 8.13.6.12.18.7} +{26.19.3.14.8.28.31.10, 17.5.3.15.17.13.5, 23.25.23.11.7.23, 15.5.1.31.28.10.8, 19.9.32.23.13.24.1} +{12.25.32.2.27.3.3.16, 12.28.12.24.28.15.5.12.30.13.21, 10.8.20.11.12.23.22, 22.26.32} +{10.7.9, 32.29.24.31.25.6.9, 14.1.11, 13.24, 31.13} +{30.16.14.9.5.4.10.7.31, 21.28.24.23.3.11.7.12.22.32} +{6.19.3, 10.31.25.31.24.16.17} +{18.7.10.27.17.24, 22.17.30, 27.18, 14.24} +{23.17.25.4.1.16.29.10, 8.9.22, 10.5.23.5.32.9.18.5.30, 19.26.32.13.1.12.30.26.22.25} +{26.16.12.3.27.9.28, 23.24.16.32.13.29, 5.10.3.9.23.30.23, 21.10.20.9.3.16.9.10.20, 26.31.11.23.3} +{5.23.31.18.24.32, 31.4.7, 14.9.15.21.21.31.1.29} +{29.5.18.27.3.21.18.6.14, 2.2.18.18.3.3.18.8.10.8, 32.3.23.7.2} +{15.9.11.20.22.15.11.13, 5.27.32.21.5.1.11.14} +{1.1.2.1, 11.7.31.15.22, 22.26.1.28.9.9.31, 19.17.13.12.32.16.3, 25.18.8.3.23.23.5.9.6} +{1.26, 16.23.30.12.31.31.19.14, 14.19.26.15.22.23, 5.18.9.25.31.21.22} +{20.17.14.7, 9.5.9.3.23.9.25.14.1.29.28, 21.32.13.22.3.13.31.23.14.12.9, 19.10.4.30.32.4.12} +{23.12.1.5.32.25.8.24.1.25, 10.31.25.31.24.16.17} +{25.22.2.25.6, 6.29.6.13.14.24.10.4.14.28, 24.10.8.25.16} +{12.15.10.17.18.13, 27.29.1.5.30.6.22.16.23.2.28, 20.30.17, 29.1.2.14.14} +{15.25.31.11.4.22.16.7.11, 22.16, 7.14.22.29.30.14.25.1.9.26.25, 25.5.30.7.16.12.21.12.11.16} +{29.20.1.11.21.16.1.2.14.28, 32.1.24.29.22.5.9.24.18.3.13} +{2.28.5.17.6.32, 29.28.9.15.8.27.31} +{32.6.31.31, 13.1.6.17.28.9.15.30.1.27.14, 3.6.24.21.20.32.3.4.26.5} +{24.10.10.31.4.29.9, 28.26.4.22.13.20.32.27.15, 3.11.32.11.22.3.7.17.8.13.23, 15.21.22} +{19.26.24.27.6.24.16.27.32.29, 13.24, 17.3} +{12.4.24.6.1.13.5.20, 28.27.24.14, 14.19.20.13.27.2.2} +{9.26.1.16, 8.21.8.23.4.18, 15.29.32.16.29.12.20.32.13.20, 9.7.31.11.8.23} +{15.28.24, 3.5, 3.20.19.10.17.27.3.6.22.23, 4.13.22.11.9.13.27.15.7} +{25.16.9.6, 26.25.10.10.13, 11.11.9.30.15.29.15.18, 6.5.27.19.13.26.1.18.9, 16.2.14.3.26.11} +{23.19.17.31.29.13.1.12.5.25, 15.17} +{5.10, 5.10.3.9.23.30.23, 1.1.1} +{15.8.10, 23.5.5.17, 32.3.12.2, 5.27.21.1.29.29.28} +{17.13.8, 31.13, 16.18.23.6.31, 26.18} +{13.28.12.6, 21.6.22.28.12.23.11.22, 12.22.20.4.12} +{6.9.29.17.4.32, 15.17} +{29.15.29.8.31.26.1, 32.15.20.28.5.1.23.4} +{24.9.8.12.29, 15.5.1.31.28.10.8} +{15.17.2.32.7, 24.25.7.27.30.8.26.17, 24.16.27.10.9} +{18.19.12.20.18.17.15.32.18.5, 1.20.18.25.3.24.25.10.9, 4.14.17.12.20.17.1.22.3} +{17.29.21.10.18.8.16.26.18.21.26, 31.13, 14.26.25.4.12.26.8, 21.14.22.29, 17.8.31.32} +{24.27.18.32.14.9.11.28.9, 5.24.4.31.3.16.25.17.13.26.11, 10.29, 27.3.3.11.21.4.25, 5.13.23.19.28.26.27.6.1.22} +{4.7.1, 31.24.26.18, 12.25.32.2.27.3.3.16, 26.24, 5.2.32.19.13.29.12.13.31.29} +{29.20.1.11.21.16.1.2.14.28, 26.24, 1.9.18.10.1.26.22.16.17} +{5.23.31.18.24.32, 11.11.11.4.23.21.25, 11.2.27.3, 1.13.16.27.11.16.30.2.9.18.4} +{25.15.11, 3.10.4.5.28.11, 26.14.5.32.10, 29.27.7.7.3.11.14.26.21.11} +{9.8.23.2.20.16, 23.14.30.27.28.26.26.23.8.32, 22.19.5.22.20.31.23.24.14.24.4, 1.1.1.2.1} +{27.5.15.1.15.16.21, 32.6.3.2.12.5.28.1.25, 19.16.31.31.29.12, 19.12.30.2.21} +{23.22.10.1.14.24, 20.29.18.16.2.21.23.11, 10.27.7.24.26.11.31.20.29, 1.25.7.9.26.17.31.20.13} +{27.18, 15.4.15, 25.9.1.5.9.11.25.4.11.27.32, 12.29.17.2.20.29.1.11.19.8.12, 9.2.10.4} +{23.28.20.25.30.24.15, 27.3} +{15.26.24.31.16.15.17.22.8.30.3, 13.32.15.32.26.14.32, 14.17.7.30.8.25.26.4, 12.3} +{5.14.27.15.11.17.3.10.27.25, 24.27.18.32.14.9.11.28.9, 30.23.2.13.14.15.29.19.4.12.24} +{21.15.31.24.29.24.26.12.20, 8.24.11.13.25.19} +{24.9.27.16.20.21, 16.29.6.23.13.28.31.6.19.26.15, 3.15.2.23.22.2.16.14, 16.13.26.18.9.29.11.17.1.24.26} +{19.7.29.31.3.20.7.21.25.27.29, 28.11.27.21.14.16} +{5.2.32.19.13.29.12.13.31.29, 30.12.6.30, 9.21.20.29.1, 2.16.3.7.22.18.29.20} +{32.15.20.28.5.1.23.4, 11.1, 2.14.12.13} +{21.17.27.23.15, 19.26.24.27.6.24.16.27.32.29, 26.9.17.1.18.19.1.11.18.29.3} +{7.13, 30.25.17.17.10.29} +{24.27.14, 22.19.21.11.6.8.29.24, 2.19.4.1.15.7.8.9.17.29, 7.13.15, 4.22} +{17.5.3.15.17.13.5, 11.2.27.3, 1.1.3} +{17.24.15.27.3.32.4.22.20.6.24, 20.17.18.21.1, 19.22.29.32.1.21.26.24.23.17, 32.8.5, 5.27.21.1.29.29.28} +{10.27.7.24.26.11.31.20.29, 3.25} +{15.26.24.31.16.15.17.22.8.30.3, 20.8.19.14.16.7} +{20.22.10, 1.12.25.26.22.8.15.23} +{13.7, 23.32.5.25.19.9.15.17.15.11, 31.13, 6.10.25.12} +{11.17.17.24.11.23.17.17.18.10.22, 7.31, 24.13.1.8} +{16.14.3.17.17.26.12.19.19.30, 28.15.25.7.13.6.19.2, 26.24, 6.19.6.4.9.11.32.17.17.3.15} +{22.23.25.28.5.27.9.9.24.31.10, 1.27.22.23.2.26.32.17.7.9} +{32.25.3.6, 23.5.5.17} +{20.5.4.9.31.14.26.6, 6.29.32.13.30.3.16, 27.18} +{18.13.6.12.26.26.26.29.18.20.1, 18.13.9.3.18.15.2, 21.14.22.29} +{19.5.20.3.4.2.3, 4.10.28, 7.7} +{25.15.11, 14.23.31.5.5.15.17.12.17.7.3, 26.31.11.23.3, 25.32.24.24.28.15.16.10, 26.19.3.14.8.28.31.10} +{20.1.24.3.30.31, 13.3.8, 4.7.1, 3.21.16.24.23.12.16.32.3} +{7.14.22.29.30.14.25.1.9.26.25, 17.9.32.31.21.31.23.17.10.32.9} +{29.5.18.27.3.21.18.6.14, 27.3, 5.10, 21.14, 7.7} +{13.17.7, 13.3.8, 14.19.30.6.4.10.10.10.22.25.11, 13.7} +{30.17.25.3.31.11.3.4.1.10, 5.24.4.31.3.16.25.17.13.26.11, 27.11.14.17.24} +{16.5.10.2.18.8.15.12.32.25.10, 8.3.18.13.30.20.27.26.17.28, 5.27.16.3.30} +{29.29.18, 4.26.2.2} +{3.4.22.19, 16.19.17.30.30.5.17.24.27} +{12.4.26.23.25.5.15.7.16, 20.29.18.16.2.21.23.11, 22.10.18, 24.18.16} +{1.1.1.1, 2.12.30.22.12, 1.22.29.5.16, 8.12.4} +{32.31.11.22.1, 2.8.13.12.17.23.16.7.11.23, 32.15.20.28.5.1.23.4, 4.31, 6.26} +{19.11.29.13.15.27.12.15.14.12, 30.30.17.5.30.21.19.5.22.22.14, 16.5.14.21.32.17.23.3.4.26} +{3.6.24.21.20.32.3.4.26.5, 20.4.1.16.31.3, 5.31.8.1.5.13.21.28.29.19.2, 23.8.13.22.21} +{20.4.27.31.1, 27.18.10.4.22} +{4.2.6.20.7.8, 27.27, 26.32.8.12.30.19.24.8.6.1.10, 4.22.17.10.19.9.8.19.28.3.9} +{23.24.11.31.10.31.18.28.13.18.6, 3.21.6.13.12.18.25} +{27.25, 5.19.1.26.20.6.20} +{11.21.16.27.16, 29.3.15.17.12.29} +{27.3, 28.14.32.29.2.3.4, 29.27.13.29.10.2, 10.16.18.9.27.2.29.32.24.13} +{31.7.14.2, 12.28.12.24.28.15.5.12.30.13.21, 27.1.11.3.25.9.6.6, 25.9.10} +{28.6.8.22.25, 26.32.21.31.27.12} +{29.3.17.17.18.32, 11.21.13.9.19, 20.8.19.14.16.7, 3.27.18.8.4.21.6.32.30.7.5, 14.2.14.11.12} +{12.7.28.26.14.21.18.31.5.15.11, 16.13.19.11.18.13.17.17, 16.31.12.27.25.9.32.29, 17.7.26.30.18.23.4} +{22.17.24.14.21.15.12.18.17.25.11, 20.30.17, 10.2.17.26.16.7.19.6.23.3, 30.4.30.11.13.23.14.24.11} +{2.1.12.19.29.28.3.31.28.28.10, 14.1.15.25.27.23.25.26.28.10, 14.15.31.29} +{1, 25.24.2.32.14.18.16, 25.18.8.3.23.23.5.9.6, 29.27.13.29.10.2, 12.16.13} +{19.16.31.31.29.12, 2.13.9.28} +{32.1.31, 19.19.25.22.11.6.15.3.2.19, 4.22.17.10.19.9.8.19.28.3.9, 25.6, 21.23.13} +{32.3.5.9.17.15, 16.28} +{32.15.20.28.5.1.23.4, 27.6.13.24.21.27.28.22.3.7.4, 24.32.27, 22.9.15.19.12} +{18.9.21.2.31.8.32, 27.22.11.13.21.25.5.1.27.21.27, 11.30} +{25.5.30.7.16.12.21.12.11.16, 18.4, 19.17.13.12.32.16.3} +{15.10.30.1.4.12.8.20, 1.1.1} +{28.31.10.28.22.26.16.15, 28.5.12.9.2.27.11.11.2, 12.10, 24.24, 22.24.22.25.15.23.13} +{28.2.27.1.20, 24.18.16} +{13.32.15.32.26.14.32, 22.23.22.30, 10.12.23.22.23.22.20.17.17.9, 9.22.10.15.5.15} +{31.24.26.18, 11.12.6.21, 20.24.14.15.4.21.12.27.4.12, 27.2.10.4.25.14.2.15.4} +{4.14, 28.2.27.1.20} +{10.32.14, 25.6.12.16.1, 4.18.29.9.16.10, 25.17.18.17.27} +{17.26.18, 14.21.5.28.3.32.24.14.25.31, 16.5.23.17, 12.16.2.4.15, 6.20.14} +{29.14.31.25.7.32.23, 1.1.2, 29.27, 4.31} +{24.24, 1.18.29.30.22.14.3.20.15.21.20, 12.7.28.26.14.21.18.31.5.15.11, 11.12.6.21, 14.6.10.29.25.26.20.24.24} +{7.19.6.17.15.26.21.9, 7.23.1.24.29.13.31.19.23.17.7, 2.6.15.26.23.26.24, 30.25.8.24.6.29.31} +{2.22.19, 15.11.26.1.30.6.23.5, 10.22.30.16.2.21.17.13, 30.20.3.2.5.15.8.7.17, 7.31.4.20.17} +{10.27.7.24.26.11.31.20.29, 8.2.18.23.5.16.17.1} +{5.27.21.1.29.29.28, 1.30.31.31.20.16.7} +{23.22.10.1.14.24, 8.1.29.18.22} +{21.7.23.9.16.5.18.14, 18.7.10.27.17.24, 25.9, 10.18.12.27.24.30.32.7.11.5.13} +{7.30.5.10.10.5.30.14.9.18, 31.17, 7.12, 28.25.11.22} +{27.11.15.9.24.31.18.4.1.30.20, 8.2.18.23.5.16.17.1} +{24.31.8, 9.28.10.26.14.26.15.14, 25.30.1.4.24.11, 6.21.30.7, 28.23.2.30.3.8.1.15.15.14.13} +{6.14, 25.10.29.3.6.21.3.31.13, 12.14.20.8.28.4} +{6.19.6.4.9.11.32.17.17.3.15, 4.13, 9.8.23.2.20.16, 24.12, 16.24.3.30.15.22.31.2} +{7.21.8, 24.12, 18.4.14.29.3, 32.3.23.7.2, 19.30.27.26.21.7.18} +{25.10.4.28.3.31.19, 28.9.3.16.17.21.23.30, 28.2.27.1.20, 1.15.17.6.28.25.24.31.27.9} +{16.21.13.1.4, 15.26.24.31.16.15.17.22.8.30.3, 20.20.7, 4.22.7.19.25} +{5.9.19.6, 12.6.14.23.19.21.9.12, 23.28.1, 3.3, 2.4.25.32.16.22.26.13.17.18} +{25.4.32, 10.11.25.2.24.18.18.21.6.26.21, 8.21.8.23.4.18, 10.22.1} +{7.13, 9.10.19.18.15.11.22.32.32.14.9, 2.1.3.30.24.17.9, 4.11.22.4.19.24.4.28.6.8.22} +{14.13.9.13.11.5.5.2.2.32.12, 12.1.28.22.25, 23.20.12.16.15.2, 28.28, 19.9.32.23.13.24.1} +{1.20.22.26.2.6.11, 2.11.32.25.23, 22.9.15.19.12, 9.3.3, 21.28.17.22.10.27.4.20.2.32} +{7.7.22.24.17.32.17.25.28, 2.27.15.14, 6.29.32.13.30.3.16, 30.24.32.15.14.10.11} +{1.15.17.6.28.25.24.31.27.9, 24.11.5, 15.10.30.1.4.12.8.20, 8.17.9.15.21.28.1.7.1.3.6} +{27.4.15.14.19.6.12, 24.9.15.1.14.29.6.4, 8.9.25.25.26.30.31.31.2.32.7, 15.9.8.20.27} +{26.14, 12.10} +{15.11.26.1.30.6.23.5, 26.5.29.7.28, 7.19.10.12.31.1.27.13.19, 28.6.8.22.25, 3.18.18} +{29.11.20.22.27, 25.4.4.1.13.32.26.20.20.3, 5.8, 26.8.28} +{3.22.18.1.5.14.9.6.14, 28.25.10.25.19.15, 25.17.9.16.17.31.23.29.24} +{20.8.19.14.16.7, 15.23.26.20.27.7} +{9.28.24, 15.28.24, 19.26.24.27.6.24.16.27.32.29, 30.12.28.2, 5.10.2.11.21.9.19} +{13.28.12.6, 28.25.11.22, 28.20.8.9.9.28.30.29, 22.18.20.23.15.9.12} +{15.23.26.20.27.7, 29.14.31.25.7.32.23, 14.29, 3.27.18.8.4.21.6.32.30.7.5, 22.12.22.28} +{16.13.26.18.9.29.11.17.1.24.26, 17.19.1.22.11.7.22.1.14.28.11} +{14.17.7.30.8.25.26.4, 15.17, 8.13.9.31.20.20.24.7.23.31.28} +{28.2.27.1.20, 21.17.27.23.15, 2.13.9.28, 25.31} +{29.1.7.26.25.11.22, 11.7.31.15.22, 3.18.18, 3.15.2.23.22.2.16.14} +{21.7.7.11, 13.3.20, 3.22.18.1.5.14.9.6.14, 31.18.32.11.7.25.20.5} +{5.4.8.25.12.27.2.29.28.3, 19.12.20.24.32.13.11.23.26} +{18.13.6.12.26.26.26.29.18.20.1, 9.19.7.13.13.25, 4.19.16.15.5.2.25.8.28.14.2} +{23.8.13.22.21, 23.5.7.12.11.23.10, 4.26.23.6.19.31.10.4.22, 4.7.1} +{31.9.3.5, 26.11, 16.9.32.14.3.7.8.7.21.22, 16.20.29.26, 6.25.17.32} +{17.25.26.23.32, 6.11.31.23.12.8.30.14.27} +{1.21.28.4.23, 30.30.17.5.30.21.19.5.22.22.14, 9.26.1.16, 27.27.25.10.31.10.21.22.21.16.12, 8.9.22} +{19.17.13.12.32.16.3, 5.14.29.2.23.16.20.22, 13.12, 21.18.2.1} +{18.15.14, 6.9.29.17.4.32, 8.3.3.25.25.15.7.13.21.18, 32.8.5} +{18.29.5.1.10.21.2, 7.19.6.17.15.26.21.9} +{15.21.23.30.9.25, 4.26.5.26.21.28.17.24.25.23} +{16.29.6.23.13.28.31.6.19.26.15, 31.29.4.29.24.30.30.32.10.23, 17.27, 2.15.18.21.5.21.4.7.30} +{28.26.25.7, 15.5.1.31.28.10.8, 17.17.14.28.6.30} +{30.32, 32.30.18.17.1.14.12.18, 9.30} +{9.26.1.16, 18.21} +{25.28.3, 22.23.25.28.5.27.9.9.24.31.10, 4.15.20.23.12.16.2.16.17, 29.5.18.27.3.21.18.6.14, 28.14.32.29.2.3.4} +{27.11.14.17.24, 24.17.31.20.12.9.19.29.18, 23.28.3.30.15.31.32.3.21.9.19} +{6.10.25.12, 4.14.32, 1.10.21, 29.1.2.14.14} +{32.6.8, 13.25.10.25.8.16} +{18.19.11.20.13.13.11, 3.10.27.4.5.6.19.12.28.12, 11.30} +{21.5.17.19.15.25.18.21.24.9, 30.12.9.25.24.6.7.24.29, 17.14.7.3.2.18.20.23.18.5} +{8.6.6.5.8.8.12, 16.5.23.17, 3.13, 9.31.4.14.31.10.17.5.2, 9.3.3} +{25.4.32, 23.10.13.32.14.20.16.11.14, 10.22.30.16.2.21.17.13, 21.6.22.28.12.23.11.22} +{6.13.31.5.7.26, 12.11.17.1.2, 23.2.22.7.32.3.27.6, 17.24.30.6.32, 5.31.8.1.5.13.21.28.29.19.2} +{26.12.27.2, 6.17.10.10.7.9.27.8.29, 30.24.23.25.32.18.22.12.29.9.22, 29.3.17.17.18.32} +{19.10.26.19.5.21.30.23, 25.17.9.16.17.31.23.29.24, 6.9.29.17.4.32, 24.13.1.8} +{23.20.24, 32.6.13.8.32, 2.22.19, 8.3.18.13.30.20.27.26.17.28} +{20.1.24.3.30.31, 23.28.20.25.30.24.15, 27.4.17.17.32.8.16.15.17.13, 8.32.30.1} +{6.18.1.4.18.23, 31.21.22.14.8.21, 10.18.12.27.24.30.32.7.11.5.13} +{19.2.26.21.16.11.2.2, 25.10, 3.19.11.6.5} +{17.22.12.10.30.11, 12.3, 6.25.17.32, 15.28.24} +{15.10.30.1.4.12.8.20, 19.3.23.4.4.21.23} +{6.1.8.6.30.29.30, 27.16, 27.5.15.1.15.16.21, 7.7.22.24.17.32.17.25.28} +{4.27.32.18, 10.5.23.5.32.9.18.5.30, 17.10.17.22.20.25.14.13} +{12.27.23.32.1.1.9.29.13, 1.1.2.1, 28.20.8.9.9.28.30.29} +{19.26.32.13.1.12.30.26.22.25, 8.1.29.18.22, 21.20.28.19.27.9} +{5.10.2.11.21.9.19, 16.5.14.21.32.17.23.3.4.26, 11.10.22.18, 1.20.18.25.3.24.25.10.9, 8.32.30.1} +{9.16.2.16.22.24.17.31.14.21.17, 7.21.8, 21.4.11.18, 22.25.4.28.9.20.12.13, 28.11.27.21.14.16} +{10.32.14, 24.13.1.8, 18.4} +{18.19.11.20.13.13.11, 22.28.20.6.32.32, 20.9.29.32.13.7.23, 13.3.8} +{28.11.27.21.14.16, 4.16.22.19.24.21, 4.2.16.13.16.11.19.10.10.25, 23.27.6.26.22} +{23.14.30.27.28.26.26.23.8.32, 9.10.32, 8.21.17.3.6.3.18} +{8.29.6.3, 14.6.10.29.25.26.20.24.24, 7.19.6.17.15.26.21.9, 17.5.3.15.17.13.5, 6.1.8.6.30.29.30} +{6.9.1.10.10.22.6, 32.3.23.7.2, 1.1.1.1} +{15.11.26.1.30.6.23.5, 30.25.17.17.10.29} +{30.27.8.6.11.19, 8.12.4, 31.17.2.30.11, 27.29.1.5.30.6.22.16.23.2.28} +{6.25.17.32, 24.27.18.32.14.9.11.28.9, 30.22.29.21.19.14.3.2.6, 21.31.31.25.5.30.26, 9.21.14.19} +{21.14.25.20.13.31.14.20, 11.32.18.31, 31.29.18.26.1.26.17} +{2.4.25.32.16.22.26.13.17.18, 8.10, 28.8.21.15.16.28.4.16.26.8} +{21.23.17.8.23.11.8.1, 16.28, 28.9.3.16.17.21.23.30} +{8.9.25.25.26.30.31.31.2.32.7, 24.1.29.32.14.15.32.6.15.22} +{27.17.17.19.24.9.14.20, 13.19.2.6.23.19.9.7.21.8.16} +{30.31.13.9, 13.8.20.9.21} +{3.15.2.23.22.2.16.14, 18.9.21.2.31.8.32, 16.8.29.7.21.2.3, 23.24.11.31.10.31.18.28.13.18.6, 10.31} +{29.1, 32.27.18.7.3.4.2} +{1.1.7.32.11.22, 10.11.25.2.24.18.18.21.6.26.21, 19.10.26.19.5.21.30.23} +{11.2.27.3, 3.3, 24.9, 21.28.24.23.3.11.7.12.22.32, 23.3.20.24} +{22.22.27.6.27.15.5.18.21.28.9, 19.7} +{21.14.22.29, 12.21.20.20, 3.9.25.26.7, 24.13.1.8} +{12.7.16.8.21.22.2.16.18, 14.19.20.13.27.2.2, 32.1.23.20.14.12.23.5.32.15, 7.19.10.12.31.1.27.13.19} +{4.15.20.23.12.16.2.16.17, 6.8.7.20.2, 4.18.29.9.16.10} +{1.27.22.23.2.26.32.17.7.9, 22.19.21.11.6.8.29.24} +{24.28.32.21, 22.16.25.18.25.7.24.29.14.8, 4.3.6.27.22.23.10} +{9.2.4.27.26, 1.20.22.26.2.6.11, 26.19.3.14.8.28.31.10, 13.17.7} +{30.25.24.22, 14.1.11} +{4.14.32, 4.3.20.27.9.1.18.30.12.5.19, 3.14.1.14.17.28.29.16, 31.4.7, 8.25.20.3.15.24.7.4.24.5.30} +{6.11.31.23.12.8.30.14.27, 15.23.26.20.27.7, 22.8.20.1.10.28.6.27, 29.5.32.20.11.7.13.24.17, 10.31.25.31.24.16.17} +{13.25.10.25.8.16, 22.17.9.11.25.15.3.9, 11.11.11.4.23.21.25, 14.8.15.30.7.29.27.31.4, 6.5.27.19.13.26.1.18.9} +{29.3.15.17.12.29, 6.17.26.25.27.11.10.9, 18.13.9.3.18.15.2, 24.15.15.17.22, 20.6.26.3.30} +{1, 24.3.23.25, 4.25.12.10.15.9.18.9, 21.9.27.22.32} +{19.7.29.31.3.20.7.21.25.27.29, 13.25.10.25.8.16, 4.1.24.24.28.24.18} +{26.31.16.18.22.13.32.23.9.20, 14.11.25, 13.26.17.3.2.19, 7.10.17.21.11.29.17.25.19.4.29, 29.14.31.25.7.32.23} +{13.30.24, 8.11.20, 29.23.15.25.1.6.6.10, 4.9} +{22.10.16.8, 19.3.23.4.4.21.23, 30.16.3.21.10, 30.32, 23.10.13.32.14.20.16.11.14} +{8.11.20, 21.17.31.10.31.13.9.26.6.14, 21.20.28.19.27.9, 12.7.16.8.21.22.2.16.18, 1.22.29.5.16} +{6.5.27.19.13.26.1.18.9, 1.1.1.2.1, 26.28.14} +{29.25.29.16.32.11.15.25.5.22.3, 26.26.22.21.14.11.29.19.14.24, 12.4.12.13.25.30.30.8.9.12, 24.1.29.32.14.15.32.6.15.22, 3.4.22.19} +{18.30.11.17, 17.7.26.30.18.23.4, 23.17.25.4.1.16.29.10, 24.23.24.4.15.25.17} +{21.15.18.18.30.3.20, 28.25.29.4.13.5.6} +{14.19.26.15.22.23, 3.21.16.24.23.12.16.32.3} +{26.5.29.7.28, 32.6.15.26.14.15.3.19, 26.17.9.13.4.25.32.2.24.9} +{2.27.15.14, 8.16.6, 30.9.24, 30.23.10.1.10.7.22.28.18.11.17, 24.24} +{32.1.23.20.14.12.23.5.32.15, 28.30.24.16.17.28.2.13.10} +{24.11.5, 10.20, 13.17.7, 27.24.11.31.21.6.29.17.24.18, 28.26.25.7} +{4.7.1, 11.10.22.18, 23.27.27.16, 31.18.25.1.14.29.25.5.22.30} +{26.9.17.1.18.19.1.11.18.29.3, 1.10.21, 1.30.31.31.20.16.7} +{29.30.7.31.22, 6.27.29.14.8.12.26.3.21.4.1} +{1.20.18.25.3.24.25.10.9, 3.19.11.6.5} +{18.31.32.28.1.4.24.24.12.25, 22.29.29.11} +{24.9, 32.27.18.7.3.4.2, 28.5.12.9.2.27.11.11.2, 26.18.32.20, 25.29} +{8.13.1, 8.9.21.16.29, 13.19.2.6.23.19.9.7.21.8.16, 29.1} +{18.18.5.11.7.4.25, 6.19.6.4.9.11.32.17.17.3.15, 30.27.8.6.11.19} +{30.16.3.21.10, 12.14.20.8.28.4, 13.8.20.9.21, 30.31.13.9, 17.7.26.30.18.23.4} +{29.3.15.17.12.29, 27.17.15.7.28.20, 30.23.10.1.10.7.22.28.18.11.17} +{14.4.19.27.28.24.19, 18.7.10.27.17.24, 4.3.6.27.22.23.10, 31.5.6.4.8.29.3, 22.10.12.23.9} +{22.13.22.21.25.17.8, 2.9, 30.2.17.8.14, 32.1.23.20.14.12.23.5.32.15, 22.15} +{14.23.31.5.5.15.17.12.17.7.3, 23.6.27} +{1.1.2, 7.16.20.17, 25.10, 22.23.18.18.9.8.23.7.23.23.16, 1.18.29.30.22.14.3.20.15.21.20} +{5.14.29.2.23.16.20.22, 3.21.16.24.23.12.16.32.3, 3.9.11.23.32.26.24.28, 9.30} +{20.1.24.3.30.31, 5.13.23.19.28.26.27.6.1.22, 5.24.24.9.32.26.31, 26.13.4.7.13.11.3} +{10.28.7.16.31, 6.29.6.13.14.24.10.4.14.28} +{11.19.23.3.6.11, 13.14.13.10.28.26.9.18.27.21, 5.14.27.15.11.17.3.10.27.25, 11.22.28.8.12.23.25.15.21.28, 26.32.21.31.27.12} +{22.17.24.14.21.15.12.18.17.25.11, 19.5.20.3.4.2.3, 3.29.19.2.24} +{7.5.28.8.17.26.31.10.15, 16.13.19.11.18.13.17.17} +{11.6.11.29.4.5.24.6.26.12, 5.21.27.13.14.11.2.16.20, 25.32.24.24.28.15.16.10, 21.5.11.18, 17.8.31.32} +{7.27.20, 11.30, 8.16.6, 8.24.11.13.25.19, 9.16.2.16.22.24.17.31.14.21.17} +{12.24.29.32.32.29.2, 24.23.29.8.24.11.21.10.28.14.27, 1.10.5.22.13, 31.17.2.30.11} +{21.7.7.11, 20.28.22.7.10.28.27.22.14.16, 6.11.31.23.12.8.30.14.27, 12.29.17.2.20.29.1.11.19.8.12, 19.6.13.14.22.13.9.29} +{21.18, 26.14.5.32.10, 32.28.1.32.28.10, 27.4.15.14.19.6.12} +{28.11.27.21.14.16, 21.9.27.22.32, 6.8.7.20.2} +{26.24.9.12.11.15.31.2, 28.5.12.9.2.27.11.11.2} +{8.16.1.16.28.6.3.22.6.23, 11.3.15.28.22.8.14, 18.30.18.31, 8.16.1.16.28.6.3.22.6.23} +{11.19.23.3.6.11, 21.14, 27.22.11.13.21.25.5.1.27.21.27, 7.13.15, 26.9.17.1.18.19.1.11.18.29.3} +{27.2.10.4.25.14.2.15.4, 19.16.26.2} +{4.14.10.19.16, 20.23.29.5.7.30.13.14.22, 30.25.8.24.6.29.31} +{15.31.11.27.19.19.20.5.5, 23.3.32.21.5.14.10.17.1, 20.25.22.19.22, 7.19.10.12.31.1.27.13.19, 20.4.1.16.31.3} +{8.9.22, 8.13.1, 19.11.29.13.15.27.12.15.14.12} +{26.16.12, 11.21.13.9.19, 20.6.26.3.30, 11.1} +{32.8.29.18.31, 3.18} +{6.26.29.10.21.28.20.19, 32.3.5.9.17.15, 31.29.4.29.24.30.30.32.10.23, 20.22.10, 29.3.17.17.18.32} +{8.14.19.18, 13.3.8} +{2.15.14.20.30.26, 30.17.2.25, 22.23.22.30, 10.26.30.15.1, 8.3.18.13.30.20.27.26.17.28} +{21.31.31.25.5.30.26, 31.28.32.4.31.4.7, 26.17.9.13.4.25.32.2.24.9} +{5.4.8.25.12.27.2.29.28.3, 25.9.1.5.9.11.25.4.11.27.32, 17.24.30.6.32, 8.5.24.9.29.32.31.30.13.9.7, 29.27.7.7.3.11.14.26.21.11} +{14.5.13.19.25.12.32.9.13.16.12, 27.6.13.24.21.27.28.22.3.7.4, 30.23.2.13.14.15.29.19.4.12.24, 27.4.15.14.19.6.12, 15.29.32.16.29.12.20.32.13.20} +{10.8.20.11.12.23.22, 11.12.6.21} +{14.19.30.6.4.10.10.10.22.25.11, 20.18.24.14.12.13.9, 2.13.9.28, 19.30.27.26.21.7.18} +{1.26, 9.16.2.16.22.24.17.31.14.21.17, 1.1.1.2.1} +{15.1.8, 4.3.6.27.22.23.10} +{14.11.25, 24.10.10.31.4.29.9, 7.12.23, 21.17.27.23.15} +{1.12.25.26.22.8.15.23, 28.11.27.21.14.16, 32.17.8.24.2.14.5.4.22} +{11.6.11.29.4.5.24.6.26.12, 11.15.11.19.29.10, 22.21.32.15.8.29.5.12.10.29, 31.28.32.4.31.4.7} +{22.19.20.5.2.20, 22.26.1.28.9.9.31, 20.32.5.1.3.20.3.30.27} +{19.17.13.12.32.16.3, 3.9.11.23.32.26.24.28} +{20.20.7, 17.19.1.22.11.7.22.1.14.28.11, 5.12.2.20.1.24.25, 12.13.16.17.29.27.16.14.9.19.9, 31.18.32.11.7.25.20.5} diff --git a/contrib/ltree/data/ltree.data b/contrib/ltree/data/ltree.data new file mode 100644 index 0000000..246443e --- /dev/null +++ b/contrib/ltree/data/ltree.data @@ -0,0 +1,1006 @@ + +1 +1.1 +1.1.1 +1.1.1.1 +1.1.1.2 +1.1.1.2.1 +1.1.2 +1.1.2.1 +1.1.3 +22.19.21.11.6.8.29.24 +26.17.9.13.4.25.32.2.24.9 +10.22.30.16.2.21.17.13 +32.24.11.8.12.23.22.19.11.17.18 +14.30.23.3 +30.18.30.16.29 +11.10.22.18 +31.18.27.15.20.29.29 +13.25.10.25.8.16 +31.24.26.18 +2.4.25.32.16.22.26.13.17.18 +17.25.10.13.21.5.7.22.2 +25.15.11 +12.29.26.18.4.21.28.8.13.3 +23.12.19.25.16.23.22.6.29.4 +28.17.26.9 +5.10.3.9.23.30.23 +24.15.15.17.22 +7.30.5.10.10.5.30.14.9.18 +27.5.22 +26.16.12 +15.28.30.19.31.6.2.2.31 +19.16.26.2 +21.15.18.18.30.3.20 +10.29.26.4.27.17.11 +15.1.6.31.30.13.32.9.10 +30.8.9.14.25.30 +11.17.10 +4.31 +4.14.16.14.1.8.1.22.17.10 +13.17.7 +6.7.7 +31.4.7 +1.14.3.7.3.17.2.29 +11.1 +10.15.16.3 +11.8.18 +22.18.20.23.15.9.12 +18.19.11.20.13.13.11 +22.19.5.22.20.31.23.24.14.24.4 +7.12.1.10.6.17.29.24.24.4 +7.19.6.17.15.26.21.9 +21.14.22.29 +3.15.2.23.22.2.16.14 +25.17.9.16.17.31.23.29.24 +23.22.10.1.14.24 +26.28.14 +27.32.26.21.31.17.32.32 +7.7.25.22.22.26 +30.31.13.9 +29.27 +23.32.5.25.19.9.15.17.15.11 +5.15.16 +10.31.25.31.24.16.17 +7.31.2.28.15.11.17.18.19.23.6 +24.1.10.20.28.18.6.27.20.30.26 +15.8.10 +4.2.16.13.16.11.19.10.10.25 +25.7.3.21.31.12.28 +30.17.25.3.31.11.3.4.1.10 +30.17.2.25 +30.20.3.2.5.15.8.7.17 +25.2.11.20.8.6.22 +19.17.13.12.32.16.3 +27.27.30.11.15.24.9.7.4.30 +13.32.15.32.26.14.32 +6.19.6.4.9.11.32.17.17.3.15 +5.19.1.26.20.6.20 +6.6.22.8 +20.17.14.7 +2.15.18.21.5.21.4.7.30 +9.2.4.27.26 +20.13 +15.29.25 +29.23.15.25.1.6.6.10 +24.24 +19.26.24.27.6.24.16.27.32.29 +12.21.20.20 +1.18.29.30.22.14.3.20.15.21.20 +23.19.17.31.29.13.1.12.5.25 +4.13 +7.23.15.32.28.27.2.2.26 +26.7.22.3.18.21.11 +25.2.3.15.11.19.5.28.25.14 +26.32.8.12.30.19.24.8.6.1.10 +10.13.12.8.4.8.11.30 +8.21.8.23.4.18 +22.21.32.15.8.29.5.12.10.29 +12.4.26.23.25.5.15.7.16 +27.12.4.2.29.22.15 +24.31.2.13.5.23.18.16 +29.32.13.4.1.16.20 +5.18.9.25.31.21.22 +27.22.11.13.21.25.5.1.27.21.27 +29.27.13.29.10.2 +27.11.15.9.24.31.18.4.1.30.20 +17.11.17.4.8.26.26.20.6 +24.2.26.24.14.15.31.23.17.26 +17.22.12.10.30.11 +21.9.27.22.32 +22.16.25.18.25.7.24.29.14.8 +15.3.31.9.27.14.9.8.14.6.32 +11.19.23.3.6.11 +30.32 +23.5.5.17 +20.20.32.29.24.5.5.26.22.32 +22.10.27.19.29.20.29.3.12.14.25 +4.13.22.11.9.13.27.15.7 +30.24.23.25.32.18.22.12.29.9.22 +14.4.19.27.28.24.19 +6.11.31.23.12.8.30.14.27 +18.13.9.3.18.15.2 +1.27.22.23.2.26.32.17.7.9 +15.29.32.16.29.12.20.32.13.20 +13.16.1.27.18.18.19.6.14.4 +32.1.24.29.22.5.9.24.18.3.13 +26.24 +25.5.30.7.16.12.21.12.11.16 +25.4.32 +4.7.1 +26.14 +19.10.26.19.5.21.30.23 +10.13.22.1.8.30.9.24.1.2.1 +14.23.31.5.5.15.17.12.17.7.3 +8.25.20.3.15.24.7.4.24.5.30 +6.17.26.25.27.11.10.9 +5.27.16.3.30 +1.21.28.4.23 +22.10.16.8 +16.5.14.21.32.17.23.3.4.26 +5.21.27.13.14.11.2.16.20 +18.29.13.24.18.3.12.18.12.12 +21.1.4.9.9.31.24.21.3.29 +5.23.31.18.24.32 +14.8.15.30.7.29.27.31.4 +23.8.13.22.21 +8.2.18.23.5.16.17.1 +23.14.12.30.18.4.16.18.7.7 +17.25.2.13.10.27.13.1 +8.9.25.25.26.30.31.31.2.32.7 +30.5 +22.17.30 +6.22.12 +31.18.32.11.7.25.20.5 +28.5.12.9.2.27.11.11.2 +12.3 +17.5.3.15.17.13.5 +18.9.21.2.31.8.32 +23.17.22.1.23.4.29.32.4.1 +13.28.14.2.8.18 +16.18.23.6.31 +24.16.27.10.9 +12.15.10.17.18.13 +28.15.18.27 +21.7.23.9.16.5.18.14 +1.10.5.22.13 +18.30.18.31 +23.24.16.32.13.29 +11.10 +18.7.10.27.17.24 +9.9.13.9.14.27 +30.25 +11.11.9.30.15.29.15.18 +10.22.1 +12.1.1 +29.27.5.22.26 +23.27.27.16 +20.32.5.1.3.20.3.30.27 +6.9.29.17.4.32 +7.13.15 +2.32.8.28.24.20.9.24.25.8.9 +23.1.23.18.12.29 +28.18.6.22.13.8.25 +27.30.12.11.20.15.11.13 +7.12 +18.31.26.18.6.15.18.11 +12.28.12.24.28.15.5.12.30.13.21 +10.16.18.9.27.2.29.32.24.13 +9.10.19.18.15.11.22.32.32.14.9 +23.22.23.14.31.32 +6.26 +2.10.10.4.20.1.12.13 +18.9.26.7 +1.26.15.23.5.31.29.11.19.28.1 +19.7.29.31.3.20.7.21.25.27.29 +8.14.19.18 +23.17.25.4.1.16.29.10 +2.16.3.7.22.18.29.20 +32.27.18.7.3.4.2 +12.2.4.28.21.30.24 +5.13.23.19.28.26.27.6.1.22 +23.17.32.15.23.16.25 +9.31.23.19.5.10.16.4.30.24.5 +10.28.7.16.31 +8.22.32.17.16.28.31.23.22.9 +20.15 +20.20.7 +8.16 +25.6.12.16.1 +14.10.11.30.5.7.6.24.9.30.26 +9.17.13.31.7 +27.19.20.1.31.29.5.22.26.3 +32.3.12.2 +24.27.18.32.14.9.11.28.9 +16.30.10.7.29.4.9.21.22.13.26 +3.1.14.8.9.16.30.22.20 +23.6.27 +12.4.12.13.25.30.30.8.9.12 +32.6.9.26.16.4.4.29.7.11 +21.22.31.24.27 +21.18 +24.31.8 +2.8.13.12.17.23.16.7.11.23 +5.8 +26.31.11.23.3 +5.14.29.2.23.16.20.22 +5.12.2.20.1.24.25 +28.8.21.15.16.28.4.16.26.8 +31.7.14.2 +21.4.11.18 +4.3.6.27.22.23.10 +12.10 +1.29.18.1.21.12.13.27.32.15 +29.28.9.15.8.27.31 +12.24.29.32.32.29.2 +20.18.24.14.12.13.9 +32.25.16 +26.7.5.8.11.9.22.1.6 +19.3.12.12 +14.16.6.29.26.13.14.16.25.26.8 +11.22.28.8.12.23.25.15.21.28 +25.30.1.4.24.11 +5.8.17.30.15.8.19.29.30.11.6 +22.24.22.25.15.23.13 +1.9.18.10.1.26.22.16.17 +5.5.12.31.23.13.17.22.20 +25.32.24.24.28.15.16.10 +3.14.30.5.32.22.29 +4.30.8.20.19.9.30.24.11 +27.4.15.14.19.6.12 +15.31.11.27.19.19.20.5.5 +19.10.8.10.4.19 +16.24.3.30.15.22.31.2 +12.7.28.26.14.21.18.31.5.15.11 +22.17.4.2.22.17 +14.11.25 +7.12.23 +6.14 +22.23.25.28.5.27.9.9.24.31.10 +19.19.25.22.11.6.15.3.2.19 +28.26.26.6.31 +32.3.23.7.2 +2.10.28.1.17.19.32.28 +24.27.14 +9.30 +17.13.19.31.12.18.10.15.14 +17.7.26.30.18.23.4 +32.30.18.17.1.14.12.18 +10.5.5.15.29.2 +25.28.3 +21.5.11.18 +18.31.32.28.1.4.24.24.12.25 +32.6.3.2.12.5.28.1.25 +19.22.29.32.1.21.26.24.23.17 +29.3.15.17.12.29 +5.24.25.15.27.30.20 +24.25.7.27.30.8.26.17 +11.12.6.21 +9.2.10.4 +5.1.5.31 +23.20.12.16.15.2 +7.11 +14.12.31 +2.22.19 +18.7.3.17.13.5.31.6.31.25.29 +17.1.12.20 +21.23.17.8.23.11.8.1 +27.15.15.15 +2.9 +30.23.10.1.10.7.22.28.18.11.17 +19.15.26.19 +31.21.22.14.8.21 +16.13.26.18.9.29.11.17.1.24.26 +24.10.8.25.16 +22.17.7.30.13.24 +16.16.28.24.11 +24.28.32.21 +14.27.29.23.4.1.17.32.6.25.22 +3.14.11.15.21.32.2.15.13 +23.14.30.27.28.26.26.23.8.32 +10.7.9 +23.23 +29.30.7.31.22 +21.4.22.20.24.28.6 +31.28.32.4.31.4.7 +17.13.14.29.27.27.13.12.15 +25.9.10 +21.23.13 +22.29.18.32.13.12.22.31.17.22 +16.28 +7.5.28.8.17.26.31.10.15 +1.10.21 +8.16.6 +12.1.28.22.25 +30.30.17.5.30.21.19.5.22.22.14 +7.13 +11.7.31.15.22 +22.26.32 +20.32.9 +18.30.11.17 +15.6.19.3 +25.3 +20.6.3.26.7.29.28.4 +4.27.32.18 +16.5.10.2.18.8.15.12.32.25.10 +8.6.6.5.8.8.12 +18.29.5.1.10.21.2 +4.16.22.19.24.21 +27.23.2.32.11.21 +16.5.12.5.15.12.24.25.3 +9.18.23 +7.31.4.20.17 +13.28.12.6 +5.31.8.1.5.13.21.28.29.19.2 +5.27.32.21.5.1.11.14 +27.25 +18.27.11.27.9.16.7.6.22.26.27 +10.32.14 +29.26.25.14.24.18.2.13.23.29 +27.2.10.4.25.14.2.15.4 +1.15.17.6.28.25.24.31.27.9 +9.3.3 +3.19.11.6.5 +8.11.20 +14.2.14.11.12 +12.16.2.4.15 +26.11 +1.25.7.9.26.17.31.20.13 +9.28.10.26.14.26.15.14 +27.4.17.17.32.8.16.15.17.13 +11.3.15.28.22.8.14 +26.13.4.7.13.11.3 +16.8.29.7.21.2.3 +25.21.8.17 +11.11.11.4.23.21.25 +20.24.14.15.4.21.12.27.4.12 +30.2.17.8.14 +3.29.32.26.8.10.25 +12.18 +7.31 +13.30.24 +11.32.18.31 +30.12.28.2 +1.8 +28.6.11.6.15.22.12.6 +7.10.17.21.11.29.17.25.19.4.29 +6.7.25.16.13.21.7.20.25.12.4 +22.17.9.11.25.15.3.9 +18.24.21.17.11.26.28.22.21.18.10 +19.3.23.4.4.21.23 +3.10.27.4.5.6.19.12.28.12 +20.4.1.16.31.3 +1.22.19.24.8.11 +8.17.9.15.21.28.1.7.1.3.6 +25.4.4.1.13.32.26.20.20.3 +6.29.6.13.14.24.10.4.14.28 +29.27.13.9.28.29.19.13.29.31.27 +10.26.30.15.1 +22.26.1.28.9.9.31 +29.23.1.21.31.8 +3.4.22.19 +24.28.13.26.8.8.31 +9.19.7.13.13.25 +28.4 +19.30.18.11.32.14 +17.27 +31.13 +7.26.18 +14.24 +30.17.4.5.13.6 +18.13.6.12.26.26.26.29.18.20.1 +5.10 +2.13.9.28 +25.19.27.2.9.20 +24.1.29.32.14.15.32.6.15.22 +32.6.13.8.32 +18.17.6.16.6.10 +26.25.24 +30.12.9.25.24.6.7.24.29 +19.17.12.15 +6.20.14 +9.6.9.21.6.11.29.13.29.20.32 +29.1.7.26.25.11.22 +3.13 +22.9.15.19.12 +12.29.17.2.20.29.1.11.19.8.12 +32.31.11.22.1 +28.26.4.22.13.20.32.27.15 +18.19.12.20.18.17.15.32.18.5 +22.10.18 +1.16.8.18.14.16.21.25.6 +14.14.25 +9.28.30.1.6.25.17.9 +31.32.12.26.31.32.14.23.28 +6.19.29.11.2.32.21.15.32.9 +24.9.27.16.20.21 +13.24 +16.31.12.27.25.9.32.29 +23.3.20.24 +13.8.20.9.21 +21.6.22.28.12.23.11.22 +9.5 +12.4.24.6.1.13.5.20 +30.9.24 +32.15.20.28.5.1.23.4 +15.9.8.20.27 +3.20.19.10.17.27.3.6.22.23 +16.9.32.14.3.7.8.7.21.22 +7.32.10.3.30.12.14 +8.13.9.31.20.20.24.7.23.31.28 +10.11.25.2.24.18.18.21.6.26.21 +8.1.29.18.22 +17.9.32.31.21.31.23.17.10.32.9 +29.29.17.31 +31.29.18.26.1.26.17 +11.14.21.24.10.7.29.23.24.28 +14.17.7.30.8.25.26.4 +22.15 +4.10.28 +10.12.9.6.6.26.14.8.23.1.25 +23.12.1.5.32.25.8.24.1.25 +7.30.19.25.23.15.14.29 +10.31 +6.26.29.10.21.28.20.19 +29.25.30.15.21.3.25.26.26 +14.19.30.6.4.10.10.10.22.25.11 +28.27.24.14 +6.8.7.20.2 +12.13.5.31 +22.30 +3.9.25.26.7 +20.28.22.7.10.28.27.22.14.16 +13.19.2.6.23.19.9.7.21.8.16 +20.30.28.15.17 +7.19.10.12.31.1.27.13.19 +23.10.5.26.12.4.20.4 +31.5.6.4.8.29.3 +17.13.8 +2.2.18.18.3.3.18.8.10.8 +12.21.15.27.24.15.8.24.24.26 +25.24.2.32.14.18.16 +10.16.19.7.15 +15.7.3.14.23.19.26 +22.22.27.6.27.15.5.18.21.28.9 +4.21.28.5.16.29.5.21 +22.31.2.32.32.11.26.23.19 +19.20.25.7.27.28.27.17.9.3.1 +15.11.26.1.30.6.23.5 +16.20.29.26 +21.18.2.1 +5.9.19.6 +4.22 +11.30.20.15.18.32.1.18.25.26.8 +7.7 +8.10 +24.32.27 +21.21.10.27 +1.12.25.26.22.8.15.23 +15.17.2.32.7 +8.27.3.4.12.26.16 +29.14.31.25.7.32.23 +1.30.18.31.12.25.4.19.28.12.15 +22.13.22.21.25.17.8 +20.23.29.5.7.30.13.14.22 +23.2.22.7.32.3.27.6 +30.15 +14.4.23.4.23.22.11.6.26.5 +15.10.30.1.4.12.8.20 +32.19.20.24.23.31.8.32.16.29 +20.31.13.12.19.2.26.16.16.22.28 +21.20.24.25.6.26.23 +7.14.22.29.30.14.25.1.9.26.25 +6.9.1.10.10.22.6 +22.13.22.8.30.32.10.24 +11.18.4.8.3.13.14.28.18.31 +9.28.24 +21.15.31.24.29.24.26.12.20 +25.22.2.25.6 +19.12.30.2.21 +21.7.7.11 +29.11.20.22.27 +5.15.10.3.23.13.32.23 +21.18.30.19.24.24 +31.9.3.5 +4.2.6.20.7.8 +8.16.1.16.28.6.3.22.6.23 +2.19.4.1.15.7.8.9.17.29 +3.20.16.13.29.20 +32.16 +6.25.17.32 +3.22.18.1.5.14.9.6.14 +24.23.24.4.15.25.17 +32.8.29.18.31 +17.14.7.3.2.18.20.23.18.5 +9.3.31.18.12.3.9.29.10 +15.28.24 +22.11 +29.27.7.7.3.11.14.26.21.11 +9.7.31.11.8.23 +23.3.32.21.5.14.10.17.1 +29.9.25.27.15.16.32.26.6.32 +12.25.32.2.27.3.3.16 +1.1.7.32.11.22 +27.24.11.31.21.6.29.17.24.18 +16.5.23.17 +4.15.20.23.12.16.2.16.17 +3.1.13.22.24.14.12.31.3.4 +16.23.30.12.31.31.19.14 +22.23.18.18.9.8.23.7.23.23.16 +3.25 +32.27.13.6.7 +22.31.21.13.13.26.11.5.19 +14.19.20.13.27.2.2 +7.16.20.17 +6.20 +15.9.11.20.22.15.11.13 +2.14.10.4.17.17.8.4.27.20 +26.31.6.8.29.8.24 +10.18.12.27.24.30.32.7.11.5.13 +26.9.20.12.22.22.32 +9.18.30.11.29.32.7.19.2 +24.13.1.8 +2.24.4.5.24.32 +25.18.8.3.23.23.5.9.6 +17.8.31.32 +2.12.30.22.12 +22.10.12.23.9 +20.23.7.11.11.31.18.16.3 +8.17.25.26.15.25 +4.5.9.4.15.19.8.26.17.26.3 +27.6.13.24.21.27.28.22.3.7.4 +24.3.23.25 +30.12.6.30 +19.2.26.21.16.11.2.2 +1.31.3 +4.2.2.32.24.25.31.3 +18.21 +23.17.28.31.28 +18.4 +15.30.17.5.32.28.2.18.27 +28.1.3 +28.11.11.30.20.11.32 +32.3.5.9.17.15 +4.14.32 +29.25.29.16.32.11.15.25.5.22.3 +3.18 +21.32.13.21 +14.1.11 +26.12.27.2 +1.20.18.25.3.24.25.10.9 +26.19.3.14.8.28.31.10 +25.11.24 +15.23.26.20.27.7 +24.9.8.12.29 +19.10.4.30.32.4.12 +14.21.6.5.26.9.32.16.25 +1.26 +31.17 +2.28.5.17.6.32 +27.23.20.30.7 +19.22.21.13.27.13.15 +26.24.9.12.11.15.31.2 +31.18 +22.12.22.28 +32.2.11 +22.23.22.30 +29.3.17.17.18.32 +22.25.4.28.9.20.12.13 +20.17.18.21.1 +21.8.9 +6.1.8.6.30.29.30 +23.10.13.32.14.20.16.11.14 +20.22.10 +31.30.23.7.7.24.32.10.11.1.31 +26.16.12.3.27.9.28 +6.27.26.1.20.24.6 +3.14.1.14.17.28.29.16 +5.27.28.26.14.15.6.20.1.31.13 +20.30.9.9.14.12.29 +10.29 +12.27.23.32.1.1.9.29.13 +25.10.29.3.6.21.3.31.13 +17.8 +12.11.20.20.29 +16.21.13.1.4 +20.29.18.16.2.21.23.11 +19.16.31.31.29.12 +20.4.27.31.1 +32.6.8 +30.3.16.26.7.27.26.9.27.21.18 +6.2.32 +18.5.6.31.5.15.15 +18.4.14.29.3 +23.25.23.11.7.23 +17.19.1.22.11.7.22.1.14.28.11 +20.14.11.2.10.14 +27.21.28.24.7.2.24.23.8 +22.30.31.24.23.22.5.20.28.1 +28.28 +29.20.1.11.21.16.1.2.14.28 +6.17.10.10.7.9.27.8.29 +19.12.20.24.32.13.11.23.26 +6.21.30.7 +12.17.10.7.17.16 +32.1.21.1.16.29.21 +9.26.1.16 +8.16.30.29.19.22.28.24.2 +16.19.17.30.30.5.17.24.27 +21.14 +15.17 +8.29.6.3 +30.25.17.17.10.29 +10.28.22.29.13.19.6.7.6.14 +12.11.17.1.2 +26.31.7 +28.30.24.16.17.28.2.13.10 +18.18.19.16.14.16.21.10.25 +14.1.15.25.27.23.25.26.28.10 +14.15.31.29 +24.12 +30.27.8.6.11.19 +32.17.8.24.2.14.5.4.22 +16.5 +28.23.2.30.3.8.1.15.15.14.13 +15.5.1.31.28.10.8 +9.31.4.14.31.10.17.5.2 +17.24.30.6.32 +17.29.21.10.18.8.16.26.18.21.26 +2.31.25 +22.20.30 +2.14.12.13 +14.30.13.5.26.9.22.23.14.10 +25.17.18.30 +30.4.30.11.13.23.14.24.11 +29.30.21.8.16.23.32 +6.10.25.12 +22.32.6.6.3.8.24.6.25.29 +16.24.7.25 +16.13.2.19.14.29.31.30.23.15.12 +22.3.6 +19.31.14.25.5.8.21.11.13.20 +32.29.24.31.25.6.9 +19.30.27.26.21.7.18 +16.9.14.28.6.21.31.31.26 +32.4.19 +13.26.17.3.2.19 +1.22.29.5.16 +10.5.23.5.32.9.18.5.30 +7.27.20 +31.21.14.20.1.22.2.5.3.27.12 +14.19.26.15.22.23 +11.21.13.9.19 +14.9.15.21.21.31.1.29 +6.19.3 +31.30.12.20 +23.27.6.26.22 +17.10.17.22.20.25.14.13 +3.5 +21.17.18.32.7.8 +27.3 +29.29.18 +27.27.25.10.31.10.21.22.21.16.12 +26.32.21.31.27.12 +20.6.26.3.30 +16.9.29 +10.20 +5.2.32.19.13.29.12.13.31.29 +8.24.11.13.25.19 +12.13.16.17.29.27.16.14.9.19.9 +12.7.16.8.21.22.2.16.18 +10.26.27.23.4.31.11.25.29 +23.28.3.30.15.31.32.3.21.9.19 +8.12.4 +26.18.32.20 +16.29.6.23.13.28.31.6.19.26.15 +29.14.12.9.17.5.32 +28.26.25.7 +24.31 +32.6.15.26.14.15.3.19 +27.1.11.3.25.9.6.6 +27.18 +32.25.3.6 +21.5.17.19.15.25.18.21.24.9 +7.23.1.24.29.13.31.19.23.17.7 +13.28.9.3 +5.14.27.15.11.17.3.10.27.25 +10.27.7.24.26.11.31.20.29 +30.23.2.13.14.15.29.19.4.12.24 +26.9.17.1.18.19.1.11.18.29.3 +4.25.12.10.15.9.18.9 +14.21.5.28.3.32.24.14.25.31 +27.27 +15.7.5.12.7.9.3.28.26 +13.16.4.28 +21.9.32.1.27 +25.6 +23.12.11.11.15.16.22.31.32.5.8 +29.10.12.17.12.16 +8.9.21.16.29 +12.6.14.23.19.21.9.12 +14.6.10.29.25.26.20.24.24 +29.5.18.27.3.21.18.6.14 +4.1.24.24.28.24.18 +9.5.9.3.23.9.25.14.1.29.28 +25.10 +21.10.20.9.3.16.9.10.20 +20.8.19.14.16.7 +21.28.17.22.10.27.4.20.2.32 +1.28.19.8.25.6.20.27.29.27 +12.23.3.19.29.15.12.6 +27.17.17.19.24.9.14.20 +3.29.19.2.24 +19.9.32.23.13.24.1 +27.3.3.11.21.4.25 +25.16.9.6 +4.14.17.12.20.17.1.22.3 +19.7 +28.15.25.7.13.6.19.2 +1.28.3.22 +13.14.13.10.28.26.9.18.27.21 +26.25.10.10.13 +27.26.29 +6.27.29.14.8.12.26.3.21.4.1 +4.14.10.19.16 +5.4.8.25.12.27.2.29.28.3 +8.21.17.3.6.3.18 +3.6.24.21.20.32.3.4.26.5 +27.17.15.7.28.20 +21.17.27.23.15 +11.29 +26.18 +27.5.15.1.15.16.21 +21.28.24.23.3.11.7.12.22.32 +3.26 +31.29.4.29.24.30.30.32.10.23 +24.9.15.1.14.29.6.4 +13.8.23.13.11.18.24.21.11.24.10 +31.17.2.30.11 +8.5.24.9.29.32.31.30.13.9.7 +21.14.13 +3.21.6.13.12.18.25 +2.13.9.23.21.2 +22.17.24.14.21.15.12.18.17.25.11 +25.28.30.24 +24.2.6.7.16.7.28 +22.28.20.6.32.32 +1.13.16.27.11.16.30.2.9.18.4 +11.17.17.24.11.23.17.17.18.10.22 +22.16 +4.22.17.10.19.9.8.19.28.3.9 +12.27.30.12.24.2.20 +23.20.24 +28.6.8.22.25 +16.2.14.3.26.11 +15.4.15 +19.11.29.13.15.27.12.15.14.12 +28.20.8.9.9.28.30.29 +30.16.3.21.10 +11.1.3.28.30.21.24.14 +32.31.26.19.13.29.4.25 +8.13.6.12.18.7 +9.21.28.8.12.15.3.13.10.11 +4.19.16.15.5.2.25.8.28.14.2 +28.25.29.4.13.5.6 +25.24.29 +1.19.22.11.14.7.32.23.19.14 +22.22.10.30.5.15.25.21.19.11 +21.30.19.6.28.1.32.2.14.14 +26.5.29.7.28 +1.4.14.32.14 +19.2.9.29.6 +6.13.31.5.7.26 +27.11.14.17.24 +15.1.8 +11.15.11.19.29.10 +10.3.19 +5.3.29.9.22 +5.20 +13.9.9.27.31.11.25.9.27.22.13 +10.2.17.26.16.7.19.6.23.3 +30.8.18.5.20.6.15 +32.1.23.20.14.12.23.5.32.15 +3.9.11.23.32.26.24.28 +4.26.23.6.19.31.10.4.22 +1.10.23.25.5.11 +4.26.2.2 +14.30.2.21.15.16.13 +11.6.11.29.4.5.24.6.26.12 +19.26.32.13.1.12.30.26.22.25 +30.25.24.22 +6.11.11.5.16.8.14.12.9 +2.24.5.3.4.10.27.26.17.28.16 +3.18.8.22.7.28.32.31.3 +25.9 +12.14.20.8.28.4 +3.21.16.24.23.12.16.32.3 +8.3.18.13.30.20.27.26.17.28 +3.3 +8.32.30.1 +26.14.5.32.10 +24.20.23 +8.16.20.24.20.6.10.21 +23.12.32.22.19.1.22.4 +24.21.14.25.11.3.20.6.6.16 +12.4.10.17.4.10.23.3 +19.6.24.32.30.13.6.25.8.28 +1.3.15.11.11.25.24.21.19 +3.11.18.21.5.20.30 +9.23.21.22.5.29.15.21 +17.24.15.27.3.32.4.22.20.6.24 +23.24.11.31.10.31.18.28.13.18.6 +4.21.9.1.2.14.8.17.13.26 +23.28.20.25.30.24.15 +4.9 +8.13.14.11.11.29.22.4.4.10 +3.10.4.5.28.11 +29.10.17.11.28.12.18.5.19.15.21 +22.19.20.5.2.20 +19.6.13.14.22.13.9.29 +1.10.4.18.22.23.24 +28.11.27.21.14.16 +1.11.10.19.6.1.26.17.2.22 +24.17.31.20.12.9.19.29.18 +24.17.24 +19.5.20.3.4.2.3 +18.31.32.29.22.1.31.11.28 +15.21.22 +10.8.20.11.12.23.22 +9.8.23.2.20.16 +21.32.13.22.3.13.31.23.14.12.9 +4.22.7.19.25 +1.30.31.31.20.16.7 +22.29.29.11 +20.30.17 +30.24.32.15.14.10.11 +30.25.8.24.6.29.31 +9.21.14.19 +21.31.31.25.5.30.26 +20.25.22.19.22 +25.17.2.20.20.3.29.21.3.12 +29.6.12.31.20.23.32.20 +2.30.26.10.14.31.18.2 +28.6 +20.5.4.9.31.14.26.6 +20.1.24.3.30.31 +13.3.8 +25.29 +2.15.14.20.30.26 +5.24.4.31.3.16.25.17.13.26.11 +29.5.32.20.11.7.13.24.17 +24.9 +17.17.14.28.6.30 +4.11.22.4.19.24.4.28.6.8.22 +11.2.27.3 +28.25.10.25.19.15 +14.26.25.4.12.26.8 +3.10 +4.14 +28.31.10.28.22.26.16.15 +28.2.27.1.20 +32.8.5 +21.17.31.10.31.13.9.26.6.14 +13.7 +9.14.27.31.26.21.25.3.20 +17.29.31.8.24.10.18.27.17 +2.1.3.30.24.17.9 +18.15.14 +6.29.32.13.30.3.16 +27.17.3.18.2.13.18 +5.27.21.1.29.29.28 +13.3.20 +14.21.22 +4.18.29.9.16.10 +29.1.2.14.14 +8.13.1 +2.32.10.13.12 +9.10.32 +15.8.3.15.27.14.29.28.6.5.25 +12.22.20.4.12 +14.13.9.13.11.5.5.2.2.32.12 +9.21.20.29.1 +28.14.32.29.2.3.4 +28.9.3.16.17.21.23.30 +24.32.17.23.24.19.23.9.20.18 +15.26.24.31.16.15.17.22.8.30.3 +6.5.27.19.13.26.1.18.9 +2.6.15.26.23.26.24 +4.26.5.26.21.28.17.24.25.23 +25.14.5.32.25 +3.27.18.8.4.21.6.32.30.7.5 +2.11.32.25.23 +8.9.22 +18.18.5.11.7.4.25 +14.3.17.1.14.15.21.4.26 +10.3 +13.12 +27.31.2.16.29.6 +8.2 +27.18.10.4.22 +16.14.3.17.17.26.12.19.19.30 +20.9.29.32.13.7.23 +8.3.3.25.25.15.7.13.21.18 +4.16.7.25.21.7 +17.25.26.23.32 +20.3.1.8.8.30.20 +31.18.25.1.14.29.25.5.22.30 +21.20.28.19.27.9 +30.16.14.9.5.4.10.7.31 +30.22.29.21.19.14.3.2.6 +8.5.30.29.9.31 +23.28.1 +11.16.16.28.14 +18.6.26.2.13.9.6.11.10.11.16 +11.30 +16.5.6.12 +32.24.29.6 +8.31.22.27 +6.18.1.4.18.23 +28.5.13 +26.26.22.21.14.11.29.19.14.24 +29.1 +24.10.10.31.4.29.9 +19.11.10.18.14.13.7.7 +27.29.1.5.30.6.22.16.23.2.28 +3.11.32.11.22.3.7.17.8.13.23 +2.12.14.28.16.21 +24.23.29.8.24.11.21.10.28.14.27 +27.4 +4.11.19.17.2.22.20.18.13.32.15 +7.21.8 +2.27.15.14 +25.9.1.5.9.11.25.4.11.27.32 +25.31 +9.16.2.16.22.24.17.31.14.21.17 +32.1.31 +9.22.10.15.5.15 +12.10.11.9.10.31.4.16.31 +30.24 +15.25.31.11.4.22.16.7.11 +22.8.20.1.10.28.6.27 +10.12.23.22.23.22.20.17.17.9 +3.18.18 +7.7.22.24.17.32.17.25.28 +31.13.9.1.5.12 +25.17.18.17.27 +32.6.31.31 +26.8.28 +14.29 +30.6.4 +27.16 +24.18.16 +27.21.27.5.13.30.17 +23.20.8 +28.14.24.26.6.15.16.32.25.13.8 +13.1.6.17.28.9.15.30.1.27.14 +5.3.17.29 +13.8.15.3.7.31.5.10.15.30 +19.12.26.24.29.3 +11.21.16.27.16 +23.5.7.12.11.23.10 +29.15.29.8.31.26.1 +4.3.20.27.9.1.18.30.12.5.19 +7.19.12.3.21.19.18.5.2.14.10 +8.26.29.13.7.25.31.28.3.32 +12.16.13 +28.25.11.22 +17.26.18 +18.6.2.2.24 +24.11.5 +17.3 +21.14.25.20.13.31.14.20 +23.31.27.16.8.30.20.27 +3.32.2.29.3.32.28.11.29.30 +5.10.2.11.21.9.19 +16.27.8.17.14.17.21.29.14 +25.10.4.28.3.31.19 +16.13.19.11.18.13.17.17 +3.26.32 +5.13.23.4.9 +26.31.16.18.22.13.32.23.9.20 +32.28.1.32.28.10 +15.21.23.30.9.25 +1.20.22.26.2.6.11 +2.1.12.19.29.28.3.31.28.28.10 +14.5.13.19.25.12.32.9.13.16.12 +5.24.24.9.32.26.31 diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out new file mode 100644 index 0000000..984cd03 --- /dev/null +++ b/contrib/ltree/expected/ltree.out @@ -0,0 +1,8134 @@ +CREATE EXTENSION ltree; +-- max length for a label +\set maxlbl 1000 +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + amname | opcname +--------+--------- +(0 rows) + +SELECT ''::ltree; + ltree +------- + +(1 row) + +SELECT '1'::ltree; + ltree +------- + 1 +(1 row) + +SELECT '1.2'::ltree; + ltree +------- + 1.2 +(1 row) + +SELECT '1.2.-3'::ltree; + ltree +-------- + 1.2.-3 +(1 row) + +SELECT '1.2._3'::ltree; + ltree +-------- + 1.2._3 +(1 row) + +-- empty labels not allowed +SELECT '.2.3'::ltree; +ERROR: ltree syntax error at character 1 +LINE 1: SELECT '.2.3'::ltree; + ^ +SELECT '1..3'::ltree; +ERROR: ltree syntax error at character 3 +LINE 1: SELECT '1..3'::ltree; + ^ +SELECT '1.2.'::ltree; +ERROR: ltree syntax error +LINE 1: SELECT '1.2.'::ltree; + ^ +DETAIL: Unexpected end of input. +SELECT repeat('x', :maxlbl)::ltree; + repeat +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +(1 row) + +SELECT repeat('x', :maxlbl + 1)::ltree; +ERROR: label string is too long +DETAIL: Label length is 1001, must be at most 1000, at character 1002. +SELECT ltree2text('1.2.3.34.sdf'); + ltree2text +-------------- + 1.2.3.34.sdf +(1 row) + +SELECT text2ltree('1.2.3.34.sdf'); + text2ltree +-------------- + 1.2.3.34.sdf +(1 row) + +SELECT subltree('Top.Child1.Child2',1,2); + subltree +---------- + Child1 +(1 row) + +SELECT subpath('Top.Child1.Child2',1,2); + subpath +--------------- + Child1.Child2 +(1 row) + +SELECT subpath('Top.Child1.Child2',-1,1); + subpath +--------- + Child2 +(1 row) + +SELECT subpath('Top.Child1.Child2',0,-2); + subpath +--------- + Top +(1 row) + +SELECT subpath('Top.Child1.Child2',0,-1); + subpath +------------ + Top.Child1 +(1 row) + +SELECT subpath('Top.Child1.Child2',0,0); + subpath +--------- + +(1 row) + +SELECT subpath('Top.Child1.Child2',1,0); + subpath +--------- + +(1 row) + +SELECT subpath('Top.Child1.Child2',0); + subpath +------------------- + Top.Child1.Child2 +(1 row) + +SELECT subpath('Top.Child1.Child2',1); + subpath +--------------- + Child1.Child2 +(1 row) + +SELECT index('1.2.3.4.5.6','1.2'); + index +------- + 0 +(1 row) + +SELECT index('a.1.2.3.4.5.6','1.2'); + index +------- + 1 +(1 row) + +SELECT index('a.1.2.3.4.5.6','1.2.3'); + index +------- + 1 +(1 row) + +SELECT index('a.1.2.3.4.5.6','1.2.3.j'); + index +------- + -1 +(1 row) + +SELECT index('a.1.2.3.4.5.6','1.2.3.j.4.5.5.5.5.5.5'); + index +------- + -1 +(1 row) + +SELECT index('a.1.2.3.4.5.6','1.2.3'); + index +------- + 1 +(1 row) + +SELECT index('a.1.2.3.4.5.6','6'); + index +------- + 6 +(1 row) + +SELECT index('a.1.2.3.4.5.6','6.1'); + index +------- + -1 +(1 row) + +SELECT index('a.1.2.3.4.5.6','5.6'); + index +------- + 5 +(1 row) + +SELECT index('0.1.2.3.5.4.5.6','5.6'); + index +------- + 6 +(1 row) + +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',3); + index +------- + 6 +(1 row) + +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',6); + index +------- + 6 +(1 row) + +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',7); + index +------- + 9 +(1 row) + +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-7); + index +------- + 6 +(1 row) + +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-4); + index +------- + 9 +(1 row) + +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-3); + index +------- + 9 +(1 row) + +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-2); + index +------- + -1 +(1 row) + +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-20000); + index +------- + 6 +(1 row) + +SELECT 'Top.Child1.Child2'::ltree || 'Child3'::text; + ?column? +-------------------------- + Top.Child1.Child2.Child3 +(1 row) + +SELECT 'Top.Child1.Child2'::ltree || 'Child3'::ltree; + ?column? +-------------------------- + Top.Child1.Child2.Child3 +(1 row) + +SELECT 'Top_0'::ltree || 'Top.Child1.Child2'::ltree; + ?column? +------------------------- + Top_0.Top.Child1.Child2 +(1 row) + +SELECT 'Top.Child1.Child2'::ltree || ''::ltree; + ?column? +------------------- + Top.Child1.Child2 +(1 row) + +SELECT ''::ltree || 'Top.Child1.Child2'::ltree; + ?column? +------------------- + Top.Child1.Child2 +(1 row) + +SELECT lca('{la.2.3,1.2.3.4.5.6,""}') IS NULL; + ?column? +---------- + t +(1 row) + +SELECT lca('{la.2.3,1.2.3.4.5.6}') IS NULL; + ?column? +---------- + f +(1 row) + +SELECT lca('{1.la.2.3,1.2.3.4.5.6}'); + lca +----- + 1 +(1 row) + +SELECT lca('{1.2.3,1.2.3.4.5.6}'); + lca +----- + 1.2 +(1 row) + +SELECT lca('{1.2.3}'); + lca +----- + 1.2 +(1 row) + +SELECT lca('{1}'), lca('{1}') IS NULL; + lca | ?column? +-----+---------- + | f +(1 row) + +SELECT lca('{}') IS NULL; + ?column? +---------- + t +(1 row) + +SELECT lca('1.la.2.3','1.2.3.4.5.6'); + lca +----- + 1 +(1 row) + +SELECT lca('1.2.3','1.2.3.4.5.6'); + lca +----- + 1.2 +(1 row) + +SELECT lca('1.2.2.3','1.2.3.4.5.6'); + lca +----- + 1.2 +(1 row) + +SELECT lca('1.2.2.3','1.2.3.4.5.6',''); + lca +----- + +(1 row) + +SELECT lca('1.2.2.3','1.2.3.4.5.6','2'); + lca +----- + +(1 row) + +SELECT lca('1.2.2.3','1.2.3.4.5.6','1'); + lca +----- + +(1 row) + +SELECT '1'::lquery; + lquery +-------- + 1 +(1 row) + +SELECT '4|3|2'::lquery; + lquery +-------- + 4|3|2 +(1 row) + +SELECT '1.2'::lquery; + lquery +-------- + 1.2 +(1 row) + +SELECT '1.4|3|2'::lquery; + lquery +--------- + 1.4|3|2 +(1 row) + +SELECT '1.0'::lquery; + lquery +-------- + 1.0 +(1 row) + +SELECT '4|3|2.0'::lquery; + lquery +--------- + 4|3|2.0 +(1 row) + +SELECT '1.2.0'::lquery; + lquery +-------- + 1.2.0 +(1 row) + +SELECT '1.4|3|2.0'::lquery; + lquery +----------- + 1.4|3|2.0 +(1 row) + +SELECT '1.*'::lquery; + lquery +-------- + 1.* +(1 row) + +SELECT '4|3|2.*'::lquery; + lquery +--------- + 4|3|2.* +(1 row) + +SELECT '1.2.*'::lquery; + lquery +-------- + 1.2.* +(1 row) + +SELECT '1.4|3|2.*'::lquery; + lquery +----------- + 1.4|3|2.* +(1 row) + +SELECT '*.1.*'::lquery; + lquery +-------- + *.1.* +(1 row) + +SELECT '*.4|3|2.*'::lquery; + lquery +----------- + *.4|3|2.* +(1 row) + +SELECT '*.1.2.*'::lquery; + lquery +--------- + *.1.2.* +(1 row) + +SELECT '*.1.4|3|2.*'::lquery; + lquery +------------- + *.1.4|3|2.* +(1 row) + +SELECT '1.*.4|3|2'::lquery; + lquery +----------- + 1.*.4|3|2 +(1 row) + +SELECT '1.*.4|3|2.0'::lquery; + lquery +------------- + 1.*.4|3|2.0 +(1 row) + +SELECT '1.*.4|3|2.*{1,4}'::lquery; + lquery +------------------ + 1.*.4|3|2.*{1,4} +(1 row) + +SELECT '1.*.4|3|2.*{,4}'::lquery; + lquery +----------------- + 1.*.4|3|2.*{,4} +(1 row) + +SELECT '1.*.4|3|2.*{1,}'::lquery; + lquery +----------------- + 1.*.4|3|2.*{1,} +(1 row) + +SELECT '1.*.4|3|2.*{1}'::lquery; + lquery +---------------- + 1.*.4|3|2.*{1} +(1 row) + +SELECT 'foo.bar{,}.!a*|b{1,}.c{,44}.d{3,4}'::lquery; + lquery +------------------------------------ + foo.bar{,}.!a*|b{1,}.c{,44}.d{3,4} +(1 row) + +SELECT 'foo*@@*'::lquery; + lquery +-------- + foo@* +(1 row) + +SELECT 'qwerty%@*.tu'::lquery; + lquery +-------------- + qwerty%@*.tu +(1 row) + +-- empty labels not allowed +SELECT '.2.3'::lquery; +ERROR: lquery syntax error at character 1 +LINE 1: SELECT '.2.3'::lquery; + ^ +SELECT '1..3'::lquery; +ERROR: lquery syntax error at character 3 +LINE 1: SELECT '1..3'::lquery; + ^ +SELECT '1.2.'::lquery; +ERROR: lquery syntax error +LINE 1: SELECT '1.2.'::lquery; + ^ +DETAIL: Unexpected end of input. +SELECT '@.2.3'::lquery; +ERROR: lquery syntax error at character 1 +LINE 1: SELECT '@.2.3'::lquery; + ^ +SELECT '1.@.3'::lquery; +ERROR: lquery syntax error at character 3 +LINE 1: SELECT '1.@.3'::lquery; + ^ +SELECT '1.2.@'::lquery; +ERROR: lquery syntax error at character 5 +LINE 1: SELECT '1.2.@'::lquery; + ^ +SELECT '!.2.3'::lquery; +ERROR: lquery syntax error at character 2 +LINE 1: SELECT '!.2.3'::lquery; + ^ +DETAIL: Empty labels are not allowed. +SELECT '1.!.3'::lquery; +ERROR: lquery syntax error at character 4 +LINE 1: SELECT '1.!.3'::lquery; + ^ +DETAIL: Empty labels are not allowed. +SELECT '1.2.!'::lquery; +ERROR: lquery syntax error at character 6 +LINE 1: SELECT '1.2.!'::lquery; + ^ +DETAIL: Empty labels are not allowed. +SELECT '1.2.3|@.4'::lquery; +ERROR: lquery syntax error at character 7 +LINE 1: SELECT '1.2.3|@.4'::lquery; + ^ +SELECT (repeat('x', :maxlbl) || '*@@*')::lquery; + lquery +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@* +(1 row) + +SELECT (repeat('x', :maxlbl + 1) || '*@@*')::lquery; +ERROR: label string is too long +DETAIL: Label length is 1001, must be at most 1000, at character 1002. +SELECT ('!' || repeat('x', :maxlbl))::lquery; + lquery +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + !xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +(1 row) + +SELECT ('!' || repeat('x', :maxlbl + 1))::lquery; +ERROR: label string is too long +DETAIL: Label length is 1001, must be at most 1000, at character 1003. +SELECT nlevel('1.2.3.4'); + nlevel +-------- + 4 +(1 row) + +SELECT nlevel(('1' || repeat('.1', 65534))::ltree); + nlevel +-------- + 65535 +(1 row) + +SELECT nlevel(('1' || repeat('.1', 65535))::ltree); +ERROR: number of ltree labels (65536) exceeds the maximum allowed (65535) +SELECT nlevel(('1' || repeat('.1', 65534))::ltree || '1'); +ERROR: number of ltree levels (65536) exceeds the maximum allowed (65535) +SELECT ('1' || repeat('.1', 65534))::lquery IS NULL; + ?column? +---------- + f +(1 row) + +SELECT ('1' || repeat('.1', 65535))::lquery IS NULL; +ERROR: number of lquery items (65536) exceeds the maximum allowed (65535) +SELECT '*{65535}'::lquery; + lquery +---------- + *{65535} +(1 row) + +SELECT '*{65536}'::lquery; +ERROR: lquery syntax error +LINE 1: SELECT '*{65536}'::lquery; + ^ +DETAIL: Low limit (65536) exceeds the maximum allowed (65535), at character 3. +SELECT '*{,65534}'::lquery; + lquery +----------- + *{,65534} +(1 row) + +SELECT '*{,65535}'::lquery; + lquery +-------- + * +(1 row) + +SELECT '*{,65536}'::lquery; +ERROR: lquery syntax error +LINE 1: SELECT '*{,65536}'::lquery; + ^ +DETAIL: High limit (65536) exceeds the maximum allowed (65535), at character 4. +SELECT '*{4,3}'::lquery; +ERROR: lquery syntax error +LINE 1: SELECT '*{4,3}'::lquery; + ^ +DETAIL: Low limit (4) is greater than high limit (3), at character 5. +SELECT '1.2'::ltree < '2.2'::ltree; + ?column? +---------- + t +(1 row) + +SELECT '1.2'::ltree <= '2.2'::ltree; + ?column? +---------- + t +(1 row) + +SELECT '2.2'::ltree = '2.2'::ltree; + ?column? +---------- + t +(1 row) + +SELECT '3.2'::ltree >= '2.2'::ltree; + ?column? +---------- + t +(1 row) + +SELECT '3.2'::ltree > '2.2'::ltree; + ?column? +---------- + t +(1 row) + +SELECT '1.2.3'::ltree @> '1.2.3.4'::ltree; + ?column? +---------- + t +(1 row) + +SELECT '1.2.3.4'::ltree @> '1.2.3.4'::ltree; + ?column? +---------- + t +(1 row) + +SELECT '1.2.3.4.5'::ltree @> '1.2.3.4'::ltree; + ?column? +---------- + f +(1 row) + +SELECT '1.3.3'::ltree @> '1.2.3.4'::ltree; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.b.c.d.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'A.b.c.d.e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'A@.b.c.d.e'; + ?column? +---------- + t +(1 row) + +SELECT 'aa.b.c.d.e'::ltree ~ 'A@.b.c.d.e'; + ?column? +---------- + f +(1 row) + +SELECT 'aa.b.c.d.e'::ltree ~ 'A*.b.c.d.e'; + ?column? +---------- + f +(1 row) + +SELECT 'aa.b.c.d.e'::ltree ~ 'A*@.b.c.d.e'; + ?column? +---------- + t +(1 row) + +SELECT 'aa.b.c.d.e'::ltree ~ 'A*@|g.b.c.d.e'; + ?column? +---------- + t +(1 row) + +SELECT 'g.b.c.d.e'::ltree ~ 'A*@|g.b.c.d.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.b.c.d.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{3}.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2}.e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{4}.e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{,4}.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,}.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,4}.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,3}.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,3}'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,4}'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,5}'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*{2,3}.e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*{2,4}.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*{2,5}.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.e.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.d.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.a.*.d.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!d.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!d'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!d.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!a.*'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!e.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*.!e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*.!d'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*.!d.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*.!f.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.a.*.!f.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.a.*.!d.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.a.!d.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.a.!d'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.!d.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.a.*.!d.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.c.*'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*.c.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!b.*.c.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!b.b.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!b.*.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!b.!c.*.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!b.*.!c.*.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*{2}.!b.*.!c.*.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*{1}.!b.*.!c.*.e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*{1}.!b.*{1}.!c.*.e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.!b.*{1}.!c.*.e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!b.*{1}.!c.*.e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*{1}.!c.*.e'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*.!c.*.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!b.!c.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!b.*.!c.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*{2}.!b.*.!c.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*{1}.!b.*.!c.*'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*{1}.!b.*{1}.!c.*'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.!b.*{1}.!c.*'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!b.*{1}.!c.*'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*{1}.!c.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*.!c.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2}.*{2}'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{1}.*{2}.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{1}.*{4}'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{5}.*'; + ?column? +---------- + f +(1 row) + +SELECT '5.0.1.0'::ltree ~ '5.!0.!0.0'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b'::ltree ~ '!a.!a'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a{,}'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a{1,}.*'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a{,}.!a{,}'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.a'::ltree ~ 'a{,}.!a{,}'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.a'::ltree ~ 'a{,2}.!a{1,}'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ 'a{,2}.!a{1,}'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!x{,}'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!c{,}'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!c{0,3}.!a{2,}'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ~ '!c{0,3}.!d{2,}.*'; + ?column? +---------- + t +(1 row) + +SELECT 'QWER_TY'::ltree ~ 'q%@*'; + ?column? +---------- + t +(1 row) + +SELECT 'QWER_TY'::ltree ~ 'q%@*%@*'; + ?column? +---------- + t +(1 row) + +SELECT 'QWER_TY'::ltree ~ 'Q_t%@*'; + ?column? +---------- + t +(1 row) + +SELECT 'QWER_GY'::ltree ~ 'q_t%@*'; + ?column? +---------- + f +(1 row) + +--ltxtquery +SELECT '!tree & aWdf@*'::ltxtquery; + ltxtquery +---------------- + !tree & aWdf@* +(1 row) + +SELECT 'tree & aw_qw%*'::ltxtquery; + ltxtquery +---------------- + tree & aw_qw%* +(1 row) + +SELECT 'tree & aw-qw%*'::ltxtquery; + ltxtquery +---------------- + tree & aw-qw%* +(1 row) + +SELECT 'ltree.awdfg'::ltree @ '!tree & aWdf@*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree.awdfg'::ltree @ '!tree & aWdf@*'::ltxtquery; + ?column? +---------- + f +(1 row) + +SELECT 'tree.awdfg'::ltree @ '!tree | aWdf@*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree.awdfg'::ltree @ 'tree | aWdf@*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree.awdfg'::ltree @ 'tree & aWdf@*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree.awdfg'::ltree @ 'tree & aWdf@'::ltxtquery; + ?column? +---------- + f +(1 row) + +SELECT 'tree.awdfg'::ltree @ 'tree & aWdf*'::ltxtquery; + ?column? +---------- + f +(1 row) + +SELECT 'tree.awdfg'::ltree @ 'tree & aWdf'::ltxtquery; + ?column? +---------- + f +(1 row) + +SELECT 'tree.awdfg'::ltree @ 'tree & awdf*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree.awdfg'::ltree @ 'tree & aWdfg@'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_qw%*'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_rw%*'::ltxtquery; + ?column? +---------- + f +(1 row) + +--arrays +SELECT '{1.2.3}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + t +(1 row) + +SELECT '{1.2.3.4}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + t +(1 row) + +SELECT '{1.2.3.4.5}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + f +(1 row) + +SELECT '{1.3.3}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + f +(1 row) + +SELECT '{5.67.8, 1.2.3}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + t +(1 row) + +SELECT '{5.67.8, 1.2.3.4}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + t +(1 row) + +SELECT '{5.67.8, 1.2.3.4.5}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + f +(1 row) + +SELECT '{5.67.8, 1.3.3}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + f +(1 row) + +SELECT '{1.2.3, 7.12.asd}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + t +(1 row) + +SELECT '{1.2.3.4, 7.12.asd}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + t +(1 row) + +SELECT '{1.2.3.4.5, 7.12.asd}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + f +(1 row) + +SELECT '{1.3.3, 7.12.asd}'::ltree[] @> '1.2.3.4'; + ?column? +---------- + f +(1 row) + +SELECT '{ltree.asd, tree.awdfg}'::ltree[] @ 'tree & aWdfg@'::ltxtquery; + ?column? +---------- + t +(1 row) + +SELECT '{j.k.l.m, g.b.c.d.e}'::ltree[] ~ 'A*@|g.b.c.d.e'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ? '{A.b.c.d.e}'; + ?column? +---------- + f +(1 row) + +SELECT 'a.b.c.d.e'::ltree ? '{a.b.c.d.e}'; + ?column? +---------- + t +(1 row) + +SELECT 'a.b.c.d.e'::ltree ? '{A.b.c.d.e, a.*}'; + ?column? +---------- + t +(1 row) + +SELECT '{a.b.c.d.e,B.df}'::ltree[] ? '{A.b.c.d.e}'; + ?column? +---------- + f +(1 row) + +SELECT '{a.b.c.d.e,B.df}'::ltree[] ? '{A.b.c.d.e,*.df}'; + ?column? +---------- + t +(1 row) + +--extractors +SELECT ('{3456,1.2.3.34}'::ltree[] ?@> '1.2.3.4') is null; + ?column? +---------- + t +(1 row) + +SELECT '{3456,1.2.3}'::ltree[] ?@> '1.2.3.4'; + ?column? +---------- + 1.2.3 +(1 row) + +SELECT '{3456,1.2.3.4}'::ltree[] ?<@ '1.2.3'; + ?column? +---------- + 1.2.3.4 +(1 row) + +SELECT ('{3456,1.2.3.4}'::ltree[] ?<@ '1.2.5') is null; + ?column? +---------- + t +(1 row) + +SELECT '{ltree.asd, tree.awdfg}'::ltree[] ?@ 'tree & aWdfg@'::ltxtquery; + ?column? +------------ + tree.awdfg +(1 row) + +SELECT '{j.k.l.m, g.b.c.d.e}'::ltree[] ?~ 'A*@|g.b.c.d.e'; + ?column? +----------- + g.b.c.d.e +(1 row) + +CREATE TABLE ltreetest (t ltree); +\copy ltreetest FROM 'data/ltree.data' +SELECT * FROM ltreetest WHERE t < '12.3' order by t asc; + t +---------------------------------- + + 1 + 1.1 + 1.1.1 + 1.1.1.1 + 1.1.1.2 + 1.1.1.2.1 + 1.1.2 + 1.1.2.1 + 1.1.3 + 1.1.7.32.11.22 + 1.10.21 + 1.10.23.25.5.11 + 1.10.4.18.22.23.24 + 1.10.5.22.13 + 1.11.10.19.6.1.26.17.2.22 + 1.12.25.26.22.8.15.23 + 1.13.16.27.11.16.30.2.9.18.4 + 1.14.3.7.3.17.2.29 + 1.15.17.6.28.25.24.31.27.9 + 1.16.8.18.14.16.21.25.6 + 1.18.29.30.22.14.3.20.15.21.20 + 1.19.22.11.14.7.32.23.19.14 + 1.20.18.25.3.24.25.10.9 + 1.20.22.26.2.6.11 + 1.21.28.4.23 + 1.22.19.24.8.11 + 1.22.29.5.16 + 1.25.7.9.26.17.31.20.13 + 1.26 + 1.26.15.23.5.31.29.11.19.28.1 + 1.27.22.23.2.26.32.17.7.9 + 1.28.19.8.25.6.20.27.29.27 + 1.28.3.22 + 1.29.18.1.21.12.13.27.32.15 + 1.3.15.11.11.25.24.21.19 + 1.30.18.31.12.25.4.19.28.12.15 + 1.30.31.31.20.16.7 + 1.31.3 + 1.4.14.32.14 + 1.8 + 1.9.18.10.1.26.22.16.17 + 10.11.25.2.24.18.18.21.6.26.21 + 10.12.23.22.23.22.20.17.17.9 + 10.12.9.6.6.26.14.8.23.1.25 + 10.13.12.8.4.8.11.30 + 10.13.22.1.8.30.9.24.1.2.1 + 10.15.16.3 + 10.16.18.9.27.2.29.32.24.13 + 10.16.19.7.15 + 10.18.12.27.24.30.32.7.11.5.13 + 10.2.17.26.16.7.19.6.23.3 + 10.20 + 10.22.1 + 10.22.30.16.2.21.17.13 + 10.26.27.23.4.31.11.25.29 + 10.26.30.15.1 + 10.27.7.24.26.11.31.20.29 + 10.28.22.29.13.19.6.7.6.14 + 10.28.7.16.31 + 10.29 + 10.29.26.4.27.17.11 + 10.3 + 10.3.19 + 10.31 + 10.31.25.31.24.16.17 + 10.32.14 + 10.5.23.5.32.9.18.5.30 + 10.5.5.15.29.2 + 10.7.9 + 10.8.20.11.12.23.22 + 11.1 + 11.1.3.28.30.21.24.14 + 11.10 + 11.10.22.18 + 11.11.11.4.23.21.25 + 11.11.9.30.15.29.15.18 + 11.12.6.21 + 11.14.21.24.10.7.29.23.24.28 + 11.15.11.19.29.10 + 11.16.16.28.14 + 11.17.10 + 11.17.17.24.11.23.17.17.18.10.22 + 11.18.4.8.3.13.14.28.18.31 + 11.19.23.3.6.11 + 11.2.27.3 + 11.21.13.9.19 + 11.21.16.27.16 + 11.22.28.8.12.23.25.15.21.28 + 11.29 + 11.3.15.28.22.8.14 + 11.30 + 11.30.20.15.18.32.1.18.25.26.8 + 11.32.18.31 + 11.6.11.29.4.5.24.6.26.12 + 11.7.31.15.22 + 11.8.18 + 12.1.1 + 12.1.28.22.25 + 12.10 + 12.10.11.9.10.31.4.16.31 + 12.11.17.1.2 + 12.11.20.20.29 + 12.13.16.17.29.27.16.14.9.19.9 + 12.13.5.31 + 12.14.20.8.28.4 + 12.15.10.17.18.13 + 12.16.13 + 12.16.2.4.15 + 12.17.10.7.17.16 + 12.18 + 12.2.4.28.21.30.24 + 12.21.15.27.24.15.8.24.24.26 + 12.21.20.20 + 12.22.20.4.12 + 12.23.3.19.29.15.12.6 + 12.24.29.32.32.29.2 + 12.25.32.2.27.3.3.16 + 12.27.23.32.1.1.9.29.13 + 12.27.30.12.24.2.20 + 12.28.12.24.28.15.5.12.30.13.21 + 12.29.17.2.20.29.1.11.19.8.12 + 12.29.26.18.4.21.28.8.13.3 +(123 rows) + +SELECT * FROM ltreetest WHERE t <= '12.3' order by t asc; + t +---------------------------------- + + 1 + 1.1 + 1.1.1 + 1.1.1.1 + 1.1.1.2 + 1.1.1.2.1 + 1.1.2 + 1.1.2.1 + 1.1.3 + 1.1.7.32.11.22 + 1.10.21 + 1.10.23.25.5.11 + 1.10.4.18.22.23.24 + 1.10.5.22.13 + 1.11.10.19.6.1.26.17.2.22 + 1.12.25.26.22.8.15.23 + 1.13.16.27.11.16.30.2.9.18.4 + 1.14.3.7.3.17.2.29 + 1.15.17.6.28.25.24.31.27.9 + 1.16.8.18.14.16.21.25.6 + 1.18.29.30.22.14.3.20.15.21.20 + 1.19.22.11.14.7.32.23.19.14 + 1.20.18.25.3.24.25.10.9 + 1.20.22.26.2.6.11 + 1.21.28.4.23 + 1.22.19.24.8.11 + 1.22.29.5.16 + 1.25.7.9.26.17.31.20.13 + 1.26 + 1.26.15.23.5.31.29.11.19.28.1 + 1.27.22.23.2.26.32.17.7.9 + 1.28.19.8.25.6.20.27.29.27 + 1.28.3.22 + 1.29.18.1.21.12.13.27.32.15 + 1.3.15.11.11.25.24.21.19 + 1.30.18.31.12.25.4.19.28.12.15 + 1.30.31.31.20.16.7 + 1.31.3 + 1.4.14.32.14 + 1.8 + 1.9.18.10.1.26.22.16.17 + 10.11.25.2.24.18.18.21.6.26.21 + 10.12.23.22.23.22.20.17.17.9 + 10.12.9.6.6.26.14.8.23.1.25 + 10.13.12.8.4.8.11.30 + 10.13.22.1.8.30.9.24.1.2.1 + 10.15.16.3 + 10.16.18.9.27.2.29.32.24.13 + 10.16.19.7.15 + 10.18.12.27.24.30.32.7.11.5.13 + 10.2.17.26.16.7.19.6.23.3 + 10.20 + 10.22.1 + 10.22.30.16.2.21.17.13 + 10.26.27.23.4.31.11.25.29 + 10.26.30.15.1 + 10.27.7.24.26.11.31.20.29 + 10.28.22.29.13.19.6.7.6.14 + 10.28.7.16.31 + 10.29 + 10.29.26.4.27.17.11 + 10.3 + 10.3.19 + 10.31 + 10.31.25.31.24.16.17 + 10.32.14 + 10.5.23.5.32.9.18.5.30 + 10.5.5.15.29.2 + 10.7.9 + 10.8.20.11.12.23.22 + 11.1 + 11.1.3.28.30.21.24.14 + 11.10 + 11.10.22.18 + 11.11.11.4.23.21.25 + 11.11.9.30.15.29.15.18 + 11.12.6.21 + 11.14.21.24.10.7.29.23.24.28 + 11.15.11.19.29.10 + 11.16.16.28.14 + 11.17.10 + 11.17.17.24.11.23.17.17.18.10.22 + 11.18.4.8.3.13.14.28.18.31 + 11.19.23.3.6.11 + 11.2.27.3 + 11.21.13.9.19 + 11.21.16.27.16 + 11.22.28.8.12.23.25.15.21.28 + 11.29 + 11.3.15.28.22.8.14 + 11.30 + 11.30.20.15.18.32.1.18.25.26.8 + 11.32.18.31 + 11.6.11.29.4.5.24.6.26.12 + 11.7.31.15.22 + 11.8.18 + 12.1.1 + 12.1.28.22.25 + 12.10 + 12.10.11.9.10.31.4.16.31 + 12.11.17.1.2 + 12.11.20.20.29 + 12.13.16.17.29.27.16.14.9.19.9 + 12.13.5.31 + 12.14.20.8.28.4 + 12.15.10.17.18.13 + 12.16.13 + 12.16.2.4.15 + 12.17.10.7.17.16 + 12.18 + 12.2.4.28.21.30.24 + 12.21.15.27.24.15.8.24.24.26 + 12.21.20.20 + 12.22.20.4.12 + 12.23.3.19.29.15.12.6 + 12.24.29.32.32.29.2 + 12.25.32.2.27.3.3.16 + 12.27.23.32.1.1.9.29.13 + 12.27.30.12.24.2.20 + 12.28.12.24.28.15.5.12.30.13.21 + 12.29.17.2.20.29.1.11.19.8.12 + 12.29.26.18.4.21.28.8.13.3 + 12.3 +(124 rows) + +SELECT * FROM ltreetest WHERE t = '12.3' order by t asc; + t +------ + 12.3 +(1 row) + +SELECT * FROM ltreetest WHERE t >= '12.3' order by t asc; + t +---------------------------------- + 12.3 + 12.4.10.17.4.10.23.3 + 12.4.12.13.25.30.30.8.9.12 + 12.4.24.6.1.13.5.20 + 12.4.26.23.25.5.15.7.16 + 12.6.14.23.19.21.9.12 + 12.7.16.8.21.22.2.16.18 + 12.7.28.26.14.21.18.31.5.15.11 + 13.1.6.17.28.9.15.30.1.27.14 + 13.12 + 13.14.13.10.28.26.9.18.27.21 + 13.16.1.27.18.18.19.6.14.4 + 13.16.4.28 + 13.17.7 + 13.19.2.6.23.19.9.7.21.8.16 + 13.24 + 13.25.10.25.8.16 + 13.26.17.3.2.19 + 13.28.12.6 + 13.28.14.2.8.18 + 13.28.9.3 + 13.3.20 + 13.3.8 + 13.30.24 + 13.32.15.32.26.14.32 + 13.7 + 13.8.15.3.7.31.5.10.15.30 + 13.8.20.9.21 + 13.8.23.13.11.18.24.21.11.24.10 + 13.9.9.27.31.11.25.9.27.22.13 + 14.1.11 + 14.1.15.25.27.23.25.26.28.10 + 14.10.11.30.5.7.6.24.9.30.26 + 14.11.25 + 14.12.31 + 14.13.9.13.11.5.5.2.2.32.12 + 14.14.25 + 14.15.31.29 + 14.16.6.29.26.13.14.16.25.26.8 + 14.17.7.30.8.25.26.4 + 14.19.20.13.27.2.2 + 14.19.26.15.22.23 + 14.19.30.6.4.10.10.10.22.25.11 + 14.2.14.11.12 + 14.21.22 + 14.21.5.28.3.32.24.14.25.31 + 14.21.6.5.26.9.32.16.25 + 14.23.31.5.5.15.17.12.17.7.3 + 14.24 + 14.26.25.4.12.26.8 + 14.27.29.23.4.1.17.32.6.25.22 + 14.29 + 14.3.17.1.14.15.21.4.26 + 14.30.13.5.26.9.22.23.14.10 + 14.30.2.21.15.16.13 + 14.30.23.3 + 14.4.19.27.28.24.19 + 14.4.23.4.23.22.11.6.26.5 + 14.5.13.19.25.12.32.9.13.16.12 + 14.6.10.29.25.26.20.24.24 + 14.8.15.30.7.29.27.31.4 + 14.9.15.21.21.31.1.29 + 15.1.6.31.30.13.32.9.10 + 15.1.8 + 15.10.30.1.4.12.8.20 + 15.11.26.1.30.6.23.5 + 15.17 + 15.17.2.32.7 + 15.21.22 + 15.21.23.30.9.25 + 15.23.26.20.27.7 + 15.25.31.11.4.22.16.7.11 + 15.26.24.31.16.15.17.22.8.30.3 + 15.28.24 + 15.28.30.19.31.6.2.2.31 + 15.29.25 + 15.29.32.16.29.12.20.32.13.20 + 15.3.31.9.27.14.9.8.14.6.32 + 15.30.17.5.32.28.2.18.27 + 15.31.11.27.19.19.20.5.5 + 15.4.15 + 15.5.1.31.28.10.8 + 15.6.19.3 + 15.7.3.14.23.19.26 + 15.7.5.12.7.9.3.28.26 + 15.8.10 + 15.8.3.15.27.14.29.28.6.5.25 + 15.9.11.20.22.15.11.13 + 15.9.8.20.27 + 16.13.19.11.18.13.17.17 + 16.13.2.19.14.29.31.30.23.15.12 + 16.13.26.18.9.29.11.17.1.24.26 + 16.14.3.17.17.26.12.19.19.30 + 16.16.28.24.11 + 16.18.23.6.31 + 16.19.17.30.30.5.17.24.27 + 16.2.14.3.26.11 + 16.20.29.26 + 16.21.13.1.4 + 16.23.30.12.31.31.19.14 + 16.24.3.30.15.22.31.2 + 16.24.7.25 + 16.27.8.17.14.17.21.29.14 + 16.28 + 16.29.6.23.13.28.31.6.19.26.15 + 16.30.10.7.29.4.9.21.22.13.26 + 16.31.12.27.25.9.32.29 + 16.5 + 16.5.10.2.18.8.15.12.32.25.10 + 16.5.12.5.15.12.24.25.3 + 16.5.14.21.32.17.23.3.4.26 + 16.5.23.17 + 16.5.6.12 + 16.8.29.7.21.2.3 + 16.9.14.28.6.21.31.31.26 + 16.9.29 + 16.9.32.14.3.7.8.7.21.22 + 17.1.12.20 + 17.10.17.22.20.25.14.13 + 17.11.17.4.8.26.26.20.6 + 17.13.14.29.27.27.13.12.15 + 17.13.19.31.12.18.10.15.14 + 17.13.8 + 17.14.7.3.2.18.20.23.18.5 + 17.17.14.28.6.30 + 17.19.1.22.11.7.22.1.14.28.11 + 17.22.12.10.30.11 + 17.24.15.27.3.32.4.22.20.6.24 + 17.24.30.6.32 + 17.25.10.13.21.5.7.22.2 + 17.25.2.13.10.27.13.1 + 17.25.26.23.32 + 17.26.18 + 17.27 + 17.29.21.10.18.8.16.26.18.21.26 + 17.29.31.8.24.10.18.27.17 + 17.3 + 17.5.3.15.17.13.5 + 17.7.26.30.18.23.4 + 17.8 + 17.8.31.32 + 17.9.32.31.21.31.23.17.10.32.9 + 18.13.6.12.26.26.26.29.18.20.1 + 18.13.9.3.18.15.2 + 18.15.14 + 18.17.6.16.6.10 + 18.18.19.16.14.16.21.10.25 + 18.18.5.11.7.4.25 + 18.19.11.20.13.13.11 + 18.19.12.20.18.17.15.32.18.5 + 18.21 + 18.24.21.17.11.26.28.22.21.18.10 + 18.27.11.27.9.16.7.6.22.26.27 + 18.29.13.24.18.3.12.18.12.12 + 18.29.5.1.10.21.2 + 18.30.11.17 + 18.30.18.31 + 18.31.26.18.6.15.18.11 + 18.31.32.28.1.4.24.24.12.25 + 18.31.32.29.22.1.31.11.28 + 18.4 + 18.4.14.29.3 + 18.5.6.31.5.15.15 + 18.6.2.2.24 + 18.6.26.2.13.9.6.11.10.11.16 + 18.7.10.27.17.24 + 18.7.3.17.13.5.31.6.31.25.29 + 18.9.21.2.31.8.32 + 18.9.26.7 + 19.10.26.19.5.21.30.23 + 19.10.4.30.32.4.12 + 19.10.8.10.4.19 + 19.11.10.18.14.13.7.7 + 19.11.29.13.15.27.12.15.14.12 + 19.12.20.24.32.13.11.23.26 + 19.12.26.24.29.3 + 19.12.30.2.21 + 19.15.26.19 + 19.16.26.2 + 19.16.31.31.29.12 + 19.17.12.15 + 19.17.13.12.32.16.3 + 19.19.25.22.11.6.15.3.2.19 + 19.2.26.21.16.11.2.2 + 19.2.9.29.6 + 19.20.25.7.27.28.27.17.9.3.1 + 19.22.21.13.27.13.15 + 19.22.29.32.1.21.26.24.23.17 + 19.26.24.27.6.24.16.27.32.29 + 19.26.32.13.1.12.30.26.22.25 + 19.3.12.12 + 19.3.23.4.4.21.23 + 19.30.18.11.32.14 + 19.30.27.26.21.7.18 + 19.31.14.25.5.8.21.11.13.20 + 19.5.20.3.4.2.3 + 19.6.13.14.22.13.9.29 + 19.6.24.32.30.13.6.25.8.28 + 19.7 + 19.7.29.31.3.20.7.21.25.27.29 + 19.9.32.23.13.24.1 + 2.1.12.19.29.28.3.31.28.28.10 + 2.1.3.30.24.17.9 + 2.10.10.4.20.1.12.13 + 2.10.28.1.17.19.32.28 + 2.11.32.25.23 + 2.12.14.28.16.21 + 2.12.30.22.12 + 2.13.9.23.21.2 + 2.13.9.28 + 2.14.10.4.17.17.8.4.27.20 + 2.14.12.13 + 2.15.14.20.30.26 + 2.15.18.21.5.21.4.7.30 + 2.16.3.7.22.18.29.20 + 2.19.4.1.15.7.8.9.17.29 + 2.2.18.18.3.3.18.8.10.8 + 2.22.19 + 2.24.4.5.24.32 + 2.24.5.3.4.10.27.26.17.28.16 + 2.27.15.14 + 2.28.5.17.6.32 + 2.30.26.10.14.31.18.2 + 2.31.25 + 2.32.10.13.12 + 2.32.8.28.24.20.9.24.25.8.9 + 2.4.25.32.16.22.26.13.17.18 + 2.6.15.26.23.26.24 + 2.8.13.12.17.23.16.7.11.23 + 2.9 + 20.1.24.3.30.31 + 20.13 + 20.14.11.2.10.14 + 20.15 + 20.17.14.7 + 20.17.18.21.1 + 20.18.24.14.12.13.9 + 20.20.32.29.24.5.5.26.22.32 + 20.20.7 + 20.22.10 + 20.23.29.5.7.30.13.14.22 + 20.23.7.11.11.31.18.16.3 + 20.24.14.15.4.21.12.27.4.12 + 20.25.22.19.22 + 20.28.22.7.10.28.27.22.14.16 + 20.29.18.16.2.21.23.11 + 20.3.1.8.8.30.20 + 20.30.17 + 20.30.28.15.17 + 20.30.9.9.14.12.29 + 20.31.13.12.19.2.26.16.16.22.28 + 20.32.5.1.3.20.3.30.27 + 20.32.9 + 20.4.1.16.31.3 + 20.4.27.31.1 + 20.5.4.9.31.14.26.6 + 20.6.26.3.30 + 20.6.3.26.7.29.28.4 + 20.8.19.14.16.7 + 20.9.29.32.13.7.23 + 21.1.4.9.9.31.24.21.3.29 + 21.10.20.9.3.16.9.10.20 + 21.14 + 21.14.13 + 21.14.22.29 + 21.14.25.20.13.31.14.20 + 21.15.18.18.30.3.20 + 21.15.31.24.29.24.26.12.20 + 21.17.18.32.7.8 + 21.17.27.23.15 + 21.17.31.10.31.13.9.26.6.14 + 21.18 + 21.18.2.1 + 21.18.30.19.24.24 + 21.20.24.25.6.26.23 + 21.20.28.19.27.9 + 21.21.10.27 + 21.22.31.24.27 + 21.23.13 + 21.23.17.8.23.11.8.1 + 21.28.17.22.10.27.4.20.2.32 + 21.28.24.23.3.11.7.12.22.32 + 21.30.19.6.28.1.32.2.14.14 + 21.31.31.25.5.30.26 + 21.32.13.21 + 21.32.13.22.3.13.31.23.14.12.9 + 21.4.11.18 + 21.4.22.20.24.28.6 + 21.5.11.18 + 21.5.17.19.15.25.18.21.24.9 + 21.6.22.28.12.23.11.22 + 21.7.23.9.16.5.18.14 + 21.7.7.11 + 21.8.9 + 21.9.27.22.32 + 21.9.32.1.27 + 22.10.12.23.9 + 22.10.16.8 + 22.10.18 + 22.10.27.19.29.20.29.3.12.14.25 + 22.11 + 22.12.22.28 + 22.13.22.21.25.17.8 + 22.13.22.8.30.32.10.24 + 22.15 + 22.16 + 22.16.25.18.25.7.24.29.14.8 + 22.17.24.14.21.15.12.18.17.25.11 + 22.17.30 + 22.17.4.2.22.17 + 22.17.7.30.13.24 + 22.17.9.11.25.15.3.9 + 22.18.20.23.15.9.12 + 22.19.20.5.2.20 + 22.19.21.11.6.8.29.24 + 22.19.5.22.20.31.23.24.14.24.4 + 22.20.30 + 22.21.32.15.8.29.5.12.10.29 + 22.22.10.30.5.15.25.21.19.11 + 22.22.27.6.27.15.5.18.21.28.9 + 22.23.18.18.9.8.23.7.23.23.16 + 22.23.22.30 + 22.23.25.28.5.27.9.9.24.31.10 + 22.24.22.25.15.23.13 + 22.25.4.28.9.20.12.13 + 22.26.1.28.9.9.31 + 22.26.32 + 22.28.20.6.32.32 + 22.29.18.32.13.12.22.31.17.22 + 22.29.29.11 + 22.3.6 + 22.30 + 22.30.31.24.23.22.5.20.28.1 + 22.31.2.32.32.11.26.23.19 + 22.31.21.13.13.26.11.5.19 + 22.32.6.6.3.8.24.6.25.29 + 22.8.20.1.10.28.6.27 + 22.9.15.19.12 + 23.1.23.18.12.29 + 23.10.13.32.14.20.16.11.14 + 23.10.5.26.12.4.20.4 + 23.12.1.5.32.25.8.24.1.25 + 23.12.11.11.15.16.22.31.32.5.8 + 23.12.19.25.16.23.22.6.29.4 + 23.12.32.22.19.1.22.4 + 23.14.12.30.18.4.16.18.7.7 + 23.14.30.27.28.26.26.23.8.32 + 23.17.22.1.23.4.29.32.4.1 + 23.17.25.4.1.16.29.10 + 23.17.28.31.28 + 23.17.32.15.23.16.25 + 23.19.17.31.29.13.1.12.5.25 + 23.2.22.7.32.3.27.6 + 23.20.12.16.15.2 + 23.20.24 + 23.20.8 + 23.22.10.1.14.24 + 23.22.23.14.31.32 + 23.23 + 23.24.11.31.10.31.18.28.13.18.6 + 23.24.16.32.13.29 + 23.25.23.11.7.23 + 23.27.27.16 + 23.27.6.26.22 + 23.28.1 + 23.28.20.25.30.24.15 + 23.28.3.30.15.31.32.3.21.9.19 + 23.3.20.24 + 23.3.32.21.5.14.10.17.1 + 23.31.27.16.8.30.20.27 + 23.32.5.25.19.9.15.17.15.11 + 23.5.5.17 + 23.5.7.12.11.23.10 + 23.6.27 + 23.8.13.22.21 + 24.1.10.20.28.18.6.27.20.30.26 + 24.1.29.32.14.15.32.6.15.22 + 24.10.10.31.4.29.9 + 24.10.8.25.16 + 24.11.5 + 24.12 + 24.13.1.8 + 24.15.15.17.22 + 24.16.27.10.9 + 24.17.24 + 24.17.31.20.12.9.19.29.18 + 24.18.16 + 24.2.26.24.14.15.31.23.17.26 + 24.2.6.7.16.7.28 + 24.20.23 + 24.21.14.25.11.3.20.6.6.16 + 24.23.24.4.15.25.17 + 24.23.29.8.24.11.21.10.28.14.27 + 24.24 + 24.25.7.27.30.8.26.17 + 24.27.14 + 24.27.18.32.14.9.11.28.9 + 24.28.13.26.8.8.31 + 24.28.32.21 + 24.3.23.25 + 24.31 + 24.31.2.13.5.23.18.16 + 24.31.8 + 24.32.17.23.24.19.23.9.20.18 + 24.32.27 + 24.9 + 24.9.15.1.14.29.6.4 + 24.9.27.16.20.21 + 24.9.8.12.29 + 25.10 + 25.10.29.3.6.21.3.31.13 + 25.10.4.28.3.31.19 + 25.11.24 + 25.14.5.32.25 + 25.15.11 + 25.16.9.6 + 25.17.18.17.27 + 25.17.18.30 + 25.17.2.20.20.3.29.21.3.12 + 25.17.9.16.17.31.23.29.24 + 25.18.8.3.23.23.5.9.6 + 25.19.27.2.9.20 + 25.2.11.20.8.6.22 + 25.2.3.15.11.19.5.28.25.14 + 25.21.8.17 + 25.22.2.25.6 + 25.24.2.32.14.18.16 + 25.24.29 + 25.28.3 + 25.28.30.24 + 25.29 + 25.3 + 25.30.1.4.24.11 + 25.31 + 25.32.24.24.28.15.16.10 + 25.4.32 + 25.4.4.1.13.32.26.20.20.3 + 25.5.30.7.16.12.21.12.11.16 + 25.6 + 25.6.12.16.1 + 25.7.3.21.31.12.28 + 25.9 + 25.9.1.5.9.11.25.4.11.27.32 + 25.9.10 + 26.11 + 26.12.27.2 + 26.13.4.7.13.11.3 + 26.14 + 26.14.5.32.10 + 26.16.12 + 26.16.12.3.27.9.28 + 26.17.9.13.4.25.32.2.24.9 + 26.18 + 26.18.32.20 + 26.19.3.14.8.28.31.10 + 26.24 + 26.24.9.12.11.15.31.2 + 26.25.10.10.13 + 26.25.24 + 26.26.22.21.14.11.29.19.14.24 + 26.28.14 + 26.31.11.23.3 + 26.31.16.18.22.13.32.23.9.20 + 26.31.6.8.29.8.24 + 26.31.7 + 26.32.21.31.27.12 + 26.32.8.12.30.19.24.8.6.1.10 + 26.5.29.7.28 + 26.7.22.3.18.21.11 + 26.7.5.8.11.9.22.1.6 + 26.8.28 + 26.9.17.1.18.19.1.11.18.29.3 + 26.9.20.12.22.22.32 + 27.1.11.3.25.9.6.6 + 27.11.14.17.24 + 27.11.15.9.24.31.18.4.1.30.20 + 27.12.4.2.29.22.15 + 27.15.15.15 + 27.16 + 27.17.15.7.28.20 + 27.17.17.19.24.9.14.20 + 27.17.3.18.2.13.18 + 27.18 + 27.18.10.4.22 + 27.19.20.1.31.29.5.22.26.3 + 27.2.10.4.25.14.2.15.4 + 27.21.27.5.13.30.17 + 27.21.28.24.7.2.24.23.8 + 27.22.11.13.21.25.5.1.27.21.27 + 27.23.2.32.11.21 + 27.23.20.30.7 + 27.24.11.31.21.6.29.17.24.18 + 27.25 + 27.26.29 + 27.27 + 27.27.25.10.31.10.21.22.21.16.12 + 27.27.30.11.15.24.9.7.4.30 + 27.29.1.5.30.6.22.16.23.2.28 + 27.3 + 27.3.3.11.21.4.25 + 27.30.12.11.20.15.11.13 + 27.31.2.16.29.6 + 27.32.26.21.31.17.32.32 + 27.4 + 27.4.15.14.19.6.12 + 27.4.17.17.32.8.16.15.17.13 + 27.5.15.1.15.16.21 + 27.5.22 + 27.6.13.24.21.27.28.22.3.7.4 + 28.1.3 + 28.11.11.30.20.11.32 + 28.11.27.21.14.16 + 28.14.24.26.6.15.16.32.25.13.8 + 28.14.32.29.2.3.4 + 28.15.18.27 + 28.15.25.7.13.6.19.2 + 28.17.26.9 + 28.18.6.22.13.8.25 + 28.2.27.1.20 + 28.20.8.9.9.28.30.29 + 28.23.2.30.3.8.1.15.15.14.13 + 28.25.10.25.19.15 + 28.25.11.22 + 28.25.29.4.13.5.6 + 28.26.25.7 + 28.26.26.6.31 + 28.26.4.22.13.20.32.27.15 + 28.27.24.14 + 28.28 + 28.30.24.16.17.28.2.13.10 + 28.31.10.28.22.26.16.15 + 28.4 + 28.5.12.9.2.27.11.11.2 + 28.5.13 + 28.6 + 28.6.11.6.15.22.12.6 + 28.6.8.22.25 + 28.8.21.15.16.28.4.16.26.8 + 28.9.3.16.17.21.23.30 + 29.1 + 29.1.2.14.14 + 29.1.7.26.25.11.22 + 29.10.12.17.12.16 + 29.10.17.11.28.12.18.5.19.15.21 + 29.11.20.22.27 + 29.14.12.9.17.5.32 + 29.14.31.25.7.32.23 + 29.15.29.8.31.26.1 + 29.20.1.11.21.16.1.2.14.28 + 29.23.1.21.31.8 + 29.23.15.25.1.6.6.10 + 29.25.29.16.32.11.15.25.5.22.3 + 29.25.30.15.21.3.25.26.26 + 29.26.25.14.24.18.2.13.23.29 + 29.27 + 29.27.13.29.10.2 + 29.27.13.9.28.29.19.13.29.31.27 + 29.27.5.22.26 + 29.27.7.7.3.11.14.26.21.11 + 29.28.9.15.8.27.31 + 29.29.17.31 + 29.29.18 + 29.3.15.17.12.29 + 29.3.17.17.18.32 + 29.30.21.8.16.23.32 + 29.30.7.31.22 + 29.32.13.4.1.16.20 + 29.5.18.27.3.21.18.6.14 + 29.5.32.20.11.7.13.24.17 + 29.6.12.31.20.23.32.20 + 29.9.25.27.15.16.32.26.6.32 + 3.1.13.22.24.14.12.31.3.4 + 3.1.14.8.9.16.30.22.20 + 3.10 + 3.10.27.4.5.6.19.12.28.12 + 3.10.4.5.28.11 + 3.11.18.21.5.20.30 + 3.11.32.11.22.3.7.17.8.13.23 + 3.13 + 3.14.1.14.17.28.29.16 + 3.14.11.15.21.32.2.15.13 + 3.14.30.5.32.22.29 + 3.15.2.23.22.2.16.14 + 3.18 + 3.18.18 + 3.18.8.22.7.28.32.31.3 + 3.19.11.6.5 + 3.20.16.13.29.20 + 3.20.19.10.17.27.3.6.22.23 + 3.21.16.24.23.12.16.32.3 + 3.21.6.13.12.18.25 + 3.22.18.1.5.14.9.6.14 + 3.25 + 3.26 + 3.26.32 + 3.27.18.8.4.21.6.32.30.7.5 + 3.29.19.2.24 + 3.29.32.26.8.10.25 + 3.3 + 3.32.2.29.3.32.28.11.29.30 + 3.4.22.19 + 3.5 + 3.6.24.21.20.32.3.4.26.5 + 3.9.11.23.32.26.24.28 + 3.9.25.26.7 + 30.12.28.2 + 30.12.6.30 + 30.12.9.25.24.6.7.24.29 + 30.15 + 30.16.14.9.5.4.10.7.31 + 30.16.3.21.10 + 30.17.2.25 + 30.17.25.3.31.11.3.4.1.10 + 30.17.4.5.13.6 + 30.18.30.16.29 + 30.2.17.8.14 + 30.20.3.2.5.15.8.7.17 + 30.22.29.21.19.14.3.2.6 + 30.23.10.1.10.7.22.28.18.11.17 + 30.23.2.13.14.15.29.19.4.12.24 + 30.24 + 30.24.23.25.32.18.22.12.29.9.22 + 30.24.32.15.14.10.11 + 30.25 + 30.25.17.17.10.29 + 30.25.24.22 + 30.25.8.24.6.29.31 + 30.27.8.6.11.19 + 30.3.16.26.7.27.26.9.27.21.18 + 30.30.17.5.30.21.19.5.22.22.14 + 30.31.13.9 + 30.32 + 30.4.30.11.13.23.14.24.11 + 30.5 + 30.6.4 + 30.8.18.5.20.6.15 + 30.8.9.14.25.30 + 30.9.24 + 31.13 + 31.13.9.1.5.12 + 31.17 + 31.17.2.30.11 + 31.18 + 31.18.25.1.14.29.25.5.22.30 + 31.18.27.15.20.29.29 + 31.18.32.11.7.25.20.5 + 31.21.14.20.1.22.2.5.3.27.12 + 31.21.22.14.8.21 + 31.24.26.18 + 31.28.32.4.31.4.7 + 31.29.18.26.1.26.17 + 31.29.4.29.24.30.30.32.10.23 + 31.30.12.20 + 31.30.23.7.7.24.32.10.11.1.31 + 31.32.12.26.31.32.14.23.28 + 31.4.7 + 31.5.6.4.8.29.3 + 31.7.14.2 + 31.9.3.5 + 32.1.21.1.16.29.21 + 32.1.23.20.14.12.23.5.32.15 + 32.1.24.29.22.5.9.24.18.3.13 + 32.1.31 + 32.15.20.28.5.1.23.4 + 32.16 + 32.17.8.24.2.14.5.4.22 + 32.19.20.24.23.31.8.32.16.29 + 32.2.11 + 32.24.11.8.12.23.22.19.11.17.18 + 32.24.29.6 + 32.25.16 + 32.25.3.6 + 32.27.13.6.7 + 32.27.18.7.3.4.2 + 32.28.1.32.28.10 + 32.29.24.31.25.6.9 + 32.3.12.2 + 32.3.23.7.2 + 32.3.5.9.17.15 + 32.30.18.17.1.14.12.18 + 32.31.11.22.1 + 32.31.26.19.13.29.4.25 + 32.4.19 + 32.6.13.8.32 + 32.6.15.26.14.15.3.19 + 32.6.3.2.12.5.28.1.25 + 32.6.31.31 + 32.6.8 + 32.6.9.26.16.4.4.29.7.11 + 32.8.29.18.31 + 32.8.5 + 4.1.24.24.28.24.18 + 4.10.28 + 4.11.19.17.2.22.20.18.13.32.15 + 4.11.22.4.19.24.4.28.6.8.22 + 4.13 + 4.13.22.11.9.13.27.15.7 + 4.14 + 4.14.10.19.16 + 4.14.16.14.1.8.1.22.17.10 + 4.14.17.12.20.17.1.22.3 + 4.14.32 + 4.15.20.23.12.16.2.16.17 + 4.16.22.19.24.21 + 4.16.7.25.21.7 + 4.18.29.9.16.10 + 4.19.16.15.5.2.25.8.28.14.2 + 4.2.16.13.16.11.19.10.10.25 + 4.2.2.32.24.25.31.3 + 4.2.6.20.7.8 + 4.21.28.5.16.29.5.21 + 4.21.9.1.2.14.8.17.13.26 + 4.22 + 4.22.17.10.19.9.8.19.28.3.9 + 4.22.7.19.25 + 4.25.12.10.15.9.18.9 + 4.26.2.2 + 4.26.23.6.19.31.10.4.22 + 4.26.5.26.21.28.17.24.25.23 + 4.27.32.18 + 4.3.20.27.9.1.18.30.12.5.19 + 4.3.6.27.22.23.10 + 4.30.8.20.19.9.30.24.11 + 4.31 + 4.5.9.4.15.19.8.26.17.26.3 + 4.7.1 + 4.9 + 5.1.5.31 + 5.10 + 5.10.2.11.21.9.19 + 5.10.3.9.23.30.23 + 5.12.2.20.1.24.25 + 5.13.23.19.28.26.27.6.1.22 + 5.13.23.4.9 + 5.14.27.15.11.17.3.10.27.25 + 5.14.29.2.23.16.20.22 + 5.15.10.3.23.13.32.23 + 5.15.16 + 5.18.9.25.31.21.22 + 5.19.1.26.20.6.20 + 5.2.32.19.13.29.12.13.31.29 + 5.20 + 5.21.27.13.14.11.2.16.20 + 5.23.31.18.24.32 + 5.24.24.9.32.26.31 + 5.24.25.15.27.30.20 + 5.24.4.31.3.16.25.17.13.26.11 + 5.27.16.3.30 + 5.27.21.1.29.29.28 + 5.27.28.26.14.15.6.20.1.31.13 + 5.27.32.21.5.1.11.14 + 5.3.17.29 + 5.3.29.9.22 + 5.31.8.1.5.13.21.28.29.19.2 + 5.4.8.25.12.27.2.29.28.3 + 5.5.12.31.23.13.17.22.20 + 5.8 + 5.8.17.30.15.8.19.29.30.11.6 + 5.9.19.6 + 6.1.8.6.30.29.30 + 6.10.25.12 + 6.11.11.5.16.8.14.12.9 + 6.11.31.23.12.8.30.14.27 + 6.13.31.5.7.26 + 6.14 + 6.17.10.10.7.9.27.8.29 + 6.17.26.25.27.11.10.9 + 6.18.1.4.18.23 + 6.19.29.11.2.32.21.15.32.9 + 6.19.3 + 6.19.6.4.9.11.32.17.17.3.15 + 6.2.32 + 6.20 + 6.20.14 + 6.21.30.7 + 6.22.12 + 6.25.17.32 + 6.26 + 6.26.29.10.21.28.20.19 + 6.27.26.1.20.24.6 + 6.27.29.14.8.12.26.3.21.4.1 + 6.29.32.13.30.3.16 + 6.29.6.13.14.24.10.4.14.28 + 6.5.27.19.13.26.1.18.9 + 6.6.22.8 + 6.7.25.16.13.21.7.20.25.12.4 + 6.7.7 + 6.8.7.20.2 + 6.9.1.10.10.22.6 + 6.9.29.17.4.32 + 7.10.17.21.11.29.17.25.19.4.29 + 7.11 + 7.12 + 7.12.1.10.6.17.29.24.24.4 + 7.12.23 + 7.13 + 7.13.15 + 7.14.22.29.30.14.25.1.9.26.25 + 7.16.20.17 + 7.19.10.12.31.1.27.13.19 + 7.19.12.3.21.19.18.5.2.14.10 + 7.19.6.17.15.26.21.9 + 7.21.8 + 7.23.1.24.29.13.31.19.23.17.7 + 7.23.15.32.28.27.2.2.26 + 7.26.18 + 7.27.20 + 7.30.19.25.23.15.14.29 + 7.30.5.10.10.5.30.14.9.18 + 7.31 + 7.31.2.28.15.11.17.18.19.23.6 + 7.31.4.20.17 + 7.32.10.3.30.12.14 + 7.5.28.8.17.26.31.10.15 + 7.7 + 7.7.22.24.17.32.17.25.28 + 7.7.25.22.22.26 + 8.1.29.18.22 + 8.10 + 8.11.20 + 8.12.4 + 8.13.1 + 8.13.14.11.11.29.22.4.4.10 + 8.13.6.12.18.7 + 8.13.9.31.20.20.24.7.23.31.28 + 8.14.19.18 + 8.16 + 8.16.1.16.28.6.3.22.6.23 + 8.16.20.24.20.6.10.21 + 8.16.30.29.19.22.28.24.2 + 8.16.6 + 8.17.25.26.15.25 + 8.17.9.15.21.28.1.7.1.3.6 + 8.2 + 8.2.18.23.5.16.17.1 + 8.21.17.3.6.3.18 + 8.21.8.23.4.18 + 8.22.32.17.16.28.31.23.22.9 + 8.24.11.13.25.19 + 8.25.20.3.15.24.7.4.24.5.30 + 8.26.29.13.7.25.31.28.3.32 + 8.27.3.4.12.26.16 + 8.29.6.3 + 8.3.18.13.30.20.27.26.17.28 + 8.3.3.25.25.15.7.13.21.18 + 8.31.22.27 + 8.32.30.1 + 8.5.24.9.29.32.31.30.13.9.7 + 8.5.30.29.9.31 + 8.6.6.5.8.8.12 + 8.9.21.16.29 + 8.9.22 + 8.9.25.25.26.30.31.31.2.32.7 + 9.10.19.18.15.11.22.32.32.14.9 + 9.10.32 + 9.14.27.31.26.21.25.3.20 + 9.16.2.16.22.24.17.31.14.21.17 + 9.17.13.31.7 + 9.18.23 + 9.18.30.11.29.32.7.19.2 + 9.19.7.13.13.25 + 9.2.10.4 + 9.2.4.27.26 + 9.21.14.19 + 9.21.20.29.1 + 9.21.28.8.12.15.3.13.10.11 + 9.22.10.15.5.15 + 9.23.21.22.5.29.15.21 + 9.26.1.16 + 9.28.10.26.14.26.15.14 + 9.28.24 + 9.28.30.1.6.25.17.9 + 9.3.3 + 9.3.31.18.12.3.9.29.10 + 9.30 + 9.31.23.19.5.10.16.4.30.24.5 + 9.31.4.14.31.10.17.5.2 + 9.5 + 9.5.9.3.23.9.25.14.1.29.28 + 9.6.9.21.6.11.29.13.29.20.32 + 9.7.31.11.8.23 + 9.8.23.2.20.16 + 9.9.13.9.14.27 +(883 rows) + +SELECT * FROM ltreetest WHERE t > '12.3' order by t asc; + t +---------------------------------- + 12.4.10.17.4.10.23.3 + 12.4.12.13.25.30.30.8.9.12 + 12.4.24.6.1.13.5.20 + 12.4.26.23.25.5.15.7.16 + 12.6.14.23.19.21.9.12 + 12.7.16.8.21.22.2.16.18 + 12.7.28.26.14.21.18.31.5.15.11 + 13.1.6.17.28.9.15.30.1.27.14 + 13.12 + 13.14.13.10.28.26.9.18.27.21 + 13.16.1.27.18.18.19.6.14.4 + 13.16.4.28 + 13.17.7 + 13.19.2.6.23.19.9.7.21.8.16 + 13.24 + 13.25.10.25.8.16 + 13.26.17.3.2.19 + 13.28.12.6 + 13.28.14.2.8.18 + 13.28.9.3 + 13.3.20 + 13.3.8 + 13.30.24 + 13.32.15.32.26.14.32 + 13.7 + 13.8.15.3.7.31.5.10.15.30 + 13.8.20.9.21 + 13.8.23.13.11.18.24.21.11.24.10 + 13.9.9.27.31.11.25.9.27.22.13 + 14.1.11 + 14.1.15.25.27.23.25.26.28.10 + 14.10.11.30.5.7.6.24.9.30.26 + 14.11.25 + 14.12.31 + 14.13.9.13.11.5.5.2.2.32.12 + 14.14.25 + 14.15.31.29 + 14.16.6.29.26.13.14.16.25.26.8 + 14.17.7.30.8.25.26.4 + 14.19.20.13.27.2.2 + 14.19.26.15.22.23 + 14.19.30.6.4.10.10.10.22.25.11 + 14.2.14.11.12 + 14.21.22 + 14.21.5.28.3.32.24.14.25.31 + 14.21.6.5.26.9.32.16.25 + 14.23.31.5.5.15.17.12.17.7.3 + 14.24 + 14.26.25.4.12.26.8 + 14.27.29.23.4.1.17.32.6.25.22 + 14.29 + 14.3.17.1.14.15.21.4.26 + 14.30.13.5.26.9.22.23.14.10 + 14.30.2.21.15.16.13 + 14.30.23.3 + 14.4.19.27.28.24.19 + 14.4.23.4.23.22.11.6.26.5 + 14.5.13.19.25.12.32.9.13.16.12 + 14.6.10.29.25.26.20.24.24 + 14.8.15.30.7.29.27.31.4 + 14.9.15.21.21.31.1.29 + 15.1.6.31.30.13.32.9.10 + 15.1.8 + 15.10.30.1.4.12.8.20 + 15.11.26.1.30.6.23.5 + 15.17 + 15.17.2.32.7 + 15.21.22 + 15.21.23.30.9.25 + 15.23.26.20.27.7 + 15.25.31.11.4.22.16.7.11 + 15.26.24.31.16.15.17.22.8.30.3 + 15.28.24 + 15.28.30.19.31.6.2.2.31 + 15.29.25 + 15.29.32.16.29.12.20.32.13.20 + 15.3.31.9.27.14.9.8.14.6.32 + 15.30.17.5.32.28.2.18.27 + 15.31.11.27.19.19.20.5.5 + 15.4.15 + 15.5.1.31.28.10.8 + 15.6.19.3 + 15.7.3.14.23.19.26 + 15.7.5.12.7.9.3.28.26 + 15.8.10 + 15.8.3.15.27.14.29.28.6.5.25 + 15.9.11.20.22.15.11.13 + 15.9.8.20.27 + 16.13.19.11.18.13.17.17 + 16.13.2.19.14.29.31.30.23.15.12 + 16.13.26.18.9.29.11.17.1.24.26 + 16.14.3.17.17.26.12.19.19.30 + 16.16.28.24.11 + 16.18.23.6.31 + 16.19.17.30.30.5.17.24.27 + 16.2.14.3.26.11 + 16.20.29.26 + 16.21.13.1.4 + 16.23.30.12.31.31.19.14 + 16.24.3.30.15.22.31.2 + 16.24.7.25 + 16.27.8.17.14.17.21.29.14 + 16.28 + 16.29.6.23.13.28.31.6.19.26.15 + 16.30.10.7.29.4.9.21.22.13.26 + 16.31.12.27.25.9.32.29 + 16.5 + 16.5.10.2.18.8.15.12.32.25.10 + 16.5.12.5.15.12.24.25.3 + 16.5.14.21.32.17.23.3.4.26 + 16.5.23.17 + 16.5.6.12 + 16.8.29.7.21.2.3 + 16.9.14.28.6.21.31.31.26 + 16.9.29 + 16.9.32.14.3.7.8.7.21.22 + 17.1.12.20 + 17.10.17.22.20.25.14.13 + 17.11.17.4.8.26.26.20.6 + 17.13.14.29.27.27.13.12.15 + 17.13.19.31.12.18.10.15.14 + 17.13.8 + 17.14.7.3.2.18.20.23.18.5 + 17.17.14.28.6.30 + 17.19.1.22.11.7.22.1.14.28.11 + 17.22.12.10.30.11 + 17.24.15.27.3.32.4.22.20.6.24 + 17.24.30.6.32 + 17.25.10.13.21.5.7.22.2 + 17.25.2.13.10.27.13.1 + 17.25.26.23.32 + 17.26.18 + 17.27 + 17.29.21.10.18.8.16.26.18.21.26 + 17.29.31.8.24.10.18.27.17 + 17.3 + 17.5.3.15.17.13.5 + 17.7.26.30.18.23.4 + 17.8 + 17.8.31.32 + 17.9.32.31.21.31.23.17.10.32.9 + 18.13.6.12.26.26.26.29.18.20.1 + 18.13.9.3.18.15.2 + 18.15.14 + 18.17.6.16.6.10 + 18.18.19.16.14.16.21.10.25 + 18.18.5.11.7.4.25 + 18.19.11.20.13.13.11 + 18.19.12.20.18.17.15.32.18.5 + 18.21 + 18.24.21.17.11.26.28.22.21.18.10 + 18.27.11.27.9.16.7.6.22.26.27 + 18.29.13.24.18.3.12.18.12.12 + 18.29.5.1.10.21.2 + 18.30.11.17 + 18.30.18.31 + 18.31.26.18.6.15.18.11 + 18.31.32.28.1.4.24.24.12.25 + 18.31.32.29.22.1.31.11.28 + 18.4 + 18.4.14.29.3 + 18.5.6.31.5.15.15 + 18.6.2.2.24 + 18.6.26.2.13.9.6.11.10.11.16 + 18.7.10.27.17.24 + 18.7.3.17.13.5.31.6.31.25.29 + 18.9.21.2.31.8.32 + 18.9.26.7 + 19.10.26.19.5.21.30.23 + 19.10.4.30.32.4.12 + 19.10.8.10.4.19 + 19.11.10.18.14.13.7.7 + 19.11.29.13.15.27.12.15.14.12 + 19.12.20.24.32.13.11.23.26 + 19.12.26.24.29.3 + 19.12.30.2.21 + 19.15.26.19 + 19.16.26.2 + 19.16.31.31.29.12 + 19.17.12.15 + 19.17.13.12.32.16.3 + 19.19.25.22.11.6.15.3.2.19 + 19.2.26.21.16.11.2.2 + 19.2.9.29.6 + 19.20.25.7.27.28.27.17.9.3.1 + 19.22.21.13.27.13.15 + 19.22.29.32.1.21.26.24.23.17 + 19.26.24.27.6.24.16.27.32.29 + 19.26.32.13.1.12.30.26.22.25 + 19.3.12.12 + 19.3.23.4.4.21.23 + 19.30.18.11.32.14 + 19.30.27.26.21.7.18 + 19.31.14.25.5.8.21.11.13.20 + 19.5.20.3.4.2.3 + 19.6.13.14.22.13.9.29 + 19.6.24.32.30.13.6.25.8.28 + 19.7 + 19.7.29.31.3.20.7.21.25.27.29 + 19.9.32.23.13.24.1 + 2.1.12.19.29.28.3.31.28.28.10 + 2.1.3.30.24.17.9 + 2.10.10.4.20.1.12.13 + 2.10.28.1.17.19.32.28 + 2.11.32.25.23 + 2.12.14.28.16.21 + 2.12.30.22.12 + 2.13.9.23.21.2 + 2.13.9.28 + 2.14.10.4.17.17.8.4.27.20 + 2.14.12.13 + 2.15.14.20.30.26 + 2.15.18.21.5.21.4.7.30 + 2.16.3.7.22.18.29.20 + 2.19.4.1.15.7.8.9.17.29 + 2.2.18.18.3.3.18.8.10.8 + 2.22.19 + 2.24.4.5.24.32 + 2.24.5.3.4.10.27.26.17.28.16 + 2.27.15.14 + 2.28.5.17.6.32 + 2.30.26.10.14.31.18.2 + 2.31.25 + 2.32.10.13.12 + 2.32.8.28.24.20.9.24.25.8.9 + 2.4.25.32.16.22.26.13.17.18 + 2.6.15.26.23.26.24 + 2.8.13.12.17.23.16.7.11.23 + 2.9 + 20.1.24.3.30.31 + 20.13 + 20.14.11.2.10.14 + 20.15 + 20.17.14.7 + 20.17.18.21.1 + 20.18.24.14.12.13.9 + 20.20.32.29.24.5.5.26.22.32 + 20.20.7 + 20.22.10 + 20.23.29.5.7.30.13.14.22 + 20.23.7.11.11.31.18.16.3 + 20.24.14.15.4.21.12.27.4.12 + 20.25.22.19.22 + 20.28.22.7.10.28.27.22.14.16 + 20.29.18.16.2.21.23.11 + 20.3.1.8.8.30.20 + 20.30.17 + 20.30.28.15.17 + 20.30.9.9.14.12.29 + 20.31.13.12.19.2.26.16.16.22.28 + 20.32.5.1.3.20.3.30.27 + 20.32.9 + 20.4.1.16.31.3 + 20.4.27.31.1 + 20.5.4.9.31.14.26.6 + 20.6.26.3.30 + 20.6.3.26.7.29.28.4 + 20.8.19.14.16.7 + 20.9.29.32.13.7.23 + 21.1.4.9.9.31.24.21.3.29 + 21.10.20.9.3.16.9.10.20 + 21.14 + 21.14.13 + 21.14.22.29 + 21.14.25.20.13.31.14.20 + 21.15.18.18.30.3.20 + 21.15.31.24.29.24.26.12.20 + 21.17.18.32.7.8 + 21.17.27.23.15 + 21.17.31.10.31.13.9.26.6.14 + 21.18 + 21.18.2.1 + 21.18.30.19.24.24 + 21.20.24.25.6.26.23 + 21.20.28.19.27.9 + 21.21.10.27 + 21.22.31.24.27 + 21.23.13 + 21.23.17.8.23.11.8.1 + 21.28.17.22.10.27.4.20.2.32 + 21.28.24.23.3.11.7.12.22.32 + 21.30.19.6.28.1.32.2.14.14 + 21.31.31.25.5.30.26 + 21.32.13.21 + 21.32.13.22.3.13.31.23.14.12.9 + 21.4.11.18 + 21.4.22.20.24.28.6 + 21.5.11.18 + 21.5.17.19.15.25.18.21.24.9 + 21.6.22.28.12.23.11.22 + 21.7.23.9.16.5.18.14 + 21.7.7.11 + 21.8.9 + 21.9.27.22.32 + 21.9.32.1.27 + 22.10.12.23.9 + 22.10.16.8 + 22.10.18 + 22.10.27.19.29.20.29.3.12.14.25 + 22.11 + 22.12.22.28 + 22.13.22.21.25.17.8 + 22.13.22.8.30.32.10.24 + 22.15 + 22.16 + 22.16.25.18.25.7.24.29.14.8 + 22.17.24.14.21.15.12.18.17.25.11 + 22.17.30 + 22.17.4.2.22.17 + 22.17.7.30.13.24 + 22.17.9.11.25.15.3.9 + 22.18.20.23.15.9.12 + 22.19.20.5.2.20 + 22.19.21.11.6.8.29.24 + 22.19.5.22.20.31.23.24.14.24.4 + 22.20.30 + 22.21.32.15.8.29.5.12.10.29 + 22.22.10.30.5.15.25.21.19.11 + 22.22.27.6.27.15.5.18.21.28.9 + 22.23.18.18.9.8.23.7.23.23.16 + 22.23.22.30 + 22.23.25.28.5.27.9.9.24.31.10 + 22.24.22.25.15.23.13 + 22.25.4.28.9.20.12.13 + 22.26.1.28.9.9.31 + 22.26.32 + 22.28.20.6.32.32 + 22.29.18.32.13.12.22.31.17.22 + 22.29.29.11 + 22.3.6 + 22.30 + 22.30.31.24.23.22.5.20.28.1 + 22.31.2.32.32.11.26.23.19 + 22.31.21.13.13.26.11.5.19 + 22.32.6.6.3.8.24.6.25.29 + 22.8.20.1.10.28.6.27 + 22.9.15.19.12 + 23.1.23.18.12.29 + 23.10.13.32.14.20.16.11.14 + 23.10.5.26.12.4.20.4 + 23.12.1.5.32.25.8.24.1.25 + 23.12.11.11.15.16.22.31.32.5.8 + 23.12.19.25.16.23.22.6.29.4 + 23.12.32.22.19.1.22.4 + 23.14.12.30.18.4.16.18.7.7 + 23.14.30.27.28.26.26.23.8.32 + 23.17.22.1.23.4.29.32.4.1 + 23.17.25.4.1.16.29.10 + 23.17.28.31.28 + 23.17.32.15.23.16.25 + 23.19.17.31.29.13.1.12.5.25 + 23.2.22.7.32.3.27.6 + 23.20.12.16.15.2 + 23.20.24 + 23.20.8 + 23.22.10.1.14.24 + 23.22.23.14.31.32 + 23.23 + 23.24.11.31.10.31.18.28.13.18.6 + 23.24.16.32.13.29 + 23.25.23.11.7.23 + 23.27.27.16 + 23.27.6.26.22 + 23.28.1 + 23.28.20.25.30.24.15 + 23.28.3.30.15.31.32.3.21.9.19 + 23.3.20.24 + 23.3.32.21.5.14.10.17.1 + 23.31.27.16.8.30.20.27 + 23.32.5.25.19.9.15.17.15.11 + 23.5.5.17 + 23.5.7.12.11.23.10 + 23.6.27 + 23.8.13.22.21 + 24.1.10.20.28.18.6.27.20.30.26 + 24.1.29.32.14.15.32.6.15.22 + 24.10.10.31.4.29.9 + 24.10.8.25.16 + 24.11.5 + 24.12 + 24.13.1.8 + 24.15.15.17.22 + 24.16.27.10.9 + 24.17.24 + 24.17.31.20.12.9.19.29.18 + 24.18.16 + 24.2.26.24.14.15.31.23.17.26 + 24.2.6.7.16.7.28 + 24.20.23 + 24.21.14.25.11.3.20.6.6.16 + 24.23.24.4.15.25.17 + 24.23.29.8.24.11.21.10.28.14.27 + 24.24 + 24.25.7.27.30.8.26.17 + 24.27.14 + 24.27.18.32.14.9.11.28.9 + 24.28.13.26.8.8.31 + 24.28.32.21 + 24.3.23.25 + 24.31 + 24.31.2.13.5.23.18.16 + 24.31.8 + 24.32.17.23.24.19.23.9.20.18 + 24.32.27 + 24.9 + 24.9.15.1.14.29.6.4 + 24.9.27.16.20.21 + 24.9.8.12.29 + 25.10 + 25.10.29.3.6.21.3.31.13 + 25.10.4.28.3.31.19 + 25.11.24 + 25.14.5.32.25 + 25.15.11 + 25.16.9.6 + 25.17.18.17.27 + 25.17.18.30 + 25.17.2.20.20.3.29.21.3.12 + 25.17.9.16.17.31.23.29.24 + 25.18.8.3.23.23.5.9.6 + 25.19.27.2.9.20 + 25.2.11.20.8.6.22 + 25.2.3.15.11.19.5.28.25.14 + 25.21.8.17 + 25.22.2.25.6 + 25.24.2.32.14.18.16 + 25.24.29 + 25.28.3 + 25.28.30.24 + 25.29 + 25.3 + 25.30.1.4.24.11 + 25.31 + 25.32.24.24.28.15.16.10 + 25.4.32 + 25.4.4.1.13.32.26.20.20.3 + 25.5.30.7.16.12.21.12.11.16 + 25.6 + 25.6.12.16.1 + 25.7.3.21.31.12.28 + 25.9 + 25.9.1.5.9.11.25.4.11.27.32 + 25.9.10 + 26.11 + 26.12.27.2 + 26.13.4.7.13.11.3 + 26.14 + 26.14.5.32.10 + 26.16.12 + 26.16.12.3.27.9.28 + 26.17.9.13.4.25.32.2.24.9 + 26.18 + 26.18.32.20 + 26.19.3.14.8.28.31.10 + 26.24 + 26.24.9.12.11.15.31.2 + 26.25.10.10.13 + 26.25.24 + 26.26.22.21.14.11.29.19.14.24 + 26.28.14 + 26.31.11.23.3 + 26.31.16.18.22.13.32.23.9.20 + 26.31.6.8.29.8.24 + 26.31.7 + 26.32.21.31.27.12 + 26.32.8.12.30.19.24.8.6.1.10 + 26.5.29.7.28 + 26.7.22.3.18.21.11 + 26.7.5.8.11.9.22.1.6 + 26.8.28 + 26.9.17.1.18.19.1.11.18.29.3 + 26.9.20.12.22.22.32 + 27.1.11.3.25.9.6.6 + 27.11.14.17.24 + 27.11.15.9.24.31.18.4.1.30.20 + 27.12.4.2.29.22.15 + 27.15.15.15 + 27.16 + 27.17.15.7.28.20 + 27.17.17.19.24.9.14.20 + 27.17.3.18.2.13.18 + 27.18 + 27.18.10.4.22 + 27.19.20.1.31.29.5.22.26.3 + 27.2.10.4.25.14.2.15.4 + 27.21.27.5.13.30.17 + 27.21.28.24.7.2.24.23.8 + 27.22.11.13.21.25.5.1.27.21.27 + 27.23.2.32.11.21 + 27.23.20.30.7 + 27.24.11.31.21.6.29.17.24.18 + 27.25 + 27.26.29 + 27.27 + 27.27.25.10.31.10.21.22.21.16.12 + 27.27.30.11.15.24.9.7.4.30 + 27.29.1.5.30.6.22.16.23.2.28 + 27.3 + 27.3.3.11.21.4.25 + 27.30.12.11.20.15.11.13 + 27.31.2.16.29.6 + 27.32.26.21.31.17.32.32 + 27.4 + 27.4.15.14.19.6.12 + 27.4.17.17.32.8.16.15.17.13 + 27.5.15.1.15.16.21 + 27.5.22 + 27.6.13.24.21.27.28.22.3.7.4 + 28.1.3 + 28.11.11.30.20.11.32 + 28.11.27.21.14.16 + 28.14.24.26.6.15.16.32.25.13.8 + 28.14.32.29.2.3.4 + 28.15.18.27 + 28.15.25.7.13.6.19.2 + 28.17.26.9 + 28.18.6.22.13.8.25 + 28.2.27.1.20 + 28.20.8.9.9.28.30.29 + 28.23.2.30.3.8.1.15.15.14.13 + 28.25.10.25.19.15 + 28.25.11.22 + 28.25.29.4.13.5.6 + 28.26.25.7 + 28.26.26.6.31 + 28.26.4.22.13.20.32.27.15 + 28.27.24.14 + 28.28 + 28.30.24.16.17.28.2.13.10 + 28.31.10.28.22.26.16.15 + 28.4 + 28.5.12.9.2.27.11.11.2 + 28.5.13 + 28.6 + 28.6.11.6.15.22.12.6 + 28.6.8.22.25 + 28.8.21.15.16.28.4.16.26.8 + 28.9.3.16.17.21.23.30 + 29.1 + 29.1.2.14.14 + 29.1.7.26.25.11.22 + 29.10.12.17.12.16 + 29.10.17.11.28.12.18.5.19.15.21 + 29.11.20.22.27 + 29.14.12.9.17.5.32 + 29.14.31.25.7.32.23 + 29.15.29.8.31.26.1 + 29.20.1.11.21.16.1.2.14.28 + 29.23.1.21.31.8 + 29.23.15.25.1.6.6.10 + 29.25.29.16.32.11.15.25.5.22.3 + 29.25.30.15.21.3.25.26.26 + 29.26.25.14.24.18.2.13.23.29 + 29.27 + 29.27.13.29.10.2 + 29.27.13.9.28.29.19.13.29.31.27 + 29.27.5.22.26 + 29.27.7.7.3.11.14.26.21.11 + 29.28.9.15.8.27.31 + 29.29.17.31 + 29.29.18 + 29.3.15.17.12.29 + 29.3.17.17.18.32 + 29.30.21.8.16.23.32 + 29.30.7.31.22 + 29.32.13.4.1.16.20 + 29.5.18.27.3.21.18.6.14 + 29.5.32.20.11.7.13.24.17 + 29.6.12.31.20.23.32.20 + 29.9.25.27.15.16.32.26.6.32 + 3.1.13.22.24.14.12.31.3.4 + 3.1.14.8.9.16.30.22.20 + 3.10 + 3.10.27.4.5.6.19.12.28.12 + 3.10.4.5.28.11 + 3.11.18.21.5.20.30 + 3.11.32.11.22.3.7.17.8.13.23 + 3.13 + 3.14.1.14.17.28.29.16 + 3.14.11.15.21.32.2.15.13 + 3.14.30.5.32.22.29 + 3.15.2.23.22.2.16.14 + 3.18 + 3.18.18 + 3.18.8.22.7.28.32.31.3 + 3.19.11.6.5 + 3.20.16.13.29.20 + 3.20.19.10.17.27.3.6.22.23 + 3.21.16.24.23.12.16.32.3 + 3.21.6.13.12.18.25 + 3.22.18.1.5.14.9.6.14 + 3.25 + 3.26 + 3.26.32 + 3.27.18.8.4.21.6.32.30.7.5 + 3.29.19.2.24 + 3.29.32.26.8.10.25 + 3.3 + 3.32.2.29.3.32.28.11.29.30 + 3.4.22.19 + 3.5 + 3.6.24.21.20.32.3.4.26.5 + 3.9.11.23.32.26.24.28 + 3.9.25.26.7 + 30.12.28.2 + 30.12.6.30 + 30.12.9.25.24.6.7.24.29 + 30.15 + 30.16.14.9.5.4.10.7.31 + 30.16.3.21.10 + 30.17.2.25 + 30.17.25.3.31.11.3.4.1.10 + 30.17.4.5.13.6 + 30.18.30.16.29 + 30.2.17.8.14 + 30.20.3.2.5.15.8.7.17 + 30.22.29.21.19.14.3.2.6 + 30.23.10.1.10.7.22.28.18.11.17 + 30.23.2.13.14.15.29.19.4.12.24 + 30.24 + 30.24.23.25.32.18.22.12.29.9.22 + 30.24.32.15.14.10.11 + 30.25 + 30.25.17.17.10.29 + 30.25.24.22 + 30.25.8.24.6.29.31 + 30.27.8.6.11.19 + 30.3.16.26.7.27.26.9.27.21.18 + 30.30.17.5.30.21.19.5.22.22.14 + 30.31.13.9 + 30.32 + 30.4.30.11.13.23.14.24.11 + 30.5 + 30.6.4 + 30.8.18.5.20.6.15 + 30.8.9.14.25.30 + 30.9.24 + 31.13 + 31.13.9.1.5.12 + 31.17 + 31.17.2.30.11 + 31.18 + 31.18.25.1.14.29.25.5.22.30 + 31.18.27.15.20.29.29 + 31.18.32.11.7.25.20.5 + 31.21.14.20.1.22.2.5.3.27.12 + 31.21.22.14.8.21 + 31.24.26.18 + 31.28.32.4.31.4.7 + 31.29.18.26.1.26.17 + 31.29.4.29.24.30.30.32.10.23 + 31.30.12.20 + 31.30.23.7.7.24.32.10.11.1.31 + 31.32.12.26.31.32.14.23.28 + 31.4.7 + 31.5.6.4.8.29.3 + 31.7.14.2 + 31.9.3.5 + 32.1.21.1.16.29.21 + 32.1.23.20.14.12.23.5.32.15 + 32.1.24.29.22.5.9.24.18.3.13 + 32.1.31 + 32.15.20.28.5.1.23.4 + 32.16 + 32.17.8.24.2.14.5.4.22 + 32.19.20.24.23.31.8.32.16.29 + 32.2.11 + 32.24.11.8.12.23.22.19.11.17.18 + 32.24.29.6 + 32.25.16 + 32.25.3.6 + 32.27.13.6.7 + 32.27.18.7.3.4.2 + 32.28.1.32.28.10 + 32.29.24.31.25.6.9 + 32.3.12.2 + 32.3.23.7.2 + 32.3.5.9.17.15 + 32.30.18.17.1.14.12.18 + 32.31.11.22.1 + 32.31.26.19.13.29.4.25 + 32.4.19 + 32.6.13.8.32 + 32.6.15.26.14.15.3.19 + 32.6.3.2.12.5.28.1.25 + 32.6.31.31 + 32.6.8 + 32.6.9.26.16.4.4.29.7.11 + 32.8.29.18.31 + 32.8.5 + 4.1.24.24.28.24.18 + 4.10.28 + 4.11.19.17.2.22.20.18.13.32.15 + 4.11.22.4.19.24.4.28.6.8.22 + 4.13 + 4.13.22.11.9.13.27.15.7 + 4.14 + 4.14.10.19.16 + 4.14.16.14.1.8.1.22.17.10 + 4.14.17.12.20.17.1.22.3 + 4.14.32 + 4.15.20.23.12.16.2.16.17 + 4.16.22.19.24.21 + 4.16.7.25.21.7 + 4.18.29.9.16.10 + 4.19.16.15.5.2.25.8.28.14.2 + 4.2.16.13.16.11.19.10.10.25 + 4.2.2.32.24.25.31.3 + 4.2.6.20.7.8 + 4.21.28.5.16.29.5.21 + 4.21.9.1.2.14.8.17.13.26 + 4.22 + 4.22.17.10.19.9.8.19.28.3.9 + 4.22.7.19.25 + 4.25.12.10.15.9.18.9 + 4.26.2.2 + 4.26.23.6.19.31.10.4.22 + 4.26.5.26.21.28.17.24.25.23 + 4.27.32.18 + 4.3.20.27.9.1.18.30.12.5.19 + 4.3.6.27.22.23.10 + 4.30.8.20.19.9.30.24.11 + 4.31 + 4.5.9.4.15.19.8.26.17.26.3 + 4.7.1 + 4.9 + 5.1.5.31 + 5.10 + 5.10.2.11.21.9.19 + 5.10.3.9.23.30.23 + 5.12.2.20.1.24.25 + 5.13.23.19.28.26.27.6.1.22 + 5.13.23.4.9 + 5.14.27.15.11.17.3.10.27.25 + 5.14.29.2.23.16.20.22 + 5.15.10.3.23.13.32.23 + 5.15.16 + 5.18.9.25.31.21.22 + 5.19.1.26.20.6.20 + 5.2.32.19.13.29.12.13.31.29 + 5.20 + 5.21.27.13.14.11.2.16.20 + 5.23.31.18.24.32 + 5.24.24.9.32.26.31 + 5.24.25.15.27.30.20 + 5.24.4.31.3.16.25.17.13.26.11 + 5.27.16.3.30 + 5.27.21.1.29.29.28 + 5.27.28.26.14.15.6.20.1.31.13 + 5.27.32.21.5.1.11.14 + 5.3.17.29 + 5.3.29.9.22 + 5.31.8.1.5.13.21.28.29.19.2 + 5.4.8.25.12.27.2.29.28.3 + 5.5.12.31.23.13.17.22.20 + 5.8 + 5.8.17.30.15.8.19.29.30.11.6 + 5.9.19.6 + 6.1.8.6.30.29.30 + 6.10.25.12 + 6.11.11.5.16.8.14.12.9 + 6.11.31.23.12.8.30.14.27 + 6.13.31.5.7.26 + 6.14 + 6.17.10.10.7.9.27.8.29 + 6.17.26.25.27.11.10.9 + 6.18.1.4.18.23 + 6.19.29.11.2.32.21.15.32.9 + 6.19.3 + 6.19.6.4.9.11.32.17.17.3.15 + 6.2.32 + 6.20 + 6.20.14 + 6.21.30.7 + 6.22.12 + 6.25.17.32 + 6.26 + 6.26.29.10.21.28.20.19 + 6.27.26.1.20.24.6 + 6.27.29.14.8.12.26.3.21.4.1 + 6.29.32.13.30.3.16 + 6.29.6.13.14.24.10.4.14.28 + 6.5.27.19.13.26.1.18.9 + 6.6.22.8 + 6.7.25.16.13.21.7.20.25.12.4 + 6.7.7 + 6.8.7.20.2 + 6.9.1.10.10.22.6 + 6.9.29.17.4.32 + 7.10.17.21.11.29.17.25.19.4.29 + 7.11 + 7.12 + 7.12.1.10.6.17.29.24.24.4 + 7.12.23 + 7.13 + 7.13.15 + 7.14.22.29.30.14.25.1.9.26.25 + 7.16.20.17 + 7.19.10.12.31.1.27.13.19 + 7.19.12.3.21.19.18.5.2.14.10 + 7.19.6.17.15.26.21.9 + 7.21.8 + 7.23.1.24.29.13.31.19.23.17.7 + 7.23.15.32.28.27.2.2.26 + 7.26.18 + 7.27.20 + 7.30.19.25.23.15.14.29 + 7.30.5.10.10.5.30.14.9.18 + 7.31 + 7.31.2.28.15.11.17.18.19.23.6 + 7.31.4.20.17 + 7.32.10.3.30.12.14 + 7.5.28.8.17.26.31.10.15 + 7.7 + 7.7.22.24.17.32.17.25.28 + 7.7.25.22.22.26 + 8.1.29.18.22 + 8.10 + 8.11.20 + 8.12.4 + 8.13.1 + 8.13.14.11.11.29.22.4.4.10 + 8.13.6.12.18.7 + 8.13.9.31.20.20.24.7.23.31.28 + 8.14.19.18 + 8.16 + 8.16.1.16.28.6.3.22.6.23 + 8.16.20.24.20.6.10.21 + 8.16.30.29.19.22.28.24.2 + 8.16.6 + 8.17.25.26.15.25 + 8.17.9.15.21.28.1.7.1.3.6 + 8.2 + 8.2.18.23.5.16.17.1 + 8.21.17.3.6.3.18 + 8.21.8.23.4.18 + 8.22.32.17.16.28.31.23.22.9 + 8.24.11.13.25.19 + 8.25.20.3.15.24.7.4.24.5.30 + 8.26.29.13.7.25.31.28.3.32 + 8.27.3.4.12.26.16 + 8.29.6.3 + 8.3.18.13.30.20.27.26.17.28 + 8.3.3.25.25.15.7.13.21.18 + 8.31.22.27 + 8.32.30.1 + 8.5.24.9.29.32.31.30.13.9.7 + 8.5.30.29.9.31 + 8.6.6.5.8.8.12 + 8.9.21.16.29 + 8.9.22 + 8.9.25.25.26.30.31.31.2.32.7 + 9.10.19.18.15.11.22.32.32.14.9 + 9.10.32 + 9.14.27.31.26.21.25.3.20 + 9.16.2.16.22.24.17.31.14.21.17 + 9.17.13.31.7 + 9.18.23 + 9.18.30.11.29.32.7.19.2 + 9.19.7.13.13.25 + 9.2.10.4 + 9.2.4.27.26 + 9.21.14.19 + 9.21.20.29.1 + 9.21.28.8.12.15.3.13.10.11 + 9.22.10.15.5.15 + 9.23.21.22.5.29.15.21 + 9.26.1.16 + 9.28.10.26.14.26.15.14 + 9.28.24 + 9.28.30.1.6.25.17.9 + 9.3.3 + 9.3.31.18.12.3.9.29.10 + 9.30 + 9.31.23.19.5.10.16.4.30.24.5 + 9.31.4.14.31.10.17.5.2 + 9.5 + 9.5.9.3.23.9.25.14.1.29.28 + 9.6.9.21.6.11.29.13.29.20.32 + 9.7.31.11.8.23 + 9.8.23.2.20.16 + 9.9.13.9.14.27 +(882 rows) + +SELECT * FROM ltreetest WHERE t @> '1.1.1' order by t asc; + t +------- + + 1 + 1.1 + 1.1.1 +(4 rows) + +SELECT * FROM ltreetest WHERE t <@ '1.1.1' order by t asc; + t +----------- + 1.1.1 + 1.1.1.1 + 1.1.1.2 + 1.1.1.2.1 +(4 rows) + +SELECT * FROM ltreetest WHERE t @ '23 & 1' order by t asc; + t +-------------------------------- + 1.10.23.25.5.11 + 1.10.4.18.22.23.24 + 1.12.25.26.22.8.15.23 + 1.19.22.11.14.7.32.23.19.14 + 1.21.28.4.23 + 1.26.15.23.5.31.29.11.19.28.1 + 1.27.22.23.2.26.32.17.7.9 + 10.12.9.6.6.26.14.8.23.1.25 + 12.27.23.32.1.1.9.29.13 + 14.1.15.25.27.23.25.26.28.10 + 14.27.29.23.4.1.17.32.6.25.22 + 15.11.26.1.30.6.23.5 + 19.22.29.32.1.21.26.24.23.17 + 19.9.32.23.13.24.1 + 21.23.17.8.23.11.8.1 + 22.30.31.24.23.22.5.20.28.1 + 23.1.23.18.12.29 + 23.12.1.5.32.25.8.24.1.25 + 23.12.32.22.19.1.22.4 + 23.17.22.1.23.4.29.32.4.1 + 23.17.25.4.1.16.29.10 + 23.19.17.31.29.13.1.12.5.25 + 23.22.10.1.14.24 + 23.28.1 + 23.3.32.21.5.14.10.17.1 + 27.29.1.5.30.6.22.16.23.2.28 + 28.23.2.30.3.8.1.15.15.14.13 + 29.23.1.21.31.8 + 29.23.15.25.1.6.6.10 + 30.23.10.1.10.7.22.28.18.11.17 + 31.30.23.7.7.24.32.10.11.1.31 + 32.1.23.20.14.12.23.5.32.15 + 32.15.20.28.5.1.23.4 + 5.13.23.19.28.26.27.6.1.22 + 6.18.1.4.18.23 + 7.23.1.24.29.13.31.19.23.17.7 + 8.16.1.16.28.6.3.22.6.23 + 8.2.18.23.5.16.17.1 + 9.5.9.3.23.9.25.14.1.29.28 +(39 rows) + +SELECT * FROM ltreetest WHERE t ~ '1.1.1.*' order by t asc; + t +----------- + 1.1.1 + 1.1.1.1 + 1.1.1.2 + 1.1.1.2.1 +(4 rows) + +SELECT * FROM ltreetest WHERE t ~ '*.1' order by t asc; + t +-------------------------------- + 1 + 1.1 + 1.1.1 + 1.1.1.1 + 1.1.1.2.1 + 1.1.2.1 + 1.26.15.23.5.31.29.11.19.28.1 + 10.13.22.1.8.30.9.24.1.2.1 + 10.22.1 + 10.26.30.15.1 + 11.1 + 12.1.1 + 17.25.2.13.10.27.13.1 + 18.13.6.12.26.26.26.29.18.20.1 + 19.20.25.7.27.28.27.17.9.3.1 + 19.9.32.23.13.24.1 + 20.17.18.21.1 + 20.4.27.31.1 + 21.18.2.1 + 21.23.17.8.23.11.8.1 + 22.30.31.24.23.22.5.20.28.1 + 23.17.22.1.23.4.29.32.4.1 + 23.28.1 + 23.3.32.21.5.14.10.17.1 + 25.6.12.16.1 + 29.1 + 29.15.29.8.31.26.1 + 32.31.11.22.1 + 4.7.1 + 6.27.29.14.8.12.26.3.21.4.1 + 8.13.1 + 8.2.18.23.5.16.17.1 + 8.32.30.1 + 9.21.20.29.1 +(34 rows) + +SELECT * FROM ltreetest WHERE t ~ '23.*{1}.1' order by t asc; + t +--------- + 23.28.1 +(1 row) + +SELECT * FROM ltreetest WHERE t ~ '23.*.1' order by t asc; + t +--------------------------- + 23.17.22.1.23.4.29.32.4.1 + 23.28.1 + 23.3.32.21.5.14.10.17.1 +(3 rows) + +SELECT * FROM ltreetest WHERE t ~ '23.*.2' order by t asc; + t +------------------ + 23.20.12.16.15.2 +(1 row) + +SELECT * FROM ltreetest WHERE t ? '{23.*.1,23.*.2}' order by t asc; + t +--------------------------- + 23.17.22.1.23.4.29.32.4.1 + 23.20.12.16.15.2 + 23.28.1 + 23.3.32.21.5.14.10.17.1 +(4 rows) + +create unique index tstidx on ltreetest (t); +set enable_seqscan=off; +SELECT * FROM ltreetest WHERE t < '12.3' order by t asc; + t +---------------------------------- + + 1 + 1.1 + 1.1.1 + 1.1.1.1 + 1.1.1.2 + 1.1.1.2.1 + 1.1.2 + 1.1.2.1 + 1.1.3 + 1.1.7.32.11.22 + 1.10.21 + 1.10.23.25.5.11 + 1.10.4.18.22.23.24 + 1.10.5.22.13 + 1.11.10.19.6.1.26.17.2.22 + 1.12.25.26.22.8.15.23 + 1.13.16.27.11.16.30.2.9.18.4 + 1.14.3.7.3.17.2.29 + 1.15.17.6.28.25.24.31.27.9 + 1.16.8.18.14.16.21.25.6 + 1.18.29.30.22.14.3.20.15.21.20 + 1.19.22.11.14.7.32.23.19.14 + 1.20.18.25.3.24.25.10.9 + 1.20.22.26.2.6.11 + 1.21.28.4.23 + 1.22.19.24.8.11 + 1.22.29.5.16 + 1.25.7.9.26.17.31.20.13 + 1.26 + 1.26.15.23.5.31.29.11.19.28.1 + 1.27.22.23.2.26.32.17.7.9 + 1.28.19.8.25.6.20.27.29.27 + 1.28.3.22 + 1.29.18.1.21.12.13.27.32.15 + 1.3.15.11.11.25.24.21.19 + 1.30.18.31.12.25.4.19.28.12.15 + 1.30.31.31.20.16.7 + 1.31.3 + 1.4.14.32.14 + 1.8 + 1.9.18.10.1.26.22.16.17 + 10.11.25.2.24.18.18.21.6.26.21 + 10.12.23.22.23.22.20.17.17.9 + 10.12.9.6.6.26.14.8.23.1.25 + 10.13.12.8.4.8.11.30 + 10.13.22.1.8.30.9.24.1.2.1 + 10.15.16.3 + 10.16.18.9.27.2.29.32.24.13 + 10.16.19.7.15 + 10.18.12.27.24.30.32.7.11.5.13 + 10.2.17.26.16.7.19.6.23.3 + 10.20 + 10.22.1 + 10.22.30.16.2.21.17.13 + 10.26.27.23.4.31.11.25.29 + 10.26.30.15.1 + 10.27.7.24.26.11.31.20.29 + 10.28.22.29.13.19.6.7.6.14 + 10.28.7.16.31 + 10.29 + 10.29.26.4.27.17.11 + 10.3 + 10.3.19 + 10.31 + 10.31.25.31.24.16.17 + 10.32.14 + 10.5.23.5.32.9.18.5.30 + 10.5.5.15.29.2 + 10.7.9 + 10.8.20.11.12.23.22 + 11.1 + 11.1.3.28.30.21.24.14 + 11.10 + 11.10.22.18 + 11.11.11.4.23.21.25 + 11.11.9.30.15.29.15.18 + 11.12.6.21 + 11.14.21.24.10.7.29.23.24.28 + 11.15.11.19.29.10 + 11.16.16.28.14 + 11.17.10 + 11.17.17.24.11.23.17.17.18.10.22 + 11.18.4.8.3.13.14.28.18.31 + 11.19.23.3.6.11 + 11.2.27.3 + 11.21.13.9.19 + 11.21.16.27.16 + 11.22.28.8.12.23.25.15.21.28 + 11.29 + 11.3.15.28.22.8.14 + 11.30 + 11.30.20.15.18.32.1.18.25.26.8 + 11.32.18.31 + 11.6.11.29.4.5.24.6.26.12 + 11.7.31.15.22 + 11.8.18 + 12.1.1 + 12.1.28.22.25 + 12.10 + 12.10.11.9.10.31.4.16.31 + 12.11.17.1.2 + 12.11.20.20.29 + 12.13.16.17.29.27.16.14.9.19.9 + 12.13.5.31 + 12.14.20.8.28.4 + 12.15.10.17.18.13 + 12.16.13 + 12.16.2.4.15 + 12.17.10.7.17.16 + 12.18 + 12.2.4.28.21.30.24 + 12.21.15.27.24.15.8.24.24.26 + 12.21.20.20 + 12.22.20.4.12 + 12.23.3.19.29.15.12.6 + 12.24.29.32.32.29.2 + 12.25.32.2.27.3.3.16 + 12.27.23.32.1.1.9.29.13 + 12.27.30.12.24.2.20 + 12.28.12.24.28.15.5.12.30.13.21 + 12.29.17.2.20.29.1.11.19.8.12 + 12.29.26.18.4.21.28.8.13.3 +(123 rows) + +SELECT * FROM ltreetest WHERE t <= '12.3' order by t asc; + t +---------------------------------- + + 1 + 1.1 + 1.1.1 + 1.1.1.1 + 1.1.1.2 + 1.1.1.2.1 + 1.1.2 + 1.1.2.1 + 1.1.3 + 1.1.7.32.11.22 + 1.10.21 + 1.10.23.25.5.11 + 1.10.4.18.22.23.24 + 1.10.5.22.13 + 1.11.10.19.6.1.26.17.2.22 + 1.12.25.26.22.8.15.23 + 1.13.16.27.11.16.30.2.9.18.4 + 1.14.3.7.3.17.2.29 + 1.15.17.6.28.25.24.31.27.9 + 1.16.8.18.14.16.21.25.6 + 1.18.29.30.22.14.3.20.15.21.20 + 1.19.22.11.14.7.32.23.19.14 + 1.20.18.25.3.24.25.10.9 + 1.20.22.26.2.6.11 + 1.21.28.4.23 + 1.22.19.24.8.11 + 1.22.29.5.16 + 1.25.7.9.26.17.31.20.13 + 1.26 + 1.26.15.23.5.31.29.11.19.28.1 + 1.27.22.23.2.26.32.17.7.9 + 1.28.19.8.25.6.20.27.29.27 + 1.28.3.22 + 1.29.18.1.21.12.13.27.32.15 + 1.3.15.11.11.25.24.21.19 + 1.30.18.31.12.25.4.19.28.12.15 + 1.30.31.31.20.16.7 + 1.31.3 + 1.4.14.32.14 + 1.8 + 1.9.18.10.1.26.22.16.17 + 10.11.25.2.24.18.18.21.6.26.21 + 10.12.23.22.23.22.20.17.17.9 + 10.12.9.6.6.26.14.8.23.1.25 + 10.13.12.8.4.8.11.30 + 10.13.22.1.8.30.9.24.1.2.1 + 10.15.16.3 + 10.16.18.9.27.2.29.32.24.13 + 10.16.19.7.15 + 10.18.12.27.24.30.32.7.11.5.13 + 10.2.17.26.16.7.19.6.23.3 + 10.20 + 10.22.1 + 10.22.30.16.2.21.17.13 + 10.26.27.23.4.31.11.25.29 + 10.26.30.15.1 + 10.27.7.24.26.11.31.20.29 + 10.28.22.29.13.19.6.7.6.14 + 10.28.7.16.31 + 10.29 + 10.29.26.4.27.17.11 + 10.3 + 10.3.19 + 10.31 + 10.31.25.31.24.16.17 + 10.32.14 + 10.5.23.5.32.9.18.5.30 + 10.5.5.15.29.2 + 10.7.9 + 10.8.20.11.12.23.22 + 11.1 + 11.1.3.28.30.21.24.14 + 11.10 + 11.10.22.18 + 11.11.11.4.23.21.25 + 11.11.9.30.15.29.15.18 + 11.12.6.21 + 11.14.21.24.10.7.29.23.24.28 + 11.15.11.19.29.10 + 11.16.16.28.14 + 11.17.10 + 11.17.17.24.11.23.17.17.18.10.22 + 11.18.4.8.3.13.14.28.18.31 + 11.19.23.3.6.11 + 11.2.27.3 + 11.21.13.9.19 + 11.21.16.27.16 + 11.22.28.8.12.23.25.15.21.28 + 11.29 + 11.3.15.28.22.8.14 + 11.30 + 11.30.20.15.18.32.1.18.25.26.8 + 11.32.18.31 + 11.6.11.29.4.5.24.6.26.12 + 11.7.31.15.22 + 11.8.18 + 12.1.1 + 12.1.28.22.25 + 12.10 + 12.10.11.9.10.31.4.16.31 + 12.11.17.1.2 + 12.11.20.20.29 + 12.13.16.17.29.27.16.14.9.19.9 + 12.13.5.31 + 12.14.20.8.28.4 + 12.15.10.17.18.13 + 12.16.13 + 12.16.2.4.15 + 12.17.10.7.17.16 + 12.18 + 12.2.4.28.21.30.24 + 12.21.15.27.24.15.8.24.24.26 + 12.21.20.20 + 12.22.20.4.12 + 12.23.3.19.29.15.12.6 + 12.24.29.32.32.29.2 + 12.25.32.2.27.3.3.16 + 12.27.23.32.1.1.9.29.13 + 12.27.30.12.24.2.20 + 12.28.12.24.28.15.5.12.30.13.21 + 12.29.17.2.20.29.1.11.19.8.12 + 12.29.26.18.4.21.28.8.13.3 + 12.3 +(124 rows) + +SELECT * FROM ltreetest WHERE t = '12.3' order by t asc; + t +------ + 12.3 +(1 row) + +SELECT * FROM ltreetest WHERE t >= '12.3' order by t asc; + t +---------------------------------- + 12.3 + 12.4.10.17.4.10.23.3 + 12.4.12.13.25.30.30.8.9.12 + 12.4.24.6.1.13.5.20 + 12.4.26.23.25.5.15.7.16 + 12.6.14.23.19.21.9.12 + 12.7.16.8.21.22.2.16.18 + 12.7.28.26.14.21.18.31.5.15.11 + 13.1.6.17.28.9.15.30.1.27.14 + 13.12 + 13.14.13.10.28.26.9.18.27.21 + 13.16.1.27.18.18.19.6.14.4 + 13.16.4.28 + 13.17.7 + 13.19.2.6.23.19.9.7.21.8.16 + 13.24 + 13.25.10.25.8.16 + 13.26.17.3.2.19 + 13.28.12.6 + 13.28.14.2.8.18 + 13.28.9.3 + 13.3.20 + 13.3.8 + 13.30.24 + 13.32.15.32.26.14.32 + 13.7 + 13.8.15.3.7.31.5.10.15.30 + 13.8.20.9.21 + 13.8.23.13.11.18.24.21.11.24.10 + 13.9.9.27.31.11.25.9.27.22.13 + 14.1.11 + 14.1.15.25.27.23.25.26.28.10 + 14.10.11.30.5.7.6.24.9.30.26 + 14.11.25 + 14.12.31 + 14.13.9.13.11.5.5.2.2.32.12 + 14.14.25 + 14.15.31.29 + 14.16.6.29.26.13.14.16.25.26.8 + 14.17.7.30.8.25.26.4 + 14.19.20.13.27.2.2 + 14.19.26.15.22.23 + 14.19.30.6.4.10.10.10.22.25.11 + 14.2.14.11.12 + 14.21.22 + 14.21.5.28.3.32.24.14.25.31 + 14.21.6.5.26.9.32.16.25 + 14.23.31.5.5.15.17.12.17.7.3 + 14.24 + 14.26.25.4.12.26.8 + 14.27.29.23.4.1.17.32.6.25.22 + 14.29 + 14.3.17.1.14.15.21.4.26 + 14.30.13.5.26.9.22.23.14.10 + 14.30.2.21.15.16.13 + 14.30.23.3 + 14.4.19.27.28.24.19 + 14.4.23.4.23.22.11.6.26.5 + 14.5.13.19.25.12.32.9.13.16.12 + 14.6.10.29.25.26.20.24.24 + 14.8.15.30.7.29.27.31.4 + 14.9.15.21.21.31.1.29 + 15.1.6.31.30.13.32.9.10 + 15.1.8 + 15.10.30.1.4.12.8.20 + 15.11.26.1.30.6.23.5 + 15.17 + 15.17.2.32.7 + 15.21.22 + 15.21.23.30.9.25 + 15.23.26.20.27.7 + 15.25.31.11.4.22.16.7.11 + 15.26.24.31.16.15.17.22.8.30.3 + 15.28.24 + 15.28.30.19.31.6.2.2.31 + 15.29.25 + 15.29.32.16.29.12.20.32.13.20 + 15.3.31.9.27.14.9.8.14.6.32 + 15.30.17.5.32.28.2.18.27 + 15.31.11.27.19.19.20.5.5 + 15.4.15 + 15.5.1.31.28.10.8 + 15.6.19.3 + 15.7.3.14.23.19.26 + 15.7.5.12.7.9.3.28.26 + 15.8.10 + 15.8.3.15.27.14.29.28.6.5.25 + 15.9.11.20.22.15.11.13 + 15.9.8.20.27 + 16.13.19.11.18.13.17.17 + 16.13.2.19.14.29.31.30.23.15.12 + 16.13.26.18.9.29.11.17.1.24.26 + 16.14.3.17.17.26.12.19.19.30 + 16.16.28.24.11 + 16.18.23.6.31 + 16.19.17.30.30.5.17.24.27 + 16.2.14.3.26.11 + 16.20.29.26 + 16.21.13.1.4 + 16.23.30.12.31.31.19.14 + 16.24.3.30.15.22.31.2 + 16.24.7.25 + 16.27.8.17.14.17.21.29.14 + 16.28 + 16.29.6.23.13.28.31.6.19.26.15 + 16.30.10.7.29.4.9.21.22.13.26 + 16.31.12.27.25.9.32.29 + 16.5 + 16.5.10.2.18.8.15.12.32.25.10 + 16.5.12.5.15.12.24.25.3 + 16.5.14.21.32.17.23.3.4.26 + 16.5.23.17 + 16.5.6.12 + 16.8.29.7.21.2.3 + 16.9.14.28.6.21.31.31.26 + 16.9.29 + 16.9.32.14.3.7.8.7.21.22 + 17.1.12.20 + 17.10.17.22.20.25.14.13 + 17.11.17.4.8.26.26.20.6 + 17.13.14.29.27.27.13.12.15 + 17.13.19.31.12.18.10.15.14 + 17.13.8 + 17.14.7.3.2.18.20.23.18.5 + 17.17.14.28.6.30 + 17.19.1.22.11.7.22.1.14.28.11 + 17.22.12.10.30.11 + 17.24.15.27.3.32.4.22.20.6.24 + 17.24.30.6.32 + 17.25.10.13.21.5.7.22.2 + 17.25.2.13.10.27.13.1 + 17.25.26.23.32 + 17.26.18 + 17.27 + 17.29.21.10.18.8.16.26.18.21.26 + 17.29.31.8.24.10.18.27.17 + 17.3 + 17.5.3.15.17.13.5 + 17.7.26.30.18.23.4 + 17.8 + 17.8.31.32 + 17.9.32.31.21.31.23.17.10.32.9 + 18.13.6.12.26.26.26.29.18.20.1 + 18.13.9.3.18.15.2 + 18.15.14 + 18.17.6.16.6.10 + 18.18.19.16.14.16.21.10.25 + 18.18.5.11.7.4.25 + 18.19.11.20.13.13.11 + 18.19.12.20.18.17.15.32.18.5 + 18.21 + 18.24.21.17.11.26.28.22.21.18.10 + 18.27.11.27.9.16.7.6.22.26.27 + 18.29.13.24.18.3.12.18.12.12 + 18.29.5.1.10.21.2 + 18.30.11.17 + 18.30.18.31 + 18.31.26.18.6.15.18.11 + 18.31.32.28.1.4.24.24.12.25 + 18.31.32.29.22.1.31.11.28 + 18.4 + 18.4.14.29.3 + 18.5.6.31.5.15.15 + 18.6.2.2.24 + 18.6.26.2.13.9.6.11.10.11.16 + 18.7.10.27.17.24 + 18.7.3.17.13.5.31.6.31.25.29 + 18.9.21.2.31.8.32 + 18.9.26.7 + 19.10.26.19.5.21.30.23 + 19.10.4.30.32.4.12 + 19.10.8.10.4.19 + 19.11.10.18.14.13.7.7 + 19.11.29.13.15.27.12.15.14.12 + 19.12.20.24.32.13.11.23.26 + 19.12.26.24.29.3 + 19.12.30.2.21 + 19.15.26.19 + 19.16.26.2 + 19.16.31.31.29.12 + 19.17.12.15 + 19.17.13.12.32.16.3 + 19.19.25.22.11.6.15.3.2.19 + 19.2.26.21.16.11.2.2 + 19.2.9.29.6 + 19.20.25.7.27.28.27.17.9.3.1 + 19.22.21.13.27.13.15 + 19.22.29.32.1.21.26.24.23.17 + 19.26.24.27.6.24.16.27.32.29 + 19.26.32.13.1.12.30.26.22.25 + 19.3.12.12 + 19.3.23.4.4.21.23 + 19.30.18.11.32.14 + 19.30.27.26.21.7.18 + 19.31.14.25.5.8.21.11.13.20 + 19.5.20.3.4.2.3 + 19.6.13.14.22.13.9.29 + 19.6.24.32.30.13.6.25.8.28 + 19.7 + 19.7.29.31.3.20.7.21.25.27.29 + 19.9.32.23.13.24.1 + 2.1.12.19.29.28.3.31.28.28.10 + 2.1.3.30.24.17.9 + 2.10.10.4.20.1.12.13 + 2.10.28.1.17.19.32.28 + 2.11.32.25.23 + 2.12.14.28.16.21 + 2.12.30.22.12 + 2.13.9.23.21.2 + 2.13.9.28 + 2.14.10.4.17.17.8.4.27.20 + 2.14.12.13 + 2.15.14.20.30.26 + 2.15.18.21.5.21.4.7.30 + 2.16.3.7.22.18.29.20 + 2.19.4.1.15.7.8.9.17.29 + 2.2.18.18.3.3.18.8.10.8 + 2.22.19 + 2.24.4.5.24.32 + 2.24.5.3.4.10.27.26.17.28.16 + 2.27.15.14 + 2.28.5.17.6.32 + 2.30.26.10.14.31.18.2 + 2.31.25 + 2.32.10.13.12 + 2.32.8.28.24.20.9.24.25.8.9 + 2.4.25.32.16.22.26.13.17.18 + 2.6.15.26.23.26.24 + 2.8.13.12.17.23.16.7.11.23 + 2.9 + 20.1.24.3.30.31 + 20.13 + 20.14.11.2.10.14 + 20.15 + 20.17.14.7 + 20.17.18.21.1 + 20.18.24.14.12.13.9 + 20.20.32.29.24.5.5.26.22.32 + 20.20.7 + 20.22.10 + 20.23.29.5.7.30.13.14.22 + 20.23.7.11.11.31.18.16.3 + 20.24.14.15.4.21.12.27.4.12 + 20.25.22.19.22 + 20.28.22.7.10.28.27.22.14.16 + 20.29.18.16.2.21.23.11 + 20.3.1.8.8.30.20 + 20.30.17 + 20.30.28.15.17 + 20.30.9.9.14.12.29 + 20.31.13.12.19.2.26.16.16.22.28 + 20.32.5.1.3.20.3.30.27 + 20.32.9 + 20.4.1.16.31.3 + 20.4.27.31.1 + 20.5.4.9.31.14.26.6 + 20.6.26.3.30 + 20.6.3.26.7.29.28.4 + 20.8.19.14.16.7 + 20.9.29.32.13.7.23 + 21.1.4.9.9.31.24.21.3.29 + 21.10.20.9.3.16.9.10.20 + 21.14 + 21.14.13 + 21.14.22.29 + 21.14.25.20.13.31.14.20 + 21.15.18.18.30.3.20 + 21.15.31.24.29.24.26.12.20 + 21.17.18.32.7.8 + 21.17.27.23.15 + 21.17.31.10.31.13.9.26.6.14 + 21.18 + 21.18.2.1 + 21.18.30.19.24.24 + 21.20.24.25.6.26.23 + 21.20.28.19.27.9 + 21.21.10.27 + 21.22.31.24.27 + 21.23.13 + 21.23.17.8.23.11.8.1 + 21.28.17.22.10.27.4.20.2.32 + 21.28.24.23.3.11.7.12.22.32 + 21.30.19.6.28.1.32.2.14.14 + 21.31.31.25.5.30.26 + 21.32.13.21 + 21.32.13.22.3.13.31.23.14.12.9 + 21.4.11.18 + 21.4.22.20.24.28.6 + 21.5.11.18 + 21.5.17.19.15.25.18.21.24.9 + 21.6.22.28.12.23.11.22 + 21.7.23.9.16.5.18.14 + 21.7.7.11 + 21.8.9 + 21.9.27.22.32 + 21.9.32.1.27 + 22.10.12.23.9 + 22.10.16.8 + 22.10.18 + 22.10.27.19.29.20.29.3.12.14.25 + 22.11 + 22.12.22.28 + 22.13.22.21.25.17.8 + 22.13.22.8.30.32.10.24 + 22.15 + 22.16 + 22.16.25.18.25.7.24.29.14.8 + 22.17.24.14.21.15.12.18.17.25.11 + 22.17.30 + 22.17.4.2.22.17 + 22.17.7.30.13.24 + 22.17.9.11.25.15.3.9 + 22.18.20.23.15.9.12 + 22.19.20.5.2.20 + 22.19.21.11.6.8.29.24 + 22.19.5.22.20.31.23.24.14.24.4 + 22.20.30 + 22.21.32.15.8.29.5.12.10.29 + 22.22.10.30.5.15.25.21.19.11 + 22.22.27.6.27.15.5.18.21.28.9 + 22.23.18.18.9.8.23.7.23.23.16 + 22.23.22.30 + 22.23.25.28.5.27.9.9.24.31.10 + 22.24.22.25.15.23.13 + 22.25.4.28.9.20.12.13 + 22.26.1.28.9.9.31 + 22.26.32 + 22.28.20.6.32.32 + 22.29.18.32.13.12.22.31.17.22 + 22.29.29.11 + 22.3.6 + 22.30 + 22.30.31.24.23.22.5.20.28.1 + 22.31.2.32.32.11.26.23.19 + 22.31.21.13.13.26.11.5.19 + 22.32.6.6.3.8.24.6.25.29 + 22.8.20.1.10.28.6.27 + 22.9.15.19.12 + 23.1.23.18.12.29 + 23.10.13.32.14.20.16.11.14 + 23.10.5.26.12.4.20.4 + 23.12.1.5.32.25.8.24.1.25 + 23.12.11.11.15.16.22.31.32.5.8 + 23.12.19.25.16.23.22.6.29.4 + 23.12.32.22.19.1.22.4 + 23.14.12.30.18.4.16.18.7.7 + 23.14.30.27.28.26.26.23.8.32 + 23.17.22.1.23.4.29.32.4.1 + 23.17.25.4.1.16.29.10 + 23.17.28.31.28 + 23.17.32.15.23.16.25 + 23.19.17.31.29.13.1.12.5.25 + 23.2.22.7.32.3.27.6 + 23.20.12.16.15.2 + 23.20.24 + 23.20.8 + 23.22.10.1.14.24 + 23.22.23.14.31.32 + 23.23 + 23.24.11.31.10.31.18.28.13.18.6 + 23.24.16.32.13.29 + 23.25.23.11.7.23 + 23.27.27.16 + 23.27.6.26.22 + 23.28.1 + 23.28.20.25.30.24.15 + 23.28.3.30.15.31.32.3.21.9.19 + 23.3.20.24 + 23.3.32.21.5.14.10.17.1 + 23.31.27.16.8.30.20.27 + 23.32.5.25.19.9.15.17.15.11 + 23.5.5.17 + 23.5.7.12.11.23.10 + 23.6.27 + 23.8.13.22.21 + 24.1.10.20.28.18.6.27.20.30.26 + 24.1.29.32.14.15.32.6.15.22 + 24.10.10.31.4.29.9 + 24.10.8.25.16 + 24.11.5 + 24.12 + 24.13.1.8 + 24.15.15.17.22 + 24.16.27.10.9 + 24.17.24 + 24.17.31.20.12.9.19.29.18 + 24.18.16 + 24.2.26.24.14.15.31.23.17.26 + 24.2.6.7.16.7.28 + 24.20.23 + 24.21.14.25.11.3.20.6.6.16 + 24.23.24.4.15.25.17 + 24.23.29.8.24.11.21.10.28.14.27 + 24.24 + 24.25.7.27.30.8.26.17 + 24.27.14 + 24.27.18.32.14.9.11.28.9 + 24.28.13.26.8.8.31 + 24.28.32.21 + 24.3.23.25 + 24.31 + 24.31.2.13.5.23.18.16 + 24.31.8 + 24.32.17.23.24.19.23.9.20.18 + 24.32.27 + 24.9 + 24.9.15.1.14.29.6.4 + 24.9.27.16.20.21 + 24.9.8.12.29 + 25.10 + 25.10.29.3.6.21.3.31.13 + 25.10.4.28.3.31.19 + 25.11.24 + 25.14.5.32.25 + 25.15.11 + 25.16.9.6 + 25.17.18.17.27 + 25.17.18.30 + 25.17.2.20.20.3.29.21.3.12 + 25.17.9.16.17.31.23.29.24 + 25.18.8.3.23.23.5.9.6 + 25.19.27.2.9.20 + 25.2.11.20.8.6.22 + 25.2.3.15.11.19.5.28.25.14 + 25.21.8.17 + 25.22.2.25.6 + 25.24.2.32.14.18.16 + 25.24.29 + 25.28.3 + 25.28.30.24 + 25.29 + 25.3 + 25.30.1.4.24.11 + 25.31 + 25.32.24.24.28.15.16.10 + 25.4.32 + 25.4.4.1.13.32.26.20.20.3 + 25.5.30.7.16.12.21.12.11.16 + 25.6 + 25.6.12.16.1 + 25.7.3.21.31.12.28 + 25.9 + 25.9.1.5.9.11.25.4.11.27.32 + 25.9.10 + 26.11 + 26.12.27.2 + 26.13.4.7.13.11.3 + 26.14 + 26.14.5.32.10 + 26.16.12 + 26.16.12.3.27.9.28 + 26.17.9.13.4.25.32.2.24.9 + 26.18 + 26.18.32.20 + 26.19.3.14.8.28.31.10 + 26.24 + 26.24.9.12.11.15.31.2 + 26.25.10.10.13 + 26.25.24 + 26.26.22.21.14.11.29.19.14.24 + 26.28.14 + 26.31.11.23.3 + 26.31.16.18.22.13.32.23.9.20 + 26.31.6.8.29.8.24 + 26.31.7 + 26.32.21.31.27.12 + 26.32.8.12.30.19.24.8.6.1.10 + 26.5.29.7.28 + 26.7.22.3.18.21.11 + 26.7.5.8.11.9.22.1.6 + 26.8.28 + 26.9.17.1.18.19.1.11.18.29.3 + 26.9.20.12.22.22.32 + 27.1.11.3.25.9.6.6 + 27.11.14.17.24 + 27.11.15.9.24.31.18.4.1.30.20 + 27.12.4.2.29.22.15 + 27.15.15.15 + 27.16 + 27.17.15.7.28.20 + 27.17.17.19.24.9.14.20 + 27.17.3.18.2.13.18 + 27.18 + 27.18.10.4.22 + 27.19.20.1.31.29.5.22.26.3 + 27.2.10.4.25.14.2.15.4 + 27.21.27.5.13.30.17 + 27.21.28.24.7.2.24.23.8 + 27.22.11.13.21.25.5.1.27.21.27 + 27.23.2.32.11.21 + 27.23.20.30.7 + 27.24.11.31.21.6.29.17.24.18 + 27.25 + 27.26.29 + 27.27 + 27.27.25.10.31.10.21.22.21.16.12 + 27.27.30.11.15.24.9.7.4.30 + 27.29.1.5.30.6.22.16.23.2.28 + 27.3 + 27.3.3.11.21.4.25 + 27.30.12.11.20.15.11.13 + 27.31.2.16.29.6 + 27.32.26.21.31.17.32.32 + 27.4 + 27.4.15.14.19.6.12 + 27.4.17.17.32.8.16.15.17.13 + 27.5.15.1.15.16.21 + 27.5.22 + 27.6.13.24.21.27.28.22.3.7.4 + 28.1.3 + 28.11.11.30.20.11.32 + 28.11.27.21.14.16 + 28.14.24.26.6.15.16.32.25.13.8 + 28.14.32.29.2.3.4 + 28.15.18.27 + 28.15.25.7.13.6.19.2 + 28.17.26.9 + 28.18.6.22.13.8.25 + 28.2.27.1.20 + 28.20.8.9.9.28.30.29 + 28.23.2.30.3.8.1.15.15.14.13 + 28.25.10.25.19.15 + 28.25.11.22 + 28.25.29.4.13.5.6 + 28.26.25.7 + 28.26.26.6.31 + 28.26.4.22.13.20.32.27.15 + 28.27.24.14 + 28.28 + 28.30.24.16.17.28.2.13.10 + 28.31.10.28.22.26.16.15 + 28.4 + 28.5.12.9.2.27.11.11.2 + 28.5.13 + 28.6 + 28.6.11.6.15.22.12.6 + 28.6.8.22.25 + 28.8.21.15.16.28.4.16.26.8 + 28.9.3.16.17.21.23.30 + 29.1 + 29.1.2.14.14 + 29.1.7.26.25.11.22 + 29.10.12.17.12.16 + 29.10.17.11.28.12.18.5.19.15.21 + 29.11.20.22.27 + 29.14.12.9.17.5.32 + 29.14.31.25.7.32.23 + 29.15.29.8.31.26.1 + 29.20.1.11.21.16.1.2.14.28 + 29.23.1.21.31.8 + 29.23.15.25.1.6.6.10 + 29.25.29.16.32.11.15.25.5.22.3 + 29.25.30.15.21.3.25.26.26 + 29.26.25.14.24.18.2.13.23.29 + 29.27 + 29.27.13.29.10.2 + 29.27.13.9.28.29.19.13.29.31.27 + 29.27.5.22.26 + 29.27.7.7.3.11.14.26.21.11 + 29.28.9.15.8.27.31 + 29.29.17.31 + 29.29.18 + 29.3.15.17.12.29 + 29.3.17.17.18.32 + 29.30.21.8.16.23.32 + 29.30.7.31.22 + 29.32.13.4.1.16.20 + 29.5.18.27.3.21.18.6.14 + 29.5.32.20.11.7.13.24.17 + 29.6.12.31.20.23.32.20 + 29.9.25.27.15.16.32.26.6.32 + 3.1.13.22.24.14.12.31.3.4 + 3.1.14.8.9.16.30.22.20 + 3.10 + 3.10.27.4.5.6.19.12.28.12 + 3.10.4.5.28.11 + 3.11.18.21.5.20.30 + 3.11.32.11.22.3.7.17.8.13.23 + 3.13 + 3.14.1.14.17.28.29.16 + 3.14.11.15.21.32.2.15.13 + 3.14.30.5.32.22.29 + 3.15.2.23.22.2.16.14 + 3.18 + 3.18.18 + 3.18.8.22.7.28.32.31.3 + 3.19.11.6.5 + 3.20.16.13.29.20 + 3.20.19.10.17.27.3.6.22.23 + 3.21.16.24.23.12.16.32.3 + 3.21.6.13.12.18.25 + 3.22.18.1.5.14.9.6.14 + 3.25 + 3.26 + 3.26.32 + 3.27.18.8.4.21.6.32.30.7.5 + 3.29.19.2.24 + 3.29.32.26.8.10.25 + 3.3 + 3.32.2.29.3.32.28.11.29.30 + 3.4.22.19 + 3.5 + 3.6.24.21.20.32.3.4.26.5 + 3.9.11.23.32.26.24.28 + 3.9.25.26.7 + 30.12.28.2 + 30.12.6.30 + 30.12.9.25.24.6.7.24.29 + 30.15 + 30.16.14.9.5.4.10.7.31 + 30.16.3.21.10 + 30.17.2.25 + 30.17.25.3.31.11.3.4.1.10 + 30.17.4.5.13.6 + 30.18.30.16.29 + 30.2.17.8.14 + 30.20.3.2.5.15.8.7.17 + 30.22.29.21.19.14.3.2.6 + 30.23.10.1.10.7.22.28.18.11.17 + 30.23.2.13.14.15.29.19.4.12.24 + 30.24 + 30.24.23.25.32.18.22.12.29.9.22 + 30.24.32.15.14.10.11 + 30.25 + 30.25.17.17.10.29 + 30.25.24.22 + 30.25.8.24.6.29.31 + 30.27.8.6.11.19 + 30.3.16.26.7.27.26.9.27.21.18 + 30.30.17.5.30.21.19.5.22.22.14 + 30.31.13.9 + 30.32 + 30.4.30.11.13.23.14.24.11 + 30.5 + 30.6.4 + 30.8.18.5.20.6.15 + 30.8.9.14.25.30 + 30.9.24 + 31.13 + 31.13.9.1.5.12 + 31.17 + 31.17.2.30.11 + 31.18 + 31.18.25.1.14.29.25.5.22.30 + 31.18.27.15.20.29.29 + 31.18.32.11.7.25.20.5 + 31.21.14.20.1.22.2.5.3.27.12 + 31.21.22.14.8.21 + 31.24.26.18 + 31.28.32.4.31.4.7 + 31.29.18.26.1.26.17 + 31.29.4.29.24.30.30.32.10.23 + 31.30.12.20 + 31.30.23.7.7.24.32.10.11.1.31 + 31.32.12.26.31.32.14.23.28 + 31.4.7 + 31.5.6.4.8.29.3 + 31.7.14.2 + 31.9.3.5 + 32.1.21.1.16.29.21 + 32.1.23.20.14.12.23.5.32.15 + 32.1.24.29.22.5.9.24.18.3.13 + 32.1.31 + 32.15.20.28.5.1.23.4 + 32.16 + 32.17.8.24.2.14.5.4.22 + 32.19.20.24.23.31.8.32.16.29 + 32.2.11 + 32.24.11.8.12.23.22.19.11.17.18 + 32.24.29.6 + 32.25.16 + 32.25.3.6 + 32.27.13.6.7 + 32.27.18.7.3.4.2 + 32.28.1.32.28.10 + 32.29.24.31.25.6.9 + 32.3.12.2 + 32.3.23.7.2 + 32.3.5.9.17.15 + 32.30.18.17.1.14.12.18 + 32.31.11.22.1 + 32.31.26.19.13.29.4.25 + 32.4.19 + 32.6.13.8.32 + 32.6.15.26.14.15.3.19 + 32.6.3.2.12.5.28.1.25 + 32.6.31.31 + 32.6.8 + 32.6.9.26.16.4.4.29.7.11 + 32.8.29.18.31 + 32.8.5 + 4.1.24.24.28.24.18 + 4.10.28 + 4.11.19.17.2.22.20.18.13.32.15 + 4.11.22.4.19.24.4.28.6.8.22 + 4.13 + 4.13.22.11.9.13.27.15.7 + 4.14 + 4.14.10.19.16 + 4.14.16.14.1.8.1.22.17.10 + 4.14.17.12.20.17.1.22.3 + 4.14.32 + 4.15.20.23.12.16.2.16.17 + 4.16.22.19.24.21 + 4.16.7.25.21.7 + 4.18.29.9.16.10 + 4.19.16.15.5.2.25.8.28.14.2 + 4.2.16.13.16.11.19.10.10.25 + 4.2.2.32.24.25.31.3 + 4.2.6.20.7.8 + 4.21.28.5.16.29.5.21 + 4.21.9.1.2.14.8.17.13.26 + 4.22 + 4.22.17.10.19.9.8.19.28.3.9 + 4.22.7.19.25 + 4.25.12.10.15.9.18.9 + 4.26.2.2 + 4.26.23.6.19.31.10.4.22 + 4.26.5.26.21.28.17.24.25.23 + 4.27.32.18 + 4.3.20.27.9.1.18.30.12.5.19 + 4.3.6.27.22.23.10 + 4.30.8.20.19.9.30.24.11 + 4.31 + 4.5.9.4.15.19.8.26.17.26.3 + 4.7.1 + 4.9 + 5.1.5.31 + 5.10 + 5.10.2.11.21.9.19 + 5.10.3.9.23.30.23 + 5.12.2.20.1.24.25 + 5.13.23.19.28.26.27.6.1.22 + 5.13.23.4.9 + 5.14.27.15.11.17.3.10.27.25 + 5.14.29.2.23.16.20.22 + 5.15.10.3.23.13.32.23 + 5.15.16 + 5.18.9.25.31.21.22 + 5.19.1.26.20.6.20 + 5.2.32.19.13.29.12.13.31.29 + 5.20 + 5.21.27.13.14.11.2.16.20 + 5.23.31.18.24.32 + 5.24.24.9.32.26.31 + 5.24.25.15.27.30.20 + 5.24.4.31.3.16.25.17.13.26.11 + 5.27.16.3.30 + 5.27.21.1.29.29.28 + 5.27.28.26.14.15.6.20.1.31.13 + 5.27.32.21.5.1.11.14 + 5.3.17.29 + 5.3.29.9.22 + 5.31.8.1.5.13.21.28.29.19.2 + 5.4.8.25.12.27.2.29.28.3 + 5.5.12.31.23.13.17.22.20 + 5.8 + 5.8.17.30.15.8.19.29.30.11.6 + 5.9.19.6 + 6.1.8.6.30.29.30 + 6.10.25.12 + 6.11.11.5.16.8.14.12.9 + 6.11.31.23.12.8.30.14.27 + 6.13.31.5.7.26 + 6.14 + 6.17.10.10.7.9.27.8.29 + 6.17.26.25.27.11.10.9 + 6.18.1.4.18.23 + 6.19.29.11.2.32.21.15.32.9 + 6.19.3 + 6.19.6.4.9.11.32.17.17.3.15 + 6.2.32 + 6.20 + 6.20.14 + 6.21.30.7 + 6.22.12 + 6.25.17.32 + 6.26 + 6.26.29.10.21.28.20.19 + 6.27.26.1.20.24.6 + 6.27.29.14.8.12.26.3.21.4.1 + 6.29.32.13.30.3.16 + 6.29.6.13.14.24.10.4.14.28 + 6.5.27.19.13.26.1.18.9 + 6.6.22.8 + 6.7.25.16.13.21.7.20.25.12.4 + 6.7.7 + 6.8.7.20.2 + 6.9.1.10.10.22.6 + 6.9.29.17.4.32 + 7.10.17.21.11.29.17.25.19.4.29 + 7.11 + 7.12 + 7.12.1.10.6.17.29.24.24.4 + 7.12.23 + 7.13 + 7.13.15 + 7.14.22.29.30.14.25.1.9.26.25 + 7.16.20.17 + 7.19.10.12.31.1.27.13.19 + 7.19.12.3.21.19.18.5.2.14.10 + 7.19.6.17.15.26.21.9 + 7.21.8 + 7.23.1.24.29.13.31.19.23.17.7 + 7.23.15.32.28.27.2.2.26 + 7.26.18 + 7.27.20 + 7.30.19.25.23.15.14.29 + 7.30.5.10.10.5.30.14.9.18 + 7.31 + 7.31.2.28.15.11.17.18.19.23.6 + 7.31.4.20.17 + 7.32.10.3.30.12.14 + 7.5.28.8.17.26.31.10.15 + 7.7 + 7.7.22.24.17.32.17.25.28 + 7.7.25.22.22.26 + 8.1.29.18.22 + 8.10 + 8.11.20 + 8.12.4 + 8.13.1 + 8.13.14.11.11.29.22.4.4.10 + 8.13.6.12.18.7 + 8.13.9.31.20.20.24.7.23.31.28 + 8.14.19.18 + 8.16 + 8.16.1.16.28.6.3.22.6.23 + 8.16.20.24.20.6.10.21 + 8.16.30.29.19.22.28.24.2 + 8.16.6 + 8.17.25.26.15.25 + 8.17.9.15.21.28.1.7.1.3.6 + 8.2 + 8.2.18.23.5.16.17.1 + 8.21.17.3.6.3.18 + 8.21.8.23.4.18 + 8.22.32.17.16.28.31.23.22.9 + 8.24.11.13.25.19 + 8.25.20.3.15.24.7.4.24.5.30 + 8.26.29.13.7.25.31.28.3.32 + 8.27.3.4.12.26.16 + 8.29.6.3 + 8.3.18.13.30.20.27.26.17.28 + 8.3.3.25.25.15.7.13.21.18 + 8.31.22.27 + 8.32.30.1 + 8.5.24.9.29.32.31.30.13.9.7 + 8.5.30.29.9.31 + 8.6.6.5.8.8.12 + 8.9.21.16.29 + 8.9.22 + 8.9.25.25.26.30.31.31.2.32.7 + 9.10.19.18.15.11.22.32.32.14.9 + 9.10.32 + 9.14.27.31.26.21.25.3.20 + 9.16.2.16.22.24.17.31.14.21.17 + 9.17.13.31.7 + 9.18.23 + 9.18.30.11.29.32.7.19.2 + 9.19.7.13.13.25 + 9.2.10.4 + 9.2.4.27.26 + 9.21.14.19 + 9.21.20.29.1 + 9.21.28.8.12.15.3.13.10.11 + 9.22.10.15.5.15 + 9.23.21.22.5.29.15.21 + 9.26.1.16 + 9.28.10.26.14.26.15.14 + 9.28.24 + 9.28.30.1.6.25.17.9 + 9.3.3 + 9.3.31.18.12.3.9.29.10 + 9.30 + 9.31.23.19.5.10.16.4.30.24.5 + 9.31.4.14.31.10.17.5.2 + 9.5 + 9.5.9.3.23.9.25.14.1.29.28 + 9.6.9.21.6.11.29.13.29.20.32 + 9.7.31.11.8.23 + 9.8.23.2.20.16 + 9.9.13.9.14.27 +(883 rows) + +SELECT * FROM ltreetest WHERE t > '12.3' order by t asc; + t +---------------------------------- + 12.4.10.17.4.10.23.3 + 12.4.12.13.25.30.30.8.9.12 + 12.4.24.6.1.13.5.20 + 12.4.26.23.25.5.15.7.16 + 12.6.14.23.19.21.9.12 + 12.7.16.8.21.22.2.16.18 + 12.7.28.26.14.21.18.31.5.15.11 + 13.1.6.17.28.9.15.30.1.27.14 + 13.12 + 13.14.13.10.28.26.9.18.27.21 + 13.16.1.27.18.18.19.6.14.4 + 13.16.4.28 + 13.17.7 + 13.19.2.6.23.19.9.7.21.8.16 + 13.24 + 13.25.10.25.8.16 + 13.26.17.3.2.19 + 13.28.12.6 + 13.28.14.2.8.18 + 13.28.9.3 + 13.3.20 + 13.3.8 + 13.30.24 + 13.32.15.32.26.14.32 + 13.7 + 13.8.15.3.7.31.5.10.15.30 + 13.8.20.9.21 + 13.8.23.13.11.18.24.21.11.24.10 + 13.9.9.27.31.11.25.9.27.22.13 + 14.1.11 + 14.1.15.25.27.23.25.26.28.10 + 14.10.11.30.5.7.6.24.9.30.26 + 14.11.25 + 14.12.31 + 14.13.9.13.11.5.5.2.2.32.12 + 14.14.25 + 14.15.31.29 + 14.16.6.29.26.13.14.16.25.26.8 + 14.17.7.30.8.25.26.4 + 14.19.20.13.27.2.2 + 14.19.26.15.22.23 + 14.19.30.6.4.10.10.10.22.25.11 + 14.2.14.11.12 + 14.21.22 + 14.21.5.28.3.32.24.14.25.31 + 14.21.6.5.26.9.32.16.25 + 14.23.31.5.5.15.17.12.17.7.3 + 14.24 + 14.26.25.4.12.26.8 + 14.27.29.23.4.1.17.32.6.25.22 + 14.29 + 14.3.17.1.14.15.21.4.26 + 14.30.13.5.26.9.22.23.14.10 + 14.30.2.21.15.16.13 + 14.30.23.3 + 14.4.19.27.28.24.19 + 14.4.23.4.23.22.11.6.26.5 + 14.5.13.19.25.12.32.9.13.16.12 + 14.6.10.29.25.26.20.24.24 + 14.8.15.30.7.29.27.31.4 + 14.9.15.21.21.31.1.29 + 15.1.6.31.30.13.32.9.10 + 15.1.8 + 15.10.30.1.4.12.8.20 + 15.11.26.1.30.6.23.5 + 15.17 + 15.17.2.32.7 + 15.21.22 + 15.21.23.30.9.25 + 15.23.26.20.27.7 + 15.25.31.11.4.22.16.7.11 + 15.26.24.31.16.15.17.22.8.30.3 + 15.28.24 + 15.28.30.19.31.6.2.2.31 + 15.29.25 + 15.29.32.16.29.12.20.32.13.20 + 15.3.31.9.27.14.9.8.14.6.32 + 15.30.17.5.32.28.2.18.27 + 15.31.11.27.19.19.20.5.5 + 15.4.15 + 15.5.1.31.28.10.8 + 15.6.19.3 + 15.7.3.14.23.19.26 + 15.7.5.12.7.9.3.28.26 + 15.8.10 + 15.8.3.15.27.14.29.28.6.5.25 + 15.9.11.20.22.15.11.13 + 15.9.8.20.27 + 16.13.19.11.18.13.17.17 + 16.13.2.19.14.29.31.30.23.15.12 + 16.13.26.18.9.29.11.17.1.24.26 + 16.14.3.17.17.26.12.19.19.30 + 16.16.28.24.11 + 16.18.23.6.31 + 16.19.17.30.30.5.17.24.27 + 16.2.14.3.26.11 + 16.20.29.26 + 16.21.13.1.4 + 16.23.30.12.31.31.19.14 + 16.24.3.30.15.22.31.2 + 16.24.7.25 + 16.27.8.17.14.17.21.29.14 + 16.28 + 16.29.6.23.13.28.31.6.19.26.15 + 16.30.10.7.29.4.9.21.22.13.26 + 16.31.12.27.25.9.32.29 + 16.5 + 16.5.10.2.18.8.15.12.32.25.10 + 16.5.12.5.15.12.24.25.3 + 16.5.14.21.32.17.23.3.4.26 + 16.5.23.17 + 16.5.6.12 + 16.8.29.7.21.2.3 + 16.9.14.28.6.21.31.31.26 + 16.9.29 + 16.9.32.14.3.7.8.7.21.22 + 17.1.12.20 + 17.10.17.22.20.25.14.13 + 17.11.17.4.8.26.26.20.6 + 17.13.14.29.27.27.13.12.15 + 17.13.19.31.12.18.10.15.14 + 17.13.8 + 17.14.7.3.2.18.20.23.18.5 + 17.17.14.28.6.30 + 17.19.1.22.11.7.22.1.14.28.11 + 17.22.12.10.30.11 + 17.24.15.27.3.32.4.22.20.6.24 + 17.24.30.6.32 + 17.25.10.13.21.5.7.22.2 + 17.25.2.13.10.27.13.1 + 17.25.26.23.32 + 17.26.18 + 17.27 + 17.29.21.10.18.8.16.26.18.21.26 + 17.29.31.8.24.10.18.27.17 + 17.3 + 17.5.3.15.17.13.5 + 17.7.26.30.18.23.4 + 17.8 + 17.8.31.32 + 17.9.32.31.21.31.23.17.10.32.9 + 18.13.6.12.26.26.26.29.18.20.1 + 18.13.9.3.18.15.2 + 18.15.14 + 18.17.6.16.6.10 + 18.18.19.16.14.16.21.10.25 + 18.18.5.11.7.4.25 + 18.19.11.20.13.13.11 + 18.19.12.20.18.17.15.32.18.5 + 18.21 + 18.24.21.17.11.26.28.22.21.18.10 + 18.27.11.27.9.16.7.6.22.26.27 + 18.29.13.24.18.3.12.18.12.12 + 18.29.5.1.10.21.2 + 18.30.11.17 + 18.30.18.31 + 18.31.26.18.6.15.18.11 + 18.31.32.28.1.4.24.24.12.25 + 18.31.32.29.22.1.31.11.28 + 18.4 + 18.4.14.29.3 + 18.5.6.31.5.15.15 + 18.6.2.2.24 + 18.6.26.2.13.9.6.11.10.11.16 + 18.7.10.27.17.24 + 18.7.3.17.13.5.31.6.31.25.29 + 18.9.21.2.31.8.32 + 18.9.26.7 + 19.10.26.19.5.21.30.23 + 19.10.4.30.32.4.12 + 19.10.8.10.4.19 + 19.11.10.18.14.13.7.7 + 19.11.29.13.15.27.12.15.14.12 + 19.12.20.24.32.13.11.23.26 + 19.12.26.24.29.3 + 19.12.30.2.21 + 19.15.26.19 + 19.16.26.2 + 19.16.31.31.29.12 + 19.17.12.15 + 19.17.13.12.32.16.3 + 19.19.25.22.11.6.15.3.2.19 + 19.2.26.21.16.11.2.2 + 19.2.9.29.6 + 19.20.25.7.27.28.27.17.9.3.1 + 19.22.21.13.27.13.15 + 19.22.29.32.1.21.26.24.23.17 + 19.26.24.27.6.24.16.27.32.29 + 19.26.32.13.1.12.30.26.22.25 + 19.3.12.12 + 19.3.23.4.4.21.23 + 19.30.18.11.32.14 + 19.30.27.26.21.7.18 + 19.31.14.25.5.8.21.11.13.20 + 19.5.20.3.4.2.3 + 19.6.13.14.22.13.9.29 + 19.6.24.32.30.13.6.25.8.28 + 19.7 + 19.7.29.31.3.20.7.21.25.27.29 + 19.9.32.23.13.24.1 + 2.1.12.19.29.28.3.31.28.28.10 + 2.1.3.30.24.17.9 + 2.10.10.4.20.1.12.13 + 2.10.28.1.17.19.32.28 + 2.11.32.25.23 + 2.12.14.28.16.21 + 2.12.30.22.12 + 2.13.9.23.21.2 + 2.13.9.28 + 2.14.10.4.17.17.8.4.27.20 + 2.14.12.13 + 2.15.14.20.30.26 + 2.15.18.21.5.21.4.7.30 + 2.16.3.7.22.18.29.20 + 2.19.4.1.15.7.8.9.17.29 + 2.2.18.18.3.3.18.8.10.8 + 2.22.19 + 2.24.4.5.24.32 + 2.24.5.3.4.10.27.26.17.28.16 + 2.27.15.14 + 2.28.5.17.6.32 + 2.30.26.10.14.31.18.2 + 2.31.25 + 2.32.10.13.12 + 2.32.8.28.24.20.9.24.25.8.9 + 2.4.25.32.16.22.26.13.17.18 + 2.6.15.26.23.26.24 + 2.8.13.12.17.23.16.7.11.23 + 2.9 + 20.1.24.3.30.31 + 20.13 + 20.14.11.2.10.14 + 20.15 + 20.17.14.7 + 20.17.18.21.1 + 20.18.24.14.12.13.9 + 20.20.32.29.24.5.5.26.22.32 + 20.20.7 + 20.22.10 + 20.23.29.5.7.30.13.14.22 + 20.23.7.11.11.31.18.16.3 + 20.24.14.15.4.21.12.27.4.12 + 20.25.22.19.22 + 20.28.22.7.10.28.27.22.14.16 + 20.29.18.16.2.21.23.11 + 20.3.1.8.8.30.20 + 20.30.17 + 20.30.28.15.17 + 20.30.9.9.14.12.29 + 20.31.13.12.19.2.26.16.16.22.28 + 20.32.5.1.3.20.3.30.27 + 20.32.9 + 20.4.1.16.31.3 + 20.4.27.31.1 + 20.5.4.9.31.14.26.6 + 20.6.26.3.30 + 20.6.3.26.7.29.28.4 + 20.8.19.14.16.7 + 20.9.29.32.13.7.23 + 21.1.4.9.9.31.24.21.3.29 + 21.10.20.9.3.16.9.10.20 + 21.14 + 21.14.13 + 21.14.22.29 + 21.14.25.20.13.31.14.20 + 21.15.18.18.30.3.20 + 21.15.31.24.29.24.26.12.20 + 21.17.18.32.7.8 + 21.17.27.23.15 + 21.17.31.10.31.13.9.26.6.14 + 21.18 + 21.18.2.1 + 21.18.30.19.24.24 + 21.20.24.25.6.26.23 + 21.20.28.19.27.9 + 21.21.10.27 + 21.22.31.24.27 + 21.23.13 + 21.23.17.8.23.11.8.1 + 21.28.17.22.10.27.4.20.2.32 + 21.28.24.23.3.11.7.12.22.32 + 21.30.19.6.28.1.32.2.14.14 + 21.31.31.25.5.30.26 + 21.32.13.21 + 21.32.13.22.3.13.31.23.14.12.9 + 21.4.11.18 + 21.4.22.20.24.28.6 + 21.5.11.18 + 21.5.17.19.15.25.18.21.24.9 + 21.6.22.28.12.23.11.22 + 21.7.23.9.16.5.18.14 + 21.7.7.11 + 21.8.9 + 21.9.27.22.32 + 21.9.32.1.27 + 22.10.12.23.9 + 22.10.16.8 + 22.10.18 + 22.10.27.19.29.20.29.3.12.14.25 + 22.11 + 22.12.22.28 + 22.13.22.21.25.17.8 + 22.13.22.8.30.32.10.24 + 22.15 + 22.16 + 22.16.25.18.25.7.24.29.14.8 + 22.17.24.14.21.15.12.18.17.25.11 + 22.17.30 + 22.17.4.2.22.17 + 22.17.7.30.13.24 + 22.17.9.11.25.15.3.9 + 22.18.20.23.15.9.12 + 22.19.20.5.2.20 + 22.19.21.11.6.8.29.24 + 22.19.5.22.20.31.23.24.14.24.4 + 22.20.30 + 22.21.32.15.8.29.5.12.10.29 + 22.22.10.30.5.15.25.21.19.11 + 22.22.27.6.27.15.5.18.21.28.9 + 22.23.18.18.9.8.23.7.23.23.16 + 22.23.22.30 + 22.23.25.28.5.27.9.9.24.31.10 + 22.24.22.25.15.23.13 + 22.25.4.28.9.20.12.13 + 22.26.1.28.9.9.31 + 22.26.32 + 22.28.20.6.32.32 + 22.29.18.32.13.12.22.31.17.22 + 22.29.29.11 + 22.3.6 + 22.30 + 22.30.31.24.23.22.5.20.28.1 + 22.31.2.32.32.11.26.23.19 + 22.31.21.13.13.26.11.5.19 + 22.32.6.6.3.8.24.6.25.29 + 22.8.20.1.10.28.6.27 + 22.9.15.19.12 + 23.1.23.18.12.29 + 23.10.13.32.14.20.16.11.14 + 23.10.5.26.12.4.20.4 + 23.12.1.5.32.25.8.24.1.25 + 23.12.11.11.15.16.22.31.32.5.8 + 23.12.19.25.16.23.22.6.29.4 + 23.12.32.22.19.1.22.4 + 23.14.12.30.18.4.16.18.7.7 + 23.14.30.27.28.26.26.23.8.32 + 23.17.22.1.23.4.29.32.4.1 + 23.17.25.4.1.16.29.10 + 23.17.28.31.28 + 23.17.32.15.23.16.25 + 23.19.17.31.29.13.1.12.5.25 + 23.2.22.7.32.3.27.6 + 23.20.12.16.15.2 + 23.20.24 + 23.20.8 + 23.22.10.1.14.24 + 23.22.23.14.31.32 + 23.23 + 23.24.11.31.10.31.18.28.13.18.6 + 23.24.16.32.13.29 + 23.25.23.11.7.23 + 23.27.27.16 + 23.27.6.26.22 + 23.28.1 + 23.28.20.25.30.24.15 + 23.28.3.30.15.31.32.3.21.9.19 + 23.3.20.24 + 23.3.32.21.5.14.10.17.1 + 23.31.27.16.8.30.20.27 + 23.32.5.25.19.9.15.17.15.11 + 23.5.5.17 + 23.5.7.12.11.23.10 + 23.6.27 + 23.8.13.22.21 + 24.1.10.20.28.18.6.27.20.30.26 + 24.1.29.32.14.15.32.6.15.22 + 24.10.10.31.4.29.9 + 24.10.8.25.16 + 24.11.5 + 24.12 + 24.13.1.8 + 24.15.15.17.22 + 24.16.27.10.9 + 24.17.24 + 24.17.31.20.12.9.19.29.18 + 24.18.16 + 24.2.26.24.14.15.31.23.17.26 + 24.2.6.7.16.7.28 + 24.20.23 + 24.21.14.25.11.3.20.6.6.16 + 24.23.24.4.15.25.17 + 24.23.29.8.24.11.21.10.28.14.27 + 24.24 + 24.25.7.27.30.8.26.17 + 24.27.14 + 24.27.18.32.14.9.11.28.9 + 24.28.13.26.8.8.31 + 24.28.32.21 + 24.3.23.25 + 24.31 + 24.31.2.13.5.23.18.16 + 24.31.8 + 24.32.17.23.24.19.23.9.20.18 + 24.32.27 + 24.9 + 24.9.15.1.14.29.6.4 + 24.9.27.16.20.21 + 24.9.8.12.29 + 25.10 + 25.10.29.3.6.21.3.31.13 + 25.10.4.28.3.31.19 + 25.11.24 + 25.14.5.32.25 + 25.15.11 + 25.16.9.6 + 25.17.18.17.27 + 25.17.18.30 + 25.17.2.20.20.3.29.21.3.12 + 25.17.9.16.17.31.23.29.24 + 25.18.8.3.23.23.5.9.6 + 25.19.27.2.9.20 + 25.2.11.20.8.6.22 + 25.2.3.15.11.19.5.28.25.14 + 25.21.8.17 + 25.22.2.25.6 + 25.24.2.32.14.18.16 + 25.24.29 + 25.28.3 + 25.28.30.24 + 25.29 + 25.3 + 25.30.1.4.24.11 + 25.31 + 25.32.24.24.28.15.16.10 + 25.4.32 + 25.4.4.1.13.32.26.20.20.3 + 25.5.30.7.16.12.21.12.11.16 + 25.6 + 25.6.12.16.1 + 25.7.3.21.31.12.28 + 25.9 + 25.9.1.5.9.11.25.4.11.27.32 + 25.9.10 + 26.11 + 26.12.27.2 + 26.13.4.7.13.11.3 + 26.14 + 26.14.5.32.10 + 26.16.12 + 26.16.12.3.27.9.28 + 26.17.9.13.4.25.32.2.24.9 + 26.18 + 26.18.32.20 + 26.19.3.14.8.28.31.10 + 26.24 + 26.24.9.12.11.15.31.2 + 26.25.10.10.13 + 26.25.24 + 26.26.22.21.14.11.29.19.14.24 + 26.28.14 + 26.31.11.23.3 + 26.31.16.18.22.13.32.23.9.20 + 26.31.6.8.29.8.24 + 26.31.7 + 26.32.21.31.27.12 + 26.32.8.12.30.19.24.8.6.1.10 + 26.5.29.7.28 + 26.7.22.3.18.21.11 + 26.7.5.8.11.9.22.1.6 + 26.8.28 + 26.9.17.1.18.19.1.11.18.29.3 + 26.9.20.12.22.22.32 + 27.1.11.3.25.9.6.6 + 27.11.14.17.24 + 27.11.15.9.24.31.18.4.1.30.20 + 27.12.4.2.29.22.15 + 27.15.15.15 + 27.16 + 27.17.15.7.28.20 + 27.17.17.19.24.9.14.20 + 27.17.3.18.2.13.18 + 27.18 + 27.18.10.4.22 + 27.19.20.1.31.29.5.22.26.3 + 27.2.10.4.25.14.2.15.4 + 27.21.27.5.13.30.17 + 27.21.28.24.7.2.24.23.8 + 27.22.11.13.21.25.5.1.27.21.27 + 27.23.2.32.11.21 + 27.23.20.30.7 + 27.24.11.31.21.6.29.17.24.18 + 27.25 + 27.26.29 + 27.27 + 27.27.25.10.31.10.21.22.21.16.12 + 27.27.30.11.15.24.9.7.4.30 + 27.29.1.5.30.6.22.16.23.2.28 + 27.3 + 27.3.3.11.21.4.25 + 27.30.12.11.20.15.11.13 + 27.31.2.16.29.6 + 27.32.26.21.31.17.32.32 + 27.4 + 27.4.15.14.19.6.12 + 27.4.17.17.32.8.16.15.17.13 + 27.5.15.1.15.16.21 + 27.5.22 + 27.6.13.24.21.27.28.22.3.7.4 + 28.1.3 + 28.11.11.30.20.11.32 + 28.11.27.21.14.16 + 28.14.24.26.6.15.16.32.25.13.8 + 28.14.32.29.2.3.4 + 28.15.18.27 + 28.15.25.7.13.6.19.2 + 28.17.26.9 + 28.18.6.22.13.8.25 + 28.2.27.1.20 + 28.20.8.9.9.28.30.29 + 28.23.2.30.3.8.1.15.15.14.13 + 28.25.10.25.19.15 + 28.25.11.22 + 28.25.29.4.13.5.6 + 28.26.25.7 + 28.26.26.6.31 + 28.26.4.22.13.20.32.27.15 + 28.27.24.14 + 28.28 + 28.30.24.16.17.28.2.13.10 + 28.31.10.28.22.26.16.15 + 28.4 + 28.5.12.9.2.27.11.11.2 + 28.5.13 + 28.6 + 28.6.11.6.15.22.12.6 + 28.6.8.22.25 + 28.8.21.15.16.28.4.16.26.8 + 28.9.3.16.17.21.23.30 + 29.1 + 29.1.2.14.14 + 29.1.7.26.25.11.22 + 29.10.12.17.12.16 + 29.10.17.11.28.12.18.5.19.15.21 + 29.11.20.22.27 + 29.14.12.9.17.5.32 + 29.14.31.25.7.32.23 + 29.15.29.8.31.26.1 + 29.20.1.11.21.16.1.2.14.28 + 29.23.1.21.31.8 + 29.23.15.25.1.6.6.10 + 29.25.29.16.32.11.15.25.5.22.3 + 29.25.30.15.21.3.25.26.26 + 29.26.25.14.24.18.2.13.23.29 + 29.27 + 29.27.13.29.10.2 + 29.27.13.9.28.29.19.13.29.31.27 + 29.27.5.22.26 + 29.27.7.7.3.11.14.26.21.11 + 29.28.9.15.8.27.31 + 29.29.17.31 + 29.29.18 + 29.3.15.17.12.29 + 29.3.17.17.18.32 + 29.30.21.8.16.23.32 + 29.30.7.31.22 + 29.32.13.4.1.16.20 + 29.5.18.27.3.21.18.6.14 + 29.5.32.20.11.7.13.24.17 + 29.6.12.31.20.23.32.20 + 29.9.25.27.15.16.32.26.6.32 + 3.1.13.22.24.14.12.31.3.4 + 3.1.14.8.9.16.30.22.20 + 3.10 + 3.10.27.4.5.6.19.12.28.12 + 3.10.4.5.28.11 + 3.11.18.21.5.20.30 + 3.11.32.11.22.3.7.17.8.13.23 + 3.13 + 3.14.1.14.17.28.29.16 + 3.14.11.15.21.32.2.15.13 + 3.14.30.5.32.22.29 + 3.15.2.23.22.2.16.14 + 3.18 + 3.18.18 + 3.18.8.22.7.28.32.31.3 + 3.19.11.6.5 + 3.20.16.13.29.20 + 3.20.19.10.17.27.3.6.22.23 + 3.21.16.24.23.12.16.32.3 + 3.21.6.13.12.18.25 + 3.22.18.1.5.14.9.6.14 + 3.25 + 3.26 + 3.26.32 + 3.27.18.8.4.21.6.32.30.7.5 + 3.29.19.2.24 + 3.29.32.26.8.10.25 + 3.3 + 3.32.2.29.3.32.28.11.29.30 + 3.4.22.19 + 3.5 + 3.6.24.21.20.32.3.4.26.5 + 3.9.11.23.32.26.24.28 + 3.9.25.26.7 + 30.12.28.2 + 30.12.6.30 + 30.12.9.25.24.6.7.24.29 + 30.15 + 30.16.14.9.5.4.10.7.31 + 30.16.3.21.10 + 30.17.2.25 + 30.17.25.3.31.11.3.4.1.10 + 30.17.4.5.13.6 + 30.18.30.16.29 + 30.2.17.8.14 + 30.20.3.2.5.15.8.7.17 + 30.22.29.21.19.14.3.2.6 + 30.23.10.1.10.7.22.28.18.11.17 + 30.23.2.13.14.15.29.19.4.12.24 + 30.24 + 30.24.23.25.32.18.22.12.29.9.22 + 30.24.32.15.14.10.11 + 30.25 + 30.25.17.17.10.29 + 30.25.24.22 + 30.25.8.24.6.29.31 + 30.27.8.6.11.19 + 30.3.16.26.7.27.26.9.27.21.18 + 30.30.17.5.30.21.19.5.22.22.14 + 30.31.13.9 + 30.32 + 30.4.30.11.13.23.14.24.11 + 30.5 + 30.6.4 + 30.8.18.5.20.6.15 + 30.8.9.14.25.30 + 30.9.24 + 31.13 + 31.13.9.1.5.12 + 31.17 + 31.17.2.30.11 + 31.18 + 31.18.25.1.14.29.25.5.22.30 + 31.18.27.15.20.29.29 + 31.18.32.11.7.25.20.5 + 31.21.14.20.1.22.2.5.3.27.12 + 31.21.22.14.8.21 + 31.24.26.18 + 31.28.32.4.31.4.7 + 31.29.18.26.1.26.17 + 31.29.4.29.24.30.30.32.10.23 + 31.30.12.20 + 31.30.23.7.7.24.32.10.11.1.31 + 31.32.12.26.31.32.14.23.28 + 31.4.7 + 31.5.6.4.8.29.3 + 31.7.14.2 + 31.9.3.5 + 32.1.21.1.16.29.21 + 32.1.23.20.14.12.23.5.32.15 + 32.1.24.29.22.5.9.24.18.3.13 + 32.1.31 + 32.15.20.28.5.1.23.4 + 32.16 + 32.17.8.24.2.14.5.4.22 + 32.19.20.24.23.31.8.32.16.29 + 32.2.11 + 32.24.11.8.12.23.22.19.11.17.18 + 32.24.29.6 + 32.25.16 + 32.25.3.6 + 32.27.13.6.7 + 32.27.18.7.3.4.2 + 32.28.1.32.28.10 + 32.29.24.31.25.6.9 + 32.3.12.2 + 32.3.23.7.2 + 32.3.5.9.17.15 + 32.30.18.17.1.14.12.18 + 32.31.11.22.1 + 32.31.26.19.13.29.4.25 + 32.4.19 + 32.6.13.8.32 + 32.6.15.26.14.15.3.19 + 32.6.3.2.12.5.28.1.25 + 32.6.31.31 + 32.6.8 + 32.6.9.26.16.4.4.29.7.11 + 32.8.29.18.31 + 32.8.5 + 4.1.24.24.28.24.18 + 4.10.28 + 4.11.19.17.2.22.20.18.13.32.15 + 4.11.22.4.19.24.4.28.6.8.22 + 4.13 + 4.13.22.11.9.13.27.15.7 + 4.14 + 4.14.10.19.16 + 4.14.16.14.1.8.1.22.17.10 + 4.14.17.12.20.17.1.22.3 + 4.14.32 + 4.15.20.23.12.16.2.16.17 + 4.16.22.19.24.21 + 4.16.7.25.21.7 + 4.18.29.9.16.10 + 4.19.16.15.5.2.25.8.28.14.2 + 4.2.16.13.16.11.19.10.10.25 + 4.2.2.32.24.25.31.3 + 4.2.6.20.7.8 + 4.21.28.5.16.29.5.21 + 4.21.9.1.2.14.8.17.13.26 + 4.22 + 4.22.17.10.19.9.8.19.28.3.9 + 4.22.7.19.25 + 4.25.12.10.15.9.18.9 + 4.26.2.2 + 4.26.23.6.19.31.10.4.22 + 4.26.5.26.21.28.17.24.25.23 + 4.27.32.18 + 4.3.20.27.9.1.18.30.12.5.19 + 4.3.6.27.22.23.10 + 4.30.8.20.19.9.30.24.11 + 4.31 + 4.5.9.4.15.19.8.26.17.26.3 + 4.7.1 + 4.9 + 5.1.5.31 + 5.10 + 5.10.2.11.21.9.19 + 5.10.3.9.23.30.23 + 5.12.2.20.1.24.25 + 5.13.23.19.28.26.27.6.1.22 + 5.13.23.4.9 + 5.14.27.15.11.17.3.10.27.25 + 5.14.29.2.23.16.20.22 + 5.15.10.3.23.13.32.23 + 5.15.16 + 5.18.9.25.31.21.22 + 5.19.1.26.20.6.20 + 5.2.32.19.13.29.12.13.31.29 + 5.20 + 5.21.27.13.14.11.2.16.20 + 5.23.31.18.24.32 + 5.24.24.9.32.26.31 + 5.24.25.15.27.30.20 + 5.24.4.31.3.16.25.17.13.26.11 + 5.27.16.3.30 + 5.27.21.1.29.29.28 + 5.27.28.26.14.15.6.20.1.31.13 + 5.27.32.21.5.1.11.14 + 5.3.17.29 + 5.3.29.9.22 + 5.31.8.1.5.13.21.28.29.19.2 + 5.4.8.25.12.27.2.29.28.3 + 5.5.12.31.23.13.17.22.20 + 5.8 + 5.8.17.30.15.8.19.29.30.11.6 + 5.9.19.6 + 6.1.8.6.30.29.30 + 6.10.25.12 + 6.11.11.5.16.8.14.12.9 + 6.11.31.23.12.8.30.14.27 + 6.13.31.5.7.26 + 6.14 + 6.17.10.10.7.9.27.8.29 + 6.17.26.25.27.11.10.9 + 6.18.1.4.18.23 + 6.19.29.11.2.32.21.15.32.9 + 6.19.3 + 6.19.6.4.9.11.32.17.17.3.15 + 6.2.32 + 6.20 + 6.20.14 + 6.21.30.7 + 6.22.12 + 6.25.17.32 + 6.26 + 6.26.29.10.21.28.20.19 + 6.27.26.1.20.24.6 + 6.27.29.14.8.12.26.3.21.4.1 + 6.29.32.13.30.3.16 + 6.29.6.13.14.24.10.4.14.28 + 6.5.27.19.13.26.1.18.9 + 6.6.22.8 + 6.7.25.16.13.21.7.20.25.12.4 + 6.7.7 + 6.8.7.20.2 + 6.9.1.10.10.22.6 + 6.9.29.17.4.32 + 7.10.17.21.11.29.17.25.19.4.29 + 7.11 + 7.12 + 7.12.1.10.6.17.29.24.24.4 + 7.12.23 + 7.13 + 7.13.15 + 7.14.22.29.30.14.25.1.9.26.25 + 7.16.20.17 + 7.19.10.12.31.1.27.13.19 + 7.19.12.3.21.19.18.5.2.14.10 + 7.19.6.17.15.26.21.9 + 7.21.8 + 7.23.1.24.29.13.31.19.23.17.7 + 7.23.15.32.28.27.2.2.26 + 7.26.18 + 7.27.20 + 7.30.19.25.23.15.14.29 + 7.30.5.10.10.5.30.14.9.18 + 7.31 + 7.31.2.28.15.11.17.18.19.23.6 + 7.31.4.20.17 + 7.32.10.3.30.12.14 + 7.5.28.8.17.26.31.10.15 + 7.7 + 7.7.22.24.17.32.17.25.28 + 7.7.25.22.22.26 + 8.1.29.18.22 + 8.10 + 8.11.20 + 8.12.4 + 8.13.1 + 8.13.14.11.11.29.22.4.4.10 + 8.13.6.12.18.7 + 8.13.9.31.20.20.24.7.23.31.28 + 8.14.19.18 + 8.16 + 8.16.1.16.28.6.3.22.6.23 + 8.16.20.24.20.6.10.21 + 8.16.30.29.19.22.28.24.2 + 8.16.6 + 8.17.25.26.15.25 + 8.17.9.15.21.28.1.7.1.3.6 + 8.2 + 8.2.18.23.5.16.17.1 + 8.21.17.3.6.3.18 + 8.21.8.23.4.18 + 8.22.32.17.16.28.31.23.22.9 + 8.24.11.13.25.19 + 8.25.20.3.15.24.7.4.24.5.30 + 8.26.29.13.7.25.31.28.3.32 + 8.27.3.4.12.26.16 + 8.29.6.3 + 8.3.18.13.30.20.27.26.17.28 + 8.3.3.25.25.15.7.13.21.18 + 8.31.22.27 + 8.32.30.1 + 8.5.24.9.29.32.31.30.13.9.7 + 8.5.30.29.9.31 + 8.6.6.5.8.8.12 + 8.9.21.16.29 + 8.9.22 + 8.9.25.25.26.30.31.31.2.32.7 + 9.10.19.18.15.11.22.32.32.14.9 + 9.10.32 + 9.14.27.31.26.21.25.3.20 + 9.16.2.16.22.24.17.31.14.21.17 + 9.17.13.31.7 + 9.18.23 + 9.18.30.11.29.32.7.19.2 + 9.19.7.13.13.25 + 9.2.10.4 + 9.2.4.27.26 + 9.21.14.19 + 9.21.20.29.1 + 9.21.28.8.12.15.3.13.10.11 + 9.22.10.15.5.15 + 9.23.21.22.5.29.15.21 + 9.26.1.16 + 9.28.10.26.14.26.15.14 + 9.28.24 + 9.28.30.1.6.25.17.9 + 9.3.3 + 9.3.31.18.12.3.9.29.10 + 9.30 + 9.31.23.19.5.10.16.4.30.24.5 + 9.31.4.14.31.10.17.5.2 + 9.5 + 9.5.9.3.23.9.25.14.1.29.28 + 9.6.9.21.6.11.29.13.29.20.32 + 9.7.31.11.8.23 + 9.8.23.2.20.16 + 9.9.13.9.14.27 +(882 rows) + +drop index tstidx; +create index tstidx on ltreetest using gist (t); +set enable_seqscan=off; +SELECT * FROM ltreetest WHERE t < '12.3' order by t asc; + t +---------------------------------- + + 1 + 1.1 + 1.1.1 + 1.1.1.1 + 1.1.1.2 + 1.1.1.2.1 + 1.1.2 + 1.1.2.1 + 1.1.3 + 1.1.7.32.11.22 + 1.10.21 + 1.10.23.25.5.11 + 1.10.4.18.22.23.24 + 1.10.5.22.13 + 1.11.10.19.6.1.26.17.2.22 + 1.12.25.26.22.8.15.23 + 1.13.16.27.11.16.30.2.9.18.4 + 1.14.3.7.3.17.2.29 + 1.15.17.6.28.25.24.31.27.9 + 1.16.8.18.14.16.21.25.6 + 1.18.29.30.22.14.3.20.15.21.20 + 1.19.22.11.14.7.32.23.19.14 + 1.20.18.25.3.24.25.10.9 + 1.20.22.26.2.6.11 + 1.21.28.4.23 + 1.22.19.24.8.11 + 1.22.29.5.16 + 1.25.7.9.26.17.31.20.13 + 1.26 + 1.26.15.23.5.31.29.11.19.28.1 + 1.27.22.23.2.26.32.17.7.9 + 1.28.19.8.25.6.20.27.29.27 + 1.28.3.22 + 1.29.18.1.21.12.13.27.32.15 + 1.3.15.11.11.25.24.21.19 + 1.30.18.31.12.25.4.19.28.12.15 + 1.30.31.31.20.16.7 + 1.31.3 + 1.4.14.32.14 + 1.8 + 1.9.18.10.1.26.22.16.17 + 10.11.25.2.24.18.18.21.6.26.21 + 10.12.23.22.23.22.20.17.17.9 + 10.12.9.6.6.26.14.8.23.1.25 + 10.13.12.8.4.8.11.30 + 10.13.22.1.8.30.9.24.1.2.1 + 10.15.16.3 + 10.16.18.9.27.2.29.32.24.13 + 10.16.19.7.15 + 10.18.12.27.24.30.32.7.11.5.13 + 10.2.17.26.16.7.19.6.23.3 + 10.20 + 10.22.1 + 10.22.30.16.2.21.17.13 + 10.26.27.23.4.31.11.25.29 + 10.26.30.15.1 + 10.27.7.24.26.11.31.20.29 + 10.28.22.29.13.19.6.7.6.14 + 10.28.7.16.31 + 10.29 + 10.29.26.4.27.17.11 + 10.3 + 10.3.19 + 10.31 + 10.31.25.31.24.16.17 + 10.32.14 + 10.5.23.5.32.9.18.5.30 + 10.5.5.15.29.2 + 10.7.9 + 10.8.20.11.12.23.22 + 11.1 + 11.1.3.28.30.21.24.14 + 11.10 + 11.10.22.18 + 11.11.11.4.23.21.25 + 11.11.9.30.15.29.15.18 + 11.12.6.21 + 11.14.21.24.10.7.29.23.24.28 + 11.15.11.19.29.10 + 11.16.16.28.14 + 11.17.10 + 11.17.17.24.11.23.17.17.18.10.22 + 11.18.4.8.3.13.14.28.18.31 + 11.19.23.3.6.11 + 11.2.27.3 + 11.21.13.9.19 + 11.21.16.27.16 + 11.22.28.8.12.23.25.15.21.28 + 11.29 + 11.3.15.28.22.8.14 + 11.30 + 11.30.20.15.18.32.1.18.25.26.8 + 11.32.18.31 + 11.6.11.29.4.5.24.6.26.12 + 11.7.31.15.22 + 11.8.18 + 12.1.1 + 12.1.28.22.25 + 12.10 + 12.10.11.9.10.31.4.16.31 + 12.11.17.1.2 + 12.11.20.20.29 + 12.13.16.17.29.27.16.14.9.19.9 + 12.13.5.31 + 12.14.20.8.28.4 + 12.15.10.17.18.13 + 12.16.13 + 12.16.2.4.15 + 12.17.10.7.17.16 + 12.18 + 12.2.4.28.21.30.24 + 12.21.15.27.24.15.8.24.24.26 + 12.21.20.20 + 12.22.20.4.12 + 12.23.3.19.29.15.12.6 + 12.24.29.32.32.29.2 + 12.25.32.2.27.3.3.16 + 12.27.23.32.1.1.9.29.13 + 12.27.30.12.24.2.20 + 12.28.12.24.28.15.5.12.30.13.21 + 12.29.17.2.20.29.1.11.19.8.12 + 12.29.26.18.4.21.28.8.13.3 +(123 rows) + +SELECT * FROM ltreetest WHERE t <= '12.3' order by t asc; + t +---------------------------------- + + 1 + 1.1 + 1.1.1 + 1.1.1.1 + 1.1.1.2 + 1.1.1.2.1 + 1.1.2 + 1.1.2.1 + 1.1.3 + 1.1.7.32.11.22 + 1.10.21 + 1.10.23.25.5.11 + 1.10.4.18.22.23.24 + 1.10.5.22.13 + 1.11.10.19.6.1.26.17.2.22 + 1.12.25.26.22.8.15.23 + 1.13.16.27.11.16.30.2.9.18.4 + 1.14.3.7.3.17.2.29 + 1.15.17.6.28.25.24.31.27.9 + 1.16.8.18.14.16.21.25.6 + 1.18.29.30.22.14.3.20.15.21.20 + 1.19.22.11.14.7.32.23.19.14 + 1.20.18.25.3.24.25.10.9 + 1.20.22.26.2.6.11 + 1.21.28.4.23 + 1.22.19.24.8.11 + 1.22.29.5.16 + 1.25.7.9.26.17.31.20.13 + 1.26 + 1.26.15.23.5.31.29.11.19.28.1 + 1.27.22.23.2.26.32.17.7.9 + 1.28.19.8.25.6.20.27.29.27 + 1.28.3.22 + 1.29.18.1.21.12.13.27.32.15 + 1.3.15.11.11.25.24.21.19 + 1.30.18.31.12.25.4.19.28.12.15 + 1.30.31.31.20.16.7 + 1.31.3 + 1.4.14.32.14 + 1.8 + 1.9.18.10.1.26.22.16.17 + 10.11.25.2.24.18.18.21.6.26.21 + 10.12.23.22.23.22.20.17.17.9 + 10.12.9.6.6.26.14.8.23.1.25 + 10.13.12.8.4.8.11.30 + 10.13.22.1.8.30.9.24.1.2.1 + 10.15.16.3 + 10.16.18.9.27.2.29.32.24.13 + 10.16.19.7.15 + 10.18.12.27.24.30.32.7.11.5.13 + 10.2.17.26.16.7.19.6.23.3 + 10.20 + 10.22.1 + 10.22.30.16.2.21.17.13 + 10.26.27.23.4.31.11.25.29 + 10.26.30.15.1 + 10.27.7.24.26.11.31.20.29 + 10.28.22.29.13.19.6.7.6.14 + 10.28.7.16.31 + 10.29 + 10.29.26.4.27.17.11 + 10.3 + 10.3.19 + 10.31 + 10.31.25.31.24.16.17 + 10.32.14 + 10.5.23.5.32.9.18.5.30 + 10.5.5.15.29.2 + 10.7.9 + 10.8.20.11.12.23.22 + 11.1 + 11.1.3.28.30.21.24.14 + 11.10 + 11.10.22.18 + 11.11.11.4.23.21.25 + 11.11.9.30.15.29.15.18 + 11.12.6.21 + 11.14.21.24.10.7.29.23.24.28 + 11.15.11.19.29.10 + 11.16.16.28.14 + 11.17.10 + 11.17.17.24.11.23.17.17.18.10.22 + 11.18.4.8.3.13.14.28.18.31 + 11.19.23.3.6.11 + 11.2.27.3 + 11.21.13.9.19 + 11.21.16.27.16 + 11.22.28.8.12.23.25.15.21.28 + 11.29 + 11.3.15.28.22.8.14 + 11.30 + 11.30.20.15.18.32.1.18.25.26.8 + 11.32.18.31 + 11.6.11.29.4.5.24.6.26.12 + 11.7.31.15.22 + 11.8.18 + 12.1.1 + 12.1.28.22.25 + 12.10 + 12.10.11.9.10.31.4.16.31 + 12.11.17.1.2 + 12.11.20.20.29 + 12.13.16.17.29.27.16.14.9.19.9 + 12.13.5.31 + 12.14.20.8.28.4 + 12.15.10.17.18.13 + 12.16.13 + 12.16.2.4.15 + 12.17.10.7.17.16 + 12.18 + 12.2.4.28.21.30.24 + 12.21.15.27.24.15.8.24.24.26 + 12.21.20.20 + 12.22.20.4.12 + 12.23.3.19.29.15.12.6 + 12.24.29.32.32.29.2 + 12.25.32.2.27.3.3.16 + 12.27.23.32.1.1.9.29.13 + 12.27.30.12.24.2.20 + 12.28.12.24.28.15.5.12.30.13.21 + 12.29.17.2.20.29.1.11.19.8.12 + 12.29.26.18.4.21.28.8.13.3 + 12.3 +(124 rows) + +SELECT * FROM ltreetest WHERE t = '12.3' order by t asc; + t +------ + 12.3 +(1 row) + +SELECT * FROM ltreetest WHERE t >= '12.3' order by t asc; + t +---------------------------------- + 12.3 + 12.4.10.17.4.10.23.3 + 12.4.12.13.25.30.30.8.9.12 + 12.4.24.6.1.13.5.20 + 12.4.26.23.25.5.15.7.16 + 12.6.14.23.19.21.9.12 + 12.7.16.8.21.22.2.16.18 + 12.7.28.26.14.21.18.31.5.15.11 + 13.1.6.17.28.9.15.30.1.27.14 + 13.12 + 13.14.13.10.28.26.9.18.27.21 + 13.16.1.27.18.18.19.6.14.4 + 13.16.4.28 + 13.17.7 + 13.19.2.6.23.19.9.7.21.8.16 + 13.24 + 13.25.10.25.8.16 + 13.26.17.3.2.19 + 13.28.12.6 + 13.28.14.2.8.18 + 13.28.9.3 + 13.3.20 + 13.3.8 + 13.30.24 + 13.32.15.32.26.14.32 + 13.7 + 13.8.15.3.7.31.5.10.15.30 + 13.8.20.9.21 + 13.8.23.13.11.18.24.21.11.24.10 + 13.9.9.27.31.11.25.9.27.22.13 + 14.1.11 + 14.1.15.25.27.23.25.26.28.10 + 14.10.11.30.5.7.6.24.9.30.26 + 14.11.25 + 14.12.31 + 14.13.9.13.11.5.5.2.2.32.12 + 14.14.25 + 14.15.31.29 + 14.16.6.29.26.13.14.16.25.26.8 + 14.17.7.30.8.25.26.4 + 14.19.20.13.27.2.2 + 14.19.26.15.22.23 + 14.19.30.6.4.10.10.10.22.25.11 + 14.2.14.11.12 + 14.21.22 + 14.21.5.28.3.32.24.14.25.31 + 14.21.6.5.26.9.32.16.25 + 14.23.31.5.5.15.17.12.17.7.3 + 14.24 + 14.26.25.4.12.26.8 + 14.27.29.23.4.1.17.32.6.25.22 + 14.29 + 14.3.17.1.14.15.21.4.26 + 14.30.13.5.26.9.22.23.14.10 + 14.30.2.21.15.16.13 + 14.30.23.3 + 14.4.19.27.28.24.19 + 14.4.23.4.23.22.11.6.26.5 + 14.5.13.19.25.12.32.9.13.16.12 + 14.6.10.29.25.26.20.24.24 + 14.8.15.30.7.29.27.31.4 + 14.9.15.21.21.31.1.29 + 15.1.6.31.30.13.32.9.10 + 15.1.8 + 15.10.30.1.4.12.8.20 + 15.11.26.1.30.6.23.5 + 15.17 + 15.17.2.32.7 + 15.21.22 + 15.21.23.30.9.25 + 15.23.26.20.27.7 + 15.25.31.11.4.22.16.7.11 + 15.26.24.31.16.15.17.22.8.30.3 + 15.28.24 + 15.28.30.19.31.6.2.2.31 + 15.29.25 + 15.29.32.16.29.12.20.32.13.20 + 15.3.31.9.27.14.9.8.14.6.32 + 15.30.17.5.32.28.2.18.27 + 15.31.11.27.19.19.20.5.5 + 15.4.15 + 15.5.1.31.28.10.8 + 15.6.19.3 + 15.7.3.14.23.19.26 + 15.7.5.12.7.9.3.28.26 + 15.8.10 + 15.8.3.15.27.14.29.28.6.5.25 + 15.9.11.20.22.15.11.13 + 15.9.8.20.27 + 16.13.19.11.18.13.17.17 + 16.13.2.19.14.29.31.30.23.15.12 + 16.13.26.18.9.29.11.17.1.24.26 + 16.14.3.17.17.26.12.19.19.30 + 16.16.28.24.11 + 16.18.23.6.31 + 16.19.17.30.30.5.17.24.27 + 16.2.14.3.26.11 + 16.20.29.26 + 16.21.13.1.4 + 16.23.30.12.31.31.19.14 + 16.24.3.30.15.22.31.2 + 16.24.7.25 + 16.27.8.17.14.17.21.29.14 + 16.28 + 16.29.6.23.13.28.31.6.19.26.15 + 16.30.10.7.29.4.9.21.22.13.26 + 16.31.12.27.25.9.32.29 + 16.5 + 16.5.10.2.18.8.15.12.32.25.10 + 16.5.12.5.15.12.24.25.3 + 16.5.14.21.32.17.23.3.4.26 + 16.5.23.17 + 16.5.6.12 + 16.8.29.7.21.2.3 + 16.9.14.28.6.21.31.31.26 + 16.9.29 + 16.9.32.14.3.7.8.7.21.22 + 17.1.12.20 + 17.10.17.22.20.25.14.13 + 17.11.17.4.8.26.26.20.6 + 17.13.14.29.27.27.13.12.15 + 17.13.19.31.12.18.10.15.14 + 17.13.8 + 17.14.7.3.2.18.20.23.18.5 + 17.17.14.28.6.30 + 17.19.1.22.11.7.22.1.14.28.11 + 17.22.12.10.30.11 + 17.24.15.27.3.32.4.22.20.6.24 + 17.24.30.6.32 + 17.25.10.13.21.5.7.22.2 + 17.25.2.13.10.27.13.1 + 17.25.26.23.32 + 17.26.18 + 17.27 + 17.29.21.10.18.8.16.26.18.21.26 + 17.29.31.8.24.10.18.27.17 + 17.3 + 17.5.3.15.17.13.5 + 17.7.26.30.18.23.4 + 17.8 + 17.8.31.32 + 17.9.32.31.21.31.23.17.10.32.9 + 18.13.6.12.26.26.26.29.18.20.1 + 18.13.9.3.18.15.2 + 18.15.14 + 18.17.6.16.6.10 + 18.18.19.16.14.16.21.10.25 + 18.18.5.11.7.4.25 + 18.19.11.20.13.13.11 + 18.19.12.20.18.17.15.32.18.5 + 18.21 + 18.24.21.17.11.26.28.22.21.18.10 + 18.27.11.27.9.16.7.6.22.26.27 + 18.29.13.24.18.3.12.18.12.12 + 18.29.5.1.10.21.2 + 18.30.11.17 + 18.30.18.31 + 18.31.26.18.6.15.18.11 + 18.31.32.28.1.4.24.24.12.25 + 18.31.32.29.22.1.31.11.28 + 18.4 + 18.4.14.29.3 + 18.5.6.31.5.15.15 + 18.6.2.2.24 + 18.6.26.2.13.9.6.11.10.11.16 + 18.7.10.27.17.24 + 18.7.3.17.13.5.31.6.31.25.29 + 18.9.21.2.31.8.32 + 18.9.26.7 + 19.10.26.19.5.21.30.23 + 19.10.4.30.32.4.12 + 19.10.8.10.4.19 + 19.11.10.18.14.13.7.7 + 19.11.29.13.15.27.12.15.14.12 + 19.12.20.24.32.13.11.23.26 + 19.12.26.24.29.3 + 19.12.30.2.21 + 19.15.26.19 + 19.16.26.2 + 19.16.31.31.29.12 + 19.17.12.15 + 19.17.13.12.32.16.3 + 19.19.25.22.11.6.15.3.2.19 + 19.2.26.21.16.11.2.2 + 19.2.9.29.6 + 19.20.25.7.27.28.27.17.9.3.1 + 19.22.21.13.27.13.15 + 19.22.29.32.1.21.26.24.23.17 + 19.26.24.27.6.24.16.27.32.29 + 19.26.32.13.1.12.30.26.22.25 + 19.3.12.12 + 19.3.23.4.4.21.23 + 19.30.18.11.32.14 + 19.30.27.26.21.7.18 + 19.31.14.25.5.8.21.11.13.20 + 19.5.20.3.4.2.3 + 19.6.13.14.22.13.9.29 + 19.6.24.32.30.13.6.25.8.28 + 19.7 + 19.7.29.31.3.20.7.21.25.27.29 + 19.9.32.23.13.24.1 + 2.1.12.19.29.28.3.31.28.28.10 + 2.1.3.30.24.17.9 + 2.10.10.4.20.1.12.13 + 2.10.28.1.17.19.32.28 + 2.11.32.25.23 + 2.12.14.28.16.21 + 2.12.30.22.12 + 2.13.9.23.21.2 + 2.13.9.28 + 2.14.10.4.17.17.8.4.27.20 + 2.14.12.13 + 2.15.14.20.30.26 + 2.15.18.21.5.21.4.7.30 + 2.16.3.7.22.18.29.20 + 2.19.4.1.15.7.8.9.17.29 + 2.2.18.18.3.3.18.8.10.8 + 2.22.19 + 2.24.4.5.24.32 + 2.24.5.3.4.10.27.26.17.28.16 + 2.27.15.14 + 2.28.5.17.6.32 + 2.30.26.10.14.31.18.2 + 2.31.25 + 2.32.10.13.12 + 2.32.8.28.24.20.9.24.25.8.9 + 2.4.25.32.16.22.26.13.17.18 + 2.6.15.26.23.26.24 + 2.8.13.12.17.23.16.7.11.23 + 2.9 + 20.1.24.3.30.31 + 20.13 + 20.14.11.2.10.14 + 20.15 + 20.17.14.7 + 20.17.18.21.1 + 20.18.24.14.12.13.9 + 20.20.32.29.24.5.5.26.22.32 + 20.20.7 + 20.22.10 + 20.23.29.5.7.30.13.14.22 + 20.23.7.11.11.31.18.16.3 + 20.24.14.15.4.21.12.27.4.12 + 20.25.22.19.22 + 20.28.22.7.10.28.27.22.14.16 + 20.29.18.16.2.21.23.11 + 20.3.1.8.8.30.20 + 20.30.17 + 20.30.28.15.17 + 20.30.9.9.14.12.29 + 20.31.13.12.19.2.26.16.16.22.28 + 20.32.5.1.3.20.3.30.27 + 20.32.9 + 20.4.1.16.31.3 + 20.4.27.31.1 + 20.5.4.9.31.14.26.6 + 20.6.26.3.30 + 20.6.3.26.7.29.28.4 + 20.8.19.14.16.7 + 20.9.29.32.13.7.23 + 21.1.4.9.9.31.24.21.3.29 + 21.10.20.9.3.16.9.10.20 + 21.14 + 21.14.13 + 21.14.22.29 + 21.14.25.20.13.31.14.20 + 21.15.18.18.30.3.20 + 21.15.31.24.29.24.26.12.20 + 21.17.18.32.7.8 + 21.17.27.23.15 + 21.17.31.10.31.13.9.26.6.14 + 21.18 + 21.18.2.1 + 21.18.30.19.24.24 + 21.20.24.25.6.26.23 + 21.20.28.19.27.9 + 21.21.10.27 + 21.22.31.24.27 + 21.23.13 + 21.23.17.8.23.11.8.1 + 21.28.17.22.10.27.4.20.2.32 + 21.28.24.23.3.11.7.12.22.32 + 21.30.19.6.28.1.32.2.14.14 + 21.31.31.25.5.30.26 + 21.32.13.21 + 21.32.13.22.3.13.31.23.14.12.9 + 21.4.11.18 + 21.4.22.20.24.28.6 + 21.5.11.18 + 21.5.17.19.15.25.18.21.24.9 + 21.6.22.28.12.23.11.22 + 21.7.23.9.16.5.18.14 + 21.7.7.11 + 21.8.9 + 21.9.27.22.32 + 21.9.32.1.27 + 22.10.12.23.9 + 22.10.16.8 + 22.10.18 + 22.10.27.19.29.20.29.3.12.14.25 + 22.11 + 22.12.22.28 + 22.13.22.21.25.17.8 + 22.13.22.8.30.32.10.24 + 22.15 + 22.16 + 22.16.25.18.25.7.24.29.14.8 + 22.17.24.14.21.15.12.18.17.25.11 + 22.17.30 + 22.17.4.2.22.17 + 22.17.7.30.13.24 + 22.17.9.11.25.15.3.9 + 22.18.20.23.15.9.12 + 22.19.20.5.2.20 + 22.19.21.11.6.8.29.24 + 22.19.5.22.20.31.23.24.14.24.4 + 22.20.30 + 22.21.32.15.8.29.5.12.10.29 + 22.22.10.30.5.15.25.21.19.11 + 22.22.27.6.27.15.5.18.21.28.9 + 22.23.18.18.9.8.23.7.23.23.16 + 22.23.22.30 + 22.23.25.28.5.27.9.9.24.31.10 + 22.24.22.25.15.23.13 + 22.25.4.28.9.20.12.13 + 22.26.1.28.9.9.31 + 22.26.32 + 22.28.20.6.32.32 + 22.29.18.32.13.12.22.31.17.22 + 22.29.29.11 + 22.3.6 + 22.30 + 22.30.31.24.23.22.5.20.28.1 + 22.31.2.32.32.11.26.23.19 + 22.31.21.13.13.26.11.5.19 + 22.32.6.6.3.8.24.6.25.29 + 22.8.20.1.10.28.6.27 + 22.9.15.19.12 + 23.1.23.18.12.29 + 23.10.13.32.14.20.16.11.14 + 23.10.5.26.12.4.20.4 + 23.12.1.5.32.25.8.24.1.25 + 23.12.11.11.15.16.22.31.32.5.8 + 23.12.19.25.16.23.22.6.29.4 + 23.12.32.22.19.1.22.4 + 23.14.12.30.18.4.16.18.7.7 + 23.14.30.27.28.26.26.23.8.32 + 23.17.22.1.23.4.29.32.4.1 + 23.17.25.4.1.16.29.10 + 23.17.28.31.28 + 23.17.32.15.23.16.25 + 23.19.17.31.29.13.1.12.5.25 + 23.2.22.7.32.3.27.6 + 23.20.12.16.15.2 + 23.20.24 + 23.20.8 + 23.22.10.1.14.24 + 23.22.23.14.31.32 + 23.23 + 23.24.11.31.10.31.18.28.13.18.6 + 23.24.16.32.13.29 + 23.25.23.11.7.23 + 23.27.27.16 + 23.27.6.26.22 + 23.28.1 + 23.28.20.25.30.24.15 + 23.28.3.30.15.31.32.3.21.9.19 + 23.3.20.24 + 23.3.32.21.5.14.10.17.1 + 23.31.27.16.8.30.20.27 + 23.32.5.25.19.9.15.17.15.11 + 23.5.5.17 + 23.5.7.12.11.23.10 + 23.6.27 + 23.8.13.22.21 + 24.1.10.20.28.18.6.27.20.30.26 + 24.1.29.32.14.15.32.6.15.22 + 24.10.10.31.4.29.9 + 24.10.8.25.16 + 24.11.5 + 24.12 + 24.13.1.8 + 24.15.15.17.22 + 24.16.27.10.9 + 24.17.24 + 24.17.31.20.12.9.19.29.18 + 24.18.16 + 24.2.26.24.14.15.31.23.17.26 + 24.2.6.7.16.7.28 + 24.20.23 + 24.21.14.25.11.3.20.6.6.16 + 24.23.24.4.15.25.17 + 24.23.29.8.24.11.21.10.28.14.27 + 24.24 + 24.25.7.27.30.8.26.17 + 24.27.14 + 24.27.18.32.14.9.11.28.9 + 24.28.13.26.8.8.31 + 24.28.32.21 + 24.3.23.25 + 24.31 + 24.31.2.13.5.23.18.16 + 24.31.8 + 24.32.17.23.24.19.23.9.20.18 + 24.32.27 + 24.9 + 24.9.15.1.14.29.6.4 + 24.9.27.16.20.21 + 24.9.8.12.29 + 25.10 + 25.10.29.3.6.21.3.31.13 + 25.10.4.28.3.31.19 + 25.11.24 + 25.14.5.32.25 + 25.15.11 + 25.16.9.6 + 25.17.18.17.27 + 25.17.18.30 + 25.17.2.20.20.3.29.21.3.12 + 25.17.9.16.17.31.23.29.24 + 25.18.8.3.23.23.5.9.6 + 25.19.27.2.9.20 + 25.2.11.20.8.6.22 + 25.2.3.15.11.19.5.28.25.14 + 25.21.8.17 + 25.22.2.25.6 + 25.24.2.32.14.18.16 + 25.24.29 + 25.28.3 + 25.28.30.24 + 25.29 + 25.3 + 25.30.1.4.24.11 + 25.31 + 25.32.24.24.28.15.16.10 + 25.4.32 + 25.4.4.1.13.32.26.20.20.3 + 25.5.30.7.16.12.21.12.11.16 + 25.6 + 25.6.12.16.1 + 25.7.3.21.31.12.28 + 25.9 + 25.9.1.5.9.11.25.4.11.27.32 + 25.9.10 + 26.11 + 26.12.27.2 + 26.13.4.7.13.11.3 + 26.14 + 26.14.5.32.10 + 26.16.12 + 26.16.12.3.27.9.28 + 26.17.9.13.4.25.32.2.24.9 + 26.18 + 26.18.32.20 + 26.19.3.14.8.28.31.10 + 26.24 + 26.24.9.12.11.15.31.2 + 26.25.10.10.13 + 26.25.24 + 26.26.22.21.14.11.29.19.14.24 + 26.28.14 + 26.31.11.23.3 + 26.31.16.18.22.13.32.23.9.20 + 26.31.6.8.29.8.24 + 26.31.7 + 26.32.21.31.27.12 + 26.32.8.12.30.19.24.8.6.1.10 + 26.5.29.7.28 + 26.7.22.3.18.21.11 + 26.7.5.8.11.9.22.1.6 + 26.8.28 + 26.9.17.1.18.19.1.11.18.29.3 + 26.9.20.12.22.22.32 + 27.1.11.3.25.9.6.6 + 27.11.14.17.24 + 27.11.15.9.24.31.18.4.1.30.20 + 27.12.4.2.29.22.15 + 27.15.15.15 + 27.16 + 27.17.15.7.28.20 + 27.17.17.19.24.9.14.20 + 27.17.3.18.2.13.18 + 27.18 + 27.18.10.4.22 + 27.19.20.1.31.29.5.22.26.3 + 27.2.10.4.25.14.2.15.4 + 27.21.27.5.13.30.17 + 27.21.28.24.7.2.24.23.8 + 27.22.11.13.21.25.5.1.27.21.27 + 27.23.2.32.11.21 + 27.23.20.30.7 + 27.24.11.31.21.6.29.17.24.18 + 27.25 + 27.26.29 + 27.27 + 27.27.25.10.31.10.21.22.21.16.12 + 27.27.30.11.15.24.9.7.4.30 + 27.29.1.5.30.6.22.16.23.2.28 + 27.3 + 27.3.3.11.21.4.25 + 27.30.12.11.20.15.11.13 + 27.31.2.16.29.6 + 27.32.26.21.31.17.32.32 + 27.4 + 27.4.15.14.19.6.12 + 27.4.17.17.32.8.16.15.17.13 + 27.5.15.1.15.16.21 + 27.5.22 + 27.6.13.24.21.27.28.22.3.7.4 + 28.1.3 + 28.11.11.30.20.11.32 + 28.11.27.21.14.16 + 28.14.24.26.6.15.16.32.25.13.8 + 28.14.32.29.2.3.4 + 28.15.18.27 + 28.15.25.7.13.6.19.2 + 28.17.26.9 + 28.18.6.22.13.8.25 + 28.2.27.1.20 + 28.20.8.9.9.28.30.29 + 28.23.2.30.3.8.1.15.15.14.13 + 28.25.10.25.19.15 + 28.25.11.22 + 28.25.29.4.13.5.6 + 28.26.25.7 + 28.26.26.6.31 + 28.26.4.22.13.20.32.27.15 + 28.27.24.14 + 28.28 + 28.30.24.16.17.28.2.13.10 + 28.31.10.28.22.26.16.15 + 28.4 + 28.5.12.9.2.27.11.11.2 + 28.5.13 + 28.6 + 28.6.11.6.15.22.12.6 + 28.6.8.22.25 + 28.8.21.15.16.28.4.16.26.8 + 28.9.3.16.17.21.23.30 + 29.1 + 29.1.2.14.14 + 29.1.7.26.25.11.22 + 29.10.12.17.12.16 + 29.10.17.11.28.12.18.5.19.15.21 + 29.11.20.22.27 + 29.14.12.9.17.5.32 + 29.14.31.25.7.32.23 + 29.15.29.8.31.26.1 + 29.20.1.11.21.16.1.2.14.28 + 29.23.1.21.31.8 + 29.23.15.25.1.6.6.10 + 29.25.29.16.32.11.15.25.5.22.3 + 29.25.30.15.21.3.25.26.26 + 29.26.25.14.24.18.2.13.23.29 + 29.27 + 29.27.13.29.10.2 + 29.27.13.9.28.29.19.13.29.31.27 + 29.27.5.22.26 + 29.27.7.7.3.11.14.26.21.11 + 29.28.9.15.8.27.31 + 29.29.17.31 + 29.29.18 + 29.3.15.17.12.29 + 29.3.17.17.18.32 + 29.30.21.8.16.23.32 + 29.30.7.31.22 + 29.32.13.4.1.16.20 + 29.5.18.27.3.21.18.6.14 + 29.5.32.20.11.7.13.24.17 + 29.6.12.31.20.23.32.20 + 29.9.25.27.15.16.32.26.6.32 + 3.1.13.22.24.14.12.31.3.4 + 3.1.14.8.9.16.30.22.20 + 3.10 + 3.10.27.4.5.6.19.12.28.12 + 3.10.4.5.28.11 + 3.11.18.21.5.20.30 + 3.11.32.11.22.3.7.17.8.13.23 + 3.13 + 3.14.1.14.17.28.29.16 + 3.14.11.15.21.32.2.15.13 + 3.14.30.5.32.22.29 + 3.15.2.23.22.2.16.14 + 3.18 + 3.18.18 + 3.18.8.22.7.28.32.31.3 + 3.19.11.6.5 + 3.20.16.13.29.20 + 3.20.19.10.17.27.3.6.22.23 + 3.21.16.24.23.12.16.32.3 + 3.21.6.13.12.18.25 + 3.22.18.1.5.14.9.6.14 + 3.25 + 3.26 + 3.26.32 + 3.27.18.8.4.21.6.32.30.7.5 + 3.29.19.2.24 + 3.29.32.26.8.10.25 + 3.3 + 3.32.2.29.3.32.28.11.29.30 + 3.4.22.19 + 3.5 + 3.6.24.21.20.32.3.4.26.5 + 3.9.11.23.32.26.24.28 + 3.9.25.26.7 + 30.12.28.2 + 30.12.6.30 + 30.12.9.25.24.6.7.24.29 + 30.15 + 30.16.14.9.5.4.10.7.31 + 30.16.3.21.10 + 30.17.2.25 + 30.17.25.3.31.11.3.4.1.10 + 30.17.4.5.13.6 + 30.18.30.16.29 + 30.2.17.8.14 + 30.20.3.2.5.15.8.7.17 + 30.22.29.21.19.14.3.2.6 + 30.23.10.1.10.7.22.28.18.11.17 + 30.23.2.13.14.15.29.19.4.12.24 + 30.24 + 30.24.23.25.32.18.22.12.29.9.22 + 30.24.32.15.14.10.11 + 30.25 + 30.25.17.17.10.29 + 30.25.24.22 + 30.25.8.24.6.29.31 + 30.27.8.6.11.19 + 30.3.16.26.7.27.26.9.27.21.18 + 30.30.17.5.30.21.19.5.22.22.14 + 30.31.13.9 + 30.32 + 30.4.30.11.13.23.14.24.11 + 30.5 + 30.6.4 + 30.8.18.5.20.6.15 + 30.8.9.14.25.30 + 30.9.24 + 31.13 + 31.13.9.1.5.12 + 31.17 + 31.17.2.30.11 + 31.18 + 31.18.25.1.14.29.25.5.22.30 + 31.18.27.15.20.29.29 + 31.18.32.11.7.25.20.5 + 31.21.14.20.1.22.2.5.3.27.12 + 31.21.22.14.8.21 + 31.24.26.18 + 31.28.32.4.31.4.7 + 31.29.18.26.1.26.17 + 31.29.4.29.24.30.30.32.10.23 + 31.30.12.20 + 31.30.23.7.7.24.32.10.11.1.31 + 31.32.12.26.31.32.14.23.28 + 31.4.7 + 31.5.6.4.8.29.3 + 31.7.14.2 + 31.9.3.5 + 32.1.21.1.16.29.21 + 32.1.23.20.14.12.23.5.32.15 + 32.1.24.29.22.5.9.24.18.3.13 + 32.1.31 + 32.15.20.28.5.1.23.4 + 32.16 + 32.17.8.24.2.14.5.4.22 + 32.19.20.24.23.31.8.32.16.29 + 32.2.11 + 32.24.11.8.12.23.22.19.11.17.18 + 32.24.29.6 + 32.25.16 + 32.25.3.6 + 32.27.13.6.7 + 32.27.18.7.3.4.2 + 32.28.1.32.28.10 + 32.29.24.31.25.6.9 + 32.3.12.2 + 32.3.23.7.2 + 32.3.5.9.17.15 + 32.30.18.17.1.14.12.18 + 32.31.11.22.1 + 32.31.26.19.13.29.4.25 + 32.4.19 + 32.6.13.8.32 + 32.6.15.26.14.15.3.19 + 32.6.3.2.12.5.28.1.25 + 32.6.31.31 + 32.6.8 + 32.6.9.26.16.4.4.29.7.11 + 32.8.29.18.31 + 32.8.5 + 4.1.24.24.28.24.18 + 4.10.28 + 4.11.19.17.2.22.20.18.13.32.15 + 4.11.22.4.19.24.4.28.6.8.22 + 4.13 + 4.13.22.11.9.13.27.15.7 + 4.14 + 4.14.10.19.16 + 4.14.16.14.1.8.1.22.17.10 + 4.14.17.12.20.17.1.22.3 + 4.14.32 + 4.15.20.23.12.16.2.16.17 + 4.16.22.19.24.21 + 4.16.7.25.21.7 + 4.18.29.9.16.10 + 4.19.16.15.5.2.25.8.28.14.2 + 4.2.16.13.16.11.19.10.10.25 + 4.2.2.32.24.25.31.3 + 4.2.6.20.7.8 + 4.21.28.5.16.29.5.21 + 4.21.9.1.2.14.8.17.13.26 + 4.22 + 4.22.17.10.19.9.8.19.28.3.9 + 4.22.7.19.25 + 4.25.12.10.15.9.18.9 + 4.26.2.2 + 4.26.23.6.19.31.10.4.22 + 4.26.5.26.21.28.17.24.25.23 + 4.27.32.18 + 4.3.20.27.9.1.18.30.12.5.19 + 4.3.6.27.22.23.10 + 4.30.8.20.19.9.30.24.11 + 4.31 + 4.5.9.4.15.19.8.26.17.26.3 + 4.7.1 + 4.9 + 5.1.5.31 + 5.10 + 5.10.2.11.21.9.19 + 5.10.3.9.23.30.23 + 5.12.2.20.1.24.25 + 5.13.23.19.28.26.27.6.1.22 + 5.13.23.4.9 + 5.14.27.15.11.17.3.10.27.25 + 5.14.29.2.23.16.20.22 + 5.15.10.3.23.13.32.23 + 5.15.16 + 5.18.9.25.31.21.22 + 5.19.1.26.20.6.20 + 5.2.32.19.13.29.12.13.31.29 + 5.20 + 5.21.27.13.14.11.2.16.20 + 5.23.31.18.24.32 + 5.24.24.9.32.26.31 + 5.24.25.15.27.30.20 + 5.24.4.31.3.16.25.17.13.26.11 + 5.27.16.3.30 + 5.27.21.1.29.29.28 + 5.27.28.26.14.15.6.20.1.31.13 + 5.27.32.21.5.1.11.14 + 5.3.17.29 + 5.3.29.9.22 + 5.31.8.1.5.13.21.28.29.19.2 + 5.4.8.25.12.27.2.29.28.3 + 5.5.12.31.23.13.17.22.20 + 5.8 + 5.8.17.30.15.8.19.29.30.11.6 + 5.9.19.6 + 6.1.8.6.30.29.30 + 6.10.25.12 + 6.11.11.5.16.8.14.12.9 + 6.11.31.23.12.8.30.14.27 + 6.13.31.5.7.26 + 6.14 + 6.17.10.10.7.9.27.8.29 + 6.17.26.25.27.11.10.9 + 6.18.1.4.18.23 + 6.19.29.11.2.32.21.15.32.9 + 6.19.3 + 6.19.6.4.9.11.32.17.17.3.15 + 6.2.32 + 6.20 + 6.20.14 + 6.21.30.7 + 6.22.12 + 6.25.17.32 + 6.26 + 6.26.29.10.21.28.20.19 + 6.27.26.1.20.24.6 + 6.27.29.14.8.12.26.3.21.4.1 + 6.29.32.13.30.3.16 + 6.29.6.13.14.24.10.4.14.28 + 6.5.27.19.13.26.1.18.9 + 6.6.22.8 + 6.7.25.16.13.21.7.20.25.12.4 + 6.7.7 + 6.8.7.20.2 + 6.9.1.10.10.22.6 + 6.9.29.17.4.32 + 7.10.17.21.11.29.17.25.19.4.29 + 7.11 + 7.12 + 7.12.1.10.6.17.29.24.24.4 + 7.12.23 + 7.13 + 7.13.15 + 7.14.22.29.30.14.25.1.9.26.25 + 7.16.20.17 + 7.19.10.12.31.1.27.13.19 + 7.19.12.3.21.19.18.5.2.14.10 + 7.19.6.17.15.26.21.9 + 7.21.8 + 7.23.1.24.29.13.31.19.23.17.7 + 7.23.15.32.28.27.2.2.26 + 7.26.18 + 7.27.20 + 7.30.19.25.23.15.14.29 + 7.30.5.10.10.5.30.14.9.18 + 7.31 + 7.31.2.28.15.11.17.18.19.23.6 + 7.31.4.20.17 + 7.32.10.3.30.12.14 + 7.5.28.8.17.26.31.10.15 + 7.7 + 7.7.22.24.17.32.17.25.28 + 7.7.25.22.22.26 + 8.1.29.18.22 + 8.10 + 8.11.20 + 8.12.4 + 8.13.1 + 8.13.14.11.11.29.22.4.4.10 + 8.13.6.12.18.7 + 8.13.9.31.20.20.24.7.23.31.28 + 8.14.19.18 + 8.16 + 8.16.1.16.28.6.3.22.6.23 + 8.16.20.24.20.6.10.21 + 8.16.30.29.19.22.28.24.2 + 8.16.6 + 8.17.25.26.15.25 + 8.17.9.15.21.28.1.7.1.3.6 + 8.2 + 8.2.18.23.5.16.17.1 + 8.21.17.3.6.3.18 + 8.21.8.23.4.18 + 8.22.32.17.16.28.31.23.22.9 + 8.24.11.13.25.19 + 8.25.20.3.15.24.7.4.24.5.30 + 8.26.29.13.7.25.31.28.3.32 + 8.27.3.4.12.26.16 + 8.29.6.3 + 8.3.18.13.30.20.27.26.17.28 + 8.3.3.25.25.15.7.13.21.18 + 8.31.22.27 + 8.32.30.1 + 8.5.24.9.29.32.31.30.13.9.7 + 8.5.30.29.9.31 + 8.6.6.5.8.8.12 + 8.9.21.16.29 + 8.9.22 + 8.9.25.25.26.30.31.31.2.32.7 + 9.10.19.18.15.11.22.32.32.14.9 + 9.10.32 + 9.14.27.31.26.21.25.3.20 + 9.16.2.16.22.24.17.31.14.21.17 + 9.17.13.31.7 + 9.18.23 + 9.18.30.11.29.32.7.19.2 + 9.19.7.13.13.25 + 9.2.10.4 + 9.2.4.27.26 + 9.21.14.19 + 9.21.20.29.1 + 9.21.28.8.12.15.3.13.10.11 + 9.22.10.15.5.15 + 9.23.21.22.5.29.15.21 + 9.26.1.16 + 9.28.10.26.14.26.15.14 + 9.28.24 + 9.28.30.1.6.25.17.9 + 9.3.3 + 9.3.31.18.12.3.9.29.10 + 9.30 + 9.31.23.19.5.10.16.4.30.24.5 + 9.31.4.14.31.10.17.5.2 + 9.5 + 9.5.9.3.23.9.25.14.1.29.28 + 9.6.9.21.6.11.29.13.29.20.32 + 9.7.31.11.8.23 + 9.8.23.2.20.16 + 9.9.13.9.14.27 +(883 rows) + +SELECT * FROM ltreetest WHERE t > '12.3' order by t asc; + t +---------------------------------- + 12.4.10.17.4.10.23.3 + 12.4.12.13.25.30.30.8.9.12 + 12.4.24.6.1.13.5.20 + 12.4.26.23.25.5.15.7.16 + 12.6.14.23.19.21.9.12 + 12.7.16.8.21.22.2.16.18 + 12.7.28.26.14.21.18.31.5.15.11 + 13.1.6.17.28.9.15.30.1.27.14 + 13.12 + 13.14.13.10.28.26.9.18.27.21 + 13.16.1.27.18.18.19.6.14.4 + 13.16.4.28 + 13.17.7 + 13.19.2.6.23.19.9.7.21.8.16 + 13.24 + 13.25.10.25.8.16 + 13.26.17.3.2.19 + 13.28.12.6 + 13.28.14.2.8.18 + 13.28.9.3 + 13.3.20 + 13.3.8 + 13.30.24 + 13.32.15.32.26.14.32 + 13.7 + 13.8.15.3.7.31.5.10.15.30 + 13.8.20.9.21 + 13.8.23.13.11.18.24.21.11.24.10 + 13.9.9.27.31.11.25.9.27.22.13 + 14.1.11 + 14.1.15.25.27.23.25.26.28.10 + 14.10.11.30.5.7.6.24.9.30.26 + 14.11.25 + 14.12.31 + 14.13.9.13.11.5.5.2.2.32.12 + 14.14.25 + 14.15.31.29 + 14.16.6.29.26.13.14.16.25.26.8 + 14.17.7.30.8.25.26.4 + 14.19.20.13.27.2.2 + 14.19.26.15.22.23 + 14.19.30.6.4.10.10.10.22.25.11 + 14.2.14.11.12 + 14.21.22 + 14.21.5.28.3.32.24.14.25.31 + 14.21.6.5.26.9.32.16.25 + 14.23.31.5.5.15.17.12.17.7.3 + 14.24 + 14.26.25.4.12.26.8 + 14.27.29.23.4.1.17.32.6.25.22 + 14.29 + 14.3.17.1.14.15.21.4.26 + 14.30.13.5.26.9.22.23.14.10 + 14.30.2.21.15.16.13 + 14.30.23.3 + 14.4.19.27.28.24.19 + 14.4.23.4.23.22.11.6.26.5 + 14.5.13.19.25.12.32.9.13.16.12 + 14.6.10.29.25.26.20.24.24 + 14.8.15.30.7.29.27.31.4 + 14.9.15.21.21.31.1.29 + 15.1.6.31.30.13.32.9.10 + 15.1.8 + 15.10.30.1.4.12.8.20 + 15.11.26.1.30.6.23.5 + 15.17 + 15.17.2.32.7 + 15.21.22 + 15.21.23.30.9.25 + 15.23.26.20.27.7 + 15.25.31.11.4.22.16.7.11 + 15.26.24.31.16.15.17.22.8.30.3 + 15.28.24 + 15.28.30.19.31.6.2.2.31 + 15.29.25 + 15.29.32.16.29.12.20.32.13.20 + 15.3.31.9.27.14.9.8.14.6.32 + 15.30.17.5.32.28.2.18.27 + 15.31.11.27.19.19.20.5.5 + 15.4.15 + 15.5.1.31.28.10.8 + 15.6.19.3 + 15.7.3.14.23.19.26 + 15.7.5.12.7.9.3.28.26 + 15.8.10 + 15.8.3.15.27.14.29.28.6.5.25 + 15.9.11.20.22.15.11.13 + 15.9.8.20.27 + 16.13.19.11.18.13.17.17 + 16.13.2.19.14.29.31.30.23.15.12 + 16.13.26.18.9.29.11.17.1.24.26 + 16.14.3.17.17.26.12.19.19.30 + 16.16.28.24.11 + 16.18.23.6.31 + 16.19.17.30.30.5.17.24.27 + 16.2.14.3.26.11 + 16.20.29.26 + 16.21.13.1.4 + 16.23.30.12.31.31.19.14 + 16.24.3.30.15.22.31.2 + 16.24.7.25 + 16.27.8.17.14.17.21.29.14 + 16.28 + 16.29.6.23.13.28.31.6.19.26.15 + 16.30.10.7.29.4.9.21.22.13.26 + 16.31.12.27.25.9.32.29 + 16.5 + 16.5.10.2.18.8.15.12.32.25.10 + 16.5.12.5.15.12.24.25.3 + 16.5.14.21.32.17.23.3.4.26 + 16.5.23.17 + 16.5.6.12 + 16.8.29.7.21.2.3 + 16.9.14.28.6.21.31.31.26 + 16.9.29 + 16.9.32.14.3.7.8.7.21.22 + 17.1.12.20 + 17.10.17.22.20.25.14.13 + 17.11.17.4.8.26.26.20.6 + 17.13.14.29.27.27.13.12.15 + 17.13.19.31.12.18.10.15.14 + 17.13.8 + 17.14.7.3.2.18.20.23.18.5 + 17.17.14.28.6.30 + 17.19.1.22.11.7.22.1.14.28.11 + 17.22.12.10.30.11 + 17.24.15.27.3.32.4.22.20.6.24 + 17.24.30.6.32 + 17.25.10.13.21.5.7.22.2 + 17.25.2.13.10.27.13.1 + 17.25.26.23.32 + 17.26.18 + 17.27 + 17.29.21.10.18.8.16.26.18.21.26 + 17.29.31.8.24.10.18.27.17 + 17.3 + 17.5.3.15.17.13.5 + 17.7.26.30.18.23.4 + 17.8 + 17.8.31.32 + 17.9.32.31.21.31.23.17.10.32.9 + 18.13.6.12.26.26.26.29.18.20.1 + 18.13.9.3.18.15.2 + 18.15.14 + 18.17.6.16.6.10 + 18.18.19.16.14.16.21.10.25 + 18.18.5.11.7.4.25 + 18.19.11.20.13.13.11 + 18.19.12.20.18.17.15.32.18.5 + 18.21 + 18.24.21.17.11.26.28.22.21.18.10 + 18.27.11.27.9.16.7.6.22.26.27 + 18.29.13.24.18.3.12.18.12.12 + 18.29.5.1.10.21.2 + 18.30.11.17 + 18.30.18.31 + 18.31.26.18.6.15.18.11 + 18.31.32.28.1.4.24.24.12.25 + 18.31.32.29.22.1.31.11.28 + 18.4 + 18.4.14.29.3 + 18.5.6.31.5.15.15 + 18.6.2.2.24 + 18.6.26.2.13.9.6.11.10.11.16 + 18.7.10.27.17.24 + 18.7.3.17.13.5.31.6.31.25.29 + 18.9.21.2.31.8.32 + 18.9.26.7 + 19.10.26.19.5.21.30.23 + 19.10.4.30.32.4.12 + 19.10.8.10.4.19 + 19.11.10.18.14.13.7.7 + 19.11.29.13.15.27.12.15.14.12 + 19.12.20.24.32.13.11.23.26 + 19.12.26.24.29.3 + 19.12.30.2.21 + 19.15.26.19 + 19.16.26.2 + 19.16.31.31.29.12 + 19.17.12.15 + 19.17.13.12.32.16.3 + 19.19.25.22.11.6.15.3.2.19 + 19.2.26.21.16.11.2.2 + 19.2.9.29.6 + 19.20.25.7.27.28.27.17.9.3.1 + 19.22.21.13.27.13.15 + 19.22.29.32.1.21.26.24.23.17 + 19.26.24.27.6.24.16.27.32.29 + 19.26.32.13.1.12.30.26.22.25 + 19.3.12.12 + 19.3.23.4.4.21.23 + 19.30.18.11.32.14 + 19.30.27.26.21.7.18 + 19.31.14.25.5.8.21.11.13.20 + 19.5.20.3.4.2.3 + 19.6.13.14.22.13.9.29 + 19.6.24.32.30.13.6.25.8.28 + 19.7 + 19.7.29.31.3.20.7.21.25.27.29 + 19.9.32.23.13.24.1 + 2.1.12.19.29.28.3.31.28.28.10 + 2.1.3.30.24.17.9 + 2.10.10.4.20.1.12.13 + 2.10.28.1.17.19.32.28 + 2.11.32.25.23 + 2.12.14.28.16.21 + 2.12.30.22.12 + 2.13.9.23.21.2 + 2.13.9.28 + 2.14.10.4.17.17.8.4.27.20 + 2.14.12.13 + 2.15.14.20.30.26 + 2.15.18.21.5.21.4.7.30 + 2.16.3.7.22.18.29.20 + 2.19.4.1.15.7.8.9.17.29 + 2.2.18.18.3.3.18.8.10.8 + 2.22.19 + 2.24.4.5.24.32 + 2.24.5.3.4.10.27.26.17.28.16 + 2.27.15.14 + 2.28.5.17.6.32 + 2.30.26.10.14.31.18.2 + 2.31.25 + 2.32.10.13.12 + 2.32.8.28.24.20.9.24.25.8.9 + 2.4.25.32.16.22.26.13.17.18 + 2.6.15.26.23.26.24 + 2.8.13.12.17.23.16.7.11.23 + 2.9 + 20.1.24.3.30.31 + 20.13 + 20.14.11.2.10.14 + 20.15 + 20.17.14.7 + 20.17.18.21.1 + 20.18.24.14.12.13.9 + 20.20.32.29.24.5.5.26.22.32 + 20.20.7 + 20.22.10 + 20.23.29.5.7.30.13.14.22 + 20.23.7.11.11.31.18.16.3 + 20.24.14.15.4.21.12.27.4.12 + 20.25.22.19.22 + 20.28.22.7.10.28.27.22.14.16 + 20.29.18.16.2.21.23.11 + 20.3.1.8.8.30.20 + 20.30.17 + 20.30.28.15.17 + 20.30.9.9.14.12.29 + 20.31.13.12.19.2.26.16.16.22.28 + 20.32.5.1.3.20.3.30.27 + 20.32.9 + 20.4.1.16.31.3 + 20.4.27.31.1 + 20.5.4.9.31.14.26.6 + 20.6.26.3.30 + 20.6.3.26.7.29.28.4 + 20.8.19.14.16.7 + 20.9.29.32.13.7.23 + 21.1.4.9.9.31.24.21.3.29 + 21.10.20.9.3.16.9.10.20 + 21.14 + 21.14.13 + 21.14.22.29 + 21.14.25.20.13.31.14.20 + 21.15.18.18.30.3.20 + 21.15.31.24.29.24.26.12.20 + 21.17.18.32.7.8 + 21.17.27.23.15 + 21.17.31.10.31.13.9.26.6.14 + 21.18 + 21.18.2.1 + 21.18.30.19.24.24 + 21.20.24.25.6.26.23 + 21.20.28.19.27.9 + 21.21.10.27 + 21.22.31.24.27 + 21.23.13 + 21.23.17.8.23.11.8.1 + 21.28.17.22.10.27.4.20.2.32 + 21.28.24.23.3.11.7.12.22.32 + 21.30.19.6.28.1.32.2.14.14 + 21.31.31.25.5.30.26 + 21.32.13.21 + 21.32.13.22.3.13.31.23.14.12.9 + 21.4.11.18 + 21.4.22.20.24.28.6 + 21.5.11.18 + 21.5.17.19.15.25.18.21.24.9 + 21.6.22.28.12.23.11.22 + 21.7.23.9.16.5.18.14 + 21.7.7.11 + 21.8.9 + 21.9.27.22.32 + 21.9.32.1.27 + 22.10.12.23.9 + 22.10.16.8 + 22.10.18 + 22.10.27.19.29.20.29.3.12.14.25 + 22.11 + 22.12.22.28 + 22.13.22.21.25.17.8 + 22.13.22.8.30.32.10.24 + 22.15 + 22.16 + 22.16.25.18.25.7.24.29.14.8 + 22.17.24.14.21.15.12.18.17.25.11 + 22.17.30 + 22.17.4.2.22.17 + 22.17.7.30.13.24 + 22.17.9.11.25.15.3.9 + 22.18.20.23.15.9.12 + 22.19.20.5.2.20 + 22.19.21.11.6.8.29.24 + 22.19.5.22.20.31.23.24.14.24.4 + 22.20.30 + 22.21.32.15.8.29.5.12.10.29 + 22.22.10.30.5.15.25.21.19.11 + 22.22.27.6.27.15.5.18.21.28.9 + 22.23.18.18.9.8.23.7.23.23.16 + 22.23.22.30 + 22.23.25.28.5.27.9.9.24.31.10 + 22.24.22.25.15.23.13 + 22.25.4.28.9.20.12.13 + 22.26.1.28.9.9.31 + 22.26.32 + 22.28.20.6.32.32 + 22.29.18.32.13.12.22.31.17.22 + 22.29.29.11 + 22.3.6 + 22.30 + 22.30.31.24.23.22.5.20.28.1 + 22.31.2.32.32.11.26.23.19 + 22.31.21.13.13.26.11.5.19 + 22.32.6.6.3.8.24.6.25.29 + 22.8.20.1.10.28.6.27 + 22.9.15.19.12 + 23.1.23.18.12.29 + 23.10.13.32.14.20.16.11.14 + 23.10.5.26.12.4.20.4 + 23.12.1.5.32.25.8.24.1.25 + 23.12.11.11.15.16.22.31.32.5.8 + 23.12.19.25.16.23.22.6.29.4 + 23.12.32.22.19.1.22.4 + 23.14.12.30.18.4.16.18.7.7 + 23.14.30.27.28.26.26.23.8.32 + 23.17.22.1.23.4.29.32.4.1 + 23.17.25.4.1.16.29.10 + 23.17.28.31.28 + 23.17.32.15.23.16.25 + 23.19.17.31.29.13.1.12.5.25 + 23.2.22.7.32.3.27.6 + 23.20.12.16.15.2 + 23.20.24 + 23.20.8 + 23.22.10.1.14.24 + 23.22.23.14.31.32 + 23.23 + 23.24.11.31.10.31.18.28.13.18.6 + 23.24.16.32.13.29 + 23.25.23.11.7.23 + 23.27.27.16 + 23.27.6.26.22 + 23.28.1 + 23.28.20.25.30.24.15 + 23.28.3.30.15.31.32.3.21.9.19 + 23.3.20.24 + 23.3.32.21.5.14.10.17.1 + 23.31.27.16.8.30.20.27 + 23.32.5.25.19.9.15.17.15.11 + 23.5.5.17 + 23.5.7.12.11.23.10 + 23.6.27 + 23.8.13.22.21 + 24.1.10.20.28.18.6.27.20.30.26 + 24.1.29.32.14.15.32.6.15.22 + 24.10.10.31.4.29.9 + 24.10.8.25.16 + 24.11.5 + 24.12 + 24.13.1.8 + 24.15.15.17.22 + 24.16.27.10.9 + 24.17.24 + 24.17.31.20.12.9.19.29.18 + 24.18.16 + 24.2.26.24.14.15.31.23.17.26 + 24.2.6.7.16.7.28 + 24.20.23 + 24.21.14.25.11.3.20.6.6.16 + 24.23.24.4.15.25.17 + 24.23.29.8.24.11.21.10.28.14.27 + 24.24 + 24.25.7.27.30.8.26.17 + 24.27.14 + 24.27.18.32.14.9.11.28.9 + 24.28.13.26.8.8.31 + 24.28.32.21 + 24.3.23.25 + 24.31 + 24.31.2.13.5.23.18.16 + 24.31.8 + 24.32.17.23.24.19.23.9.20.18 + 24.32.27 + 24.9 + 24.9.15.1.14.29.6.4 + 24.9.27.16.20.21 + 24.9.8.12.29 + 25.10 + 25.10.29.3.6.21.3.31.13 + 25.10.4.28.3.31.19 + 25.11.24 + 25.14.5.32.25 + 25.15.11 + 25.16.9.6 + 25.17.18.17.27 + 25.17.18.30 + 25.17.2.20.20.3.29.21.3.12 + 25.17.9.16.17.31.23.29.24 + 25.18.8.3.23.23.5.9.6 + 25.19.27.2.9.20 + 25.2.11.20.8.6.22 + 25.2.3.15.11.19.5.28.25.14 + 25.21.8.17 + 25.22.2.25.6 + 25.24.2.32.14.18.16 + 25.24.29 + 25.28.3 + 25.28.30.24 + 25.29 + 25.3 + 25.30.1.4.24.11 + 25.31 + 25.32.24.24.28.15.16.10 + 25.4.32 + 25.4.4.1.13.32.26.20.20.3 + 25.5.30.7.16.12.21.12.11.16 + 25.6 + 25.6.12.16.1 + 25.7.3.21.31.12.28 + 25.9 + 25.9.1.5.9.11.25.4.11.27.32 + 25.9.10 + 26.11 + 26.12.27.2 + 26.13.4.7.13.11.3 + 26.14 + 26.14.5.32.10 + 26.16.12 + 26.16.12.3.27.9.28 + 26.17.9.13.4.25.32.2.24.9 + 26.18 + 26.18.32.20 + 26.19.3.14.8.28.31.10 + 26.24 + 26.24.9.12.11.15.31.2 + 26.25.10.10.13 + 26.25.24 + 26.26.22.21.14.11.29.19.14.24 + 26.28.14 + 26.31.11.23.3 + 26.31.16.18.22.13.32.23.9.20 + 26.31.6.8.29.8.24 + 26.31.7 + 26.32.21.31.27.12 + 26.32.8.12.30.19.24.8.6.1.10 + 26.5.29.7.28 + 26.7.22.3.18.21.11 + 26.7.5.8.11.9.22.1.6 + 26.8.28 + 26.9.17.1.18.19.1.11.18.29.3 + 26.9.20.12.22.22.32 + 27.1.11.3.25.9.6.6 + 27.11.14.17.24 + 27.11.15.9.24.31.18.4.1.30.20 + 27.12.4.2.29.22.15 + 27.15.15.15 + 27.16 + 27.17.15.7.28.20 + 27.17.17.19.24.9.14.20 + 27.17.3.18.2.13.18 + 27.18 + 27.18.10.4.22 + 27.19.20.1.31.29.5.22.26.3 + 27.2.10.4.25.14.2.15.4 + 27.21.27.5.13.30.17 + 27.21.28.24.7.2.24.23.8 + 27.22.11.13.21.25.5.1.27.21.27 + 27.23.2.32.11.21 + 27.23.20.30.7 + 27.24.11.31.21.6.29.17.24.18 + 27.25 + 27.26.29 + 27.27 + 27.27.25.10.31.10.21.22.21.16.12 + 27.27.30.11.15.24.9.7.4.30 + 27.29.1.5.30.6.22.16.23.2.28 + 27.3 + 27.3.3.11.21.4.25 + 27.30.12.11.20.15.11.13 + 27.31.2.16.29.6 + 27.32.26.21.31.17.32.32 + 27.4 + 27.4.15.14.19.6.12 + 27.4.17.17.32.8.16.15.17.13 + 27.5.15.1.15.16.21 + 27.5.22 + 27.6.13.24.21.27.28.22.3.7.4 + 28.1.3 + 28.11.11.30.20.11.32 + 28.11.27.21.14.16 + 28.14.24.26.6.15.16.32.25.13.8 + 28.14.32.29.2.3.4 + 28.15.18.27 + 28.15.25.7.13.6.19.2 + 28.17.26.9 + 28.18.6.22.13.8.25 + 28.2.27.1.20 + 28.20.8.9.9.28.30.29 + 28.23.2.30.3.8.1.15.15.14.13 + 28.25.10.25.19.15 + 28.25.11.22 + 28.25.29.4.13.5.6 + 28.26.25.7 + 28.26.26.6.31 + 28.26.4.22.13.20.32.27.15 + 28.27.24.14 + 28.28 + 28.30.24.16.17.28.2.13.10 + 28.31.10.28.22.26.16.15 + 28.4 + 28.5.12.9.2.27.11.11.2 + 28.5.13 + 28.6 + 28.6.11.6.15.22.12.6 + 28.6.8.22.25 + 28.8.21.15.16.28.4.16.26.8 + 28.9.3.16.17.21.23.30 + 29.1 + 29.1.2.14.14 + 29.1.7.26.25.11.22 + 29.10.12.17.12.16 + 29.10.17.11.28.12.18.5.19.15.21 + 29.11.20.22.27 + 29.14.12.9.17.5.32 + 29.14.31.25.7.32.23 + 29.15.29.8.31.26.1 + 29.20.1.11.21.16.1.2.14.28 + 29.23.1.21.31.8 + 29.23.15.25.1.6.6.10 + 29.25.29.16.32.11.15.25.5.22.3 + 29.25.30.15.21.3.25.26.26 + 29.26.25.14.24.18.2.13.23.29 + 29.27 + 29.27.13.29.10.2 + 29.27.13.9.28.29.19.13.29.31.27 + 29.27.5.22.26 + 29.27.7.7.3.11.14.26.21.11 + 29.28.9.15.8.27.31 + 29.29.17.31 + 29.29.18 + 29.3.15.17.12.29 + 29.3.17.17.18.32 + 29.30.21.8.16.23.32 + 29.30.7.31.22 + 29.32.13.4.1.16.20 + 29.5.18.27.3.21.18.6.14 + 29.5.32.20.11.7.13.24.17 + 29.6.12.31.20.23.32.20 + 29.9.25.27.15.16.32.26.6.32 + 3.1.13.22.24.14.12.31.3.4 + 3.1.14.8.9.16.30.22.20 + 3.10 + 3.10.27.4.5.6.19.12.28.12 + 3.10.4.5.28.11 + 3.11.18.21.5.20.30 + 3.11.32.11.22.3.7.17.8.13.23 + 3.13 + 3.14.1.14.17.28.29.16 + 3.14.11.15.21.32.2.15.13 + 3.14.30.5.32.22.29 + 3.15.2.23.22.2.16.14 + 3.18 + 3.18.18 + 3.18.8.22.7.28.32.31.3 + 3.19.11.6.5 + 3.20.16.13.29.20 + 3.20.19.10.17.27.3.6.22.23 + 3.21.16.24.23.12.16.32.3 + 3.21.6.13.12.18.25 + 3.22.18.1.5.14.9.6.14 + 3.25 + 3.26 + 3.26.32 + 3.27.18.8.4.21.6.32.30.7.5 + 3.29.19.2.24 + 3.29.32.26.8.10.25 + 3.3 + 3.32.2.29.3.32.28.11.29.30 + 3.4.22.19 + 3.5 + 3.6.24.21.20.32.3.4.26.5 + 3.9.11.23.32.26.24.28 + 3.9.25.26.7 + 30.12.28.2 + 30.12.6.30 + 30.12.9.25.24.6.7.24.29 + 30.15 + 30.16.14.9.5.4.10.7.31 + 30.16.3.21.10 + 30.17.2.25 + 30.17.25.3.31.11.3.4.1.10 + 30.17.4.5.13.6 + 30.18.30.16.29 + 30.2.17.8.14 + 30.20.3.2.5.15.8.7.17 + 30.22.29.21.19.14.3.2.6 + 30.23.10.1.10.7.22.28.18.11.17 + 30.23.2.13.14.15.29.19.4.12.24 + 30.24 + 30.24.23.25.32.18.22.12.29.9.22 + 30.24.32.15.14.10.11 + 30.25 + 30.25.17.17.10.29 + 30.25.24.22 + 30.25.8.24.6.29.31 + 30.27.8.6.11.19 + 30.3.16.26.7.27.26.9.27.21.18 + 30.30.17.5.30.21.19.5.22.22.14 + 30.31.13.9 + 30.32 + 30.4.30.11.13.23.14.24.11 + 30.5 + 30.6.4 + 30.8.18.5.20.6.15 + 30.8.9.14.25.30 + 30.9.24 + 31.13 + 31.13.9.1.5.12 + 31.17 + 31.17.2.30.11 + 31.18 + 31.18.25.1.14.29.25.5.22.30 + 31.18.27.15.20.29.29 + 31.18.32.11.7.25.20.5 + 31.21.14.20.1.22.2.5.3.27.12 + 31.21.22.14.8.21 + 31.24.26.18 + 31.28.32.4.31.4.7 + 31.29.18.26.1.26.17 + 31.29.4.29.24.30.30.32.10.23 + 31.30.12.20 + 31.30.23.7.7.24.32.10.11.1.31 + 31.32.12.26.31.32.14.23.28 + 31.4.7 + 31.5.6.4.8.29.3 + 31.7.14.2 + 31.9.3.5 + 32.1.21.1.16.29.21 + 32.1.23.20.14.12.23.5.32.15 + 32.1.24.29.22.5.9.24.18.3.13 + 32.1.31 + 32.15.20.28.5.1.23.4 + 32.16 + 32.17.8.24.2.14.5.4.22 + 32.19.20.24.23.31.8.32.16.29 + 32.2.11 + 32.24.11.8.12.23.22.19.11.17.18 + 32.24.29.6 + 32.25.16 + 32.25.3.6 + 32.27.13.6.7 + 32.27.18.7.3.4.2 + 32.28.1.32.28.10 + 32.29.24.31.25.6.9 + 32.3.12.2 + 32.3.23.7.2 + 32.3.5.9.17.15 + 32.30.18.17.1.14.12.18 + 32.31.11.22.1 + 32.31.26.19.13.29.4.25 + 32.4.19 + 32.6.13.8.32 + 32.6.15.26.14.15.3.19 + 32.6.3.2.12.5.28.1.25 + 32.6.31.31 + 32.6.8 + 32.6.9.26.16.4.4.29.7.11 + 32.8.29.18.31 + 32.8.5 + 4.1.24.24.28.24.18 + 4.10.28 + 4.11.19.17.2.22.20.18.13.32.15 + 4.11.22.4.19.24.4.28.6.8.22 + 4.13 + 4.13.22.11.9.13.27.15.7 + 4.14 + 4.14.10.19.16 + 4.14.16.14.1.8.1.22.17.10 + 4.14.17.12.20.17.1.22.3 + 4.14.32 + 4.15.20.23.12.16.2.16.17 + 4.16.22.19.24.21 + 4.16.7.25.21.7 + 4.18.29.9.16.10 + 4.19.16.15.5.2.25.8.28.14.2 + 4.2.16.13.16.11.19.10.10.25 + 4.2.2.32.24.25.31.3 + 4.2.6.20.7.8 + 4.21.28.5.16.29.5.21 + 4.21.9.1.2.14.8.17.13.26 + 4.22 + 4.22.17.10.19.9.8.19.28.3.9 + 4.22.7.19.25 + 4.25.12.10.15.9.18.9 + 4.26.2.2 + 4.26.23.6.19.31.10.4.22 + 4.26.5.26.21.28.17.24.25.23 + 4.27.32.18 + 4.3.20.27.9.1.18.30.12.5.19 + 4.3.6.27.22.23.10 + 4.30.8.20.19.9.30.24.11 + 4.31 + 4.5.9.4.15.19.8.26.17.26.3 + 4.7.1 + 4.9 + 5.1.5.31 + 5.10 + 5.10.2.11.21.9.19 + 5.10.3.9.23.30.23 + 5.12.2.20.1.24.25 + 5.13.23.19.28.26.27.6.1.22 + 5.13.23.4.9 + 5.14.27.15.11.17.3.10.27.25 + 5.14.29.2.23.16.20.22 + 5.15.10.3.23.13.32.23 + 5.15.16 + 5.18.9.25.31.21.22 + 5.19.1.26.20.6.20 + 5.2.32.19.13.29.12.13.31.29 + 5.20 + 5.21.27.13.14.11.2.16.20 + 5.23.31.18.24.32 + 5.24.24.9.32.26.31 + 5.24.25.15.27.30.20 + 5.24.4.31.3.16.25.17.13.26.11 + 5.27.16.3.30 + 5.27.21.1.29.29.28 + 5.27.28.26.14.15.6.20.1.31.13 + 5.27.32.21.5.1.11.14 + 5.3.17.29 + 5.3.29.9.22 + 5.31.8.1.5.13.21.28.29.19.2 + 5.4.8.25.12.27.2.29.28.3 + 5.5.12.31.23.13.17.22.20 + 5.8 + 5.8.17.30.15.8.19.29.30.11.6 + 5.9.19.6 + 6.1.8.6.30.29.30 + 6.10.25.12 + 6.11.11.5.16.8.14.12.9 + 6.11.31.23.12.8.30.14.27 + 6.13.31.5.7.26 + 6.14 + 6.17.10.10.7.9.27.8.29 + 6.17.26.25.27.11.10.9 + 6.18.1.4.18.23 + 6.19.29.11.2.32.21.15.32.9 + 6.19.3 + 6.19.6.4.9.11.32.17.17.3.15 + 6.2.32 + 6.20 + 6.20.14 + 6.21.30.7 + 6.22.12 + 6.25.17.32 + 6.26 + 6.26.29.10.21.28.20.19 + 6.27.26.1.20.24.6 + 6.27.29.14.8.12.26.3.21.4.1 + 6.29.32.13.30.3.16 + 6.29.6.13.14.24.10.4.14.28 + 6.5.27.19.13.26.1.18.9 + 6.6.22.8 + 6.7.25.16.13.21.7.20.25.12.4 + 6.7.7 + 6.8.7.20.2 + 6.9.1.10.10.22.6 + 6.9.29.17.4.32 + 7.10.17.21.11.29.17.25.19.4.29 + 7.11 + 7.12 + 7.12.1.10.6.17.29.24.24.4 + 7.12.23 + 7.13 + 7.13.15 + 7.14.22.29.30.14.25.1.9.26.25 + 7.16.20.17 + 7.19.10.12.31.1.27.13.19 + 7.19.12.3.21.19.18.5.2.14.10 + 7.19.6.17.15.26.21.9 + 7.21.8 + 7.23.1.24.29.13.31.19.23.17.7 + 7.23.15.32.28.27.2.2.26 + 7.26.18 + 7.27.20 + 7.30.19.25.23.15.14.29 + 7.30.5.10.10.5.30.14.9.18 + 7.31 + 7.31.2.28.15.11.17.18.19.23.6 + 7.31.4.20.17 + 7.32.10.3.30.12.14 + 7.5.28.8.17.26.31.10.15 + 7.7 + 7.7.22.24.17.32.17.25.28 + 7.7.25.22.22.26 + 8.1.29.18.22 + 8.10 + 8.11.20 + 8.12.4 + 8.13.1 + 8.13.14.11.11.29.22.4.4.10 + 8.13.6.12.18.7 + 8.13.9.31.20.20.24.7.23.31.28 + 8.14.19.18 + 8.16 + 8.16.1.16.28.6.3.22.6.23 + 8.16.20.24.20.6.10.21 + 8.16.30.29.19.22.28.24.2 + 8.16.6 + 8.17.25.26.15.25 + 8.17.9.15.21.28.1.7.1.3.6 + 8.2 + 8.2.18.23.5.16.17.1 + 8.21.17.3.6.3.18 + 8.21.8.23.4.18 + 8.22.32.17.16.28.31.23.22.9 + 8.24.11.13.25.19 + 8.25.20.3.15.24.7.4.24.5.30 + 8.26.29.13.7.25.31.28.3.32 + 8.27.3.4.12.26.16 + 8.29.6.3 + 8.3.18.13.30.20.27.26.17.28 + 8.3.3.25.25.15.7.13.21.18 + 8.31.22.27 + 8.32.30.1 + 8.5.24.9.29.32.31.30.13.9.7 + 8.5.30.29.9.31 + 8.6.6.5.8.8.12 + 8.9.21.16.29 + 8.9.22 + 8.9.25.25.26.30.31.31.2.32.7 + 9.10.19.18.15.11.22.32.32.14.9 + 9.10.32 + 9.14.27.31.26.21.25.3.20 + 9.16.2.16.22.24.17.31.14.21.17 + 9.17.13.31.7 + 9.18.23 + 9.18.30.11.29.32.7.19.2 + 9.19.7.13.13.25 + 9.2.10.4 + 9.2.4.27.26 + 9.21.14.19 + 9.21.20.29.1 + 9.21.28.8.12.15.3.13.10.11 + 9.22.10.15.5.15 + 9.23.21.22.5.29.15.21 + 9.26.1.16 + 9.28.10.26.14.26.15.14 + 9.28.24 + 9.28.30.1.6.25.17.9 + 9.3.3 + 9.3.31.18.12.3.9.29.10 + 9.30 + 9.31.23.19.5.10.16.4.30.24.5 + 9.31.4.14.31.10.17.5.2 + 9.5 + 9.5.9.3.23.9.25.14.1.29.28 + 9.6.9.21.6.11.29.13.29.20.32 + 9.7.31.11.8.23 + 9.8.23.2.20.16 + 9.9.13.9.14.27 +(882 rows) + +SELECT * FROM ltreetest WHERE t @> '1.1.1' order by t asc; + t +------- + + 1 + 1.1 + 1.1.1 +(4 rows) + +SELECT * FROM ltreetest WHERE t <@ '1.1.1' order by t asc; + t +----------- + 1.1.1 + 1.1.1.1 + 1.1.1.2 + 1.1.1.2.1 +(4 rows) + +SELECT * FROM ltreetest WHERE t @ '23 & 1' order by t asc; + t +-------------------------------- + 1.10.23.25.5.11 + 1.10.4.18.22.23.24 + 1.12.25.26.22.8.15.23 + 1.19.22.11.14.7.32.23.19.14 + 1.21.28.4.23 + 1.26.15.23.5.31.29.11.19.28.1 + 1.27.22.23.2.26.32.17.7.9 + 10.12.9.6.6.26.14.8.23.1.25 + 12.27.23.32.1.1.9.29.13 + 14.1.15.25.27.23.25.26.28.10 + 14.27.29.23.4.1.17.32.6.25.22 + 15.11.26.1.30.6.23.5 + 19.22.29.32.1.21.26.24.23.17 + 19.9.32.23.13.24.1 + 21.23.17.8.23.11.8.1 + 22.30.31.24.23.22.5.20.28.1 + 23.1.23.18.12.29 + 23.12.1.5.32.25.8.24.1.25 + 23.12.32.22.19.1.22.4 + 23.17.22.1.23.4.29.32.4.1 + 23.17.25.4.1.16.29.10 + 23.19.17.31.29.13.1.12.5.25 + 23.22.10.1.14.24 + 23.28.1 + 23.3.32.21.5.14.10.17.1 + 27.29.1.5.30.6.22.16.23.2.28 + 28.23.2.30.3.8.1.15.15.14.13 + 29.23.1.21.31.8 + 29.23.15.25.1.6.6.10 + 30.23.10.1.10.7.22.28.18.11.17 + 31.30.23.7.7.24.32.10.11.1.31 + 32.1.23.20.14.12.23.5.32.15 + 32.15.20.28.5.1.23.4 + 5.13.23.19.28.26.27.6.1.22 + 6.18.1.4.18.23 + 7.23.1.24.29.13.31.19.23.17.7 + 8.16.1.16.28.6.3.22.6.23 + 8.2.18.23.5.16.17.1 + 9.5.9.3.23.9.25.14.1.29.28 +(39 rows) + +SELECT * FROM ltreetest WHERE t ~ '1.1.1.*' order by t asc; + t +----------- + 1.1.1 + 1.1.1.1 + 1.1.1.2 + 1.1.1.2.1 +(4 rows) + +SELECT * FROM ltreetest WHERE t ~ '*.1' order by t asc; + t +-------------------------------- + 1 + 1.1 + 1.1.1 + 1.1.1.1 + 1.1.1.2.1 + 1.1.2.1 + 1.26.15.23.5.31.29.11.19.28.1 + 10.13.22.1.8.30.9.24.1.2.1 + 10.22.1 + 10.26.30.15.1 + 11.1 + 12.1.1 + 17.25.2.13.10.27.13.1 + 18.13.6.12.26.26.26.29.18.20.1 + 19.20.25.7.27.28.27.17.9.3.1 + 19.9.32.23.13.24.1 + 20.17.18.21.1 + 20.4.27.31.1 + 21.18.2.1 + 21.23.17.8.23.11.8.1 + 22.30.31.24.23.22.5.20.28.1 + 23.17.22.1.23.4.29.32.4.1 + 23.28.1 + 23.3.32.21.5.14.10.17.1 + 25.6.12.16.1 + 29.1 + 29.15.29.8.31.26.1 + 32.31.11.22.1 + 4.7.1 + 6.27.29.14.8.12.26.3.21.4.1 + 8.13.1 + 8.2.18.23.5.16.17.1 + 8.32.30.1 + 9.21.20.29.1 +(34 rows) + +SELECT * FROM ltreetest WHERE t ~ '23.*{1}.1' order by t asc; + t +--------- + 23.28.1 +(1 row) + +SELECT * FROM ltreetest WHERE t ~ '23.*.1' order by t asc; + t +--------------------------- + 23.17.22.1.23.4.29.32.4.1 + 23.28.1 + 23.3.32.21.5.14.10.17.1 +(3 rows) + +SELECT * FROM ltreetest WHERE t ~ '23.*.2' order by t asc; + t +------------------ + 23.20.12.16.15.2 +(1 row) + +SELECT * FROM ltreetest WHERE t ? '{23.*.1,23.*.2}' order by t asc; + t +--------------------------- + 23.17.22.1.23.4.29.32.4.1 + 23.20.12.16.15.2 + 23.28.1 + 23.3.32.21.5.14.10.17.1 +(4 rows) + +drop index tstidx; +create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=0)); +ERROR: value 0 out of bounds for option "siglen" +DETAIL: Valid values are between "4" and "2024". +create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=2025)); +ERROR: value 2025 out of bounds for option "siglen" +DETAIL: Valid values are between "4" and "2024". +create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=2028)); +ERROR: value 2028 out of bounds for option "siglen" +DETAIL: Valid values are between "4" and "2024". +create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=2019)); +ERROR: siglen value must be a multiple of 4 +create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=2024)); +SELECT count(*) FROM ltreetest WHERE t < '12.3'; + count +------- + 123 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t <= '12.3'; + count +------- + 124 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t = '12.3'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t >= '12.3'; + count +------- + 883 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t > '12.3'; + count +------- + 882 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t @> '1.1.1'; + count +------- + 4 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t <@ '1.1.1'; + count +------- + 4 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t @ '23 & 1'; + count +------- + 39 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t ~ '1.1.1.*'; + count +------- + 4 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t ~ '*.1'; + count +------- + 34 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t ~ '23.*{1}.1'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t ~ '23.*.1'; + count +------- + 3 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t ~ '23.*.2'; + count +------- + 1 +(1 row) + +SELECT count(*) FROM ltreetest WHERE t ? '{23.*.1,23.*.2}'; + count +------- + 4 +(1 row) + +create table _ltreetest (t ltree[]); +\copy _ltreetest FROM 'data/_ltree.data' +SELECT count(*) FROM _ltreetest WHERE t @> '1.1.1' ; + count +------- + 15 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t <@ '1.1.1' ; + count +------- + 19 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t @ '23 & 1' ; + count +------- + 147 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '1.1.1.*' ; + count +------- + 19 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '*.1' ; + count +------- + 109 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*{1}.1' ; + count +------- + 5 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.1' ; + count +------- + 11 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.2' ; + count +------- + 5 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ? '{23.*.1,23.*.2}' ; + count +------- + 15 +(1 row) + +create index _tstidx on _ltreetest using gist (t); +set enable_seqscan=off; +SELECT count(*) FROM _ltreetest WHERE t @> '1.1.1' ; + count +------- + 15 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t <@ '1.1.1' ; + count +------- + 19 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t @ '23 & 1' ; + count +------- + 147 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '1.1.1.*' ; + count +------- + 19 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '*.1' ; + count +------- + 109 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*{1}.1' ; + count +------- + 5 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.1' ; + count +------- + 11 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.2' ; + count +------- + 5 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ? '{23.*.1,23.*.2}' ; + count +------- + 15 +(1 row) + +drop index _tstidx; +create index _tstidx on _ltreetest using gist (t gist__ltree_ops(siglen=0)); +ERROR: value 0 out of bounds for option "siglen" +DETAIL: Valid values are between "1" and "2024". +create index _tstidx on _ltreetest using gist (t gist__ltree_ops(siglen=2025)); +ERROR: value 2025 out of bounds for option "siglen" +DETAIL: Valid values are between "1" and "2024". +create index _tstidx on _ltreetest using gist (t gist__ltree_ops(siglen=2024)); +SELECT count(*) FROM _ltreetest WHERE t @> '1.1.1' ; + count +------- + 15 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t <@ '1.1.1' ; + count +------- + 19 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t @ '23 & 1' ; + count +------- + 147 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '1.1.1.*' ; + count +------- + 19 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '*.1' ; + count +------- + 109 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*{1}.1' ; + count +------- + 5 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.1' ; + count +------- + 11 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.2' ; + count +------- + 5 +(1 row) + +SELECT count(*) FROM _ltreetest WHERE t ? '{23.*.1,23.*.2}' ; + count +------- + 15 +(1 row) + +-- test non-error-throwing input +SELECT str as "value", typ as "type", + pg_input_is_valid(str,typ) as ok, + errinfo.sql_error_code, + errinfo.message, + errinfo.detail, + errinfo.hint +FROM (VALUES ('.2.3', 'ltree'), + ('1.2.', 'ltree'), + ('1.2.3','ltree'), + ('@.2.3','lquery'), + (' 2.3', 'lquery'), + ('1.2.3','lquery'), + ('$tree & aWdf@*','ltxtquery'), + ('!tree & aWdf@*','ltxtquery')) + AS a(str,typ), + LATERAL pg_input_error_info(a.str, a.typ) as errinfo; + value | type | ok | sql_error_code | message | detail | hint +----------------+-----------+----+----------------+------------------------------------+--------------------------+------ + .2.3 | ltree | f | 42601 | ltree syntax error at character 1 | | + 1.2. | ltree | f | 42601 | ltree syntax error | Unexpected end of input. | + 1.2.3 | ltree | t | | | | + @.2.3 | lquery | f | 42601 | lquery syntax error at character 1 | | + 2.3 | lquery | f | 42601 | lquery syntax error at character 1 | | + 1.2.3 | lquery | t | | | | + $tree & aWdf@* | ltxtquery | f | 42601 | operand syntax error | | + !tree & aWdf@* | ltxtquery | t | | | | +(8 rows) + diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c new file mode 100644 index 0000000..a6466f5 --- /dev/null +++ b/contrib/ltree/lquery_op.c @@ -0,0 +1,281 @@ +/* + * op function for ltree and lquery + * Teodor Sigaev + * contrib/ltree/lquery_op.c + */ +#include "postgres.h" + +#include + +#include "catalog/pg_collation.h" +#include "ltree.h" +#include "miscadmin.h" +#include "utils/array.h" +#include "utils/formatting.h" + +PG_FUNCTION_INFO_V1(ltq_regex); +PG_FUNCTION_INFO_V1(ltq_rregex); + +PG_FUNCTION_INFO_V1(lt_q_regex); +PG_FUNCTION_INFO_V1(lt_q_rregex); + +#define NEXTVAL(x) ( (lquery*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) ) + +static char * +getlexeme(char *start, char *end, int *len) +{ + char *ptr; + + while (start < end && t_iseq(start, '_')) + start += pg_mblen(start); + + ptr = start; + if (ptr >= end) + return NULL; + + while (ptr < end && !t_iseq(ptr, '_')) + ptr += pg_mblen(ptr); + + *len = ptr - start; + return start; +} + +bool +compare_subnode(ltree_level *t, char *qn, int len, int (*cmpptr) (const char *, const char *, size_t), bool anyend) +{ + char *endt = t->name + t->len; + char *endq = qn + len; + char *tn; + int lent, + lenq; + bool isok; + + while ((qn = getlexeme(qn, endq, &lenq)) != NULL) + { + tn = t->name; + isok = false; + while ((tn = getlexeme(tn, endt, &lent)) != NULL) + { + if ((lent == lenq || (lent > lenq && anyend)) && + (*cmpptr) (qn, tn, lenq) == 0) + { + + isok = true; + break; + } + tn += lent; + } + + if (!isok) + return false; + qn += lenq; + } + + return true; +} + +int +ltree_strncasecmp(const char *a, const char *b, size_t s) +{ + char *al = str_tolower(a, s, DEFAULT_COLLATION_OID); + char *bl = str_tolower(b, s, DEFAULT_COLLATION_OID); + int res; + + res = strncmp(al, bl, s); + + pfree(al); + pfree(bl); + + return res; +} + +/* + * See if an lquery_level matches an ltree_level + * + * This accounts for all flags including LQL_NOT, but does not + * consider repetition counts. + */ +static bool +checkLevel(lquery_level *curq, ltree_level *curt) +{ + lquery_variant *curvar = LQL_FIRST(curq); + bool success; + + success = (curq->flag & LQL_NOT) ? false : true; + + /* numvar == 0 means '*' which matches anything */ + if (curq->numvar == 0) + return success; + + for (int i = 0; i < curq->numvar; i++) + { + int (*cmpptr) (const char *, const char *, size_t); + + cmpptr = (curvar->flag & LVAR_INCASE) ? ltree_strncasecmp : strncmp; + + if (curvar->flag & LVAR_SUBLEXEME) + { + if (compare_subnode(curt, curvar->name, curvar->len, cmpptr, + (curvar->flag & LVAR_ANYEND))) + return success; + } + else if ((curvar->len == curt->len || + (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) && + (*cmpptr) (curvar->name, curt->name, curvar->len) == 0) + return success; + + curvar = LVAR_NEXT(curvar); + } + return !success; +} + +/* + * Try to match an lquery (of qlen items) to an ltree (of tlen items) + */ +static bool +checkCond(lquery_level *curq, int qlen, + ltree_level *curt, int tlen) +{ + /* Since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + /* Pathological patterns could take awhile, too */ + CHECK_FOR_INTERRUPTS(); + + /* Loop while we have query items to consider */ + while (qlen > 0) + { + int low, + high; + lquery_level *nextq; + + /* + * Get min and max repetition counts for this query item, dealing with + * the backwards-compatibility hack that the low/high fields aren't + * meaningful for non-'*' items unless LQL_COUNT is set. + */ + if ((curq->flag & LQL_COUNT) || curq->numvar == 0) + low = curq->low, high = curq->high; + else + low = high = 1; + + /* + * We may limit "high" to the remaining text length; this avoids + * separate tests below. + */ + if (high > tlen) + high = tlen; + + /* Fail if a match of required number of items is impossible */ + if (high < low) + return false; + + /* + * Recursively check the rest of the pattern against each possible + * start point following some of this item's match(es). + */ + nextq = LQL_NEXT(curq); + qlen--; + + for (int matchcnt = 0; matchcnt < high; matchcnt++) + { + /* + * If we've consumed an acceptable number of matches of this item, + * and the rest of the pattern matches beginning here, we're good. + */ + if (matchcnt >= low && checkCond(nextq, qlen, curt, tlen)) + return true; + + /* + * Otherwise, try to match one more text item to this query item. + */ + if (!checkLevel(curq, curt)) + return false; + + curt = LEVEL_NEXT(curt); + tlen--; + } + + /* + * Once we've consumed "high" matches, we can succeed only if the rest + * of the pattern matches beginning here. Loop around (if you prefer, + * think of this as tail recursion). + */ + curq = nextq; + } + + /* + * Once we're out of query items, we match only if there's no remaining + * text either. + */ + return (tlen == 0); +} + +Datum +ltq_regex(PG_FUNCTION_ARGS) +{ + ltree *tree = PG_GETARG_LTREE_P(0); + lquery *query = PG_GETARG_LQUERY_P(1); + bool res; + + res = checkCond(LQUERY_FIRST(query), query->numlevel, + LTREE_FIRST(tree), tree->numlevel); + + PG_FREE_IF_COPY(tree, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(res); +} + +Datum +ltq_rregex(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(ltq_regex, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); +} + +Datum +lt_q_regex(PG_FUNCTION_ARGS) +{ + ltree *tree = PG_GETARG_LTREE_P(0); + ArrayType *_query = PG_GETARG_ARRAYTYPE_P(1); + lquery *query = (lquery *) ARR_DATA_PTR(_query); + bool res = false; + int num = ArrayGetNItems(ARR_NDIM(_query), ARR_DIMS(_query)); + + if (ARR_NDIM(_query) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must be one-dimensional"))); + if (array_contains_nulls(_query)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array must not contain nulls"))); + + while (num > 0) + { + if (DatumGetBool(DirectFunctionCall2(ltq_regex, + PointerGetDatum(tree), PointerGetDatum(query)))) + { + + res = true; + break; + } + num--; + query = NEXTVAL(query); + } + + PG_FREE_IF_COPY(tree, 0); + PG_FREE_IF_COPY(_query, 1); + PG_RETURN_BOOL(res); +} + +Datum +lt_q_rregex(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(lt_q_regex, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); +} diff --git a/contrib/ltree/ltree--1.0--1.1.sql b/contrib/ltree/ltree--1.0--1.1.sql new file mode 100644 index 0000000..2ce6f5a --- /dev/null +++ b/contrib/ltree/ltree--1.0--1.1.sql @@ -0,0 +1,115 @@ +/* contrib/ltree/ltree--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION ltree UPDATE TO '1.1'" to load this file. \quit + +-- Update procedure signatures the hard way. +-- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, +-- wherein the signatures have been updated already. In that case to_regprocedure() will +-- return NULL and no updates will happen. +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) +FROM (VALUES +(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types +('ltree_consistent(internal,internal,int2,oid,internal)', '{internal,SCH.ltree,int2,oid,internal}'), +('ltree_same(internal,internal,internal)', '{SCH.ltree_gist,SCH.ltree_gist,internal}'), +('_ltree_consistent(internal,internal,int2,oid,internal)', '{internal,SCH._ltree,int2,oid,internal}'), +('_ltree_same(internal,internal,internal)', '{SCH.ltree_gist,SCH.ltree_gist,internal}') +) AS update_data (oldproc, newtypestext), +LATERAL ( + SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ +) ls +WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + +UPDATE pg_catalog.pg_proc SET + prorettype = (my_schema || '.ltree_gist')::pg_catalog.regtype +WHERE oid = pg_catalog.to_regprocedure(my_schema || '.ltree_union(internal,internal)'); + +UPDATE pg_catalog.pg_proc SET + prorettype = (my_schema || '.ltree_gist')::pg_catalog.regtype +WHERE oid = pg_catalog.to_regprocedure(my_schema || '._ltree_union(internal,internal)'); + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; + +ALTER FUNCTION ltree_in(cstring) PARALLEL SAFE; +ALTER FUNCTION ltree_out(ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_cmp(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_lt(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_le(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_eq(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_ge(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_gt(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_ne(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION subltree(ltree, int4, int4) PARALLEL SAFE; +ALTER FUNCTION subpath(ltree, int4, int4) PARALLEL SAFE; +ALTER FUNCTION subpath(ltree, int4) PARALLEL SAFE; +ALTER FUNCTION index(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION index(ltree, ltree, int4) PARALLEL SAFE; +ALTER FUNCTION nlevel(ltree) PARALLEL SAFE; +ALTER FUNCTION ltree2text(ltree) PARALLEL SAFE; +ALTER FUNCTION text2ltree(text) PARALLEL SAFE; +ALTER FUNCTION lca(_ltree) PARALLEL SAFE; +ALTER FUNCTION lca(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION lca(ltree, ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION lca(ltree, ltree, ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION lca(ltree, ltree, ltree, ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION lca(ltree, ltree, ltree, ltree, ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION lca(ltree, ltree, ltree, ltree, ltree, ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION lca(ltree, ltree, ltree, ltree, ltree, ltree, ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_isparent(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_risparent(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_addltree(ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_addtext(ltree, text) PARALLEL SAFE; +ALTER FUNCTION ltree_textadd(text, ltree) PARALLEL SAFE; +ALTER FUNCTION ltreeparentsel(internal, oid, internal, integer) PARALLEL SAFE; +ALTER FUNCTION lquery_in(cstring) PARALLEL SAFE; +ALTER FUNCTION lquery_out(lquery) PARALLEL SAFE; +ALTER FUNCTION ltq_regex(ltree, lquery) PARALLEL SAFE; +ALTER FUNCTION ltq_rregex(lquery, ltree) PARALLEL SAFE; +ALTER FUNCTION lt_q_regex(ltree, _lquery) PARALLEL SAFE; +ALTER FUNCTION lt_q_rregex(_lquery, ltree) PARALLEL SAFE; +ALTER FUNCTION ltxtq_in(cstring) PARALLEL SAFE; +ALTER FUNCTION ltxtq_out(ltxtquery) PARALLEL SAFE; +ALTER FUNCTION ltxtq_exec(ltree, ltxtquery) PARALLEL SAFE; +ALTER FUNCTION ltxtq_rexec(ltxtquery, ltree) PARALLEL SAFE; +ALTER FUNCTION ltree_gist_in(cstring) PARALLEL SAFE; +ALTER FUNCTION ltree_gist_out(ltree_gist) PARALLEL SAFE; +ALTER FUNCTION ltree_consistent(internal, ltree, int2, oid, internal) PARALLEL SAFE; +ALTER FUNCTION ltree_compress(internal) PARALLEL SAFE; +ALTER FUNCTION ltree_decompress(internal) PARALLEL SAFE; +ALTER FUNCTION ltree_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION ltree_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION ltree_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION ltree_same(ltree_gist, ltree_gist, internal) PARALLEL SAFE; +ALTER FUNCTION _ltree_isparent(_ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION _ltree_r_isparent(ltree, _ltree) PARALLEL SAFE; +ALTER FUNCTION _ltree_risparent(_ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION _ltree_r_risparent(ltree, _ltree) PARALLEL SAFE; +ALTER FUNCTION _ltq_regex(_ltree, lquery) PARALLEL SAFE; +ALTER FUNCTION _ltq_rregex(lquery, _ltree) PARALLEL SAFE; +ALTER FUNCTION _lt_q_regex(_ltree, _lquery) PARALLEL SAFE; +ALTER FUNCTION _lt_q_rregex(_lquery, _ltree) PARALLEL SAFE; +ALTER FUNCTION _ltxtq_exec(_ltree, ltxtquery) PARALLEL SAFE; +ALTER FUNCTION _ltxtq_rexec(ltxtquery, _ltree) PARALLEL SAFE; +ALTER FUNCTION _ltree_extract_isparent(_ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION _ltree_extract_risparent(_ltree, ltree) PARALLEL SAFE; +ALTER FUNCTION _ltq_extract_regex(_ltree, lquery) PARALLEL SAFE; +ALTER FUNCTION _ltxtq_extract_exec(_ltree, ltxtquery) PARALLEL SAFE; +ALTER FUNCTION _ltree_consistent(internal, _ltree, int2, oid, internal) PARALLEL SAFE; +ALTER FUNCTION _ltree_compress(internal) PARALLEL SAFE; +ALTER FUNCTION _ltree_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION _ltree_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION _ltree_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION _ltree_same(ltree_gist, ltree_gist, internal) PARALLEL SAFE; diff --git a/contrib/ltree/ltree--1.1--1.2.sql b/contrib/ltree/ltree--1.1--1.2.sql new file mode 100644 index 0000000..e38e76b --- /dev/null +++ b/contrib/ltree/ltree--1.1--1.2.sql @@ -0,0 +1,139 @@ +/* contrib/ltree/ltree--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION ltree UPDATE TO '1.2'" to load this file. \quit + +CREATE FUNCTION ltree_recv(internal) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_send(ltree) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +ALTER TYPE ltree SET ( RECEIVE = ltree_recv, SEND = ltree_send ); + +CREATE FUNCTION lquery_recv(internal) +RETURNS lquery +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lquery_send(lquery) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +ALTER TYPE lquery SET ( RECEIVE = lquery_recv, SEND = lquery_send ); + +CREATE FUNCTION ltxtq_recv(internal) +RETURNS ltxtquery +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltxtq_send(ltxtquery) +RETURNS bytea +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +ALTER TYPE ltxtquery SET ( RECEIVE = ltxtq_recv, SEND = ltxtq_send ); + + +CREATE FUNCTION ltree_gist_options(internal) +RETURNS void +AS 'MODULE_PATHNAME', 'ltree_gist_options' +LANGUAGE C IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _ltree_gist_options(internal) +RETURNS void +AS 'MODULE_PATHNAME', '_ltree_gist_options' +LANGUAGE C IMMUTABLE PARALLEL SAFE; + +ALTER OPERATOR FAMILY gist_ltree_ops USING gist +ADD FUNCTION 10 (ltree) ltree_gist_options (internal); + +ALTER OPERATOR FAMILY gist__ltree_ops USING gist +ADD FUNCTION 10 (_ltree) _ltree_gist_options (internal); + +ALTER OPERATOR < (ltree, ltree) + SET (RESTRICT = scalarltsel, JOIN = scalarltjoinsel); +ALTER OPERATOR <= (ltree, ltree) + SET (RESTRICT = scalarlesel, JOIN = scalarlejoinsel); +ALTER OPERATOR >= (ltree, ltree) + SET (RESTRICT = scalargesel, JOIN = scalargejoinsel); +ALTER OPERATOR > (ltree, ltree) + SET (RESTRICT = scalargtsel, JOIN = scalargtjoinsel); + +ALTER OPERATOR @> (ltree, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^@> (ltree, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR <@ (ltree, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^<@ (ltree, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ~ (ltree, lquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ~ (lquery, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^~ (ltree, lquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^~ (lquery, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ? (ltree, _lquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ? (_lquery, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^? (ltree, _lquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^? (_lquery, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR @ (ltree, ltxtquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR @ (ltxtquery, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^@ (ltree, ltxtquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^@ (ltxtquery, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR @> (_ltree, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR <@ (ltree, _ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR <@ (_ltree, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR @> (ltree, _ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ~ (_ltree, lquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ~ (lquery, _ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ? (_ltree, _lquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ? (_lquery, _ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR @ (_ltree, ltxtquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR @ (ltxtquery, _ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^@> (_ltree, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^<@ (ltree, _ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^<@ (_ltree, ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^@> (ltree, _ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^~ (_ltree, lquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^~ (lquery, _ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^? (_ltree, _lquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^? (_lquery, _ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^@ (_ltree, ltxtquery) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR ^@ (ltxtquery, _ltree) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); diff --git a/contrib/ltree/ltree--1.1.sql b/contrib/ltree/ltree--1.1.sql new file mode 100644 index 0000000..d46f5fc --- /dev/null +++ b/contrib/ltree/ltree--1.1.sql @@ -0,0 +1,872 @@ +/* contrib/ltree/ltree--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION ltree" to load this file. \quit + +CREATE FUNCTION ltree_in(cstring) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_out(ltree) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE TYPE ltree ( + INTERNALLENGTH = -1, + INPUT = ltree_in, + OUTPUT = ltree_out, + STORAGE = extended +); + + +--Compare function for ltree +CREATE FUNCTION ltree_cmp(ltree,ltree) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_lt(ltree,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_le(ltree,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_eq(ltree,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_ge(ltree,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_gt(ltree,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_ne(ltree,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + + +CREATE OPERATOR < ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_eq, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + SORT1 = '<', + SORT2 = '<' +); + +CREATE OPERATOR <> ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_ne, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +--util functions + +CREATE FUNCTION subltree(ltree,int4,int4) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION subpath(ltree,int4,int4) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION subpath(ltree,int4) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION index(ltree,ltree) +RETURNS int4 +AS 'MODULE_PATHNAME', 'ltree_index' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION index(ltree,ltree,int4) +RETURNS int4 +AS 'MODULE_PATHNAME', 'ltree_index' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION nlevel(ltree) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree2text(ltree) +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION text2ltree(text) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lca(_ltree) +RETURNS ltree +AS 'MODULE_PATHNAME','_lca' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lca(ltree,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lca(ltree,ltree,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lca(ltree,ltree,ltree,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lca(ltree,ltree,ltree,ltree,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lca(ltree,ltree,ltree,ltree,ltree,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lca(ltree,ltree,ltree,ltree,ltree,ltree,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lca(ltree,ltree,ltree,ltree,ltree,ltree,ltree,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_isparent(ltree,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_risparent(ltree,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_addltree(ltree,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_addtext(ltree,text) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_textadd(text,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltreeparentsel(internal, oid, internal, integer) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR @> ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_isparent, + COMMUTATOR = '<@', + RESTRICT = ltreeparentsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^@> ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_isparent, + COMMUTATOR = '^<@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR <@ ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_risparent, + COMMUTATOR = '@>', + RESTRICT = ltreeparentsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^<@ ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_risparent, + COMMUTATOR = '^@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR || ( + LEFTARG = ltree, + RIGHTARG = ltree, + PROCEDURE = ltree_addltree +); + +CREATE OPERATOR || ( + LEFTARG = ltree, + RIGHTARG = text, + PROCEDURE = ltree_addtext +); + +CREATE OPERATOR || ( + LEFTARG = text, + RIGHTARG = ltree, + PROCEDURE = ltree_textadd +); + + +-- B-tree support + +CREATE OPERATOR CLASS ltree_ops + DEFAULT FOR TYPE ltree USING btree AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 ltree_cmp(ltree, ltree); + + +--lquery type +CREATE FUNCTION lquery_in(cstring) +RETURNS lquery +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lquery_out(lquery) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE TYPE lquery ( + INTERNALLENGTH = -1, + INPUT = lquery_in, + OUTPUT = lquery_out, + STORAGE = extended +); + +CREATE FUNCTION ltq_regex(ltree,lquery) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltq_rregex(lquery,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR ~ ( + LEFTARG = ltree, + RIGHTARG = lquery, + PROCEDURE = ltq_regex, + COMMUTATOR = '~', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ~ ( + LEFTARG = lquery, + RIGHTARG = ltree, + PROCEDURE = ltq_rregex, + COMMUTATOR = '~', + RESTRICT = contsel, + JOIN = contjoinsel +); + +--not-indexed +CREATE OPERATOR ^~ ( + LEFTARG = ltree, + RIGHTARG = lquery, + PROCEDURE = ltq_regex, + COMMUTATOR = '^~', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^~ ( + LEFTARG = lquery, + RIGHTARG = ltree, + PROCEDURE = ltq_rregex, + COMMUTATOR = '^~', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION lt_q_regex(ltree,_lquery) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION lt_q_rregex(_lquery,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR ? ( + LEFTARG = ltree, + RIGHTARG = _lquery, + PROCEDURE = lt_q_regex, + COMMUTATOR = '?', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ? ( + LEFTARG = _lquery, + RIGHTARG = ltree, + PROCEDURE = lt_q_rregex, + COMMUTATOR = '?', + RESTRICT = contsel, + JOIN = contjoinsel +); + +--not-indexed +CREATE OPERATOR ^? ( + LEFTARG = ltree, + RIGHTARG = _lquery, + PROCEDURE = lt_q_regex, + COMMUTATOR = '^?', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^? ( + LEFTARG = _lquery, + RIGHTARG = ltree, + PROCEDURE = lt_q_rregex, + COMMUTATOR = '^?', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION ltxtq_in(cstring) +RETURNS ltxtquery +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltxtq_out(ltxtquery) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE TYPE ltxtquery ( + INTERNALLENGTH = -1, + INPUT = ltxtq_in, + OUTPUT = ltxtq_out, + STORAGE = extended +); + +-- operations WITH ltxtquery + +CREATE FUNCTION ltxtq_exec(ltree, ltxtquery) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltxtq_rexec(ltxtquery, ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR @ ( + LEFTARG = ltree, + RIGHTARG = ltxtquery, + PROCEDURE = ltxtq_exec, + COMMUTATOR = '@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR @ ( + LEFTARG = ltxtquery, + RIGHTARG = ltree, + PROCEDURE = ltxtq_rexec, + COMMUTATOR = '@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +--not-indexed +CREATE OPERATOR ^@ ( + LEFTARG = ltree, + RIGHTARG = ltxtquery, + PROCEDURE = ltxtq_exec, + COMMUTATOR = '^@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^@ ( + LEFTARG = ltxtquery, + RIGHTARG = ltree, + PROCEDURE = ltxtq_rexec, + COMMUTATOR = '^@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +--GiST support for ltree +CREATE FUNCTION ltree_gist_in(cstring) +RETURNS ltree_gist +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION ltree_gist_out(ltree_gist) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE TYPE ltree_gist ( + internallength = -1, + input = ltree_gist_in, + output = ltree_gist_out, + storage = plain +); + + +CREATE FUNCTION ltree_consistent(internal,ltree,int2,oid,internal) +RETURNS bool as 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ltree_compress(internal) +RETURNS internal as 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ltree_decompress(internal) +RETURNS internal as 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ltree_penalty(internal,internal,internal) +RETURNS internal as 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ltree_picksplit(internal, internal) +RETURNS internal as 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ltree_union(internal, internal) +RETURNS ltree_gist as 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION ltree_same(ltree_gist, ltree_gist, internal) +RETURNS internal as 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR CLASS gist_ltree_ops + DEFAULT FOR TYPE ltree USING gist AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + OPERATOR 10 @> , + OPERATOR 11 <@ , + OPERATOR 12 ~ (ltree, lquery) , + OPERATOR 13 ~ (lquery, ltree) , + OPERATOR 14 @ (ltree, ltxtquery) , + OPERATOR 15 @ (ltxtquery, ltree) , + OPERATOR 16 ? (ltree, _lquery) , + OPERATOR 17 ? (_lquery, ltree) , + FUNCTION 1 ltree_consistent (internal, ltree, int2, oid, internal), + FUNCTION 2 ltree_union (internal, internal), + FUNCTION 3 ltree_compress (internal), + FUNCTION 4 ltree_decompress (internal), + FUNCTION 5 ltree_penalty (internal, internal, internal), + FUNCTION 6 ltree_picksplit (internal, internal), + FUNCTION 7 ltree_same (ltree_gist, ltree_gist, internal), + STORAGE ltree_gist; + + +-- arrays of ltree + +CREATE FUNCTION _ltree_isparent(_ltree,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _ltree_r_isparent(ltree,_ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _ltree_risparent(_ltree,ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _ltree_r_risparent(ltree,_ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _ltq_regex(_ltree,lquery) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _ltq_rregex(lquery,_ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _lt_q_regex(_ltree,_lquery) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _lt_q_rregex(_lquery,_ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _ltxtq_exec(_ltree, ltxtquery) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION _ltxtq_rexec(ltxtquery, _ltree) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR @> ( + LEFTARG = _ltree, + RIGHTARG = ltree, + PROCEDURE = _ltree_isparent, + COMMUTATOR = '<@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR <@ ( + LEFTARG = ltree, + RIGHTARG = _ltree, + PROCEDURE = _ltree_r_isparent, + COMMUTATOR = '@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR <@ ( + LEFTARG = _ltree, + RIGHTARG = ltree, + PROCEDURE = _ltree_risparent, + COMMUTATOR = '@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR @> ( + LEFTARG = ltree, + RIGHTARG = _ltree, + PROCEDURE = _ltree_r_risparent, + COMMUTATOR = '<@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ~ ( + LEFTARG = _ltree, + RIGHTARG = lquery, + PROCEDURE = _ltq_regex, + COMMUTATOR = '~', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ~ ( + LEFTARG = lquery, + RIGHTARG = _ltree, + PROCEDURE = _ltq_rregex, + COMMUTATOR = '~', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ? ( + LEFTARG = _ltree, + RIGHTARG = _lquery, + PROCEDURE = _lt_q_regex, + COMMUTATOR = '?', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ? ( + LEFTARG = _lquery, + RIGHTARG = _ltree, + PROCEDURE = _lt_q_rregex, + COMMUTATOR = '?', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR @ ( + LEFTARG = _ltree, + RIGHTARG = ltxtquery, + PROCEDURE = _ltxtq_exec, + COMMUTATOR = '@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR @ ( + LEFTARG = ltxtquery, + RIGHTARG = _ltree, + PROCEDURE = _ltxtq_rexec, + COMMUTATOR = '@', + RESTRICT = contsel, + JOIN = contjoinsel +); + + +--not indexed +CREATE OPERATOR ^@> ( + LEFTARG = _ltree, + RIGHTARG = ltree, + PROCEDURE = _ltree_isparent, + COMMUTATOR = '^<@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^<@ ( + LEFTARG = ltree, + RIGHTARG = _ltree, + PROCEDURE = _ltree_r_isparent, + COMMUTATOR = '^@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^<@ ( + LEFTARG = _ltree, + RIGHTARG = ltree, + PROCEDURE = _ltree_risparent, + COMMUTATOR = '^@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^@> ( + LEFTARG = ltree, + RIGHTARG = _ltree, + PROCEDURE = _ltree_r_risparent, + COMMUTATOR = '^<@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^~ ( + LEFTARG = _ltree, + RIGHTARG = lquery, + PROCEDURE = _ltq_regex, + COMMUTATOR = '^~', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^~ ( + LEFTARG = lquery, + RIGHTARG = _ltree, + PROCEDURE = _ltq_rregex, + COMMUTATOR = '^~', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^? ( + LEFTARG = _ltree, + RIGHTARG = _lquery, + PROCEDURE = _lt_q_regex, + COMMUTATOR = '^?', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^? ( + LEFTARG = _lquery, + RIGHTARG = _ltree, + PROCEDURE = _lt_q_rregex, + COMMUTATOR = '^?', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^@ ( + LEFTARG = _ltree, + RIGHTARG = ltxtquery, + PROCEDURE = _ltxtq_exec, + COMMUTATOR = '^@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ^@ ( + LEFTARG = ltxtquery, + RIGHTARG = _ltree, + PROCEDURE = _ltxtq_rexec, + COMMUTATOR = '^@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +--extractors +CREATE FUNCTION _ltree_extract_isparent(_ltree,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR ?@> ( + LEFTARG = _ltree, + RIGHTARG = ltree, + PROCEDURE = _ltree_extract_isparent +); + +CREATE FUNCTION _ltree_extract_risparent(_ltree,ltree) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR ?<@ ( + LEFTARG = _ltree, + RIGHTARG = ltree, + PROCEDURE = _ltree_extract_risparent +); + +CREATE FUNCTION _ltq_extract_regex(_ltree,lquery) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR ?~ ( + LEFTARG = _ltree, + RIGHTARG = lquery, + PROCEDURE = _ltq_extract_regex +); + +CREATE FUNCTION _ltxtq_extract_exec(_ltree,ltxtquery) +RETURNS ltree +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR ?@ ( + LEFTARG = _ltree, + RIGHTARG = ltxtquery, + PROCEDURE = _ltxtq_extract_exec +); + +--GiST support for ltree[] +CREATE FUNCTION _ltree_consistent(internal,_ltree,int2,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION _ltree_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION _ltree_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION _ltree_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION _ltree_union(internal, internal) +RETURNS ltree_gist +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION _ltree_same(ltree_gist, ltree_gist, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR CLASS gist__ltree_ops + DEFAULT FOR TYPE _ltree USING gist AS + OPERATOR 10 <@ (_ltree, ltree), + OPERATOR 11 @> (ltree, _ltree), + OPERATOR 12 ~ (_ltree, lquery), + OPERATOR 13 ~ (lquery, _ltree), + OPERATOR 14 @ (_ltree, ltxtquery), + OPERATOR 15 @ (ltxtquery, _ltree), + OPERATOR 16 ? (_ltree, _lquery), + OPERATOR 17 ? (_lquery, _ltree), + FUNCTION 1 _ltree_consistent (internal, _ltree, int2, oid, internal), + FUNCTION 2 _ltree_union (internal, internal), + FUNCTION 3 _ltree_compress (internal), + FUNCTION 4 ltree_decompress (internal), + FUNCTION 5 _ltree_penalty (internal, internal, internal), + FUNCTION 6 _ltree_picksplit (internal, internal), + FUNCTION 7 _ltree_same (ltree_gist, ltree_gist, internal), + STORAGE ltree_gist; diff --git a/contrib/ltree/ltree.control b/contrib/ltree/ltree.control new file mode 100644 index 0000000..b408d64 --- /dev/null +++ b/contrib/ltree/ltree.control @@ -0,0 +1,6 @@ +# ltree extension +comment = 'data type for hierarchical tree-like structures' +default_version = '1.2' +module_pathname = '$libdir/ltree' +relocatable = true +trusted = true diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h new file mode 100644 index 0000000..5e07616 --- /dev/null +++ b/contrib/ltree/ltree.h @@ -0,0 +1,317 @@ +/* contrib/ltree/ltree.h */ + +#ifndef __LTREE_H__ +#define __LTREE_H__ + +#include "fmgr.h" +#include "tsearch/ts_locale.h" +#include "utils/memutils.h" + + +/* ltree */ + +/* + * We want the maximum length of a label to be encoding-independent, so + * set it somewhat arbitrarily at 1000 characters (not bytes), while using + * uint16 fields to hold the byte length. + */ +#define LTREE_LABEL_MAX_CHARS 1000 + +/* + * LOWER_NODE used to be defined in the Makefile via the compile flags. + * However the MSVC build scripts neglected to do the same which resulted in + * MSVC builds not using LOWER_NODE. Since then, the MSVC scripts have been + * modified to look for -D compile flags in Makefiles, so here, in order to + * get the historic behavior of LOWER_NODE not being defined on MSVC, we only + * define it when not building in that environment. This is important as we + * want to maintain the same LOWER_NODE behavior after a pg_upgrade. + */ +#ifndef _MSC_VER +#define LOWER_NODE +#endif + +typedef struct +{ + uint16 len; /* label string length in bytes */ + char name[FLEXIBLE_ARRAY_MEMBER]; +} ltree_level; + +#define LEVEL_HDRSIZE (offsetof(ltree_level,name)) +#define LEVEL_NEXT(x) ( (ltree_level*)( ((char*)(x)) + MAXALIGN(((ltree_level*)(x))->len + LEVEL_HDRSIZE) ) ) + +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + uint16 numlevel; /* number of labels */ + /* Array of maxalign'd ltree_level structs follows: */ + char data[FLEXIBLE_ARRAY_MEMBER]; +} ltree; + +#define LTREE_HDRSIZE MAXALIGN( offsetof(ltree, data) ) +#define LTREE_FIRST(x) ( (ltree_level*)( ((char*)(x))+LTREE_HDRSIZE ) ) +#define LTREE_MAX_LEVELS PG_UINT16_MAX /* ltree.numlevel is uint16 */ + + +/* lquery */ + +/* lquery_variant: one branch of some OR'ed alternatives */ +typedef struct +{ + int32 val; /* CRC of label string */ + uint16 len; /* label string length in bytes */ + uint8 flag; /* see LVAR_xxx flags below */ + char name[FLEXIBLE_ARRAY_MEMBER]; +} lquery_variant; + +/* + * Note: these macros contain too many MAXALIGN calls and so will sometimes + * overestimate the space needed for an lquery_variant. However, we can't + * change it without breaking on-disk compatibility for lquery. + */ +#define LVAR_HDRSIZE MAXALIGN(offsetof(lquery_variant, name)) +#define LVAR_NEXT(x) ( (lquery_variant*)( ((char*)(x)) + MAXALIGN(((lquery_variant*)(x))->len) + LVAR_HDRSIZE ) ) + +#define LVAR_ANYEND 0x01 /* '*' flag: prefix match */ +#define LVAR_INCASE 0x02 /* '@' flag: case-insensitive match */ +#define LVAR_SUBLEXEME 0x04 /* '%' flag: word-wise match */ + +/* + * In an lquery_level, "flag" contains the union of the variants' flags + * along with possible LQL_xxx flags; so those bit sets can't overlap. + * + * "low" and "high" are nominally the minimum and maximum number of matches. + * However, for backwards compatibility with pre-v13 on-disk lqueries, + * non-'*' levels (those with numvar > 0) only have valid low/high if the + * LQL_COUNT flag is set; otherwise those fields are zero, but the behavior + * is as if they were both 1. + */ +typedef struct +{ + uint16 totallen; /* total length of this level, in bytes */ + uint16 flag; /* see LQL_xxx and LVAR_xxx flags */ + uint16 numvar; /* number of variants; 0 means '*' */ + uint16 low; /* minimum repeat count */ + uint16 high; /* maximum repeat count */ + /* Array of maxalign'd lquery_variant structs follows: */ + char variants[FLEXIBLE_ARRAY_MEMBER]; +} lquery_level; + +#define LQL_HDRSIZE MAXALIGN( offsetof(lquery_level,variants) ) +#define LQL_NEXT(x) ( (lquery_level*)( ((char*)(x)) + MAXALIGN(((lquery_level*)(x))->totallen) ) ) +#define LQL_FIRST(x) ( (lquery_variant*)( ((char*)(x))+LQL_HDRSIZE ) ) + +#define LQL_NOT 0x10 /* level has '!' (NOT) prefix */ +#define LQL_COUNT 0x20 /* level is non-'*' and has repeat counts */ + +#ifdef LOWER_NODE +#define FLG_CANLOOKSIGN(x) ( ( (x) & ( LQL_NOT | LVAR_ANYEND | LVAR_SUBLEXEME ) ) == 0 ) +#else +#define FLG_CANLOOKSIGN(x) ( ( (x) & ( LQL_NOT | LVAR_ANYEND | LVAR_SUBLEXEME | LVAR_INCASE ) ) == 0 ) +#endif +#define LQL_CANLOOKSIGN(x) FLG_CANLOOKSIGN( ((lquery_level*)(x))->flag ) + +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + uint16 numlevel; /* number of lquery_levels */ + uint16 firstgood; /* number of leading simple-match levels */ + uint16 flag; /* see LQUERY_xxx flags below */ + /* Array of maxalign'd lquery_level structs follows: */ + char data[FLEXIBLE_ARRAY_MEMBER]; +} lquery; + +#define LQUERY_HDRSIZE MAXALIGN( offsetof(lquery, data) ) +#define LQUERY_FIRST(x) ( (lquery_level*)( ((char*)(x))+LQUERY_HDRSIZE ) ) +#define LQUERY_MAX_LEVELS PG_UINT16_MAX /* lquery.numlevel is uint16 */ + +#define LQUERY_HASNOT 0x01 + +/* valid label chars are alphanumerics, underscores and hyphens */ +#define ISLABEL(x) ( t_isalnum(x) || t_iseq(x, '_') || t_iseq(x, '-') ) + +/* full text query */ + +/* + * item in polish notation with back link + * to left operand + */ +typedef struct ITEM +{ + int16 type; + int16 left; + int32 val; + uint8 flag; + /* user-friendly value */ + uint8 length; + uint16 distance; +} ITEM; + +/* + *Storage: + * (len)(size)(array of ITEM)(array of operand in user-friendly form) + */ +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int32 size; + char data[FLEXIBLE_ARRAY_MEMBER]; +} ltxtquery; + +#define HDRSIZEQT MAXALIGN(VARHDRSZ + sizeof(int32)) +#define COMPUTESIZE(size,lenofoperand) ( HDRSIZEQT + (size) * sizeof(ITEM) + (lenofoperand) ) +#define LTXTQUERY_TOO_BIG(size,lenofoperand) \ + ((size) > (MaxAllocSize - HDRSIZEQT - (lenofoperand)) / sizeof(ITEM)) +#define GETQUERY(x) (ITEM*)( (char*)(x)+HDRSIZEQT ) +#define GETOPERAND(x) ( (char*)GETQUERY(x) + ((ltxtquery*)x)->size * sizeof(ITEM) ) + +#define ISOPERATOR(x) ( (x)=='!' || (x)=='&' || (x)=='|' || (x)=='(' || (x)==')' ) + +#define END 0 +#define ERR 1 +#define VAL 2 +#define OPR 3 +#define OPEN 4 +#define CLOSE 5 +#define VALTRUE 6 /* for stop words */ +#define VALFALSE 7 + + +/* use in array iterator */ +PGDLLEXPORT Datum ltree_isparent(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum ltree_risparent(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum ltq_regex(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum ltq_rregex(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum lt_q_regex(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum lt_q_rregex(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum ltxtq_exec(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum ltxtq_rexec(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum _ltq_regex(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum _ltq_rregex(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum _lt_q_regex(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum _lt_q_rregex(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum _ltxtq_exec(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum _ltxtq_rexec(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum _ltree_isparent(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum _ltree_risparent(PG_FUNCTION_ARGS); + +/* Concatenation functions */ +PGDLLEXPORT Datum ltree_addltree(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum ltree_addtext(PG_FUNCTION_ARGS); +PGDLLEXPORT Datum ltree_textadd(PG_FUNCTION_ARGS); + +/* Util function */ +PGDLLEXPORT Datum ltree_in(PG_FUNCTION_ARGS); + +bool ltree_execute(ITEM *curitem, void *checkval, + bool calcnot, bool (*chkcond) (void *checkval, ITEM *val)); + +int ltree_compare(const ltree *a, const ltree *b); +bool inner_isparent(const ltree *c, const ltree *p); +bool compare_subnode(ltree_level *t, char *qn, int len, + int (*cmpptr) (const char *, const char *, size_t), bool anyend); +ltree *lca_inner(ltree **a, int len); +int ltree_strncasecmp(const char *a, const char *b, size_t s); + +/* fmgr macros for ltree objects */ +#define DatumGetLtreeP(X) ((ltree *) PG_DETOAST_DATUM(X)) +#define DatumGetLtreePCopy(X) ((ltree *) PG_DETOAST_DATUM_COPY(X)) +#define PG_GETARG_LTREE_P(n) DatumGetLtreeP(PG_GETARG_DATUM(n)) +#define PG_GETARG_LTREE_P_COPY(n) DatumGetLtreePCopy(PG_GETARG_DATUM(n)) + +#define DatumGetLqueryP(X) ((lquery *) PG_DETOAST_DATUM(X)) +#define DatumGetLqueryPCopy(X) ((lquery *) PG_DETOAST_DATUM_COPY(X)) +#define PG_GETARG_LQUERY_P(n) DatumGetLqueryP(PG_GETARG_DATUM(n)) +#define PG_GETARG_LQUERY_P_COPY(n) DatumGetLqueryPCopy(PG_GETARG_DATUM(n)) + +#define DatumGetLtxtqueryP(X) ((ltxtquery *) PG_DETOAST_DATUM(X)) +#define DatumGetLtxtqueryPCopy(X) ((ltxtquery *) PG_DETOAST_DATUM_COPY(X)) +#define PG_GETARG_LTXTQUERY_P(n) DatumGetLtxtqueryP(PG_GETARG_DATUM(n)) +#define PG_GETARG_LTXTQUERY_P_COPY(n) DatumGetLtxtqueryPCopy(PG_GETARG_DATUM(n)) + +/* GiST support for ltree */ + +#define BITBYTE 8 +#define SIGLENBIT(siglen) ((siglen) * BITBYTE) +#define LTREE_SIGLEN_DEFAULT (2 * sizeof(int32)) +#define LTREE_SIGLEN_MAX GISTMaxIndexKeySize +#define LTREE_GET_SIGLEN() (PG_HAS_OPCLASS_OPTIONS() ? \ + ((LtreeGistOptions *) PG_GET_OPCLASS_OPTIONS())->siglen : \ + LTREE_SIGLEN_DEFAULT) + +typedef unsigned char *BITVECP; + +#define LOOPBYTE(siglen) \ + for(i = 0; i < (siglen); i++) + +#define GETBYTE(x,i) ( *( (BITVECP)(x) + (int)( (i) / BITBYTE ) ) ) +#define GETBITBYTE(x,i) ( ((unsigned char)(x)) >> i & 0x01 ) +#define CLRBIT(x,i) GETBYTE(x,i) &= ~( 0x01 << ( (i) % BITBYTE ) ) +#define SETBIT(x,i) GETBYTE(x,i) |= ( 0x01 << ( (i) % BITBYTE ) ) +#define GETBIT(x,i) ( (GETBYTE(x,i) >> ( (i) % BITBYTE )) & 0x01 ) + +#define HASHVAL(val, siglen) (((unsigned int)(val)) % SIGLENBIT(siglen)) +#define HASH(sign, val, siglen) SETBIT((sign), HASHVAL(val, siglen)) + +/* + * type of index key for ltree. Tree are combined B-Tree and R-Tree + * Storage: + * Leaf pages + * (len)(flag)(ltree) + * Non-Leaf + * (len)(flag)(sign)(left_ltree)(right_ltree) + * ALLTRUE: (len)(flag)(left_ltree)(right_ltree) + * + */ + +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + uint32 flag; + char data[FLEXIBLE_ARRAY_MEMBER]; +} ltree_gist; + +#define LTG_ONENODE 0x01 +#define LTG_ALLTRUE 0x02 +#define LTG_NORIGHT 0x04 + +#define LTG_HDRSIZE MAXALIGN(VARHDRSZ + sizeof(uint32)) +#define LTG_SIGN(x) ( (BITVECP)( ((char*)(x))+LTG_HDRSIZE ) ) +#define LTG_NODE(x) ( (ltree*)( ((char*)(x))+LTG_HDRSIZE ) ) +#define LTG_ISONENODE(x) ( ((ltree_gist*)(x))->flag & LTG_ONENODE ) +#define LTG_ISALLTRUE(x) ( ((ltree_gist*)(x))->flag & LTG_ALLTRUE ) +#define LTG_ISNORIGHT(x) ( ((ltree_gist*)(x))->flag & LTG_NORIGHT ) +#define LTG_LNODE(x, siglen) ( (ltree*)( ( ((char*)(x))+LTG_HDRSIZE ) + ( LTG_ISALLTRUE(x) ? 0 : (siglen) ) ) ) +#define LTG_RENODE(x, siglen) ( (ltree*)( ((char*)LTG_LNODE(x, siglen)) + VARSIZE(LTG_LNODE(x, siglen))) ) +#define LTG_RNODE(x, siglen) ( LTG_ISNORIGHT(x) ? LTG_LNODE(x, siglen) : LTG_RENODE(x, siglen) ) + +#define LTG_GETLNODE(x, siglen) ( LTG_ISONENODE(x) ? LTG_NODE(x) : LTG_LNODE(x, siglen) ) +#define LTG_GETRNODE(x, siglen) ( LTG_ISONENODE(x) ? LTG_NODE(x) : LTG_RNODE(x, siglen) ) + +extern ltree_gist *ltree_gist_alloc(bool isalltrue, BITVECP sign, int siglen, + ltree *left, ltree *right); + +/* GiST support for ltree[] */ + +#define LTREE_ASIGLEN_DEFAULT (7 * sizeof(int32)) +#define LTREE_ASIGLEN_MAX GISTMaxIndexKeySize +#define LTREE_GET_ASIGLEN() (PG_HAS_OPCLASS_OPTIONS() ? \ + ((LtreeGistOptions *) PG_GET_OPCLASS_OPTIONS())->siglen : \ + LTREE_ASIGLEN_DEFAULT) +#define ASIGLENBIT(siglen) ((siglen) * BITBYTE) + +#define ALOOPBYTE(siglen) \ + for (i = 0; i < (siglen); i++) + +#define AHASHVAL(val, siglen) (((unsigned int)(val)) % ASIGLENBIT(siglen)) +#define AHASH(sign, val, siglen) SETBIT((sign), AHASHVAL(val, siglen)) + +/* gist_ltree_ops and gist__ltree_ops opclass options */ +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int siglen; /* signature length in bytes */ +} LtreeGistOptions; + +/* type of key is the same to ltree_gist */ + +#endif diff --git a/contrib/ltree/ltree_gist.c b/contrib/ltree/ltree_gist.c new file mode 100644 index 0000000..932f69b --- /dev/null +++ b/contrib/ltree/ltree_gist.c @@ -0,0 +1,749 @@ +/* + * GiST support for ltree + * Teodor Sigaev + * contrib/ltree/ltree_gist.c + */ +#include "postgres.h" + +#include "access/gist.h" +#include "access/reloptions.h" +#include "access/stratnum.h" +#include "crc32.h" +#include "ltree.h" +#include "utils/array.h" + +#define NEXTVAL(x) ( (lquery*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) ) +#define ISEQ(a,b) ( (a)->numlevel == (b)->numlevel && ltree_compare(a,b)==0 ) + +PG_FUNCTION_INFO_V1(ltree_gist_in); +PG_FUNCTION_INFO_V1(ltree_gist_out); + +Datum +ltree_gist_in(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type %s", "ltree_gist"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +Datum +ltree_gist_out(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot display a value of type %s", "ltree_gist"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +ltree_gist * +ltree_gist_alloc(bool isalltrue, BITVECP sign, int siglen, + ltree *left, ltree *right) +{ + int32 size = LTG_HDRSIZE + (isalltrue ? 0 : siglen) + + (left ? VARSIZE(left) + (right ? VARSIZE(right) : 0) : 0); + ltree_gist *result = palloc(size); + + SET_VARSIZE(result, size); + + if (siglen) + { + result->flag = 0; + + if (isalltrue) + result->flag |= LTG_ALLTRUE; + else if (sign) + memcpy(LTG_SIGN(result), sign, siglen); + else + memset(LTG_SIGN(result), 0, siglen); + + if (left) + { + memcpy(LTG_LNODE(result, siglen), left, VARSIZE(left)); + + if (!right || left == right || ISEQ(left, right)) + result->flag |= LTG_NORIGHT; + else + memcpy(LTG_RNODE(result, siglen), right, VARSIZE(right)); + } + } + else + { + Assert(left); + result->flag = LTG_ONENODE; + memcpy(LTG_NODE(result), left, VARSIZE(left)); + } + + return result; +} + +PG_FUNCTION_INFO_V1(ltree_compress); +PG_FUNCTION_INFO_V1(ltree_decompress); +PG_FUNCTION_INFO_V1(ltree_same); +PG_FUNCTION_INFO_V1(ltree_union); +PG_FUNCTION_INFO_V1(ltree_penalty); +PG_FUNCTION_INFO_V1(ltree_picksplit); +PG_FUNCTION_INFO_V1(ltree_consistent); +PG_FUNCTION_INFO_V1(ltree_gist_options); + +#define GETENTRY(vec,pos) ((ltree_gist *) DatumGetPointer((vec)->vector[(pos)].key)) + +Datum +ltree_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval = entry; + + if (entry->leafkey) + { /* ltree */ + ltree *val = DatumGetLtreeP(entry->key); + ltree_gist *key = ltree_gist_alloc(false, NULL, 0, val, 0); + + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(key), + entry->rel, entry->page, + entry->offset, false); + } + PG_RETURN_POINTER(retval); +} + +Datum +ltree_decompress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + ltree_gist *key = (ltree_gist *) PG_DETOAST_DATUM(entry->key); + + if (PointerGetDatum(key) != entry->key) + { + GISTENTRY *retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + + gistentryinit(*retval, PointerGetDatum(key), + entry->rel, entry->page, + entry->offset, false); + PG_RETURN_POINTER(retval); + } + PG_RETURN_POINTER(entry); +} + +Datum +ltree_same(PG_FUNCTION_ARGS) +{ + ltree_gist *a = (ltree_gist *) PG_GETARG_POINTER(0); + ltree_gist *b = (ltree_gist *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + int siglen = LTREE_GET_SIGLEN(); + + *result = false; + if (LTG_ISONENODE(a) != LTG_ISONENODE(b)) + PG_RETURN_POINTER(result); + + if (LTG_ISONENODE(a)) + *result = ISEQ(LTG_NODE(a), LTG_NODE(b)); + else + { + int32 i; + BITVECP sa = LTG_SIGN(a), + sb = LTG_SIGN(b); + + if (LTG_ISALLTRUE(a) != LTG_ISALLTRUE(b)) + PG_RETURN_POINTER(result); + + if (!ISEQ(LTG_LNODE(a, siglen), LTG_LNODE(b, siglen))) + PG_RETURN_POINTER(result); + if (!ISEQ(LTG_RNODE(a, siglen), LTG_RNODE(b, siglen))) + PG_RETURN_POINTER(result); + + *result = true; + if (!LTG_ISALLTRUE(a)) + { + LOOPBYTE(siglen) + { + if (sa[i] != sb[i]) + { + *result = false; + break; + } + } + } + } + + PG_RETURN_POINTER(result); +} + +static void +hashing(BITVECP sign, ltree *t, int siglen) +{ + int tlen = t->numlevel; + ltree_level *cur = LTREE_FIRST(t); + int hash; + + while (tlen > 0) + { + hash = ltree_crc32_sz(cur->name, cur->len); + HASH(sign, hash, siglen); + cur = LEVEL_NEXT(cur); + tlen--; + } +} + +Datum +ltree_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int *size = (int *) PG_GETARG_POINTER(1); + int siglen = LTREE_GET_SIGLEN(); + BITVECP base = palloc0(siglen); + int32 i, + j; + ltree_gist *result, + *cur; + ltree *left = NULL, + *right = NULL, + *curtree; + bool isalltrue = false; + + for (j = 0; j < entryvec->n; j++) + { + cur = GETENTRY(entryvec, j); + if (LTG_ISONENODE(cur)) + { + curtree = LTG_NODE(cur); + hashing(base, curtree, siglen); + if (!left || ltree_compare(left, curtree) > 0) + left = curtree; + if (!right || ltree_compare(right, curtree) < 0) + right = curtree; + } + else + { + if (isalltrue || LTG_ISALLTRUE(cur)) + isalltrue = true; + else + { + BITVECP sc = LTG_SIGN(cur); + + LOOPBYTE(siglen) + ((unsigned char *) base)[i] |= sc[i]; + } + + curtree = LTG_LNODE(cur, siglen); + if (!left || ltree_compare(left, curtree) > 0) + left = curtree; + curtree = LTG_RNODE(cur, siglen); + if (!right || ltree_compare(right, curtree) < 0) + right = curtree; + } + } + + if (isalltrue == false) + { + isalltrue = true; + LOOPBYTE(siglen) + { + if (((unsigned char *) base)[i] != 0xff) + { + isalltrue = false; + break; + } + } + } + + result = ltree_gist_alloc(isalltrue, base, siglen, left, right); + + *size = VARSIZE(result); + + PG_RETURN_POINTER(result); +} + +Datum +ltree_penalty(PG_FUNCTION_ARGS) +{ + ltree_gist *origval = (ltree_gist *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key); + ltree_gist *newval = (ltree_gist *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key); + float *penalty = (float *) PG_GETARG_POINTER(2); + int siglen = LTREE_GET_SIGLEN(); + int32 cmpr, + cmpl; + + cmpl = ltree_compare(LTG_GETLNODE(origval, siglen), LTG_GETLNODE(newval, siglen)); + cmpr = ltree_compare(LTG_GETRNODE(newval, siglen), LTG_GETRNODE(origval, siglen)); + + *penalty = Max(cmpl, 0) + Max(cmpr, 0); + + PG_RETURN_POINTER(penalty); +} + +/* used for sorting */ +typedef struct rix +{ + int index; + ltree *r; +} RIX; + +static int +treekey_cmp(const void *a, const void *b) +{ + return ltree_compare(((const RIX *) a)->r, + ((const RIX *) b)->r); +} + + +Datum +ltree_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + int siglen = LTREE_GET_SIGLEN(); + OffsetNumber j; + int32 i; + RIX *array; + OffsetNumber maxoff; + int nbytes; + ltree *lu_l, + *lu_r, + *ru_l, + *ru_r; + ltree_gist *lu, + *ru; + BITVECP ls = palloc0(siglen), + rs = palloc0(siglen); + bool lisat = false, + risat = false; + + maxoff = entryvec->n - 1; + nbytes = (maxoff + 2) * sizeof(OffsetNumber); + v->spl_left = (OffsetNumber *) palloc(nbytes); + v->spl_right = (OffsetNumber *) palloc(nbytes); + v->spl_nleft = 0; + v->spl_nright = 0; + array = (RIX *) palloc(sizeof(RIX) * (maxoff + 1)); + + /* copy the data into RIXes, and sort the RIXes */ + for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) + { + array[j].index = j; + lu = GETENTRY(entryvec, j); /* use as tmp val */ + array[j].r = LTG_GETLNODE(lu, siglen); + } + + qsort(&array[FirstOffsetNumber], maxoff - FirstOffsetNumber + 1, + sizeof(RIX), treekey_cmp); + + lu_l = lu_r = ru_l = ru_r = NULL; + for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) + { + lu = GETENTRY(entryvec, array[j].index); /* use as tmp val */ + if (j <= (maxoff - FirstOffsetNumber + 1) / 2) + { + v->spl_left[v->spl_nleft] = array[j].index; + v->spl_nleft++; + if (lu_r == NULL || ltree_compare(LTG_GETRNODE(lu, siglen), lu_r) > 0) + lu_r = LTG_GETRNODE(lu, siglen); + if (LTG_ISONENODE(lu)) + hashing(ls, LTG_NODE(lu), siglen); + else + { + if (lisat || LTG_ISALLTRUE(lu)) + lisat = true; + else + { + BITVECP sc = LTG_SIGN(lu); + + LOOPBYTE(siglen) + ((unsigned char *) ls)[i] |= sc[i]; + } + } + } + else + { + v->spl_right[v->spl_nright] = array[j].index; + v->spl_nright++; + if (ru_r == NULL || ltree_compare(LTG_GETRNODE(lu, siglen), ru_r) > 0) + ru_r = LTG_GETRNODE(lu, siglen); + if (LTG_ISONENODE(lu)) + hashing(rs, LTG_NODE(lu), siglen); + else + { + if (risat || LTG_ISALLTRUE(lu)) + risat = true; + else + { + BITVECP sc = LTG_SIGN(lu); + + LOOPBYTE(siglen) + ((unsigned char *) rs)[i] |= sc[i]; + } + } + } + } + + if (lisat == false) + { + lisat = true; + LOOPBYTE(siglen) + { + if (((unsigned char *) ls)[i] != 0xff) + { + lisat = false; + break; + } + } + } + + if (risat == false) + { + risat = true; + LOOPBYTE(siglen) + { + if (((unsigned char *) rs)[i] != 0xff) + { + risat = false; + break; + } + } + } + + lu_l = LTG_GETLNODE(GETENTRY(entryvec, array[FirstOffsetNumber].index), siglen); + lu = ltree_gist_alloc(lisat, ls, siglen, lu_l, lu_r); + + ru_l = LTG_GETLNODE(GETENTRY(entryvec, array[1 + ((maxoff - FirstOffsetNumber + 1) / 2)].index), siglen); + ru = ltree_gist_alloc(risat, rs, siglen, ru_l, ru_r); + + pfree(ls); + pfree(rs); + + v->spl_ldatum = PointerGetDatum(lu); + v->spl_rdatum = PointerGetDatum(ru); + + PG_RETURN_POINTER(v); +} + +static bool +gist_isparent(ltree_gist *key, ltree *query, int siglen) +{ + int32 numlevel = query->numlevel; + int i; + + for (i = query->numlevel; i >= 0; i--) + { + query->numlevel = i; + if (ltree_compare(query, LTG_GETLNODE(key, siglen)) >= 0 && + ltree_compare(query, LTG_GETRNODE(key, siglen)) <= 0) + { + query->numlevel = numlevel; + return true; + } + } + + query->numlevel = numlevel; + return false; +} + +static ltree * +copy_ltree(ltree *src) +{ + ltree *dst = (ltree *) palloc0(VARSIZE(src)); + + memcpy(dst, src, VARSIZE(src)); + return dst; +} + +static bool +gist_ischild(ltree_gist *key, ltree *query, int siglen) +{ + ltree *left = copy_ltree(LTG_GETLNODE(key, siglen)); + ltree *right = copy_ltree(LTG_GETRNODE(key, siglen)); + bool res = true; + + if (left->numlevel > query->numlevel) + left->numlevel = query->numlevel; + + if (ltree_compare(query, left) < 0) + res = false; + + if (right->numlevel > query->numlevel) + right->numlevel = query->numlevel; + + if (res && ltree_compare(query, right) > 0) + res = false; + + pfree(left); + pfree(right); + + return res; +} + +static bool +gist_qe(ltree_gist *key, lquery *query, int siglen) +{ + lquery_level *curq = LQUERY_FIRST(query); + BITVECP sign = LTG_SIGN(key); + int qlen = query->numlevel; + + if (LTG_ISALLTRUE(key)) + return true; + + while (qlen > 0) + { + if (curq->numvar && LQL_CANLOOKSIGN(curq)) + { + bool isexist = false; + int vlen = curq->numvar; + lquery_variant *curv = LQL_FIRST(curq); + + while (vlen > 0) + { + if (GETBIT(sign, HASHVAL(curv->val, siglen))) + { + isexist = true; + break; + } + curv = LVAR_NEXT(curv); + vlen--; + } + if (!isexist) + return false; + } + + curq = LQL_NEXT(curq); + qlen--; + } + + return true; +} + +static int +gist_tqcmp(ltree *t, lquery *q) +{ + ltree_level *al = LTREE_FIRST(t); + lquery_level *ql = LQUERY_FIRST(q); + lquery_variant *bl; + int an = t->numlevel; + int bn = q->firstgood; + int res = 0; + + while (an > 0 && bn > 0) + { + bl = LQL_FIRST(ql); + if ((res = memcmp(al->name, bl->name, Min(al->len, bl->len))) == 0) + { + if (al->len != bl->len) + return al->len - bl->len; + } + else + return res; + an--; + bn--; + al = LEVEL_NEXT(al); + ql = LQL_NEXT(ql); + } + + return Min(t->numlevel, q->firstgood) - q->firstgood; +} + +static bool +gist_between(ltree_gist *key, lquery *query, int siglen) +{ + if (query->firstgood == 0) + return true; + + if (gist_tqcmp(LTG_GETLNODE(key, siglen), query) > 0) + return false; + + if (gist_tqcmp(LTG_GETRNODE(key, siglen), query) < 0) + return false; + + return true; +} + +typedef struct LtreeSignature +{ + BITVECP sign; + int siglen; +} LtreeSignature; + +static bool +checkcondition_bit(void *cxt, ITEM *val) +{ + LtreeSignature *sig = cxt; + + return (FLG_CANLOOKSIGN(val->flag)) ? GETBIT(sig->sign, HASHVAL(val->val, sig->siglen)) : true; +} + +static bool +gist_qtxt(ltree_gist *key, ltxtquery *query, int siglen) +{ + LtreeSignature sig; + + if (LTG_ISALLTRUE(key)) + return true; + + sig.sign = LTG_SIGN(key); + sig.siglen = siglen; + + return ltree_execute(GETQUERY(query), + &sig, false, + checkcondition_bit); +} + +static bool +arrq_cons(ltree_gist *key, ArrayType *_query, int siglen) +{ + lquery *query = (lquery *) ARR_DATA_PTR(_query); + int num = ArrayGetNItems(ARR_NDIM(_query), ARR_DIMS(_query)); + + if (ARR_NDIM(_query) > 1) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("array must be one-dimensional"))); + if (array_contains_nulls(_query)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array must not contain nulls"))); + + while (num > 0) + { + if (gist_qe(key, query, siglen) && gist_between(key, query, siglen)) + return true; + num--; + query = NEXTVAL(query); + } + return false; +} + +Datum +ltree_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + int siglen = LTREE_GET_SIGLEN(); + ltree_gist *key = (ltree_gist *) DatumGetPointer(entry->key); + void *query = NULL; + bool res = false; + + /* All cases served by this function are exact */ + *recheck = false; + + switch (strategy) + { + case BTLessStrategyNumber: + query = PG_GETARG_LTREE_P(1); + res = (GIST_LEAF(entry)) ? + (ltree_compare((ltree *) query, LTG_NODE(key)) > 0) + : + (ltree_compare((ltree *) query, LTG_GETLNODE(key, siglen)) >= 0); + break; + case BTLessEqualStrategyNumber: + query = PG_GETARG_LTREE_P(1); + res = (ltree_compare((ltree *) query, LTG_GETLNODE(key, siglen)) >= 0); + break; + case BTEqualStrategyNumber: + query = PG_GETARG_LTREE_P(1); + if (GIST_LEAF(entry)) + res = (ltree_compare((ltree *) query, LTG_NODE(key)) == 0); + else + res = (ltree_compare((ltree *) query, LTG_GETLNODE(key, siglen)) >= 0 + && + ltree_compare((ltree *) query, LTG_GETRNODE(key, siglen)) <= 0); + break; + case BTGreaterEqualStrategyNumber: + query = PG_GETARG_LTREE_P(1); + res = (ltree_compare((ltree *) query, LTG_GETRNODE(key, siglen)) <= 0); + break; + case BTGreaterStrategyNumber: + query = PG_GETARG_LTREE_P(1); + res = (GIST_LEAF(entry)) ? + (ltree_compare((ltree *) query, LTG_GETRNODE(key, siglen)) < 0) + : + (ltree_compare((ltree *) query, LTG_GETRNODE(key, siglen)) <= 0); + break; + case 10: + query = PG_GETARG_LTREE_P_COPY(1); + res = (GIST_LEAF(entry)) ? + inner_isparent((ltree *) query, LTG_NODE(key)) + : + gist_isparent(key, (ltree *) query, siglen); + break; + case 11: + query = PG_GETARG_LTREE_P(1); + res = (GIST_LEAF(entry)) ? + inner_isparent(LTG_NODE(key), (ltree *) query) + : + gist_ischild(key, (ltree *) query, siglen); + break; + case 12: + case 13: + query = PG_GETARG_LQUERY_P(1); + if (GIST_LEAF(entry)) + res = DatumGetBool(DirectFunctionCall2(ltq_regex, + PointerGetDatum(LTG_NODE(key)), + PointerGetDatum((lquery *) query) + )); + else + res = (gist_qe(key, (lquery *) query, siglen) && + gist_between(key, (lquery *) query, siglen)); + break; + case 14: + case 15: + query = PG_GETARG_LTXTQUERY_P(1); + if (GIST_LEAF(entry)) + res = DatumGetBool(DirectFunctionCall2(ltxtq_exec, + PointerGetDatum(LTG_NODE(key)), + PointerGetDatum((ltxtquery *) query) + )); + else + res = gist_qtxt(key, (ltxtquery *) query, siglen); + break; + case 16: + case 17: + query = PG_GETARG_ARRAYTYPE_P(1); + if (GIST_LEAF(entry)) + res = DatumGetBool(DirectFunctionCall2(lt_q_regex, + PointerGetDatum(LTG_NODE(key)), + PointerGetDatum((ArrayType *) query) + )); + else + res = arrq_cons(key, (ArrayType *) query, siglen); + break; + default: + /* internal error */ + elog(ERROR, "unrecognized StrategyNumber: %d", strategy); + } + + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(res); +} + +static void +ltree_gist_relopts_validator(void *parsed_options, relopt_value *vals, + int nvals) +{ + LtreeGistOptions *options = (LtreeGistOptions *) parsed_options; + + if (options->siglen != INTALIGN(options->siglen)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("siglen value must be a multiple of %d", ALIGNOF_INT))); +} + +Datum +ltree_gist_options(PG_FUNCTION_ARGS) +{ + local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0); + + init_local_reloptions(relopts, sizeof(LtreeGistOptions)); + add_local_int_reloption(relopts, "siglen", + "signature length in bytes", + LTREE_SIGLEN_DEFAULT, + INTALIGN(1), + LTREE_SIGLEN_MAX, + offsetof(LtreeGistOptions, siglen)); + register_reloptions_validator(relopts, ltree_gist_relopts_validator); + + PG_RETURN_VOID(); +} diff --git a/contrib/ltree/ltree_io.c b/contrib/ltree/ltree_io.c new file mode 100644 index 0000000..0a12c77 --- /dev/null +++ b/contrib/ltree/ltree_io.c @@ -0,0 +1,816 @@ +/* + * in/out function for ltree and lquery + * Teodor Sigaev + * contrib/ltree/ltree_io.c + */ +#include "postgres.h" + +#include + +#include "crc32.h" +#include "libpq/pqformat.h" +#include "ltree.h" +#include "utils/memutils.h" +#include "varatt.h" + + +typedef struct +{ + const char *start; + int len; /* length in bytes */ + int flag; + int wlen; /* length in characters */ +} nodeitem; + +#define LTPRS_WAITNAME 0 +#define LTPRS_WAITDELIM 1 + +static bool finish_nodeitem(nodeitem *lptr, const char *ptr, + bool is_lquery, int pos, struct Node *escontext); + + +/* + * expects a null terminated string + * returns an ltree + */ +static ltree * +parse_ltree(const char *buf, struct Node *escontext) +{ + const char *ptr; + nodeitem *list, + *lptr; + int num = 0, + totallen = 0; + int state = LTPRS_WAITNAME; + ltree *result; + ltree_level *curlevel; + int charlen; + int pos = 1; /* character position for error messages */ + +#define UNCHAR ereturn(escontext, NULL,\ + errcode(ERRCODE_SYNTAX_ERROR), \ + errmsg("ltree syntax error at character %d", \ + pos)) + + ptr = buf; + while (*ptr) + { + charlen = pg_mblen(ptr); + if (t_iseq(ptr, '.')) + num++; + ptr += charlen; + } + + if (num + 1 > LTREE_MAX_LEVELS) + ereturn(escontext, NULL, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of ltree labels (%d) exceeds the maximum allowed (%d)", + num + 1, LTREE_MAX_LEVELS))); + list = lptr = (nodeitem *) palloc(sizeof(nodeitem) * (num + 1)); + ptr = buf; + while (*ptr) + { + charlen = pg_mblen(ptr); + + switch (state) + { + case LTPRS_WAITNAME: + if (ISLABEL(ptr)) + { + lptr->start = ptr; + lptr->wlen = 0; + state = LTPRS_WAITDELIM; + } + else + UNCHAR; + break; + case LTPRS_WAITDELIM: + if (t_iseq(ptr, '.')) + { + if (!finish_nodeitem(lptr, ptr, false, pos, escontext)) + return NULL; + totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE); + lptr++; + state = LTPRS_WAITNAME; + } + else if (!ISLABEL(ptr)) + UNCHAR; + break; + default: + elog(ERROR, "internal error in ltree parser"); + } + + ptr += charlen; + lptr->wlen++; + pos++; + } + + if (state == LTPRS_WAITDELIM) + { + if (!finish_nodeitem(lptr, ptr, false, pos, escontext)) + return NULL; + totallen += MAXALIGN(lptr->len + LEVEL_HDRSIZE); + lptr++; + } + else if (!(state == LTPRS_WAITNAME && lptr == list)) + ereturn(escontext, NULL, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ltree syntax error"), + errdetail("Unexpected end of input."))); + + result = (ltree *) palloc0(LTREE_HDRSIZE + totallen); + SET_VARSIZE(result, LTREE_HDRSIZE + totallen); + result->numlevel = lptr - list; + curlevel = LTREE_FIRST(result); + lptr = list; + while (lptr - list < result->numlevel) + { + curlevel->len = (uint16) lptr->len; + memcpy(curlevel->name, lptr->start, lptr->len); + curlevel = LEVEL_NEXT(curlevel); + lptr++; + } + + pfree(list); + return result; + +#undef UNCHAR +} + +/* + * expects an ltree + * returns a null terminated string + */ +static char * +deparse_ltree(const ltree *in) +{ + char *buf, + *ptr; + int i; + ltree_level *curlevel; + + ptr = buf = (char *) palloc(VARSIZE(in)); + curlevel = LTREE_FIRST(in); + for (i = 0; i < in->numlevel; i++) + { + if (i != 0) + { + *ptr = '.'; + ptr++; + } + memcpy(ptr, curlevel->name, curlevel->len); + ptr += curlevel->len; + curlevel = LEVEL_NEXT(curlevel); + } + + *ptr = '\0'; + return buf; +} + +/* + * Basic ltree I/O functions + */ +PG_FUNCTION_INFO_V1(ltree_in); +Datum +ltree_in(PG_FUNCTION_ARGS) +{ + char *buf = (char *) PG_GETARG_POINTER(0); + ltree *res; + + if ((res = parse_ltree(buf, fcinfo->context)) == NULL) + PG_RETURN_NULL(); + + PG_RETURN_POINTER(res); +} + +PG_FUNCTION_INFO_V1(ltree_out); +Datum +ltree_out(PG_FUNCTION_ARGS) +{ + ltree *in = PG_GETARG_LTREE_P(0); + + PG_RETURN_POINTER(deparse_ltree(in)); +} + +/* + * ltree type send function + * + * The type is sent as text in binary mode, so this is almost the same + * as the output function, but it's prefixed with a version number so we + * can change the binary format sent in future if necessary. For now, + * only version 1 is supported. + */ +PG_FUNCTION_INFO_V1(ltree_send); +Datum +ltree_send(PG_FUNCTION_ARGS) +{ + ltree *in = PG_GETARG_LTREE_P(0); + StringInfoData buf; + int version = 1; + char *res = deparse_ltree(in); + + pq_begintypsend(&buf); + pq_sendint8(&buf, version); + pq_sendtext(&buf, res, strlen(res)); + pfree(res); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * ltree type recv function + * + * The type is sent as text in binary mode, so this is almost the same + * as the input function, but it's prefixed with a version number so we + * can change the binary format sent in future if necessary. For now, + * only version 1 is supported. + */ +PG_FUNCTION_INFO_V1(ltree_recv); +Datum +ltree_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + int version = pq_getmsgint(buf, 1); + char *str; + int nbytes; + ltree *res; + + if (version != 1) + elog(ERROR, "unsupported ltree version number %d", version); + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + res = parse_ltree(str, NULL); + pfree(str); + + PG_RETURN_POINTER(res); +} + + +#define LQPRS_WAITLEVEL 0 +#define LQPRS_WAITDELIM 1 +#define LQPRS_WAITOPEN 2 +#define LQPRS_WAITFNUM 3 +#define LQPRS_WAITSNUM 4 +#define LQPRS_WAITND 5 +#define LQPRS_WAITCLOSE 6 +#define LQPRS_WAITEND 7 +#define LQPRS_WAITVAR 8 + + +#define GETVAR(x) ( *((nodeitem**)LQL_FIRST(x)) ) +#define ITEMSIZE MAXALIGN(LQL_HDRSIZE+sizeof(nodeitem*)) +#define NEXTLEV(x) ( (lquery_level*)( ((char*)(x)) + ITEMSIZE) ) + +/* + * expects a null terminated string + * returns an lquery + */ +static lquery * +parse_lquery(const char *buf, struct Node *escontext) +{ + const char *ptr; + int num = 0, + totallen = 0, + numOR = 0; + int state = LQPRS_WAITLEVEL; + lquery *result; + nodeitem *lptr = NULL; + lquery_level *cur, + *curqlevel, + *tmpql; + lquery_variant *lrptr = NULL; + bool hasnot = false; + bool wasbad = false; + int charlen; + int pos = 1; /* character position for error messages */ + +#define UNCHAR ereturn(escontext, NULL,\ + errcode(ERRCODE_SYNTAX_ERROR), \ + errmsg("lquery syntax error at character %d", \ + pos)) + + ptr = buf; + while (*ptr) + { + charlen = pg_mblen(ptr); + + if (t_iseq(ptr, '.')) + num++; + else if (t_iseq(ptr, '|')) + numOR++; + + ptr += charlen; + } + + num++; + if (num > LQUERY_MAX_LEVELS) + ereturn(escontext, NULL, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of lquery items (%d) exceeds the maximum allowed (%d)", + num, LQUERY_MAX_LEVELS))); + curqlevel = tmpql = (lquery_level *) palloc0(ITEMSIZE * num); + ptr = buf; + while (*ptr) + { + charlen = pg_mblen(ptr); + + switch (state) + { + case LQPRS_WAITLEVEL: + if (ISLABEL(ptr)) + { + GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1)); + lptr->start = ptr; + state = LQPRS_WAITDELIM; + curqlevel->numvar = 1; + } + else if (t_iseq(ptr, '!')) + { + GETVAR(curqlevel) = lptr = (nodeitem *) palloc0(sizeof(nodeitem) * (numOR + 1)); + lptr->start = ptr + 1; + lptr->wlen = -1; /* compensate for counting ! below */ + state = LQPRS_WAITDELIM; + curqlevel->numvar = 1; + curqlevel->flag |= LQL_NOT; + hasnot = true; + } + else if (t_iseq(ptr, '*')) + state = LQPRS_WAITOPEN; + else + UNCHAR; + break; + case LQPRS_WAITVAR: + if (ISLABEL(ptr)) + { + lptr++; + lptr->start = ptr; + state = LQPRS_WAITDELIM; + curqlevel->numvar++; + } + else + UNCHAR; + break; + case LQPRS_WAITDELIM: + if (t_iseq(ptr, '@')) + { + lptr->flag |= LVAR_INCASE; + curqlevel->flag |= LVAR_INCASE; + } + else if (t_iseq(ptr, '*')) + { + lptr->flag |= LVAR_ANYEND; + curqlevel->flag |= LVAR_ANYEND; + } + else if (t_iseq(ptr, '%')) + { + lptr->flag |= LVAR_SUBLEXEME; + curqlevel->flag |= LVAR_SUBLEXEME; + } + else if (t_iseq(ptr, '|')) + { + if (!finish_nodeitem(lptr, ptr, true, pos, escontext)) + return NULL; + state = LQPRS_WAITVAR; + } + else if (t_iseq(ptr, '{')) + { + if (!finish_nodeitem(lptr, ptr, true, pos, escontext)) + return NULL; + curqlevel->flag |= LQL_COUNT; + state = LQPRS_WAITFNUM; + } + else if (t_iseq(ptr, '.')) + { + if (!finish_nodeitem(lptr, ptr, true, pos, escontext)) + return NULL; + state = LQPRS_WAITLEVEL; + curqlevel = NEXTLEV(curqlevel); + } + else if (ISLABEL(ptr)) + { + /* disallow more chars after a flag */ + if (lptr->flag) + UNCHAR; + } + else + UNCHAR; + break; + case LQPRS_WAITOPEN: + if (t_iseq(ptr, '{')) + state = LQPRS_WAITFNUM; + else if (t_iseq(ptr, '.')) + { + /* We only get here for '*', so these are correct defaults */ + curqlevel->low = 0; + curqlevel->high = LTREE_MAX_LEVELS; + curqlevel = NEXTLEV(curqlevel); + state = LQPRS_WAITLEVEL; + } + else + UNCHAR; + break; + case LQPRS_WAITFNUM: + if (t_iseq(ptr, ',')) + state = LQPRS_WAITSNUM; + else if (t_isdigit(ptr)) + { + int low = atoi(ptr); + + if (low < 0 || low > LTREE_MAX_LEVELS) + ereturn(escontext, NULL, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("lquery syntax error"), + errdetail("Low limit (%d) exceeds the maximum allowed (%d), at character %d.", + low, LTREE_MAX_LEVELS, pos))); + + curqlevel->low = (uint16) low; + state = LQPRS_WAITND; + } + else + UNCHAR; + break; + case LQPRS_WAITSNUM: + if (t_isdigit(ptr)) + { + int high = atoi(ptr); + + if (high < 0 || high > LTREE_MAX_LEVELS) + ereturn(escontext, NULL, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("lquery syntax error"), + errdetail("High limit (%d) exceeds the maximum allowed (%d), at character %d.", + high, LTREE_MAX_LEVELS, pos))); + else if (curqlevel->low > high) + ereturn(escontext, NULL, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("lquery syntax error"), + errdetail("Low limit (%d) is greater than high limit (%d), at character %d.", + curqlevel->low, high, pos))); + + curqlevel->high = (uint16) high; + state = LQPRS_WAITCLOSE; + } + else if (t_iseq(ptr, '}')) + { + curqlevel->high = LTREE_MAX_LEVELS; + state = LQPRS_WAITEND; + } + else + UNCHAR; + break; + case LQPRS_WAITCLOSE: + if (t_iseq(ptr, '}')) + state = LQPRS_WAITEND; + else if (!t_isdigit(ptr)) + UNCHAR; + break; + case LQPRS_WAITND: + if (t_iseq(ptr, '}')) + { + curqlevel->high = curqlevel->low; + state = LQPRS_WAITEND; + } + else if (t_iseq(ptr, ',')) + state = LQPRS_WAITSNUM; + else if (!t_isdigit(ptr)) + UNCHAR; + break; + case LQPRS_WAITEND: + if (t_iseq(ptr, '.')) + { + state = LQPRS_WAITLEVEL; + curqlevel = NEXTLEV(curqlevel); + } + else + UNCHAR; + break; + default: + elog(ERROR, "internal error in lquery parser"); + } + + ptr += charlen; + if (state == LQPRS_WAITDELIM) + lptr->wlen++; + pos++; + } + + if (state == LQPRS_WAITDELIM) + { + if (!finish_nodeitem(lptr, ptr, true, pos, escontext)) + return NULL; + } + else if (state == LQPRS_WAITOPEN) + curqlevel->high = LTREE_MAX_LEVELS; + else if (state != LQPRS_WAITEND) + ereturn(escontext, NULL, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("lquery syntax error"), + errdetail("Unexpected end of input."))); + + curqlevel = tmpql; + totallen = LQUERY_HDRSIZE; + while ((char *) curqlevel - (char *) tmpql < num * ITEMSIZE) + { + totallen += LQL_HDRSIZE; + if (curqlevel->numvar) + { + lptr = GETVAR(curqlevel); + while (lptr - GETVAR(curqlevel) < curqlevel->numvar) + { + totallen += MAXALIGN(LVAR_HDRSIZE + lptr->len); + lptr++; + } + } + curqlevel = NEXTLEV(curqlevel); + } + + result = (lquery *) palloc0(totallen); + SET_VARSIZE(result, totallen); + result->numlevel = num; + result->firstgood = 0; + result->flag = 0; + if (hasnot) + result->flag |= LQUERY_HASNOT; + cur = LQUERY_FIRST(result); + curqlevel = tmpql; + while ((char *) curqlevel - (char *) tmpql < num * ITEMSIZE) + { + memcpy(cur, curqlevel, LQL_HDRSIZE); + cur->totallen = LQL_HDRSIZE; + if (curqlevel->numvar) + { + lrptr = LQL_FIRST(cur); + lptr = GETVAR(curqlevel); + while (lptr - GETVAR(curqlevel) < curqlevel->numvar) + { + cur->totallen += MAXALIGN(LVAR_HDRSIZE + lptr->len); + lrptr->len = lptr->len; + lrptr->flag = lptr->flag; + lrptr->val = ltree_crc32_sz(lptr->start, lptr->len); + memcpy(lrptr->name, lptr->start, lptr->len); + lptr++; + lrptr = LVAR_NEXT(lrptr); + } + pfree(GETVAR(curqlevel)); + if (cur->numvar > 1 || cur->flag != 0) + { + /* Not a simple match */ + wasbad = true; + } + else if (wasbad == false) + { + /* count leading simple matches */ + (result->firstgood)++; + } + } + else + { + /* '*', so this isn't a simple match */ + wasbad = true; + } + curqlevel = NEXTLEV(curqlevel); + cur = LQL_NEXT(cur); + } + + pfree(tmpql); + return result; + +#undef UNCHAR +} + +/* + * Close out parsing an ltree or lquery nodeitem: + * compute the correct length, and complain if it's not OK + */ +static bool +finish_nodeitem(nodeitem *lptr, const char *ptr, bool is_lquery, int pos, + struct Node *escontext) +{ + if (is_lquery) + { + /* + * Back up over any flag characters, and discount them from length and + * position. + */ + while (ptr > lptr->start && strchr("@*%", ptr[-1]) != NULL) + { + ptr--; + lptr->wlen--; + pos--; + } + } + + /* Now compute the byte length, which we weren't tracking before. */ + lptr->len = ptr - lptr->start; + + /* Complain if it's empty or too long */ + if (lptr->len == 0) + ereturn(escontext, false, + (errcode(ERRCODE_SYNTAX_ERROR), + is_lquery ? + errmsg("lquery syntax error at character %d", pos) : + errmsg("ltree syntax error at character %d", pos), + errdetail("Empty labels are not allowed."))); + if (lptr->wlen > LTREE_LABEL_MAX_CHARS) + ereturn(escontext, false, + (errcode(ERRCODE_NAME_TOO_LONG), + errmsg("label string is too long"), + errdetail("Label length is %d, must be at most %d, at character %d.", + lptr->wlen, LTREE_LABEL_MAX_CHARS, pos))); + return true; +} + +/* + * expects an lquery + * returns a null terminated string + */ +static char * +deparse_lquery(const lquery *in) +{ + char *buf, + *ptr; + int i, + j, + totallen = 1; + lquery_level *curqlevel; + lquery_variant *curtlevel; + + curqlevel = LQUERY_FIRST(in); + for (i = 0; i < in->numlevel; i++) + { + totallen++; + if (curqlevel->numvar) + { + totallen += 1 + (curqlevel->numvar * 4) + curqlevel->totallen; + if (curqlevel->flag & LQL_COUNT) + totallen += 2 * 11 + 3; + } + else + totallen += 2 * 11 + 4; + curqlevel = LQL_NEXT(curqlevel); + } + + ptr = buf = (char *) palloc(totallen); + curqlevel = LQUERY_FIRST(in); + for (i = 0; i < in->numlevel; i++) + { + if (i != 0) + { + *ptr = '.'; + ptr++; + } + if (curqlevel->numvar) + { + if (curqlevel->flag & LQL_NOT) + { + *ptr = '!'; + ptr++; + } + curtlevel = LQL_FIRST(curqlevel); + for (j = 0; j < curqlevel->numvar; j++) + { + if (j != 0) + { + *ptr = '|'; + ptr++; + } + memcpy(ptr, curtlevel->name, curtlevel->len); + ptr += curtlevel->len; + if ((curtlevel->flag & LVAR_SUBLEXEME)) + { + *ptr = '%'; + ptr++; + } + if ((curtlevel->flag & LVAR_INCASE)) + { + *ptr = '@'; + ptr++; + } + if ((curtlevel->flag & LVAR_ANYEND)) + { + *ptr = '*'; + ptr++; + } + curtlevel = LVAR_NEXT(curtlevel); + } + } + else + { + *ptr = '*'; + ptr++; + } + + if ((curqlevel->flag & LQL_COUNT) || curqlevel->numvar == 0) + { + if (curqlevel->low == curqlevel->high) + { + sprintf(ptr, "{%d}", curqlevel->low); + } + else if (curqlevel->low == 0) + { + if (curqlevel->high == LTREE_MAX_LEVELS) + { + if (curqlevel->numvar == 0) + { + /* This is default for '*', so print nothing */ + *ptr = '\0'; + } + else + sprintf(ptr, "{,}"); + } + else + sprintf(ptr, "{,%d}", curqlevel->high); + } + else if (curqlevel->high == LTREE_MAX_LEVELS) + { + sprintf(ptr, "{%d,}", curqlevel->low); + } + else + sprintf(ptr, "{%d,%d}", curqlevel->low, curqlevel->high); + ptr = strchr(ptr, '\0'); + } + + curqlevel = LQL_NEXT(curqlevel); + } + + *ptr = '\0'; + return buf; +} + +/* + * Basic lquery I/O functions + */ +PG_FUNCTION_INFO_V1(lquery_in); +Datum +lquery_in(PG_FUNCTION_ARGS) +{ + char *buf = (char *) PG_GETARG_POINTER(0); + lquery *res; + + if ((res = parse_lquery(buf, fcinfo->context)) == NULL) + PG_RETURN_NULL(); + + PG_RETURN_POINTER(res); +} + +PG_FUNCTION_INFO_V1(lquery_out); +Datum +lquery_out(PG_FUNCTION_ARGS) +{ + lquery *in = PG_GETARG_LQUERY_P(0); + + PG_RETURN_POINTER(deparse_lquery(in)); +} + +/* + * lquery type send function + * + * The type is sent as text in binary mode, so this is almost the same + * as the output function, but it's prefixed with a version number so we + * can change the binary format sent in future if necessary. For now, + * only version 1 is supported. + */ +PG_FUNCTION_INFO_V1(lquery_send); +Datum +lquery_send(PG_FUNCTION_ARGS) +{ + lquery *in = PG_GETARG_LQUERY_P(0); + StringInfoData buf; + int version = 1; + char *res = deparse_lquery(in); + + pq_begintypsend(&buf); + pq_sendint8(&buf, version); + pq_sendtext(&buf, res, strlen(res)); + pfree(res); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} + +/* + * lquery type recv function + * + * The type is sent as text in binary mode, so this is almost the same + * as the input function, but it's prefixed with a version number so we + * can change the binary format sent in future if necessary. For now, + * only version 1 is supported. + */ +PG_FUNCTION_INFO_V1(lquery_recv); +Datum +lquery_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + int version = pq_getmsgint(buf, 1); + char *str; + int nbytes; + lquery *res; + + if (version != 1) + elog(ERROR, "unsupported lquery version number %d", version); + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + res = parse_lquery(str, NULL); + pfree(str); + + PG_RETURN_POINTER(res); +} diff --git a/contrib/ltree/ltree_op.c b/contrib/ltree/ltree_op.c new file mode 100644 index 0000000..da1db5f --- /dev/null +++ b/contrib/ltree/ltree_op.c @@ -0,0 +1,590 @@ +/* + * op function for ltree + * Teodor Sigaev + * contrib/ltree/ltree_op.c + */ +#include "postgres.h" + +#include + +#include "access/htup_details.h" +#include "catalog/pg_statistic.h" +#include "ltree.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/selfuncs.h" + +PG_MODULE_MAGIC; + +/* compare functions */ +PG_FUNCTION_INFO_V1(ltree_cmp); +PG_FUNCTION_INFO_V1(ltree_lt); +PG_FUNCTION_INFO_V1(ltree_le); +PG_FUNCTION_INFO_V1(ltree_eq); +PG_FUNCTION_INFO_V1(ltree_ne); +PG_FUNCTION_INFO_V1(ltree_ge); +PG_FUNCTION_INFO_V1(ltree_gt); +PG_FUNCTION_INFO_V1(nlevel); +PG_FUNCTION_INFO_V1(ltree_isparent); +PG_FUNCTION_INFO_V1(ltree_risparent); +PG_FUNCTION_INFO_V1(subltree); +PG_FUNCTION_INFO_V1(subpath); +PG_FUNCTION_INFO_V1(ltree_index); +PG_FUNCTION_INFO_V1(ltree_addltree); +PG_FUNCTION_INFO_V1(ltree_addtext); +PG_FUNCTION_INFO_V1(ltree_textadd); +PG_FUNCTION_INFO_V1(lca); +PG_FUNCTION_INFO_V1(ltree2text); +PG_FUNCTION_INFO_V1(text2ltree); +PG_FUNCTION_INFO_V1(ltreeparentsel); + +int +ltree_compare(const ltree *a, const ltree *b) +{ + ltree_level *al = LTREE_FIRST(a); + ltree_level *bl = LTREE_FIRST(b); + int an = a->numlevel; + int bn = b->numlevel; + + while (an > 0 && bn > 0) + { + int res; + + if ((res = memcmp(al->name, bl->name, Min(al->len, bl->len))) == 0) + { + if (al->len != bl->len) + return (al->len - bl->len) * 10 * (an + 1); + } + else + { + if (res < 0) + res = -1; + else + res = 1; + return res * 10 * (an + 1); + } + + an--; + bn--; + al = LEVEL_NEXT(al); + bl = LEVEL_NEXT(bl); + } + + return (a->numlevel - b->numlevel) * 10 * (an + 1); +} + +#define RUNCMP \ +ltree *a = PG_GETARG_LTREE_P(0); \ +ltree *b = PG_GETARG_LTREE_P(1); \ +int res = ltree_compare(a,b); \ +PG_FREE_IF_COPY(a,0); \ +PG_FREE_IF_COPY(b,1) + +Datum +ltree_cmp(PG_FUNCTION_ARGS) +{ + RUNCMP; + PG_RETURN_INT32(res); +} + +Datum +ltree_lt(PG_FUNCTION_ARGS) +{ + RUNCMP; + PG_RETURN_BOOL(res < 0); +} + +Datum +ltree_le(PG_FUNCTION_ARGS) +{ + RUNCMP; + PG_RETURN_BOOL(res <= 0); +} + +Datum +ltree_eq(PG_FUNCTION_ARGS) +{ + RUNCMP; + PG_RETURN_BOOL(res == 0); +} + +Datum +ltree_ge(PG_FUNCTION_ARGS) +{ + RUNCMP; + PG_RETURN_BOOL(res >= 0); +} + +Datum +ltree_gt(PG_FUNCTION_ARGS) +{ + RUNCMP; + PG_RETURN_BOOL(res > 0); +} + +Datum +ltree_ne(PG_FUNCTION_ARGS) +{ + RUNCMP; + PG_RETURN_BOOL(res != 0); +} + +Datum +nlevel(PG_FUNCTION_ARGS) +{ + ltree *a = PG_GETARG_LTREE_P(0); + int res = a->numlevel; + + PG_FREE_IF_COPY(a, 0); + PG_RETURN_INT32(res); +} + +bool +inner_isparent(const ltree *c, const ltree *p) +{ + ltree_level *cl = LTREE_FIRST(c); + ltree_level *pl = LTREE_FIRST(p); + int pn = p->numlevel; + + if (pn > c->numlevel) + return false; + + while (pn > 0) + { + if (cl->len != pl->len) + return false; + if (memcmp(cl->name, pl->name, cl->len) != 0) + return false; + + pn--; + cl = LEVEL_NEXT(cl); + pl = LEVEL_NEXT(pl); + } + return true; +} + +Datum +ltree_isparent(PG_FUNCTION_ARGS) +{ + ltree *c = PG_GETARG_LTREE_P(1); + ltree *p = PG_GETARG_LTREE_P(0); + bool res = inner_isparent(c, p); + + PG_FREE_IF_COPY(c, 1); + PG_FREE_IF_COPY(p, 0); + PG_RETURN_BOOL(res); +} + +Datum +ltree_risparent(PG_FUNCTION_ARGS) +{ + ltree *c = PG_GETARG_LTREE_P(0); + ltree *p = PG_GETARG_LTREE_P(1); + bool res = inner_isparent(c, p); + + PG_FREE_IF_COPY(c, 0); + PG_FREE_IF_COPY(p, 1); + PG_RETURN_BOOL(res); +} + + +static ltree * +inner_subltree(ltree *t, int32 startpos, int32 endpos) +{ + char *start = NULL, + *end = NULL; + ltree_level *ptr = LTREE_FIRST(t); + ltree *res; + int i; + + if (startpos < 0 || endpos < 0 || startpos >= t->numlevel || startpos > endpos) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid positions"))); + + if (endpos > t->numlevel) + endpos = t->numlevel; + + start = end = (char *) ptr; + for (i = 0; i < endpos; i++) + { + if (i == startpos) + start = (char *) ptr; + if (i == endpos - 1) + { + end = (char *) LEVEL_NEXT(ptr); + break; + } + ptr = LEVEL_NEXT(ptr); + } + + res = (ltree *) palloc0(LTREE_HDRSIZE + (end - start)); + SET_VARSIZE(res, LTREE_HDRSIZE + (end - start)); + res->numlevel = endpos - startpos; + + memcpy(LTREE_FIRST(res), start, end - start); + + return res; +} + +Datum +subltree(PG_FUNCTION_ARGS) +{ + ltree *t = PG_GETARG_LTREE_P(0); + ltree *res = inner_subltree(t, PG_GETARG_INT32(1), PG_GETARG_INT32(2)); + + PG_FREE_IF_COPY(t, 0); + PG_RETURN_POINTER(res); +} + +Datum +subpath(PG_FUNCTION_ARGS) +{ + ltree *t = PG_GETARG_LTREE_P(0); + int32 start = PG_GETARG_INT32(1); + int32 len = (fcinfo->nargs == 3) ? PG_GETARG_INT32(2) : 0; + int32 end; + ltree *res; + + end = start + len; + + if (start < 0) + { + start = t->numlevel + start; + end = start + len; + } + if (start < 0) + { /* start > t->numlevel */ + start = t->numlevel + start; + end = start + len; + } + + if (len < 0) + end = t->numlevel + len; + else if (len == 0) + end = (fcinfo->nargs == 3) ? start : 0xffff; + + res = inner_subltree(t, start, end); + + PG_FREE_IF_COPY(t, 0); + PG_RETURN_POINTER(res); +} + +static ltree * +ltree_concat(ltree *a, ltree *b) +{ + ltree *r; + int numlevel = (int) a->numlevel + b->numlevel; + + if (numlevel > LTREE_MAX_LEVELS) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of ltree levels (%d) exceeds the maximum allowed (%d)", + numlevel, LTREE_MAX_LEVELS))); + + r = (ltree *) palloc0(VARSIZE(a) + VARSIZE(b) - LTREE_HDRSIZE); + SET_VARSIZE(r, VARSIZE(a) + VARSIZE(b) - LTREE_HDRSIZE); + r->numlevel = (uint16) numlevel; + + memcpy(LTREE_FIRST(r), LTREE_FIRST(a), VARSIZE(a) - LTREE_HDRSIZE); + memcpy(((char *) LTREE_FIRST(r)) + VARSIZE(a) - LTREE_HDRSIZE, + LTREE_FIRST(b), + VARSIZE(b) - LTREE_HDRSIZE); + return r; +} + +Datum +ltree_addltree(PG_FUNCTION_ARGS) +{ + ltree *a = PG_GETARG_LTREE_P(0); + ltree *b = PG_GETARG_LTREE_P(1); + ltree *r; + + r = ltree_concat(a, b); + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_POINTER(r); +} + +Datum +ltree_addtext(PG_FUNCTION_ARGS) +{ + ltree *a = PG_GETARG_LTREE_P(0); + text *b = PG_GETARG_TEXT_PP(1); + char *s; + ltree *r, + *tmp; + + s = text_to_cstring(b); + + tmp = (ltree *) DatumGetPointer(DirectFunctionCall1(ltree_in, + PointerGetDatum(s))); + + pfree(s); + + r = ltree_concat(a, tmp); + + pfree(tmp); + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_POINTER(r); +} + +Datum +ltree_index(PG_FUNCTION_ARGS) +{ + ltree *a = PG_GETARG_LTREE_P(0); + ltree *b = PG_GETARG_LTREE_P(1); + int start = (fcinfo->nargs == 3) ? PG_GETARG_INT32(2) : 0; + int i, + j; + ltree_level *startptr, + *aptr, + *bptr; + bool found = false; + + if (start < 0) + { + if (-start >= a->numlevel) + start = 0; + else + start = (int) (a->numlevel) + start; + } + + if (a->numlevel - start < b->numlevel || a->numlevel == 0 || b->numlevel == 0) + { + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_INT32(-1); + } + + startptr = LTREE_FIRST(a); + for (i = 0; i <= a->numlevel - b->numlevel; i++) + { + if (i >= start) + { + aptr = startptr; + bptr = LTREE_FIRST(b); + for (j = 0; j < b->numlevel; j++) + { + if (!(aptr->len == bptr->len && memcmp(aptr->name, bptr->name, aptr->len) == 0)) + break; + aptr = LEVEL_NEXT(aptr); + bptr = LEVEL_NEXT(bptr); + } + + if (j == b->numlevel) + { + found = true; + break; + } + } + startptr = LEVEL_NEXT(startptr); + } + + if (!found) + i = -1; + + PG_FREE_IF_COPY(a, 0); + PG_FREE_IF_COPY(b, 1); + PG_RETURN_INT32(i); +} + +Datum +ltree_textadd(PG_FUNCTION_ARGS) +{ + ltree *a = PG_GETARG_LTREE_P(1); + text *b = PG_GETARG_TEXT_PP(0); + char *s; + ltree *r, + *tmp; + + s = text_to_cstring(b); + + tmp = (ltree *) DatumGetPointer(DirectFunctionCall1(ltree_in, + PointerGetDatum(s))); + + pfree(s); + + r = ltree_concat(tmp, a); + + pfree(tmp); + + PG_FREE_IF_COPY(a, 1); + PG_FREE_IF_COPY(b, 0); + PG_RETURN_POINTER(r); +} + +/* + * Common code for variants of lca(), find longest common ancestor of inputs + * + * Returns NULL if there is no common ancestor, ie, the longest common + * prefix is empty. + */ +ltree * +lca_inner(ltree **a, int len) +{ + int tmp, + num, + i, + reslen; + ltree **ptr; + ltree_level *l1, + *l2; + ltree *res; + + if (len <= 0) + return NULL; /* no inputs? */ + if ((*a)->numlevel == 0) + return NULL; /* any empty input means NULL result */ + + /* num is the length of the longest common ancestor so far */ + num = (*a)->numlevel - 1; + + /* Compare each additional input to *a */ + ptr = a + 1; + while (ptr - a < len) + { + if ((*ptr)->numlevel == 0) + return NULL; + else if ((*ptr)->numlevel == 1) + num = 0; + else + { + l1 = LTREE_FIRST(*a); + l2 = LTREE_FIRST(*ptr); + tmp = Min(num, (*ptr)->numlevel - 1); + num = 0; + for (i = 0; i < tmp; i++) + { + if (l1->len == l2->len && + memcmp(l1->name, l2->name, l1->len) == 0) + num = i + 1; + else + break; + l1 = LEVEL_NEXT(l1); + l2 = LEVEL_NEXT(l2); + } + } + ptr++; + } + + /* Now compute size of result ... */ + reslen = LTREE_HDRSIZE; + l1 = LTREE_FIRST(*a); + for (i = 0; i < num; i++) + { + reslen += MAXALIGN(l1->len + LEVEL_HDRSIZE); + l1 = LEVEL_NEXT(l1); + } + + /* ... and construct it by copying from *a */ + res = (ltree *) palloc0(reslen); + SET_VARSIZE(res, reslen); + res->numlevel = num; + + l1 = LTREE_FIRST(*a); + l2 = LTREE_FIRST(res); + + for (i = 0; i < num; i++) + { + memcpy(l2, l1, MAXALIGN(l1->len + LEVEL_HDRSIZE)); + l1 = LEVEL_NEXT(l1); + l2 = LEVEL_NEXT(l2); + } + + return res; +} + +Datum +lca(PG_FUNCTION_ARGS) +{ + int i; + ltree **a, + *res; + + a = (ltree **) palloc(sizeof(ltree *) * fcinfo->nargs); + for (i = 0; i < fcinfo->nargs; i++) + a[i] = PG_GETARG_LTREE_P(i); + res = lca_inner(a, (int) fcinfo->nargs); + for (i = 0; i < fcinfo->nargs; i++) + PG_FREE_IF_COPY(a[i], i); + pfree(a); + + if (res) + PG_RETURN_POINTER(res); + else + PG_RETURN_NULL(); +} + +Datum +text2ltree(PG_FUNCTION_ARGS) +{ + text *in = PG_GETARG_TEXT_PP(0); + char *s; + ltree *out; + + s = text_to_cstring(in); + + out = (ltree *) DatumGetPointer(DirectFunctionCall1(ltree_in, + PointerGetDatum(s))); + pfree(s); + PG_FREE_IF_COPY(in, 0); + PG_RETURN_POINTER(out); +} + + +Datum +ltree2text(PG_FUNCTION_ARGS) +{ + ltree *in = PG_GETARG_LTREE_P(0); + char *ptr; + int i; + ltree_level *curlevel; + text *out; + + out = (text *) palloc(VARSIZE(in) + VARHDRSZ); + ptr = VARDATA(out); + curlevel = LTREE_FIRST(in); + for (i = 0; i < in->numlevel; i++) + { + if (i != 0) + { + *ptr = '.'; + ptr++; + } + memcpy(ptr, curlevel->name, curlevel->len); + ptr += curlevel->len; + curlevel = LEVEL_NEXT(curlevel); + } + + SET_VARSIZE(out, ptr - ((char *) out)); + PG_FREE_IF_COPY(in, 0); + + PG_RETURN_POINTER(out); +} + + +/* + * ltreeparentsel - Selectivity of parent relationship for ltree data types. + * + * This function is not used anymore, if the ltree extension has been + * updated to 1.2 or later. + */ +Datum +ltreeparentsel(PG_FUNCTION_ARGS) +{ + PlannerInfo *root = (PlannerInfo *) PG_GETARG_POINTER(0); + Oid operator = PG_GETARG_OID(1); + List *args = (List *) PG_GETARG_POINTER(2); + int varRelid = PG_GETARG_INT32(3); + double selec; + + /* Use generic restriction selectivity logic, with default 0.001. */ + selec = generic_restriction_selectivity(root, operator, InvalidOid, + args, varRelid, + 0.001); + + PG_RETURN_FLOAT8((float8) selec); +} diff --git a/contrib/ltree/ltreetest.sql b/contrib/ltree/ltreetest.sql new file mode 100644 index 0000000..d6996ca --- /dev/null +++ b/contrib/ltree/ltreetest.sql @@ -0,0 +1,21 @@ +/* contrib/ltree/ltreetest.sql */ + +-- Adjust this setting to control where the objects get created. +SET search_path = public; + +CREATE TABLE test ( path ltree); +INSERT INTO test VALUES ('Top'); +INSERT INTO test VALUES ('Top.Science'); +INSERT INTO test VALUES ('Top.Science.Astronomy'); +INSERT INTO test VALUES ('Top.Science.Astronomy.Astrophysics'); +INSERT INTO test VALUES ('Top.Science.Astronomy.Cosmology'); +INSERT INTO test VALUES ('Top.Hobbies'); +INSERT INTO test VALUES ('Top.Hobbies.Amateurs_Astronomy'); +INSERT INTO test VALUES ('Top.Collections'); +INSERT INTO test VALUES ('Top.Collections.Pictures'); +INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy'); +INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Stars'); +INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Galaxies'); +INSERT INTO test VALUES ('Top.Collections.Pictures.Astronomy.Astronauts'); +CREATE INDEX path_gist_idx ON test USING gist(path); +CREATE INDEX path_idx ON test USING btree(path); diff --git a/contrib/ltree/ltxtquery_io.c b/contrib/ltree/ltxtquery_io.c new file mode 100644 index 0000000..121fc55 --- /dev/null +++ b/contrib/ltree/ltxtquery_io.c @@ -0,0 +1,629 @@ +/* + * txtquery io + * Teodor Sigaev + * contrib/ltree/ltxtquery_io.c + */ +#include "postgres.h" + +#include + +#include "crc32.h" +#include "libpq/pqformat.h" +#include "ltree.h" +#include "miscadmin.h" +#include "nodes/miscnodes.h" +#include "varatt.h" + + +/* parser's states */ +#define WAITOPERAND 1 +#define INOPERAND 2 +#define WAITOPERATOR 3 + +/* + * node of query tree, also used + * for storing polish notation in parser + */ +typedef struct NODE +{ + int32 type; + int32 val; + int16 distance; + int16 length; + uint16 flag; + struct NODE *next; +} NODE; + +typedef struct +{ + char *buf; + int32 state; + int32 count; + struct Node *escontext; + /* reverse polish notation in list (for temporary usage) */ + NODE *str; + /* number in str */ + int32 num; + + /* user-friendly operand */ + int32 lenop; + int32 sumlen; + char *op; + char *curop; +} QPRS_STATE; + +/* + * get token from query string + * + * caller needs to check if a soft-error was set if the result is ERR. + */ +static int32 +gettoken_query(QPRS_STATE *state, int32 *val, int32 *lenval, char **strval, uint16 *flag) +{ + int charlen; + + for (;;) + { + charlen = pg_mblen(state->buf); + + switch (state->state) + { + case WAITOPERAND: + if (t_iseq(state->buf, '!')) + { + (state->buf)++; + *val = (int32) '!'; + return OPR; + } + else if (t_iseq(state->buf, '(')) + { + state->count++; + (state->buf)++; + return OPEN; + } + else if (ISLABEL(state->buf)) + { + state->state = INOPERAND; + *strval = state->buf; + *lenval = charlen; + *flag = 0; + } + else if (!t_isspace(state->buf)) + ereturn(state->escontext, ERR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("operand syntax error"))); + break; + case INOPERAND: + if (ISLABEL(state->buf)) + { + if (*flag) + ereturn(state->escontext, ERR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("modifiers syntax error"))); + *lenval += charlen; + } + else if (t_iseq(state->buf, '%')) + *flag |= LVAR_SUBLEXEME; + else if (t_iseq(state->buf, '@')) + *flag |= LVAR_INCASE; + else if (t_iseq(state->buf, '*')) + *flag |= LVAR_ANYEND; + else + { + state->state = WAITOPERATOR; + return VAL; + } + break; + case WAITOPERATOR: + if (t_iseq(state->buf, '&') || t_iseq(state->buf, '|')) + { + state->state = WAITOPERAND; + *val = (int32) *(state->buf); + (state->buf)++; + return OPR; + } + else if (t_iseq(state->buf, ')')) + { + (state->buf)++; + state->count--; + return (state->count < 0) ? ERR : CLOSE; + } + else if (*(state->buf) == '\0') + { + return (state->count) ? ERR : END; + } + else if (!t_iseq(state->buf, ' ')) + { + return ERR; + } + break; + default: + return ERR; + break; + } + + state->buf += charlen; + } + + /* should not get here */ +} + +/* + * push new one in polish notation reverse view + */ +static bool +pushquery(QPRS_STATE *state, int32 type, int32 val, int32 distance, int32 lenval, uint16 flag) +{ + NODE *tmp = (NODE *) palloc(sizeof(NODE)); + + tmp->type = type; + tmp->val = val; + tmp->flag = flag; + if (distance > 0xffff) + ereturn(state->escontext, false, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("value is too big"))); + if (lenval > 0xff) + ereturn(state->escontext, false, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("operand is too long"))); + tmp->distance = distance; + tmp->length = lenval; + tmp->next = state->str; + state->str = tmp; + state->num++; + return true; +} + +/* + * This function is used for query text parsing + */ +static bool +pushval_asis(QPRS_STATE *state, int type, char *strval, int lenval, uint16 flag) +{ + if (lenval > 0xffff) + ereturn(state->escontext, false, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("word is too long"))); + + if (!pushquery(state, type, ltree_crc32_sz(strval, lenval), + state->curop - state->op, lenval, flag)) + return false; + + while (state->curop - state->op + lenval + 1 >= state->lenop) + { + int32 tmp = state->curop - state->op; + + state->lenop *= 2; + state->op = (char *) repalloc(state->op, state->lenop); + state->curop = state->op + tmp; + } + memcpy(state->curop, strval, lenval); + state->curop += lenval; + *(state->curop) = '\0'; + state->curop++; + state->sumlen += lenval + 1; + return true; +} + +#define STACKDEPTH 32 +/* + * make polish notation of query + */ +static int32 +makepol(QPRS_STATE *state) +{ + int32 val = 0, + type; + int32 lenval = 0; + char *strval = NULL; + int32 stack[STACKDEPTH]; + int32 lenstack = 0; + uint16 flag = 0; + + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + while ((type = gettoken_query(state, &val, &lenval, &strval, &flag)) != END) + { + switch (type) + { + case VAL: + if (!pushval_asis(state, VAL, strval, lenval, flag)) + return ERR; + while (lenstack && (stack[lenstack - 1] == (int32) '&' || + stack[lenstack - 1] == (int32) '!')) + { + lenstack--; + if (!pushquery(state, OPR, stack[lenstack], 0, 0, 0)) + return ERR; + } + break; + case OPR: + if (lenstack && val == (int32) '|') + { + if (!pushquery(state, OPR, val, 0, 0, 0)) + return ERR; + } + else + { + if (lenstack == STACKDEPTH) + /* internal error */ + elog(ERROR, "stack too short"); + stack[lenstack] = val; + lenstack++; + } + break; + case OPEN: + if (makepol(state) == ERR) + return ERR; + while (lenstack && (stack[lenstack - 1] == (int32) '&' || + stack[lenstack - 1] == (int32) '!')) + { + lenstack--; + if (!pushquery(state, OPR, stack[lenstack], 0, 0, 0)) + return ERR; + } + break; + case CLOSE: + while (lenstack) + { + lenstack--; + if (!pushquery(state, OPR, stack[lenstack], 0, 0, 0)) + return ERR; + }; + return END; + break; + case ERR: + if (SOFT_ERROR_OCCURRED(state->escontext)) + return ERR; + /* fall through */ + default: + ereturn(state->escontext, ERR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error"))); + + } + } + while (lenstack) + { + lenstack--; + if (!pushquery(state, OPR, stack[lenstack], 0, 0, 0)) + return ERR; + }; + return END; +} + +static void +findoprnd(ITEM *ptr, int32 *pos) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (ptr[*pos].type == VAL || ptr[*pos].type == VALTRUE) + { + ptr[*pos].left = 0; + (*pos)++; + } + else if (ptr[*pos].val == (int32) '!') + { + ptr[*pos].left = 1; + (*pos)++; + findoprnd(ptr, pos); + } + else + { + ITEM *curitem = &ptr[*pos]; + int32 tmp = *pos; + + (*pos)++; + findoprnd(ptr, pos); + curitem->left = *pos - tmp; + findoprnd(ptr, pos); + } +} + + +/* + * input + */ +static ltxtquery * +queryin(char *buf, struct Node *escontext) +{ + QPRS_STATE state; + int32 i; + ltxtquery *query; + int32 commonlen; + ITEM *ptr; + NODE *tmp; + int32 pos = 0; + +#ifdef BS_DEBUG + char pbuf[16384], + *cur; +#endif + + /* init state */ + state.buf = buf; + state.state = WAITOPERAND; + state.count = 0; + state.num = 0; + state.str = NULL; + state.escontext = escontext; + + /* init list of operand */ + state.sumlen = 0; + state.lenop = 64; + state.curop = state.op = (char *) palloc(state.lenop); + *(state.curop) = '\0'; + + /* parse query & make polish notation (postfix, but in reverse order) */ + if (makepol(&state) == ERR) + return NULL; + if (!state.num) + ereturn(escontext, NULL, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error"), + errdetail("Empty query."))); + + if (LTXTQUERY_TOO_BIG(state.num, state.sumlen)) + ereturn(escontext, NULL, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("ltxtquery is too large"))); + commonlen = COMPUTESIZE(state.num, state.sumlen); + + query = (ltxtquery *) palloc0(commonlen); + SET_VARSIZE(query, commonlen); + query->size = state.num; + ptr = GETQUERY(query); + + /* set item in polish notation */ + for (i = 0; i < state.num; i++) + { + ptr[i].type = state.str->type; + ptr[i].val = state.str->val; + ptr[i].distance = state.str->distance; + ptr[i].length = state.str->length; + ptr[i].flag = state.str->flag; + tmp = state.str->next; + pfree(state.str); + state.str = tmp; + } + + /* set user-friendly operand view */ + memcpy(GETOPERAND(query), state.op, state.sumlen); + pfree(state.op); + + /* set left operand's position for every operator */ + pos = 0; + findoprnd(ptr, &pos); + + return query; +} + +/* + * in without morphology + */ +PG_FUNCTION_INFO_V1(ltxtq_in); +Datum +ltxtq_in(PG_FUNCTION_ARGS) +{ + ltxtquery *res; + + if ((res = queryin((char *) PG_GETARG_POINTER(0), fcinfo->context)) == NULL) + PG_RETURN_NULL(); + PG_RETURN_POINTER(res); +} + +/* + * ltxtquery type recv function + * + * The type is sent as text in binary mode, so this is almost the same + * as the input function, but it's prefixed with a version number so we + * can change the binary format sent in future if necessary. For now, + * only version 1 is supported. + */ +PG_FUNCTION_INFO_V1(ltxtq_recv); +Datum +ltxtq_recv(PG_FUNCTION_ARGS) +{ + StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); + int version = pq_getmsgint(buf, 1); + char *str; + int nbytes; + ltxtquery *res; + + if (version != 1) + elog(ERROR, "unsupported ltxtquery version number %d", version); + + str = pq_getmsgtext(buf, buf->len - buf->cursor, &nbytes); + res = queryin(str, NULL); + pfree(str); + + PG_RETURN_POINTER(res); +} + +/* + * out function + */ +typedef struct +{ + ITEM *curpol; + char *buf; + char *cur; + char *op; + int32 buflen; +} INFIX; + +#define RESIZEBUF(inf,addsize) \ +while( ( (inf)->cur - (inf)->buf ) + (addsize) + 1 >= (inf)->buflen ) \ +{ \ + int32 len = (inf)->cur - (inf)->buf; \ + (inf)->buflen *= 2; \ + (inf)->buf = (char*) repalloc( (void*)(inf)->buf, (inf)->buflen ); \ + (inf)->cur = (inf)->buf + len; \ +} + +/* + * recursive walk on tree and print it in + * infix (human-readable) view + */ +static void +infix(INFIX *in, bool first) +{ + /* since this function recurses, it could be driven to stack overflow. */ + check_stack_depth(); + + if (in->curpol->type == VAL) + { + char *op = in->op + in->curpol->distance; + + RESIZEBUF(in, in->curpol->length * 2 + 5); + while (*op) + { + *(in->cur) = *op; + op++; + in->cur++; + } + if (in->curpol->flag & LVAR_SUBLEXEME) + { + *(in->cur) = '%'; + in->cur++; + } + if (in->curpol->flag & LVAR_INCASE) + { + *(in->cur) = '@'; + in->cur++; + } + if (in->curpol->flag & LVAR_ANYEND) + { + *(in->cur) = '*'; + in->cur++; + } + *(in->cur) = '\0'; + in->curpol++; + } + else if (in->curpol->val == (int32) '!') + { + bool isopr = false; + + RESIZEBUF(in, 1); + *(in->cur) = '!'; + in->cur++; + *(in->cur) = '\0'; + in->curpol++; + if (in->curpol->type == OPR) + { + isopr = true; + RESIZEBUF(in, 2); + sprintf(in->cur, "( "); + in->cur = strchr(in->cur, '\0'); + } + infix(in, isopr); + if (isopr) + { + RESIZEBUF(in, 2); + sprintf(in->cur, " )"); + in->cur = strchr(in->cur, '\0'); + } + } + else + { + int32 op = in->curpol->val; + INFIX nrm; + + in->curpol++; + if (op == (int32) '|' && !first) + { + RESIZEBUF(in, 2); + sprintf(in->cur, "( "); + in->cur = strchr(in->cur, '\0'); + } + + nrm.curpol = in->curpol; + nrm.op = in->op; + nrm.buflen = 16; + nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen); + + /* get right operand */ + infix(&nrm, false); + + /* get & print left operand */ + in->curpol = nrm.curpol; + infix(in, false); + + /* print operator & right operand */ + RESIZEBUF(in, 3 + (nrm.cur - nrm.buf)); + sprintf(in->cur, " %c %s", op, nrm.buf); + in->cur = strchr(in->cur, '\0'); + pfree(nrm.buf); + + if (op == (int32) '|' && !first) + { + RESIZEBUF(in, 2); + sprintf(in->cur, " )"); + in->cur = strchr(in->cur, '\0'); + } + } +} + +PG_FUNCTION_INFO_V1(ltxtq_out); +Datum +ltxtq_out(PG_FUNCTION_ARGS) +{ + ltxtquery *query = PG_GETARG_LTXTQUERY_P(0); + INFIX nrm; + + if (query->size == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error"), + errdetail("Empty query."))); + + nrm.curpol = GETQUERY(query); + nrm.buflen = 32; + nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen); + *(nrm.cur) = '\0'; + nrm.op = GETOPERAND(query); + infix(&nrm, true); + + PG_RETURN_POINTER(nrm.buf); +} + +/* + * ltxtquery type send function + * + * The type is sent as text in binary mode, so this is almost the same + * as the output function, but it's prefixed with a version number so we + * can change the binary format sent in future if necessary. For now, + * only version 1 is supported. + */ +PG_FUNCTION_INFO_V1(ltxtq_send); +Datum +ltxtq_send(PG_FUNCTION_ARGS) +{ + ltxtquery *query = PG_GETARG_LTXTQUERY_P(0); + StringInfoData buf; + int version = 1; + INFIX nrm; + + if (query->size == 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("syntax error"), + errdetail("Empty query."))); + + nrm.curpol = GETQUERY(query); + nrm.buflen = 32; + nrm.cur = nrm.buf = (char *) palloc(sizeof(char) * nrm.buflen); + *(nrm.cur) = '\0'; + nrm.op = GETOPERAND(query); + infix(&nrm, true); + + pq_begintypsend(&buf); + pq_sendint8(&buf, version); + pq_sendtext(&buf, nrm.buf, strlen(nrm.buf)); + pfree(nrm.buf); + + PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); +} diff --git a/contrib/ltree/ltxtquery_op.c b/contrib/ltree/ltxtquery_op.c new file mode 100644 index 0000000..002102c --- /dev/null +++ b/contrib/ltree/ltxtquery_op.c @@ -0,0 +1,111 @@ +/* + * txtquery operations with ltree + * Teodor Sigaev + * contrib/ltree/ltxtquery_op.c + */ +#include "postgres.h" + +#include + +#include "ltree.h" +#include "miscadmin.h" + +PG_FUNCTION_INFO_V1(ltxtq_exec); +PG_FUNCTION_INFO_V1(ltxtq_rexec); + +/* + * check for boolean condition + */ +bool +ltree_execute(ITEM *curitem, void *checkval, bool calcnot, bool (*chkcond) (void *checkval, ITEM *val)) +{ + /* since this function recurses, it could be driven to stack overflow */ + check_stack_depth(); + + if (curitem->type == VAL) + return (*chkcond) (checkval, curitem); + else if (curitem->val == (int32) '!') + { + return calcnot ? + ((ltree_execute(curitem + 1, checkval, calcnot, chkcond)) ? false : true) + : true; + } + else if (curitem->val == (int32) '&') + { + if (ltree_execute(curitem + curitem->left, checkval, calcnot, chkcond)) + return ltree_execute(curitem + 1, checkval, calcnot, chkcond); + else + return false; + } + else + { /* |-operator */ + if (ltree_execute(curitem + curitem->left, checkval, calcnot, chkcond)) + return true; + else + return ltree_execute(curitem + 1, checkval, calcnot, chkcond); + } +} + +typedef struct +{ + ltree *node; + char *operand; +} CHKVAL; + +static bool +checkcondition_str(void *checkval, ITEM *val) +{ + ltree_level *level = LTREE_FIRST(((CHKVAL *) checkval)->node); + int tlen = ((CHKVAL *) checkval)->node->numlevel; + char *op = ((CHKVAL *) checkval)->operand + val->distance; + int (*cmpptr) (const char *, const char *, size_t); + + cmpptr = (val->flag & LVAR_INCASE) ? ltree_strncasecmp : strncmp; + while (tlen > 0) + { + if (val->flag & LVAR_SUBLEXEME) + { + if (compare_subnode(level, op, val->length, cmpptr, (val->flag & LVAR_ANYEND))) + return true; + } + else if ((val->length == level->len || + (level->len > val->length && (val->flag & LVAR_ANYEND))) && + (*cmpptr) (op, level->name, val->length) == 0) + return true; + + tlen--; + level = LEVEL_NEXT(level); + } + + return false; +} + +Datum +ltxtq_exec(PG_FUNCTION_ARGS) +{ + ltree *val = PG_GETARG_LTREE_P(0); + ltxtquery *query = PG_GETARG_LTXTQUERY_P(1); + CHKVAL chkval; + bool result; + + chkval.node = val; + chkval.operand = GETOPERAND(query); + + result = ltree_execute(GETQUERY(query), + &chkval, + true, + checkcondition_str); + + PG_FREE_IF_COPY(val, 0); + PG_FREE_IF_COPY(query, 1); + PG_RETURN_BOOL(result); +} + +Datum +ltxtq_rexec(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(DirectFunctionCall2(ltxtq_exec, + PG_GETARG_DATUM(1), + PG_GETARG_DATUM(0) + )); +} diff --git a/contrib/ltree/meson.build b/contrib/ltree/meson.build new file mode 100644 index 0000000..44d0337 --- /dev/null +++ b/contrib/ltree/meson.build @@ -0,0 +1,52 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +ltree_sources = files( + '_ltree_gist.c', + '_ltree_op.c', + 'crc32.c', + 'lquery_op.c', + 'ltree_gist.c', + 'ltree_io.c', + 'ltree_op.c', + 'ltxtquery_io.c', + 'ltxtquery_op.c', +) + +# .. so that includes of ltree/ltree.h work +ltree_inc = include_directories('.', '../') + +if host_system == 'windows' + ltree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'ltree', + '--FILEDESC', 'ltree - hierarchical label data type',]) +endif + +ltree = shared_module('ltree', + ltree_sources, + kwargs: contrib_mod_args, +) +contrib_targets += ltree + +install_data( + 'ltree.control', + 'ltree--1.0--1.1.sql', + 'ltree--1.1--1.2.sql', + 'ltree--1.1.sql', + kwargs: contrib_data_args, +) + +install_headers( + 'ltree.h', + install_dir: dir_include_extension / 'ltree', +) + +tests += { + 'name': 'ltree', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'ltree', + ], + }, +} diff --git a/contrib/ltree/sql/ltree.sql b/contrib/ltree/sql/ltree.sql new file mode 100644 index 0000000..402096f --- /dev/null +++ b/contrib/ltree/sql/ltree.sql @@ -0,0 +1,411 @@ +CREATE EXTENSION ltree; + +-- max length for a label +\set maxlbl 1000 + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +SELECT ''::ltree; +SELECT '1'::ltree; +SELECT '1.2'::ltree; +SELECT '1.2.-3'::ltree; +SELECT '1.2._3'::ltree; + +-- empty labels not allowed +SELECT '.2.3'::ltree; +SELECT '1..3'::ltree; +SELECT '1.2.'::ltree; + +SELECT repeat('x', :maxlbl)::ltree; +SELECT repeat('x', :maxlbl + 1)::ltree; + +SELECT ltree2text('1.2.3.34.sdf'); +SELECT text2ltree('1.2.3.34.sdf'); + +SELECT subltree('Top.Child1.Child2',1,2); +SELECT subpath('Top.Child1.Child2',1,2); +SELECT subpath('Top.Child1.Child2',-1,1); +SELECT subpath('Top.Child1.Child2',0,-2); +SELECT subpath('Top.Child1.Child2',0,-1); +SELECT subpath('Top.Child1.Child2',0,0); +SELECT subpath('Top.Child1.Child2',1,0); +SELECT subpath('Top.Child1.Child2',0); +SELECT subpath('Top.Child1.Child2',1); + + +SELECT index('1.2.3.4.5.6','1.2'); +SELECT index('a.1.2.3.4.5.6','1.2'); +SELECT index('a.1.2.3.4.5.6','1.2.3'); +SELECT index('a.1.2.3.4.5.6','1.2.3.j'); +SELECT index('a.1.2.3.4.5.6','1.2.3.j.4.5.5.5.5.5.5'); +SELECT index('a.1.2.3.4.5.6','1.2.3'); +SELECT index('a.1.2.3.4.5.6','6'); +SELECT index('a.1.2.3.4.5.6','6.1'); +SELECT index('a.1.2.3.4.5.6','5.6'); +SELECT index('0.1.2.3.5.4.5.6','5.6'); +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',3); +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',6); +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',7); +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-7); +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-4); +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-3); +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-2); +SELECT index('0.1.2.3.5.4.5.6.8.5.6.8','5.6',-20000); + + +SELECT 'Top.Child1.Child2'::ltree || 'Child3'::text; +SELECT 'Top.Child1.Child2'::ltree || 'Child3'::ltree; +SELECT 'Top_0'::ltree || 'Top.Child1.Child2'::ltree; +SELECT 'Top.Child1.Child2'::ltree || ''::ltree; +SELECT ''::ltree || 'Top.Child1.Child2'::ltree; + +SELECT lca('{la.2.3,1.2.3.4.5.6,""}') IS NULL; +SELECT lca('{la.2.3,1.2.3.4.5.6}') IS NULL; +SELECT lca('{1.la.2.3,1.2.3.4.5.6}'); +SELECT lca('{1.2.3,1.2.3.4.5.6}'); +SELECT lca('{1.2.3}'); +SELECT lca('{1}'), lca('{1}') IS NULL; +SELECT lca('{}') IS NULL; +SELECT lca('1.la.2.3','1.2.3.4.5.6'); +SELECT lca('1.2.3','1.2.3.4.5.6'); +SELECT lca('1.2.2.3','1.2.3.4.5.6'); +SELECT lca('1.2.2.3','1.2.3.4.5.6',''); +SELECT lca('1.2.2.3','1.2.3.4.5.6','2'); +SELECT lca('1.2.2.3','1.2.3.4.5.6','1'); + + +SELECT '1'::lquery; +SELECT '4|3|2'::lquery; +SELECT '1.2'::lquery; +SELECT '1.4|3|2'::lquery; +SELECT '1.0'::lquery; +SELECT '4|3|2.0'::lquery; +SELECT '1.2.0'::lquery; +SELECT '1.4|3|2.0'::lquery; +SELECT '1.*'::lquery; +SELECT '4|3|2.*'::lquery; +SELECT '1.2.*'::lquery; +SELECT '1.4|3|2.*'::lquery; +SELECT '*.1.*'::lquery; +SELECT '*.4|3|2.*'::lquery; +SELECT '*.1.2.*'::lquery; +SELECT '*.1.4|3|2.*'::lquery; +SELECT '1.*.4|3|2'::lquery; +SELECT '1.*.4|3|2.0'::lquery; +SELECT '1.*.4|3|2.*{1,4}'::lquery; +SELECT '1.*.4|3|2.*{,4}'::lquery; +SELECT '1.*.4|3|2.*{1,}'::lquery; +SELECT '1.*.4|3|2.*{1}'::lquery; +SELECT 'foo.bar{,}.!a*|b{1,}.c{,44}.d{3,4}'::lquery; +SELECT 'foo*@@*'::lquery; +SELECT 'qwerty%@*.tu'::lquery; + +-- empty labels not allowed +SELECT '.2.3'::lquery; +SELECT '1..3'::lquery; +SELECT '1.2.'::lquery; +SELECT '@.2.3'::lquery; +SELECT '1.@.3'::lquery; +SELECT '1.2.@'::lquery; +SELECT '!.2.3'::lquery; +SELECT '1.!.3'::lquery; +SELECT '1.2.!'::lquery; +SELECT '1.2.3|@.4'::lquery; + +SELECT (repeat('x', :maxlbl) || '*@@*')::lquery; +SELECT (repeat('x', :maxlbl + 1) || '*@@*')::lquery; +SELECT ('!' || repeat('x', :maxlbl))::lquery; +SELECT ('!' || repeat('x', :maxlbl + 1))::lquery; + +SELECT nlevel('1.2.3.4'); +SELECT nlevel(('1' || repeat('.1', 65534))::ltree); +SELECT nlevel(('1' || repeat('.1', 65535))::ltree); +SELECT nlevel(('1' || repeat('.1', 65534))::ltree || '1'); +SELECT ('1' || repeat('.1', 65534))::lquery IS NULL; +SELECT ('1' || repeat('.1', 65535))::lquery IS NULL; +SELECT '*{65535}'::lquery; +SELECT '*{65536}'::lquery; +SELECT '*{,65534}'::lquery; +SELECT '*{,65535}'::lquery; +SELECT '*{,65536}'::lquery; +SELECT '*{4,3}'::lquery; + +SELECT '1.2'::ltree < '2.2'::ltree; +SELECT '1.2'::ltree <= '2.2'::ltree; +SELECT '2.2'::ltree = '2.2'::ltree; +SELECT '3.2'::ltree >= '2.2'::ltree; +SELECT '3.2'::ltree > '2.2'::ltree; + +SELECT '1.2.3'::ltree @> '1.2.3.4'::ltree; +SELECT '1.2.3.4'::ltree @> '1.2.3.4'::ltree; +SELECT '1.2.3.4.5'::ltree @> '1.2.3.4'::ltree; +SELECT '1.3.3'::ltree @> '1.2.3.4'::ltree; + +SELECT 'a.b.c.d.e'::ltree ~ 'a.b.c.d.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'A.b.c.d.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'A@.b.c.d.e'; +SELECT 'aa.b.c.d.e'::ltree ~ 'A@.b.c.d.e'; +SELECT 'aa.b.c.d.e'::ltree ~ 'A*.b.c.d.e'; +SELECT 'aa.b.c.d.e'::ltree ~ 'A*@.b.c.d.e'; +SELECT 'aa.b.c.d.e'::ltree ~ 'A*@|g.b.c.d.e'; +SELECT 'g.b.c.d.e'::ltree ~ 'A*@|g.b.c.d.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.b.c.d.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{3}.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2}.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{4}.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{,4}.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,}.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,4}.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,3}.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,3}'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,4}'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2,5}'; +SELECT 'a.b.c.d.e'::ltree ~ '*{2,3}.e'; +SELECT 'a.b.c.d.e'::ltree ~ '*{2,4}.e'; +SELECT 'a.b.c.d.e'::ltree ~ '*{2,5}.e'; +SELECT 'a.b.c.d.e'::ltree ~ '*.e'; +SELECT 'a.b.c.d.e'::ltree ~ '*.e.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.d.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.a.*.d.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!d.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!d'; +SELECT 'a.b.c.d.e'::ltree ~ '!d.*'; +SELECT 'a.b.c.d.e'::ltree ~ '!a.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!e'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!e.*'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*.!e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*.!d'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*.!d.*'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*.!f.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.a.*.!f.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.a.*.!d.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.a.!d.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.a.!d'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.!d.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.a.*.!d.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.c.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*.c.*'; +SELECT 'a.b.c.d.e'::ltree ~ '!b.*.c.*'; +SELECT 'a.b.c.d.e'::ltree ~ '!b.b.*'; +SELECT 'a.b.c.d.e'::ltree ~ '!b.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ '!b.!c.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ '!b.*.!c.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ '*{2}.!b.*.!c.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ '*{1}.!b.*.!c.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ '*{1}.!b.*{1}.!c.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.!b.*{1}.!c.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ '!b.*{1}.!c.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*{1}.!c.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*.!c.*.e'; +SELECT 'a.b.c.d.e'::ltree ~ '!b.!c.*'; +SELECT 'a.b.c.d.e'::ltree ~ '!b.*.!c.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*{2}.!b.*.!c.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*{1}.!b.*.!c.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*{1}.!b.*{1}.!c.*'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.!b.*{1}.!c.*'; +SELECT 'a.b.c.d.e'::ltree ~ '!b.*{1}.!c.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*{1}.!c.*'; +SELECT 'a.b.c.d.e'::ltree ~ '*.!b.*.!c.*'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{2}.*{2}'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{1}.*{2}.e'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{1}.*{4}'; +SELECT 'a.b.c.d.e'::ltree ~ 'a.*{5}.*'; +SELECT '5.0.1.0'::ltree ~ '5.!0.!0.0'; +SELECT 'a.b'::ltree ~ '!a.!a'; + +SELECT 'a.b.c.d.e'::ltree ~ 'a{,}'; +SELECT 'a.b.c.d.e'::ltree ~ 'a{1,}.*'; +SELECT 'a.b.c.d.e'::ltree ~ 'a{,}.!a{,}'; +SELECT 'a.b.c.d.a'::ltree ~ 'a{,}.!a{,}'; +SELECT 'a.b.c.d.a'::ltree ~ 'a{,2}.!a{1,}'; +SELECT 'a.b.c.d.e'::ltree ~ 'a{,2}.!a{1,}'; +SELECT 'a.b.c.d.e'::ltree ~ '!x{,}'; +SELECT 'a.b.c.d.e'::ltree ~ '!c{,}'; +SELECT 'a.b.c.d.e'::ltree ~ '!c{0,3}.!a{2,}'; +SELECT 'a.b.c.d.e'::ltree ~ '!c{0,3}.!d{2,}.*'; + +SELECT 'QWER_TY'::ltree ~ 'q%@*'; +SELECT 'QWER_TY'::ltree ~ 'q%@*%@*'; +SELECT 'QWER_TY'::ltree ~ 'Q_t%@*'; +SELECT 'QWER_GY'::ltree ~ 'q_t%@*'; + +--ltxtquery +SELECT '!tree & aWdf@*'::ltxtquery; +SELECT 'tree & aw_qw%*'::ltxtquery; +SELECT 'tree & aw-qw%*'::ltxtquery; + +SELECT 'ltree.awdfg'::ltree @ '!tree & aWdf@*'::ltxtquery; +SELECT 'tree.awdfg'::ltree @ '!tree & aWdf@*'::ltxtquery; +SELECT 'tree.awdfg'::ltree @ '!tree | aWdf@*'::ltxtquery; +SELECT 'tree.awdfg'::ltree @ 'tree | aWdf@*'::ltxtquery; +SELECT 'tree.awdfg'::ltree @ 'tree & aWdf@*'::ltxtquery; +SELECT 'tree.awdfg'::ltree @ 'tree & aWdf@'::ltxtquery; +SELECT 'tree.awdfg'::ltree @ 'tree & aWdf*'::ltxtquery; +SELECT 'tree.awdfg'::ltree @ 'tree & aWdf'::ltxtquery; +SELECT 'tree.awdfg'::ltree @ 'tree & awdf*'::ltxtquery; +SELECT 'tree.awdfg'::ltree @ 'tree & aWdfg@'::ltxtquery; +SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_qw%*'::ltxtquery; +SELECT 'tree.awdfg_qwerty'::ltree @ 'tree & aw_rw%*'::ltxtquery; + +--arrays + +SELECT '{1.2.3}'::ltree[] @> '1.2.3.4'; +SELECT '{1.2.3.4}'::ltree[] @> '1.2.3.4'; +SELECT '{1.2.3.4.5}'::ltree[] @> '1.2.3.4'; +SELECT '{1.3.3}'::ltree[] @> '1.2.3.4'; +SELECT '{5.67.8, 1.2.3}'::ltree[] @> '1.2.3.4'; +SELECT '{5.67.8, 1.2.3.4}'::ltree[] @> '1.2.3.4'; +SELECT '{5.67.8, 1.2.3.4.5}'::ltree[] @> '1.2.3.4'; +SELECT '{5.67.8, 1.3.3}'::ltree[] @> '1.2.3.4'; +SELECT '{1.2.3, 7.12.asd}'::ltree[] @> '1.2.3.4'; +SELECT '{1.2.3.4, 7.12.asd}'::ltree[] @> '1.2.3.4'; +SELECT '{1.2.3.4.5, 7.12.asd}'::ltree[] @> '1.2.3.4'; +SELECT '{1.3.3, 7.12.asd}'::ltree[] @> '1.2.3.4'; +SELECT '{ltree.asd, tree.awdfg}'::ltree[] @ 'tree & aWdfg@'::ltxtquery; +SELECT '{j.k.l.m, g.b.c.d.e}'::ltree[] ~ 'A*@|g.b.c.d.e'; +SELECT 'a.b.c.d.e'::ltree ? '{A.b.c.d.e}'; +SELECT 'a.b.c.d.e'::ltree ? '{a.b.c.d.e}'; +SELECT 'a.b.c.d.e'::ltree ? '{A.b.c.d.e, a.*}'; +SELECT '{a.b.c.d.e,B.df}'::ltree[] ? '{A.b.c.d.e}'; +SELECT '{a.b.c.d.e,B.df}'::ltree[] ? '{A.b.c.d.e,*.df}'; + +--extractors +SELECT ('{3456,1.2.3.34}'::ltree[] ?@> '1.2.3.4') is null; +SELECT '{3456,1.2.3}'::ltree[] ?@> '1.2.3.4'; +SELECT '{3456,1.2.3.4}'::ltree[] ?<@ '1.2.3'; +SELECT ('{3456,1.2.3.4}'::ltree[] ?<@ '1.2.5') is null; +SELECT '{ltree.asd, tree.awdfg}'::ltree[] ?@ 'tree & aWdfg@'::ltxtquery; +SELECT '{j.k.l.m, g.b.c.d.e}'::ltree[] ?~ 'A*@|g.b.c.d.e'; + +CREATE TABLE ltreetest (t ltree); +\copy ltreetest FROM 'data/ltree.data' + +SELECT * FROM ltreetest WHERE t < '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t <= '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t = '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t >= '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t > '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t @> '1.1.1' order by t asc; +SELECT * FROM ltreetest WHERE t <@ '1.1.1' order by t asc; +SELECT * FROM ltreetest WHERE t @ '23 & 1' order by t asc; +SELECT * FROM ltreetest WHERE t ~ '1.1.1.*' order by t asc; +SELECT * FROM ltreetest WHERE t ~ '*.1' order by t asc; +SELECT * FROM ltreetest WHERE t ~ '23.*{1}.1' order by t asc; +SELECT * FROM ltreetest WHERE t ~ '23.*.1' order by t asc; +SELECT * FROM ltreetest WHERE t ~ '23.*.2' order by t asc; +SELECT * FROM ltreetest WHERE t ? '{23.*.1,23.*.2}' order by t asc; + +create unique index tstidx on ltreetest (t); +set enable_seqscan=off; + +SELECT * FROM ltreetest WHERE t < '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t <= '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t = '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t >= '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t > '12.3' order by t asc; + +drop index tstidx; +create index tstidx on ltreetest using gist (t); +set enable_seqscan=off; + +SELECT * FROM ltreetest WHERE t < '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t <= '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t = '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t >= '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t > '12.3' order by t asc; +SELECT * FROM ltreetest WHERE t @> '1.1.1' order by t asc; +SELECT * FROM ltreetest WHERE t <@ '1.1.1' order by t asc; +SELECT * FROM ltreetest WHERE t @ '23 & 1' order by t asc; +SELECT * FROM ltreetest WHERE t ~ '1.1.1.*' order by t asc; +SELECT * FROM ltreetest WHERE t ~ '*.1' order by t asc; +SELECT * FROM ltreetest WHERE t ~ '23.*{1}.1' order by t asc; +SELECT * FROM ltreetest WHERE t ~ '23.*.1' order by t asc; +SELECT * FROM ltreetest WHERE t ~ '23.*.2' order by t asc; +SELECT * FROM ltreetest WHERE t ? '{23.*.1,23.*.2}' order by t asc; + +drop index tstidx; +create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=0)); +create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=2025)); +create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=2028)); +create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=2019)); +create index tstidx on ltreetest using gist (t gist_ltree_ops(siglen=2024)); + +SELECT count(*) FROM ltreetest WHERE t < '12.3'; +SELECT count(*) FROM ltreetest WHERE t <= '12.3'; +SELECT count(*) FROM ltreetest WHERE t = '12.3'; +SELECT count(*) FROM ltreetest WHERE t >= '12.3'; +SELECT count(*) FROM ltreetest WHERE t > '12.3'; +SELECT count(*) FROM ltreetest WHERE t @> '1.1.1'; +SELECT count(*) FROM ltreetest WHERE t <@ '1.1.1'; +SELECT count(*) FROM ltreetest WHERE t @ '23 & 1'; +SELECT count(*) FROM ltreetest WHERE t ~ '1.1.1.*'; +SELECT count(*) FROM ltreetest WHERE t ~ '*.1'; +SELECT count(*) FROM ltreetest WHERE t ~ '23.*{1}.1'; +SELECT count(*) FROM ltreetest WHERE t ~ '23.*.1'; +SELECT count(*) FROM ltreetest WHERE t ~ '23.*.2'; +SELECT count(*) FROM ltreetest WHERE t ? '{23.*.1,23.*.2}'; + +create table _ltreetest (t ltree[]); +\copy _ltreetest FROM 'data/_ltree.data' + +SELECT count(*) FROM _ltreetest WHERE t @> '1.1.1' ; +SELECT count(*) FROM _ltreetest WHERE t <@ '1.1.1' ; +SELECT count(*) FROM _ltreetest WHERE t @ '23 & 1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '1.1.1.*' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '*.1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*{1}.1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.2' ; +SELECT count(*) FROM _ltreetest WHERE t ? '{23.*.1,23.*.2}' ; + +create index _tstidx on _ltreetest using gist (t); +set enable_seqscan=off; + +SELECT count(*) FROM _ltreetest WHERE t @> '1.1.1' ; +SELECT count(*) FROM _ltreetest WHERE t <@ '1.1.1' ; +SELECT count(*) FROM _ltreetest WHERE t @ '23 & 1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '1.1.1.*' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '*.1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*{1}.1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.2' ; +SELECT count(*) FROM _ltreetest WHERE t ? '{23.*.1,23.*.2}' ; + +drop index _tstidx; +create index _tstidx on _ltreetest using gist (t gist__ltree_ops(siglen=0)); +create index _tstidx on _ltreetest using gist (t gist__ltree_ops(siglen=2025)); +create index _tstidx on _ltreetest using gist (t gist__ltree_ops(siglen=2024)); + +SELECT count(*) FROM _ltreetest WHERE t @> '1.1.1' ; +SELECT count(*) FROM _ltreetest WHERE t <@ '1.1.1' ; +SELECT count(*) FROM _ltreetest WHERE t @ '23 & 1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '1.1.1.*' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '*.1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*{1}.1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.1' ; +SELECT count(*) FROM _ltreetest WHERE t ~ '23.*.2' ; +SELECT count(*) FROM _ltreetest WHERE t ? '{23.*.1,23.*.2}' ; + +-- test non-error-throwing input + +SELECT str as "value", typ as "type", + pg_input_is_valid(str,typ) as ok, + errinfo.sql_error_code, + errinfo.message, + errinfo.detail, + errinfo.hint +FROM (VALUES ('.2.3', 'ltree'), + ('1.2.', 'ltree'), + ('1.2.3','ltree'), + ('@.2.3','lquery'), + (' 2.3', 'lquery'), + ('1.2.3','lquery'), + ('$tree & aWdf@*','ltxtquery'), + ('!tree & aWdf@*','ltxtquery')) + AS a(str,typ), + LATERAL pg_input_error_info(a.str, a.typ) as errinfo; diff --git a/contrib/ltree_plpython/.gitignore b/contrib/ltree_plpython/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/ltree_plpython/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/ltree_plpython/Makefile b/contrib/ltree_plpython/Makefile new file mode 100644 index 0000000..406d278 --- /dev/null +++ b/contrib/ltree_plpython/Makefile @@ -0,0 +1,39 @@ +# contrib/ltree_plpython/Makefile + +MODULE_big = ltree_plpython$(python_majorversion) +OBJS = \ + $(WIN32RES) \ + ltree_plpython.o +PGFILEDESC = "ltree_plpython - ltree transform for plpython" + +EXTENSION = ltree_plpython3u +DATA = ltree_plpython3u--1.0.sql + +REGRESS = ltree_plpython + +PG_CPPFLAGS = $(python_includespec) -DPLPYTHON_LIBNAME='"plpython$(python_majorversion)"' + +ifdef USE_PGXS +PG_CPPFLAGS += -I$(includedir_server)/extension +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +PG_CPPFLAGS += -I$(top_srcdir)/src/pl/plpython -I$(top_srcdir)/contrib +subdir = contrib/ltree_plpython +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +# We must link libpython explicitly +ifeq ($(PORTNAME), win32) +# ... see silliness in plpython Makefile ... +SHLIB_LINK_INTERNAL += $(sort $(wildcard ../../src/pl/plpython/libpython*.a)) +else +rpathdir = $(python_libdir) +SHLIB_LINK += $(python_libspec) $(python_additional_libs) +endif + +REGRESS_OPTS += --load-extension=ltree +EXTRA_INSTALL += contrib/ltree diff --git a/contrib/ltree_plpython/expected/ltree_plpython.out b/contrib/ltree_plpython/expected/ltree_plpython.out new file mode 100644 index 0000000..bd32541 --- /dev/null +++ b/contrib/ltree_plpython/expected/ltree_plpython.out @@ -0,0 +1,43 @@ +CREATE EXTENSION ltree_plpython3u CASCADE; +NOTICE: installing required extension "plpython3u" +CREATE FUNCTION test1(val ltree) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE ltree +AS $$ +plpy.info(repr(val)) +return len(val) +$$; +SELECT test1('aa.bb.cc'::ltree); +INFO: ['aa', 'bb', 'cc'] + test1 +------- + 3 +(1 row) + +CREATE FUNCTION test1n(val ltree) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE ltree +AS $$ +plpy.info(repr(val)) +return len(val) +$$; +SELECT test1n('aa.bb.cc'::ltree); +INFO: ['aa', 'bb', 'cc'] + test1n +-------- + 3 +(1 row) + +CREATE FUNCTION test2() RETURNS ltree +LANGUAGE plpython3u +TRANSFORM FOR TYPE ltree +AS $$ +return ['foo', 'bar', 'baz'] +$$; +-- plpython to ltree is not yet implemented, so this will fail, +-- because it will try to parse the Python list as an ltree input +-- string. +SELECT test2(); +ERROR: ltree syntax error at character 1 +CONTEXT: while creating return value +PL/Python function "test2" diff --git a/contrib/ltree_plpython/ltree_plpython.c b/contrib/ltree_plpython/ltree_plpython.c new file mode 100644 index 0000000..ac159ea --- /dev/null +++ b/contrib/ltree_plpython/ltree_plpython.c @@ -0,0 +1,58 @@ +#include "postgres.h" + +#include "fmgr.h" +#include "ltree/ltree.h" +#include "plpython.h" + +PG_MODULE_MAGIC; + +/* Linkage to functions in plpython module */ +typedef PyObject *(*PLyUnicode_FromStringAndSize_t) (const char *s, Py_ssize_t size); +static PLyUnicode_FromStringAndSize_t PLyUnicode_FromStringAndSize_p; + + +/* + * Module initialize function: fetch function pointers for cross-module calls. + */ +void +_PG_init(void) +{ + /* Asserts verify that typedefs above match original declarations */ + AssertVariableIsOfType(&PLyUnicode_FromStringAndSize, PLyUnicode_FromStringAndSize_t); + PLyUnicode_FromStringAndSize_p = (PLyUnicode_FromStringAndSize_t) + load_external_function("$libdir/" PLPYTHON_LIBNAME, "PLyUnicode_FromStringAndSize", + true, NULL); +} + + +/* These defines must be after the module init function */ +#define PLyUnicode_FromStringAndSize PLyUnicode_FromStringAndSize_p + + +PG_FUNCTION_INFO_V1(ltree_to_plpython); + +Datum +ltree_to_plpython(PG_FUNCTION_ARGS) +{ + ltree *in = PG_GETARG_LTREE_P(0); + int i; + PyObject *list; + ltree_level *curlevel; + + list = PyList_New(in->numlevel); + if (!list) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); + + curlevel = LTREE_FIRST(in); + for (i = 0; i < in->numlevel; i++) + { + PyList_SetItem(list, i, PLyUnicode_FromStringAndSize(curlevel->name, curlevel->len)); + curlevel = LEVEL_NEXT(curlevel); + } + + PG_FREE_IF_COPY(in, 0); + + return PointerGetDatum(list); +} diff --git a/contrib/ltree_plpython/ltree_plpython3u--1.0.sql b/contrib/ltree_plpython/ltree_plpython3u--1.0.sql new file mode 100644 index 0000000..09ada3c --- /dev/null +++ b/contrib/ltree_plpython/ltree_plpython3u--1.0.sql @@ -0,0 +1,12 @@ +/* contrib/ltree_plpython/ltree_plpython3u--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION ltree_plpython3u" to load this file. \quit + +CREATE FUNCTION ltree_to_plpython3(val internal) RETURNS internal +LANGUAGE C STRICT IMMUTABLE +AS 'MODULE_PATHNAME', 'ltree_to_plpython'; + +CREATE TRANSFORM FOR ltree LANGUAGE plpython3u ( + FROM SQL WITH FUNCTION ltree_to_plpython3(internal) +); diff --git a/contrib/ltree_plpython/ltree_plpython3u.control b/contrib/ltree_plpython/ltree_plpython3u.control new file mode 100644 index 0000000..96c9764 --- /dev/null +++ b/contrib/ltree_plpython/ltree_plpython3u.control @@ -0,0 +1,6 @@ +# ltree_plpython3u extension +comment = 'transform between ltree and plpython3u' +default_version = '1.0' +module_pathname = '$libdir/ltree_plpython3' +relocatable = true +requires = 'ltree,plpython3u' diff --git a/contrib/ltree_plpython/meson.build b/contrib/ltree_plpython/meson.build new file mode 100644 index 0000000..d6c1a25 --- /dev/null +++ b/contrib/ltree_plpython/meson.build @@ -0,0 +1,45 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not python3_dep.found() + subdir_done() +endif + +ltree_plpython_sources = files( + 'ltree_plpython.c', +) + +if host_system == 'windows' + ltree_plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'ltree_plpython3', + '--FILEDESC', 'ltree_plpython - ltree transform for plpython',]) +endif + +ltree_plpython = shared_module('ltree_plpython3', + ltree_plpython_sources, + include_directories: [plpython_inc, ltree_inc], + c_args: ['-DPLPYTHON_LIBNAME="plpython3"'], + kwargs: contrib_mod_args + { + 'dependencies': [python3_dep, contrib_mod_args['dependencies']], + }, +) +contrib_targets += ltree_plpython + +install_data( + 'ltree_plpython3u--1.0.sql', + 'ltree_plpython3u.control', + kwargs: contrib_data_args, +) + +ltree_plpython_regress = [ + 'ltree_plpython' +] + +tests += { + 'name': 'ltree_plpython', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': ltree_plpython_regress, + 'regress_args': ['--load-extension=ltree'], + }, +} diff --git a/contrib/ltree_plpython/sql/ltree_plpython.sql b/contrib/ltree_plpython/sql/ltree_plpython.sql new file mode 100644 index 0000000..0b8d283 --- /dev/null +++ b/contrib/ltree_plpython/sql/ltree_plpython.sql @@ -0,0 +1,36 @@ +CREATE EXTENSION ltree_plpython3u CASCADE; + + +CREATE FUNCTION test1(val ltree) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE ltree +AS $$ +plpy.info(repr(val)) +return len(val) +$$; + +SELECT test1('aa.bb.cc'::ltree); + + +CREATE FUNCTION test1n(val ltree) RETURNS int +LANGUAGE plpython3u +TRANSFORM FOR TYPE ltree +AS $$ +plpy.info(repr(val)) +return len(val) +$$; + +SELECT test1n('aa.bb.cc'::ltree); + + +CREATE FUNCTION test2() RETURNS ltree +LANGUAGE plpython3u +TRANSFORM FOR TYPE ltree +AS $$ +return ['foo', 'bar', 'baz'] +$$; + +-- plpython to ltree is not yet implemented, so this will fail, +-- because it will try to parse the Python list as an ltree input +-- string. +SELECT test2(); diff --git a/contrib/meson.build b/contrib/meson.build new file mode 100644 index 0000000..bd4a57c --- /dev/null +++ b/contrib/meson.build @@ -0,0 +1,68 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +contrib_mod_args = pg_mod_args + +contrib_data_dir = dir_data_extension +contrib_data_args = { + 'install_dir': contrib_data_dir, +} + +subdir('adminpack') +subdir('amcheck') +subdir('auth_delay') +subdir('auto_explain') +subdir('basic_archive') +subdir('bloom') +subdir('basebackup_to_shell') +subdir('bool_plperl') +subdir('btree_gin') +subdir('btree_gist') +subdir('citext') +subdir('cube') +subdir('dblink') +subdir('dict_int') +subdir('dict_xsyn') +subdir('earthdistance') +subdir('file_fdw') +subdir('fuzzystrmatch') +subdir('hstore') +subdir('hstore_plperl') +subdir('hstore_plpython') +subdir('intagg') +subdir('intarray') +subdir('isn') +subdir('jsonb_plperl') +subdir('jsonb_plpython') +subdir('lo') +subdir('ltree') +subdir('ltree_plpython') +subdir('oid2name') +subdir('old_snapshot') +subdir('pageinspect') +subdir('passwordcheck') +subdir('pg_buffercache') +subdir('pgcrypto') +subdir('pg_freespacemap') +subdir('pg_prewarm') +subdir('pgrowlocks') +subdir('pg_stat_statements') +subdir('pgstattuple') +subdir('pg_surgery') +subdir('pg_trgm') +subdir('pg_visibility') +subdir('pg_walinspect') +subdir('postgres_fdw') +subdir('seg') +subdir('sepgsql') +subdir('spi') +subdir('sslinfo') +# start-scripts doesn't contain build products +subdir('tablefunc') +subdir('tcn') +subdir('test_decoding') +subdir('tsm_system_rows') +subdir('tsm_system_time') +subdir('unaccent') +subdir('uuid-ossp') +subdir('vacuumlo') +subdir('xml2') diff --git a/contrib/oid2name/.gitignore b/contrib/oid2name/.gitignore new file mode 100644 index 0000000..0410fb7 --- /dev/null +++ b/contrib/oid2name/.gitignore @@ -0,0 +1,3 @@ +/oid2name + +/tmp_check/ diff --git a/contrib/oid2name/Makefile b/contrib/oid2name/Makefile new file mode 100644 index 0000000..6179ed5 --- /dev/null +++ b/contrib/oid2name/Makefile @@ -0,0 +1,25 @@ +# contrib/oid2name/Makefile + +PGFILEDESC = "oid2name - examine the file structure" +PGAPPICON = win32 + +PROGRAM = oid2name +OBJS = \ + $(WIN32RES) \ + oid2name.o + +TAP_TESTS = 1 + +PG_CPPFLAGS = -I$(libpq_srcdir) +PG_LIBS_INTERNAL = $(libpq_pgport) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/oid2name +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/oid2name/meson.build b/contrib/oid2name/meson.build new file mode 100644 index 0000000..171d2d2 --- /dev/null +++ b/contrib/oid2name/meson.build @@ -0,0 +1,29 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +oid2name_sources = files( + 'oid2name.c', +) + +if host_system == 'windows' + oid2name_sources += rc_bin_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'oid2name', + '--FILEDESC', 'oid2name - examine the file structure',]) +endif + +oid2name = executable('oid2name', + oid2name_sources, + dependencies: [frontend_code, libpq], + kwargs: default_bin_args, +) +contrib_targets += oid2name + +tests += { + 'name': 'oid2name', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_basic.pl', + ], + }, +} diff --git a/contrib/oid2name/oid2name.c b/contrib/oid2name/oid2name.c new file mode 100644 index 0000000..e8c1e2c --- /dev/null +++ b/contrib/oid2name/oid2name.c @@ -0,0 +1,650 @@ +/* + * oid2name, a PostgreSQL app to map OIDs on the filesystem + * to table and database names. + * + * Originally by + * B. Palmer, bpalmer@crimelabs.net 1-17-2001 + * + * contrib/oid2name/oid2name.c + */ +#include "postgres_fe.h" + +#include "catalog/pg_class_d.h" +#include "common/connect.h" +#include "common/logging.h" +#include "common/string.h" +#include "getopt_long.h" +#include "libpq-fe.h" +#include "pg_getopt.h" + +/* an extensible array to keep track of elements to show */ +typedef struct +{ + char **array; + int num; + int alloc; +} eary; + +/* these are the opts structures for command line params */ +struct options +{ + eary *tables; + eary *oids; + eary *filenumbers; + + bool quiet; + bool systables; + bool indexes; + bool nodb; + bool extended; + bool tablespaces; + + char *dbname; + char *hostname; + char *port; + char *username; + const char *progname; +}; + +/* function prototypes */ +static void help(const char *progname); +void get_opts(int argc, char **argv, struct options *my_opts); +void add_one_elt(char *eltname, eary *eary); +char *get_comma_elts(eary *eary); +PGconn *sql_conn(struct options *my_opts); +int sql_exec(PGconn *conn, const char *todo, bool quiet); +void sql_exec_dumpalldbs(PGconn *conn, struct options *opts); +void sql_exec_dumpalltables(PGconn *conn, struct options *opts); +void sql_exec_searchtables(PGconn *conn, struct options *opts); +void sql_exec_dumpalltbspc(PGconn *conn, struct options *opts); + +/* function to parse command line options and check for some usage errors. */ +void +get_opts(int argc, char **argv, struct options *my_opts) +{ + static struct option long_options[] = { + {"dbname", required_argument, NULL, 'd'}, + {"host", required_argument, NULL, 'h'}, + {"host", required_argument, NULL, 'H'}, /* deprecated */ + {"filenode", required_argument, NULL, 'f'}, + {"indexes", no_argument, NULL, 'i'}, + {"oid", required_argument, NULL, 'o'}, + {"port", required_argument, NULL, 'p'}, + {"quiet", no_argument, NULL, 'q'}, + {"tablespaces", no_argument, NULL, 's'}, + {"system-objects", no_argument, NULL, 'S'}, + {"table", required_argument, NULL, 't'}, + {"username", required_argument, NULL, 'U'}, + {"version", no_argument, NULL, 'V'}, + {"extended", no_argument, NULL, 'x'}, + {"help", no_argument, NULL, '?'}, + {NULL, 0, NULL, 0} + }; + + int c; + const char *progname; + int optindex; + + pg_logging_init(argv[0]); + progname = get_progname(argv[0]); + + /* set the defaults */ + my_opts->quiet = false; + my_opts->systables = false; + my_opts->indexes = false; + my_opts->nodb = false; + my_opts->extended = false; + my_opts->tablespaces = false; + my_opts->dbname = NULL; + my_opts->hostname = NULL; + my_opts->port = NULL; + my_opts->username = NULL; + my_opts->progname = progname; + + if (argc > 1) + { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) + { + help(progname); + exit(0); + } + if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) + { + puts("oid2name (PostgreSQL) " PG_VERSION); + exit(0); + } + } + + /* get opts */ + while ((c = getopt_long(argc, argv, "d:f:h:H:io:p:qsSt:U:x", long_options, &optindex)) != -1) + { + switch (c) + { + /* specify the database */ + case 'd': + my_opts->dbname = pg_strdup(optarg); + break; + + /* specify one filenumber to show */ + case 'f': + add_one_elt(optarg, my_opts->filenumbers); + break; + + /* host to connect to */ + case 'H': /* deprecated */ + case 'h': + my_opts->hostname = pg_strdup(optarg); + break; + + /* also display indexes */ + case 'i': + my_opts->indexes = true; + break; + + /* specify one Oid to show */ + case 'o': + add_one_elt(optarg, my_opts->oids); + break; + + /* port to connect to on remote host */ + case 'p': + my_opts->port = pg_strdup(optarg); + break; + + /* don't show headers */ + case 'q': + my_opts->quiet = true; + break; + + /* dump tablespaces only */ + case 's': + my_opts->tablespaces = true; + break; + + /* display system tables */ + case 'S': + my_opts->systables = true; + break; + + /* specify one tablename to show */ + case 't': + add_one_elt(optarg, my_opts->tables); + break; + + /* username */ + case 'U': + my_opts->username = pg_strdup(optarg); + break; + + /* display extra columns */ + case 'x': + my_opts->extended = true; + break; + + default: + /* getopt_long already emitted a complaint */ + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit(1); + } + } + + if (optind < argc) + { + pg_log_error("too many command-line arguments (first is \"%s\")", + argv[optind]); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit(1); + } +} + +static void +help(const char *progname) +{ + printf("%s helps examining the file structure used by PostgreSQL.\n\n" + "Usage:\n" + " %s [OPTION]...\n" + "\nOptions:\n" + " -f, --filenode=FILENODE show info for table with given file node\n" + " -i, --indexes show indexes and sequences too\n" + " -o, --oid=OID show info for table with given OID\n" + " -q, --quiet quiet (don't show headers)\n" + " -s, --tablespaces show all tablespaces\n" + " -S, --system-objects show system objects too\n" + " -t, --table=TABLE show info for named table\n" + " -V, --version output version information, then exit\n" + " -x, --extended extended (show additional columns)\n" + " -?, --help show this help, then exit\n" + "\nConnection options:\n" + " -d, --dbname=DBNAME database to connect to\n" + " -h, --host=HOSTNAME database server host or socket directory\n" + " -H (same as -h, deprecated)\n" + " -p, --port=PORT database server port number\n" + " -U, --username=USERNAME connect as specified database user\n" + "\nThe default action is to show all database OIDs.\n\n" + "Report bugs to <%s>.\n" + "%s home page: <%s>\n", + progname, progname, PACKAGE_BUGREPORT, PACKAGE_NAME, PACKAGE_URL); +} + +/* + * add_one_elt + * + * Add one element to a (possibly empty) eary struct. + */ +void +add_one_elt(char *eltname, eary *eary) +{ + if (eary->alloc == 0) + { + eary ->alloc = 8; + eary ->array = (char **) pg_malloc(8 * sizeof(char *)); + } + else if (eary->num >= eary->alloc) + { + eary ->alloc *= 2; + eary ->array = (char **) pg_realloc(eary->array, + eary->alloc * sizeof(char *)); + } + + eary ->array[eary->num] = pg_strdup(eltname); + eary ->num++; +} + +/* + * get_comma_elts + * + * Return the elements of an eary as a (freshly allocated) single string, in + * single quotes, separated by commas and properly escaped for insertion in an + * SQL statement. + */ +char * +get_comma_elts(eary *eary) +{ + char *ret, + *ptr; + int i, + length = 0; + + if (eary->num == 0) + return pg_strdup(""); + + /* + * PQescapeString wants 2 * length + 1 bytes of breath space. Add two + * chars per element for the single quotes and one for the comma. + */ + for (i = 0; i < eary->num; i++) + length += strlen(eary->array[i]); + + ret = (char *) pg_malloc(length * 2 + 4 * eary->num); + ptr = ret; + + for (i = 0; i < eary->num; i++) + { + if (i != 0) + sprintf(ptr++, ","); + sprintf(ptr++, "'"); + ptr += PQescapeString(ptr, eary->array[i], strlen(eary->array[i])); + sprintf(ptr++, "'"); + } + + return ret; +} + +/* establish connection with database. */ +PGconn * +sql_conn(struct options *my_opts) +{ + PGconn *conn; + char *password = NULL; + bool new_pass; + PGresult *res; + + /* + * Start the connection. Loop until we have a password if requested by + * backend. + */ + do + { +#define PARAMS_ARRAY_SIZE 7 + + const char *keywords[PARAMS_ARRAY_SIZE]; + const char *values[PARAMS_ARRAY_SIZE]; + + keywords[0] = "host"; + values[0] = my_opts->hostname; + keywords[1] = "port"; + values[1] = my_opts->port; + keywords[2] = "user"; + values[2] = my_opts->username; + keywords[3] = "password"; + values[3] = password; + keywords[4] = "dbname"; + values[4] = my_opts->dbname; + keywords[5] = "fallback_application_name"; + values[5] = my_opts->progname; + keywords[6] = NULL; + values[6] = NULL; + + new_pass = false; + conn = PQconnectdbParams(keywords, values, true); + + if (!conn) + pg_fatal("could not connect to database %s", + my_opts->dbname); + + if (PQstatus(conn) == CONNECTION_BAD && + PQconnectionNeedsPassword(conn) && + !password) + { + PQfinish(conn); + password = simple_prompt("Password: ", false); + new_pass = true; + } + } while (new_pass); + + /* check to see that the backend connection was successfully made */ + if (PQstatus(conn) == CONNECTION_BAD) + { + pg_log_error("%s", PQerrorMessage(conn)); + PQfinish(conn); + exit(1); + } + + res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + pg_log_error("could not clear search_path: %s", + PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + exit(1); + } + PQclear(res); + + /* return the conn if good */ + return conn; +} + +/* + * Actual code to make call to the database and print the output data. + */ +int +sql_exec(PGconn *conn, const char *todo, bool quiet) +{ + PGresult *res; + + int nfields; + int nrows; + int i, + j, + l; + int *length; + char *pad; + + /* make the call */ + res = PQexec(conn, todo); + + /* check and deal with errors */ + if (!res || PQresultStatus(res) > 2) + { + pg_log_error("query failed: %s", PQerrorMessage(conn)); + pg_log_error_detail("Query was: %s", todo); + + PQclear(res); + PQfinish(conn); + exit(1); + } + + /* get the number of fields */ + nrows = PQntuples(res); + nfields = PQnfields(res); + + /* for each field, get the needed width */ + length = (int *) pg_malloc(sizeof(int) * nfields); + for (j = 0; j < nfields; j++) + length[j] = strlen(PQfname(res, j)); + + for (i = 0; i < nrows; i++) + { + for (j = 0; j < nfields; j++) + { + l = strlen(PQgetvalue(res, i, j)); + if (l > length[j]) + length[j] = strlen(PQgetvalue(res, i, j)); + } + } + + /* print a header */ + if (!quiet) + { + for (j = 0, l = 0; j < nfields; j++) + { + fprintf(stdout, "%*s", length[j] + 2, PQfname(res, j)); + l += length[j] + 2; + } + fprintf(stdout, "\n"); + pad = (char *) pg_malloc(l + 1); + memset(pad, '-', l); + pad[l] = '\0'; + fprintf(stdout, "%s\n", pad); + free(pad); + } + + /* for each row, dump the information */ + for (i = 0; i < nrows; i++) + { + for (j = 0; j < nfields; j++) + fprintf(stdout, "%*s", length[j] + 2, PQgetvalue(res, i, j)); + fprintf(stdout, "\n"); + } + + /* cleanup */ + PQclear(res); + free(length); + + return 0; +} + +/* + * Dump all databases. There are no system objects to worry about. + */ +void +sql_exec_dumpalldbs(PGconn *conn, struct options *opts) +{ + char todo[1024]; + + /* get the oid and database name from the system pg_database table */ + snprintf(todo, sizeof(todo), + "SELECT d.oid AS \"Oid\", datname AS \"Database Name\", " + "spcname AS \"Tablespace\" FROM pg_catalog.pg_database d JOIN pg_catalog.pg_tablespace t ON " + "(dattablespace = t.oid) ORDER BY 2"); + + sql_exec(conn, todo, opts->quiet); +} + +/* + * Dump all tables, indexes and sequences in the current database. + */ +void +sql_exec_dumpalltables(PGconn *conn, struct options *opts) +{ + char todo[1024]; + char *addfields = ",c.oid AS \"Oid\", nspname AS \"Schema\", spcname as \"Tablespace\" "; + + snprintf(todo, sizeof(todo), + "SELECT pg_catalog.pg_relation_filenode(c.oid) as \"Filenode\", relname as \"Table Name\" %s " + "FROM pg_catalog.pg_class c " + " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " + " LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database()," + " pg_catalog.pg_tablespace t " + "WHERE relkind IN (" CppAsString2(RELKIND_RELATION) "," + CppAsString2(RELKIND_MATVIEW) "%s%s) AND " + " %s" + " t.oid = CASE" + " WHEN reltablespace <> 0 THEN reltablespace" + " ELSE dattablespace" + " END " + "ORDER BY relname", + opts->extended ? addfields : "", + opts->indexes ? "," CppAsString2(RELKIND_INDEX) "," CppAsString2(RELKIND_SEQUENCE) : "", + opts->systables ? "," CppAsString2(RELKIND_TOASTVALUE) : "", + opts->systables ? "" : "n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname !~ '^pg_toast' AND"); + + sql_exec(conn, todo, opts->quiet); +} + +/* + * Show oid, filenumber, name, schema and tablespace for each of the + * given objects in the current database. + */ +void +sql_exec_searchtables(PGconn *conn, struct options *opts) +{ + char *todo; + char *qualifiers, + *ptr; + char *comma_oids, + *comma_filenumbers, + *comma_tables; + bool written = false; + char *addfields = ",c.oid AS \"Oid\", nspname AS \"Schema\", spcname as \"Tablespace\" "; + + /* get tables qualifiers, whether names, filenumbers, or OIDs */ + comma_oids = get_comma_elts(opts->oids); + comma_tables = get_comma_elts(opts->tables); + comma_filenumbers = get_comma_elts(opts->filenumbers); + + /* 80 extra chars for SQL expression */ + qualifiers = (char *) pg_malloc(strlen(comma_oids) + strlen(comma_tables) + + strlen(comma_filenumbers) + 80); + ptr = qualifiers; + + if (opts->oids->num > 0) + { + ptr += sprintf(ptr, "c.oid IN (%s)", comma_oids); + written = true; + } + if (opts->filenumbers->num > 0) + { + if (written) + ptr += sprintf(ptr, " OR "); + ptr += sprintf(ptr, "pg_catalog.pg_relation_filenode(c.oid) IN (%s)", + comma_filenumbers); + written = true; + } + if (opts->tables->num > 0) + { + if (written) + ptr += sprintf(ptr, " OR "); + sprintf(ptr, "c.relname ~~ ANY (ARRAY[%s])", comma_tables); + } + free(comma_oids); + free(comma_tables); + free(comma_filenumbers); + + /* now build the query */ + todo = psprintf("SELECT pg_catalog.pg_relation_filenode(c.oid) as \"Filenode\", relname as \"Table Name\" %s\n" + "FROM pg_catalog.pg_class c\n" + " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" + " LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database(),\n" + " pg_catalog.pg_tablespace t\n" + "WHERE relkind IN (" CppAsString2(RELKIND_RELATION) "," + CppAsString2(RELKIND_MATVIEW) "," + CppAsString2(RELKIND_INDEX) "," + CppAsString2(RELKIND_SEQUENCE) "," + CppAsString2(RELKIND_TOASTVALUE) ") AND\n" + " t.oid = CASE\n" + " WHEN reltablespace <> 0 THEN reltablespace\n" + " ELSE dattablespace\n" + " END AND\n" + " (%s)\n" + "ORDER BY relname\n", + opts->extended ? addfields : "", + qualifiers); + + free(qualifiers); + + sql_exec(conn, todo, opts->quiet); +} + +void +sql_exec_dumpalltbspc(PGconn *conn, struct options *opts) +{ + char todo[1024]; + + snprintf(todo, sizeof(todo), + "SELECT oid AS \"Oid\", spcname as \"Tablespace Name\"\n" + "FROM pg_catalog.pg_tablespace"); + + sql_exec(conn, todo, opts->quiet); +} + +int +main(int argc, char **argv) +{ + struct options *my_opts; + PGconn *pgconn; + + my_opts = (struct options *) pg_malloc(sizeof(struct options)); + + my_opts->oids = (eary *) pg_malloc(sizeof(eary)); + my_opts->tables = (eary *) pg_malloc(sizeof(eary)); + my_opts->filenumbers = (eary *) pg_malloc(sizeof(eary)); + + my_opts->oids->num = my_opts->oids->alloc = 0; + my_opts->tables->num = my_opts->tables->alloc = 0; + my_opts->filenumbers->num = my_opts->filenumbers->alloc = 0; + + /* parse the opts */ + get_opts(argc, argv, my_opts); + + if (my_opts->dbname == NULL) + { + my_opts->dbname = "postgres"; + my_opts->nodb = true; + } + pgconn = sql_conn(my_opts); + + /* display only tablespaces */ + if (my_opts->tablespaces) + { + if (!my_opts->quiet) + printf("All tablespaces:\n"); + sql_exec_dumpalltbspc(pgconn, my_opts); + + PQfinish(pgconn); + exit(0); + } + + /* display the given elements in the database */ + if (my_opts->oids->num > 0 || + my_opts->tables->num > 0 || + my_opts->filenumbers->num > 0) + { + if (!my_opts->quiet) + printf("From database \"%s\":\n", my_opts->dbname); + sql_exec_searchtables(pgconn, my_opts); + + PQfinish(pgconn); + exit(0); + } + + /* no elements given; dump the given database */ + if (my_opts->dbname && !my_opts->nodb) + { + if (!my_opts->quiet) + printf("From database \"%s\":\n", my_opts->dbname); + sql_exec_dumpalltables(pgconn, my_opts); + + PQfinish(pgconn); + exit(0); + } + + /* no database either; dump all databases */ + if (!my_opts->quiet) + printf("All databases:\n"); + sql_exec_dumpalldbs(pgconn, my_opts); + + PQfinish(pgconn); + return 0; +} diff --git a/contrib/oid2name/t/001_basic.pl b/contrib/oid2name/t/001_basic.pl new file mode 100644 index 0000000..74fe622 --- /dev/null +++ b/contrib/oid2name/t/001_basic.pl @@ -0,0 +1,17 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +use strict; +use warnings; + +use PostgreSQL::Test::Utils; +use Test::More; + +######################################### +# Basic checks + +program_help_ok('oid2name'); +program_version_ok('oid2name'); +program_options_handling_ok('oid2name'); + +done_testing(); diff --git a/contrib/old_snapshot/Makefile b/contrib/old_snapshot/Makefile new file mode 100644 index 0000000..adb5575 --- /dev/null +++ b/contrib/old_snapshot/Makefile @@ -0,0 +1,21 @@ +# contrib/old_snapshot/Makefile + +MODULE_big = old_snapshot +OBJS = \ + $(WIN32RES) \ + time_mapping.o + +EXTENSION = old_snapshot +DATA = old_snapshot--1.0.sql +PGFILEDESC = "old_snapshot - utilities in support of old_snapshot_threshold" + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/old_snapshot +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/old_snapshot/meson.build b/contrib/old_snapshot/meson.build new file mode 100644 index 0000000..fe5fb90 --- /dev/null +++ b/contrib/old_snapshot/meson.build @@ -0,0 +1,23 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +old_snapshot_sources = files( + 'time_mapping.c', +) + +if host_system == 'windows' + old_snapshot_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'old_snapshot', + '--FILEDESC', 'old_snapshot - utilities in support of old_snapshot_threshold',]) +endif + +old_snapshot = shared_module('old_snapshot', + old_snapshot_sources, + kwargs: contrib_mod_args, +) +contrib_targets += old_snapshot + +install_data( + 'old_snapshot.control', + 'old_snapshot--1.0.sql', + kwargs: contrib_data_args, +) diff --git a/contrib/old_snapshot/old_snapshot--1.0.sql b/contrib/old_snapshot/old_snapshot--1.0.sql new file mode 100644 index 0000000..9ebb882 --- /dev/null +++ b/contrib/old_snapshot/old_snapshot--1.0.sql @@ -0,0 +1,14 @@ +/* contrib/old_snapshot/old_snapshot--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION old_snapshot" to load this file. \quit + +-- Show visibility map and page-level visibility information for each block. +CREATE FUNCTION pg_old_snapshot_time_mapping(array_offset OUT int4, + end_timestamp OUT timestamptz, + newest_xmin OUT xid) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_old_snapshot_time_mapping' +LANGUAGE C STRICT; + +-- XXX. Do we want REVOKE commands here? diff --git a/contrib/old_snapshot/old_snapshot.control b/contrib/old_snapshot/old_snapshot.control new file mode 100644 index 0000000..491eec5 --- /dev/null +++ b/contrib/old_snapshot/old_snapshot.control @@ -0,0 +1,5 @@ +# old_snapshot extension +comment = 'utilities in support of old_snapshot_threshold' +default_version = '1.0' +module_pathname = '$libdir/old_snapshot' +relocatable = true diff --git a/contrib/old_snapshot/time_mapping.c b/contrib/old_snapshot/time_mapping.c new file mode 100644 index 0000000..352308c --- /dev/null +++ b/contrib/old_snapshot/time_mapping.c @@ -0,0 +1,142 @@ +/*------------------------------------------------------------------------- + * + * time_mapping.c + * time to XID mapping information + * + * Copyright (c) 2020-2023, PostgreSQL Global Development Group + * + * contrib/old_snapshot/time_mapping.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "funcapi.h" +#include "storage/lwlock.h" +#include "utils/old_snapshot.h" +#include "utils/snapmgr.h" +#include "utils/timestamp.h" + +/* + * Backend-private copy of the information from oldSnapshotControl which relates + * to the time to XID mapping, plus an index so that we can iterate. + * + * Note that the length of the xid_by_minute array is given by + * OLD_SNAPSHOT_TIME_MAP_ENTRIES (which is not a compile-time constant). + */ +typedef struct +{ + int current_index; + int head_offset; + TimestampTz head_timestamp; + int count_used; + TransactionId xid_by_minute[FLEXIBLE_ARRAY_MEMBER]; +} OldSnapshotTimeMapping; + +#define NUM_TIME_MAPPING_COLUMNS 3 + +PG_MODULE_MAGIC; +PG_FUNCTION_INFO_V1(pg_old_snapshot_time_mapping); + +static OldSnapshotTimeMapping *GetOldSnapshotTimeMapping(void); +static HeapTuple MakeOldSnapshotTimeMappingTuple(TupleDesc tupdesc, + OldSnapshotTimeMapping *mapping); + +/* + * SQL-callable set-returning function. + */ +Datum +pg_old_snapshot_time_mapping(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + OldSnapshotTimeMapping *mapping; + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + mapping = GetOldSnapshotTimeMapping(); + funcctx->user_fctx = mapping; + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + funcctx->tuple_desc = tupdesc; + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + mapping = (OldSnapshotTimeMapping *) funcctx->user_fctx; + + while (mapping->current_index < mapping->count_used) + { + HeapTuple tuple; + + tuple = MakeOldSnapshotTimeMappingTuple(funcctx->tuple_desc, mapping); + ++mapping->current_index; + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + + SRF_RETURN_DONE(funcctx); +} + +/* + * Get the old snapshot time mapping data from shared memory. + */ +static OldSnapshotTimeMapping * +GetOldSnapshotTimeMapping(void) +{ + OldSnapshotTimeMapping *mapping; + + mapping = palloc(offsetof(OldSnapshotTimeMapping, xid_by_minute) + + sizeof(TransactionId) * OLD_SNAPSHOT_TIME_MAP_ENTRIES); + mapping->current_index = 0; + + LWLockAcquire(OldSnapshotTimeMapLock, LW_SHARED); + mapping->head_offset = oldSnapshotControl->head_offset; + mapping->head_timestamp = oldSnapshotControl->head_timestamp; + mapping->count_used = oldSnapshotControl->count_used; + for (int i = 0; i < OLD_SNAPSHOT_TIME_MAP_ENTRIES; ++i) + mapping->xid_by_minute[i] = oldSnapshotControl->xid_by_minute[i]; + LWLockRelease(OldSnapshotTimeMapLock); + + return mapping; +} + +/* + * Convert one entry from the old snapshot time mapping to a HeapTuple. + */ +static HeapTuple +MakeOldSnapshotTimeMappingTuple(TupleDesc tupdesc, OldSnapshotTimeMapping *mapping) +{ + Datum values[NUM_TIME_MAPPING_COLUMNS]; + bool nulls[NUM_TIME_MAPPING_COLUMNS]; + int array_position; + TimestampTz timestamp; + + /* + * Figure out the array position corresponding to the current index. + * + * Index 0 means the oldest entry in the mapping, which is stored at + * mapping->head_offset. Index 1 means the next-oldest entry, which is a + * the following index, and so on. We wrap around when we reach the end of + * the array. + */ + array_position = (mapping->head_offset + mapping->current_index) + % OLD_SNAPSHOT_TIME_MAP_ENTRIES; + + /* + * No explicit timestamp is stored for any entry other than the oldest + * one, but each entry corresponds to 1-minute period, so we can just add. + */ + timestamp = TimestampTzPlusMilliseconds(mapping->head_timestamp, + mapping->current_index * 60000); + + /* Initialize nulls and values arrays. */ + memset(nulls, 0, sizeof(nulls)); + values[0] = Int32GetDatum(array_position); + values[1] = TimestampTzGetDatum(timestamp); + values[2] = TransactionIdGetDatum(mapping->xid_by_minute[array_position]); + + return heap_form_tuple(tupdesc, values, nulls); +} diff --git a/contrib/pageinspect/.gitignore b/contrib/pageinspect/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pageinspect/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile new file mode 100644 index 0000000..95e030b --- /dev/null +++ b/contrib/pageinspect/Makefile @@ -0,0 +1,36 @@ +# contrib/pageinspect/Makefile + +MODULE_big = pageinspect +OBJS = \ + $(WIN32RES) \ + brinfuncs.o \ + btreefuncs.o \ + fsmfuncs.o \ + ginfuncs.o \ + gistfuncs.o \ + hashfuncs.o \ + heapfuncs.o \ + rawpage.o + +EXTENSION = pageinspect +DATA = pageinspect--1.11--1.12.sql pageinspect--1.10--1.11.sql \ + pageinspect--1.9--1.10.sql pageinspect--1.8--1.9.sql \ + pageinspect--1.7--1.8.sql pageinspect--1.6--1.7.sql \ + pageinspect--1.5.sql pageinspect--1.5--1.6.sql \ + pageinspect--1.4--1.5.sql pageinspect--1.3--1.4.sql \ + pageinspect--1.2--1.3.sql pageinspect--1.1--1.2.sql \ + pageinspect--1.0--1.1.sql +PGFILEDESC = "pageinspect - functions to inspect contents of database pages" + +REGRESS = page btree brin gin gist hash checksum oldextversions + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pageinspect +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pageinspect/brinfuncs.c b/contrib/pageinspect/brinfuncs.c new file mode 100644 index 0000000..a781f26 --- /dev/null +++ b/contrib/pageinspect/brinfuncs.c @@ -0,0 +1,422 @@ +/* + * brinfuncs.c + * Functions to investigate BRIN indexes + * + * Copyright (c) 2014-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pageinspect/brinfuncs.c + */ +#include "postgres.h" + +#include "access/brin.h" +#include "access/brin_internal.h" +#include "access/brin_page.h" +#include "access/brin_revmap.h" +#include "access/brin_tuple.h" +#include "access/htup_details.h" +#include "catalog/index.h" +#include "catalog/pg_am_d.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "pageinspect.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" + +PG_FUNCTION_INFO_V1(brin_page_type); +PG_FUNCTION_INFO_V1(brin_page_items); +PG_FUNCTION_INFO_V1(brin_metapage_info); +PG_FUNCTION_INFO_V1(brin_revmap_data); + +#define IS_BRIN(r) ((r)->rd_rel->relam == BRIN_AM_OID) + +typedef struct brin_column_state +{ + int nstored; + FmgrInfo outputFn[FLEXIBLE_ARRAY_MEMBER]; +} brin_column_state; + + +static Page verify_brin_page(bytea *raw_page, uint16 type, + const char *strtype); + +Datum +brin_page_type(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Page page; + char *type; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + page = get_page_from_raw(raw_page); + + if (PageIsNew(page)) + PG_RETURN_NULL(); + + /* verify the special space has the expected size */ + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "BRIN"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(BrinSpecialSpace)), + (int) PageGetSpecialSize(page)))); + + switch (BrinPageType(page)) + { + case BRIN_PAGETYPE_META: + type = "meta"; + break; + case BRIN_PAGETYPE_REVMAP: + type = "revmap"; + break; + case BRIN_PAGETYPE_REGULAR: + type = "regular"; + break; + default: + type = psprintf("unknown (%02x)", BrinPageType(page)); + break; + } + + PG_RETURN_TEXT_P(cstring_to_text(type)); +} + +/* + * Verify that the given bytea contains a BRIN page of the indicated page + * type, or die in the attempt. A pointer to the page is returned. + */ +static Page +verify_brin_page(bytea *raw_page, uint16 type, const char *strtype) +{ + Page page = get_page_from_raw(raw_page); + + if (PageIsNew(page)) + return page; + + /* verify the special space has the expected size */ + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BrinSpecialSpace))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "BRIN"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(BrinSpecialSpace)), + (int) PageGetSpecialSize(page)))); + + /* verify the special space says this page is what we want */ + if (BrinPageType(page) != type) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("page is not a BRIN page of type \"%s\"", strtype), + errdetail("Expected special type %08x, got %08x.", + type, BrinPageType(page)))); + + return page; +} + + +/* + * Extract all item values from a BRIN index page + * + * Usage: SELECT * FROM brin_page_items(get_raw_page('idx', 1), 'idx'::regclass); + */ +Datum +brin_page_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Oid indexRelid = PG_GETARG_OID(1); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Relation indexRel; + brin_column_state **columns; + BrinDesc *bdesc; + BrinMemTuple *dtup; + Page page; + OffsetNumber offset; + AttrNumber attno; + bool unusedItem; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + InitMaterializedSRF(fcinfo, 0); + + indexRel = index_open(indexRelid, AccessShareLock); + + if (!IS_BRIN(indexRel)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a %s index", + RelationGetRelationName(indexRel), "BRIN"))); + + bdesc = brin_build_desc(indexRel); + + /* minimally verify the page we got */ + page = verify_brin_page(raw_page, BRIN_PAGETYPE_REGULAR, "regular"); + + if (PageIsNew(page)) + { + brin_free_desc(bdesc); + index_close(indexRel, AccessShareLock); + PG_RETURN_NULL(); + } + + /* + * Initialize output functions for all indexed datatypes; simplifies + * calling them later. + */ + columns = palloc(sizeof(brin_column_state *) * RelationGetDescr(indexRel)->natts); + for (attno = 1; attno <= bdesc->bd_tupdesc->natts; attno++) + { + Oid output; + bool isVarlena; + BrinOpcInfo *opcinfo; + int i; + brin_column_state *column; + + opcinfo = bdesc->bd_info[attno - 1]; + column = palloc(offsetof(brin_column_state, outputFn) + + sizeof(FmgrInfo) * opcinfo->oi_nstored); + + column->nstored = opcinfo->oi_nstored; + for (i = 0; i < opcinfo->oi_nstored; i++) + { + getTypeOutputInfo(opcinfo->oi_typcache[i]->type_id, &output, &isVarlena); + fmgr_info(output, &column->outputFn[i]); + } + + columns[attno - 1] = column; + } + + offset = FirstOffsetNumber; + unusedItem = false; + dtup = NULL; + for (;;) + { + Datum values[8]; + bool nulls[8] = {0}; + + /* + * This loop is called once for every attribute of every tuple in the + * page. At the start of a tuple, we get a NULL dtup; that's our + * signal for obtaining and decoding the next one. If that's not the + * case, we output the next attribute. + */ + if (dtup == NULL) + { + ItemId itemId; + + /* verify item status: if there's no data, we can't decode */ + itemId = PageGetItemId(page, offset); + if (ItemIdIsUsed(itemId)) + { + dtup = brin_deform_tuple(bdesc, + (BrinTuple *) PageGetItem(page, itemId), + NULL); + attno = 1; + unusedItem = false; + } + else + unusedItem = true; + } + else + attno++; + + if (unusedItem) + { + values[0] = UInt16GetDatum(offset); + nulls[1] = true; + nulls[2] = true; + nulls[3] = true; + nulls[4] = true; + nulls[5] = true; + nulls[6] = true; + nulls[7] = true; + } + else + { + int att = attno - 1; + + values[0] = UInt16GetDatum(offset); + switch (TupleDescAttr(rsinfo->setDesc, 1)->atttypid) + { + case INT8OID: + values[1] = Int64GetDatum((int64) dtup->bt_blkno); + break; + case INT4OID: + /* support for old extension version */ + values[1] = UInt32GetDatum(dtup->bt_blkno); + break; + default: + elog(ERROR, "incorrect output types"); + } + values[2] = UInt16GetDatum(attno); + values[3] = BoolGetDatum(dtup->bt_columns[att].bv_allnulls); + values[4] = BoolGetDatum(dtup->bt_columns[att].bv_hasnulls); + values[5] = BoolGetDatum(dtup->bt_placeholder); + values[6] = BoolGetDatum(dtup->bt_empty_range); + if (!dtup->bt_columns[att].bv_allnulls) + { + BrinValues *bvalues = &dtup->bt_columns[att]; + StringInfoData s; + bool first; + int i; + + initStringInfo(&s); + appendStringInfoChar(&s, '{'); + + first = true; + for (i = 0; i < columns[att]->nstored; i++) + { + char *val; + + if (!first) + appendStringInfoString(&s, " .. "); + first = false; + val = OutputFunctionCall(&columns[att]->outputFn[i], + bvalues->bv_values[i]); + appendStringInfoString(&s, val); + pfree(val); + } + appendStringInfoChar(&s, '}'); + + values[7] = CStringGetTextDatum(s.data); + pfree(s.data); + } + else + { + nulls[7] = true; + } + } + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + + /* + * If the item was unused, jump straight to the next one; otherwise, + * the only cleanup needed here is to set our signal to go to the next + * tuple in the following iteration, by freeing the current one. + */ + if (unusedItem) + offset = OffsetNumberNext(offset); + else if (attno >= bdesc->bd_tupdesc->natts) + { + pfree(dtup); + dtup = NULL; + offset = OffsetNumberNext(offset); + } + + /* + * If we're beyond the end of the page, we're done. + */ + if (offset > PageGetMaxOffsetNumber(page)) + break; + } + + brin_free_desc(bdesc); + index_close(indexRel, AccessShareLock); + + return (Datum) 0; +} + +Datum +brin_metapage_info(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Page page; + BrinMetaPageData *meta; + TupleDesc tupdesc; + Datum values[4]; + bool nulls[4] = {0}; + HeapTuple htup; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + page = verify_brin_page(raw_page, BRIN_PAGETYPE_META, "metapage"); + + if (PageIsNew(page)) + PG_RETURN_NULL(); + + /* 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); + + /* Extract values from the metapage */ + meta = (BrinMetaPageData *) PageGetContents(page); + values[0] = CStringGetTextDatum(psprintf("0x%08X", meta->brinMagic)); + values[1] = Int32GetDatum(meta->brinVersion); + values[2] = Int32GetDatum(meta->pagesPerRange); + values[3] = Int64GetDatum(meta->lastRevmapPage); + + htup = heap_form_tuple(tupdesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(htup)); +} + +/* + * Return the TID array stored in a BRIN revmap page + */ +Datum +brin_revmap_data(PG_FUNCTION_ARGS) +{ + struct + { + ItemPointerData *tids; + int idx; + } *state; + FuncCallContext *fctx; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + if (SRF_IS_FIRSTCALL()) + { + bytea *raw_page = PG_GETARG_BYTEA_P(0); + MemoryContext mctx; + Page page; + + /* create a function context for cross-call persistence */ + fctx = SRF_FIRSTCALL_INIT(); + + /* switch to memory context appropriate for multiple function calls */ + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + /* minimally verify the page we got */ + page = verify_brin_page(raw_page, BRIN_PAGETYPE_REVMAP, "revmap"); + + if (PageIsNew(page)) + { + MemoryContextSwitchTo(mctx); + PG_RETURN_NULL(); + } + + state = palloc(sizeof(*state)); + state->tids = ((RevmapContents *) PageGetContents(page))->rm_tids; + state->idx = 0; + + fctx->user_fctx = state; + + MemoryContextSwitchTo(mctx); + } + + fctx = SRF_PERCALL_SETUP(); + state = fctx->user_fctx; + + if (state->idx < REVMAP_PAGE_MAXITEMS) + SRF_RETURN_NEXT(fctx, PointerGetDatum(&state->tids[state->idx++])); + + SRF_RETURN_DONE(fctx); +} diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c new file mode 100644 index 0000000..9cdc8e1 --- /dev/null +++ b/contrib/pageinspect/btreefuncs.c @@ -0,0 +1,939 @@ +/* + * contrib/pageinspect/btreefuncs.c + * + * + * btreefuncs.c + * + * Copyright (c) 2006 Satoshi Nagayasu + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose, without fee, and without a + * written agreement is hereby granted, provided that the above + * copyright notice and this paragraph and the following two + * paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, + * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS + * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, + * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ + +#include "postgres.h" + +#include "access/nbtree.h" +#include "access/relation.h" +#include "catalog/namespace.h" +#include "catalog/pg_am.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "pageinspect.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/rel.h" +#include "utils/varlena.h" + +PG_FUNCTION_INFO_V1(bt_metap); +PG_FUNCTION_INFO_V1(bt_page_items_1_9); +PG_FUNCTION_INFO_V1(bt_page_items); +PG_FUNCTION_INFO_V1(bt_page_items_bytea); +PG_FUNCTION_INFO_V1(bt_page_stats_1_9); +PG_FUNCTION_INFO_V1(bt_page_stats); +PG_FUNCTION_INFO_V1(bt_multi_page_stats); + +#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX) +#define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID) + +/* ------------------------------------------------ + * structure for single btree page statistics + * ------------------------------------------------ + */ +typedef struct BTPageStat +{ + uint32 blkno; + uint32 live_items; + uint32 dead_items; + uint32 page_size; + uint32 max_avail; + uint32 free_size; + uint32 avg_item_size; + char type; + + /* opaque data */ + BlockNumber btpo_prev; + BlockNumber btpo_next; + uint32 btpo_level; + uint16 btpo_flags; + BTCycleId btpo_cycleid; +} BTPageStat; + +/* + * cross-call data structure for SRF for page stats + */ +typedef struct ua_page_stats +{ + Oid relid; + int64 blkno; + int64 blk_count; + bool allpages; +} ua_page_stats; + +/* + * cross-call data structure for SRF for page items + */ +typedef struct ua_page_items +{ + Page page; + OffsetNumber offset; + bool leafpage; + bool rightmost; + TupleDesc tupd; +} ua_page_items; + + +/* ------------------------------------------------- + * GetBTPageStatistics() + * + * Collect statistics of single b-tree page + * ------------------------------------------------- + */ +static void +GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat *stat) +{ + Page page = BufferGetPage(buffer); + PageHeader phdr = (PageHeader) page; + OffsetNumber maxoff = PageGetMaxOffsetNumber(page); + BTPageOpaque opaque = BTPageGetOpaque(page); + int item_size = 0; + int off; + + stat->blkno = blkno; + + stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData); + + stat->dead_items = stat->live_items = 0; + + stat->page_size = PageGetPageSize(page); + + /* page type (flags) */ + if (P_ISDELETED(opaque)) + { + /* We divide deleted pages into leaf ('d') or internal ('D') */ + if (P_ISLEAF(opaque) || !P_HAS_FULLXID(opaque)) + stat->type = 'd'; + else + stat->type = 'D'; + + /* + * Report safexid in a deleted page. + * + * Handle pg_upgrade'd deleted pages that used the previous safexid + * representation in btpo_level field (this used to be a union type + * called "bpto"). + */ + if (P_HAS_FULLXID(opaque)) + { + FullTransactionId safexid = BTPageGetDeleteXid(page); + + elog(DEBUG2, "deleted page from block %u has safexid %u:%u", + blkno, EpochFromFullTransactionId(safexid), + XidFromFullTransactionId(safexid)); + } + else + elog(DEBUG2, "deleted page from block %u has safexid %u", + blkno, opaque->btpo_level); + + /* Don't interpret BTDeletedPageData as index tuples */ + maxoff = InvalidOffsetNumber; + } + else if (P_IGNORE(opaque)) + stat->type = 'e'; + else if (P_ISLEAF(opaque)) + stat->type = 'l'; + else if (P_ISROOT(opaque)) + stat->type = 'r'; + else + stat->type = 'i'; + + /* btpage opaque data */ + stat->btpo_prev = opaque->btpo_prev; + stat->btpo_next = opaque->btpo_next; + stat->btpo_level = opaque->btpo_level; + stat->btpo_flags = opaque->btpo_flags; + stat->btpo_cycleid = opaque->btpo_cycleid; + + /* count live and dead tuples, and free space */ + for (off = FirstOffsetNumber; off <= maxoff; off++) + { + IndexTuple itup; + + ItemId id = PageGetItemId(page, off); + + itup = (IndexTuple) PageGetItem(page, id); + + item_size += IndexTupleSize(itup); + + if (!ItemIdIsDead(id)) + stat->live_items++; + else + stat->dead_items++; + } + stat->free_size = PageGetFreeSpace(page); + + if ((stat->live_items + stat->dead_items) > 0) + stat->avg_item_size = item_size / (stat->live_items + stat->dead_items); + else + stat->avg_item_size = 0; +} + +/* ----------------------------------------------- + * check_relation_block_range() + * + * Verify that a block number (given as int64) is valid for the relation. + * ----------------------------------------------- + */ +static void +check_relation_block_range(Relation rel, int64 blkno) +{ + /* Ensure we can cast to BlockNumber */ + if (blkno < 0 || blkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number %lld", + (long long) blkno))); + + if ((BlockNumber) (blkno) >= RelationGetNumberOfBlocks(rel)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("block number %lld is out of range", + (long long) blkno))); +} + +/* ----------------------------------------------- + * bt_index_block_validate() + * + * Validate index type is btree and block number + * is valid (and not the metapage). + * ----------------------------------------------- + */ +static void +bt_index_block_validate(Relation rel, int64 blkno) +{ + if (!IS_INDEX(rel) || !IS_BTREE(rel)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a %s index", + RelationGetRelationName(rel), "btree"))); + + /* + * Reject attempts to read non-local temporary relations; we would be + * likely to get wrong data since we have no visibility into the owning + * session's local buffers. + */ + if (RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary tables of other sessions"))); + + if (blkno == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("block 0 is a meta page"))); + + check_relation_block_range(rel, blkno); +} + +/* ----------------------------------------------- + * bt_page_stats() + * + * Usage: SELECT * FROM bt_page_stats('t1_pkey', 1); + * Arguments are index relation name and block number + * ----------------------------------------------- + */ +static Datum +bt_page_stats_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version) +{ + text *relname = PG_GETARG_TEXT_PP(0); + int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1)); + Buffer buffer; + Relation rel; + RangeVar *relrv; + Datum result; + HeapTuple tuple; + TupleDesc tupleDesc; + int j; + char *values[11]; + BTPageStat stat; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pageinspect functions"))); + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + bt_index_block_validate(rel, blkno); + + buffer = ReadBuffer(rel, blkno); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + /* keep compiler quiet */ + stat.btpo_prev = stat.btpo_next = InvalidBlockNumber; + stat.btpo_flags = stat.free_size = stat.avg_item_size = 0; + + GetBTPageStatistics(blkno, buffer, &stat); + + UnlockReleaseBuffer(buffer); + relation_close(rel, AccessShareLock); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + j = 0; + values[j++] = psprintf("%u", stat.blkno); + values[j++] = psprintf("%c", stat.type); + values[j++] = psprintf("%u", stat.live_items); + values[j++] = psprintf("%u", stat.dead_items); + values[j++] = psprintf("%u", stat.avg_item_size); + values[j++] = psprintf("%u", stat.page_size); + values[j++] = psprintf("%u", stat.free_size); + values[j++] = psprintf("%u", stat.btpo_prev); + values[j++] = psprintf("%u", stat.btpo_next); + values[j++] = psprintf("%u", stat.btpo_level); + values[j++] = psprintf("%d", stat.btpo_flags); + + tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc), + values); + + result = HeapTupleGetDatum(tuple); + + PG_RETURN_DATUM(result); +} + +Datum +bt_page_stats_1_9(PG_FUNCTION_ARGS) +{ + return bt_page_stats_internal(fcinfo, PAGEINSPECT_V1_9); +} + +/* entry point for old extension version */ +Datum +bt_page_stats(PG_FUNCTION_ARGS) +{ + return bt_page_stats_internal(fcinfo, PAGEINSPECT_V1_8); +} + + +/* ----------------------------------------------- + * bt_multi_page_stats() + * + * Usage: SELECT * FROM bt_page_stats('t1_pkey', 1, 2); + * Arguments are index relation name, first block number, number of blocks + * (but number of blocks can be negative to mean "read all the rest") + * ----------------------------------------------- + */ +Datum +bt_multi_page_stats(PG_FUNCTION_ARGS) +{ + Relation rel; + ua_page_stats *uargs; + FuncCallContext *fctx; + MemoryContext mctx; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pageinspect functions"))); + + if (SRF_IS_FIRSTCALL()) + { + text *relname = PG_GETARG_TEXT_PP(0); + int64 blkno = PG_GETARG_INT64(1); + int64 blk_count = PG_GETARG_INT64(2); + RangeVar *relrv; + + fctx = SRF_FIRSTCALL_INIT(); + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + /* Check that rel is a valid btree index and 1st block number is OK */ + bt_index_block_validate(rel, blkno); + + /* + * Check if upper bound of the specified range is valid. If only one + * page is requested, skip as we've already validated the page. (Also, + * it's important to skip this if blk_count is negative.) + */ + if (blk_count > 1) + check_relation_block_range(rel, blkno + blk_count - 1); + + /* Save arguments for reuse */ + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + uargs = palloc(sizeof(ua_page_stats)); + + uargs->relid = RelationGetRelid(rel); + uargs->blkno = blkno; + uargs->blk_count = blk_count; + uargs->allpages = (blk_count < 0); + + fctx->user_fctx = uargs; + + MemoryContextSwitchTo(mctx); + + /* + * To avoid possibly leaking a relcache reference if the SRF isn't run + * to completion, we close and re-open the index rel each time + * through, using the index's OID for re-opens to ensure we get the + * same rel. Keep the AccessShareLock though, to ensure it doesn't go + * away underneath us. + */ + relation_close(rel, NoLock); + } + + fctx = SRF_PERCALL_SETUP(); + uargs = fctx->user_fctx; + + /* We should have lock already */ + rel = relation_open(uargs->relid, NoLock); + + /* In all-pages mode, recheck the index length each time */ + if (uargs->allpages) + uargs->blk_count = RelationGetNumberOfBlocks(rel) - uargs->blkno; + + if (uargs->blk_count > 0) + { + /* We need to fetch next block statistics */ + Buffer buffer; + Datum result; + HeapTuple tuple; + int j; + char *values[11]; + BTPageStat stat; + TupleDesc tupleDesc; + + buffer = ReadBuffer(rel, uargs->blkno); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + /* keep compiler quiet */ + stat.btpo_prev = stat.btpo_next = InvalidBlockNumber; + stat.btpo_flags = stat.free_size = stat.avg_item_size = 0; + + GetBTPageStatistics(uargs->blkno, buffer, &stat); + + UnlockReleaseBuffer(buffer); + relation_close(rel, NoLock); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + j = 0; + values[j++] = psprintf("%u", stat.blkno); + values[j++] = psprintf("%c", stat.type); + values[j++] = psprintf("%u", stat.live_items); + values[j++] = psprintf("%u", stat.dead_items); + values[j++] = psprintf("%u", stat.avg_item_size); + values[j++] = psprintf("%u", stat.page_size); + values[j++] = psprintf("%u", stat.free_size); + values[j++] = psprintf("%u", stat.btpo_prev); + values[j++] = psprintf("%u", stat.btpo_next); + values[j++] = psprintf("%u", stat.btpo_level); + values[j++] = psprintf("%d", stat.btpo_flags); + + /* Construct tuple to be returned */ + tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc), + values); + + result = HeapTupleGetDatum(tuple); + + /* + * Move to the next block number and decrement the number of blocks + * still to be fetched + */ + uargs->blkno++; + uargs->blk_count--; + + SRF_RETURN_NEXT(fctx, result); + } + + /* Done, so finally we can release the index lock */ + relation_close(rel, AccessShareLock); + SRF_RETURN_DONE(fctx); +} + +/*------------------------------------------------------- + * bt_page_print_tuples() + * + * Form a tuple describing index tuple at a given offset + * ------------------------------------------------------ + */ +static Datum +bt_page_print_tuples(ua_page_items *uargs) +{ + Page page = uargs->page; + OffsetNumber offset = uargs->offset; + bool leafpage = uargs->leafpage; + bool rightmost = uargs->rightmost; + bool ispivottuple; + Datum values[9]; + bool nulls[9]; + HeapTuple tuple; + ItemId id; + IndexTuple itup; + int j; + int off; + int dlen; + char *dump, + *datacstring; + char *ptr; + ItemPointer htid; + + id = PageGetItemId(page, offset); + + if (!ItemIdIsValid(id)) + elog(ERROR, "invalid ItemId"); + + itup = (IndexTuple) PageGetItem(page, id); + + j = 0; + memset(nulls, 0, sizeof(nulls)); + values[j++] = DatumGetInt16(offset); + values[j++] = ItemPointerGetDatum(&itup->t_tid); + values[j++] = Int32GetDatum((int) IndexTupleSize(itup)); + values[j++] = BoolGetDatum(IndexTupleHasNulls(itup)); + values[j++] = BoolGetDatum(IndexTupleHasVarwidths(itup)); + + ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info); + dlen = IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info); + + /* + * Make sure that "data" column does not include posting list or pivot + * tuple representation of heap TID(s). + * + * Note: BTreeTupleIsPivot() won't work reliably on !heapkeyspace indexes + * (those built before BTREE_VERSION 4), but we have no way of determining + * if this page came from a !heapkeyspace index. We may only have a bytea + * nbtree page image to go on, so in general there is no metapage that we + * can check. + * + * That's okay here because BTreeTupleIsPivot() can only return false for + * a !heapkeyspace pivot, never true for a !heapkeyspace non-pivot. Since + * heap TID isn't part of the keyspace in a !heapkeyspace index anyway, + * there cannot possibly be a pivot tuple heap TID representation that we + * fail to make an adjustment for. A !heapkeyspace index can have + * BTreeTupleIsPivot() return true (due to things like suffix truncation + * for INCLUDE indexes in Postgres v11), but when that happens + * BTreeTupleGetHeapTID() can be trusted to work reliably (i.e. return + * NULL). + * + * Note: BTreeTupleIsPosting() always works reliably, even with + * !heapkeyspace indexes. + */ + if (BTreeTupleIsPosting(itup)) + dlen -= IndexTupleSize(itup) - BTreeTupleGetPostingOffset(itup); + else if (BTreeTupleIsPivot(itup) && BTreeTupleGetHeapTID(itup) != NULL) + dlen -= MAXALIGN(sizeof(ItemPointerData)); + + if (dlen < 0 || dlen > INDEX_SIZE_MASK) + elog(ERROR, "invalid tuple length %d for tuple at offset number %u", + dlen, offset); + dump = palloc0(dlen * 3 + 1); + datacstring = dump; + for (off = 0; off < dlen; off++) + { + if (off > 0) + *dump++ = ' '; + sprintf(dump, "%02x", *(ptr + off) & 0xff); + dump += 2; + } + values[j++] = CStringGetTextDatum(datacstring); + pfree(datacstring); + + /* + * We need to work around the BTreeTupleIsPivot() !heapkeyspace limitation + * again. Deduce whether or not tuple must be a pivot tuple based on + * whether or not the page is a leaf page, as well as the page offset + * number of the tuple. + */ + ispivottuple = (!leafpage || (!rightmost && offset == P_HIKEY)); + + /* LP_DEAD bit can never be set for pivot tuples, so show a NULL there */ + if (!ispivottuple) + values[j++] = BoolGetDatum(ItemIdIsDead(id)); + else + { + Assert(!ItemIdIsDead(id)); + nulls[j++] = true; + } + + htid = BTreeTupleGetHeapTID(itup); + if (ispivottuple && !BTreeTupleIsPivot(itup)) + { + /* Don't show bogus heap TID in !heapkeyspace pivot tuple */ + htid = NULL; + } + + if (htid) + values[j++] = ItemPointerGetDatum(htid); + else + nulls[j++] = true; + + if (BTreeTupleIsPosting(itup)) + { + /* Build an array of item pointers */ + ItemPointer tids; + Datum *tids_datum; + int nposting; + + tids = BTreeTupleGetPosting(itup); + nposting = BTreeTupleGetNPosting(itup); + tids_datum = (Datum *) palloc(nposting * sizeof(Datum)); + for (int i = 0; i < nposting; i++) + tids_datum[i] = ItemPointerGetDatum(&tids[i]); + values[j++] = PointerGetDatum(construct_array_builtin(tids_datum, nposting, TIDOID)); + pfree(tids_datum); + } + else + nulls[j++] = true; + + /* Build and return the result tuple */ + tuple = heap_form_tuple(uargs->tupd, values, nulls); + + return HeapTupleGetDatum(tuple); +} + +/*------------------------------------------------------- + * bt_page_items() + * + * Get IndexTupleData set in a btree page + * + * Usage: SELECT * FROM bt_page_items('t1_pkey', 1); + *------------------------------------------------------- + */ +static Datum +bt_page_items_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version) +{ + text *relname = PG_GETARG_TEXT_PP(0); + int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1)); + Datum result; + FuncCallContext *fctx; + MemoryContext mctx; + ua_page_items *uargs; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pageinspect functions"))); + + if (SRF_IS_FIRSTCALL()) + { + RangeVar *relrv; + Relation rel; + Buffer buffer; + BTPageOpaque opaque; + TupleDesc tupleDesc; + + fctx = SRF_FIRSTCALL_INIT(); + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + bt_index_block_validate(rel, blkno); + + buffer = ReadBuffer(rel, blkno); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + /* + * We copy the page into local storage to avoid holding pin on the + * buffer longer than we must, and possibly failing to release it at + * all if the calling query doesn't fetch all rows. + */ + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + uargs = palloc(sizeof(ua_page_items)); + + uargs->page = palloc(BLCKSZ); + memcpy(uargs->page, BufferGetPage(buffer), BLCKSZ); + + UnlockReleaseBuffer(buffer); + relation_close(rel, AccessShareLock); + + uargs->offset = FirstOffsetNumber; + + opaque = BTPageGetOpaque(uargs->page); + + if (!P_ISDELETED(opaque)) + fctx->max_calls = PageGetMaxOffsetNumber(uargs->page); + else + { + /* Don't interpret BTDeletedPageData as index tuples */ + elog(NOTICE, "page from block " INT64_FORMAT " is deleted", blkno); + fctx->max_calls = 0; + } + uargs->leafpage = P_ISLEAF(opaque); + uargs->rightmost = P_RIGHTMOST(opaque); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + tupleDesc = BlessTupleDesc(tupleDesc); + + uargs->tupd = tupleDesc; + + fctx->user_fctx = uargs; + + MemoryContextSwitchTo(mctx); + } + + fctx = SRF_PERCALL_SETUP(); + uargs = fctx->user_fctx; + + if (fctx->call_cntr < fctx->max_calls) + { + result = bt_page_print_tuples(uargs); + uargs->offset++; + SRF_RETURN_NEXT(fctx, result); + } + + SRF_RETURN_DONE(fctx); +} + +Datum +bt_page_items_1_9(PG_FUNCTION_ARGS) +{ + return bt_page_items_internal(fcinfo, PAGEINSPECT_V1_9); +} + +/* entry point for old extension version */ +Datum +bt_page_items(PG_FUNCTION_ARGS) +{ + return bt_page_items_internal(fcinfo, PAGEINSPECT_V1_8); +} + +/*------------------------------------------------------- + * bt_page_items_bytea() + * + * Get IndexTupleData set in a btree page + * + * Usage: SELECT * FROM bt_page_items(get_raw_page('t1_pkey', 1)); + *------------------------------------------------------- + */ + +Datum +bt_page_items_bytea(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Datum result; + FuncCallContext *fctx; + ua_page_items *uargs; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + if (SRF_IS_FIRSTCALL()) + { + BTPageOpaque opaque; + MemoryContext mctx; + TupleDesc tupleDesc; + + fctx = SRF_FIRSTCALL_INIT(); + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + uargs = palloc(sizeof(ua_page_items)); + + uargs->page = get_page_from_raw(raw_page); + + if (PageIsNew(uargs->page)) + { + MemoryContextSwitchTo(mctx); + PG_RETURN_NULL(); + } + + uargs->offset = FirstOffsetNumber; + + /* verify the special space has the expected size */ + if (PageGetSpecialSize(uargs->page) != MAXALIGN(sizeof(BTPageOpaqueData))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "btree"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(BTPageOpaqueData)), + (int) PageGetSpecialSize(uargs->page)))); + + opaque = BTPageGetOpaque(uargs->page); + + if (P_ISMETA(opaque)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("block is a meta page"))); + + if (P_ISLEAF(opaque) && opaque->btpo_level != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("block is not a valid btree leaf page"))); + + if (P_ISDELETED(opaque)) + elog(NOTICE, "page is deleted"); + + if (!P_ISDELETED(opaque)) + fctx->max_calls = PageGetMaxOffsetNumber(uargs->page); + else + { + /* Don't interpret BTDeletedPageData as index tuples */ + elog(NOTICE, "page from block is deleted"); + fctx->max_calls = 0; + } + uargs->leafpage = P_ISLEAF(opaque); + uargs->rightmost = P_RIGHTMOST(opaque); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + tupleDesc = BlessTupleDesc(tupleDesc); + + uargs->tupd = tupleDesc; + + fctx->user_fctx = uargs; + + MemoryContextSwitchTo(mctx); + } + + fctx = SRF_PERCALL_SETUP(); + uargs = fctx->user_fctx; + + if (fctx->call_cntr < fctx->max_calls) + { + result = bt_page_print_tuples(uargs); + uargs->offset++; + SRF_RETURN_NEXT(fctx, result); + } + + SRF_RETURN_DONE(fctx); +} + +/* Number of output arguments (columns) for bt_metap() */ +#define BT_METAP_COLS_V1_8 9 + +/* ------------------------------------------------ + * bt_metap() + * + * Get a btree's meta-page information + * + * Usage: SELECT * FROM bt_metap('t1_pkey') + * ------------------------------------------------ + */ +Datum +bt_metap(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + Datum result; + Relation rel; + RangeVar *relrv; + BTMetaPageData *metad; + TupleDesc tupleDesc; + int j; + char *values[9]; + Buffer buffer; + Page page; + HeapTuple tuple; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pageinspect functions"))); + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + if (!IS_INDEX(rel) || !IS_BTREE(rel)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a %s index", + RelationGetRelationName(rel), "btree"))); + + /* + * Reject attempts to read non-local temporary relations; we would be + * likely to get wrong data since we have no visibility into the owning + * session's local buffers. + */ + if (RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary tables of other sessions"))); + + buffer = ReadBuffer(rel, 0); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + page = BufferGetPage(buffer); + metad = BTPageGetMeta(page); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* + * We need a kluge here to detect API versions prior to 1.8. Earlier + * versions incorrectly used int4 for certain columns. + * + * There is no way to reliably avoid the problems created by the old + * function definition at this point, so insist that the user update the + * extension. + */ + if (tupleDesc->natts < BT_METAP_COLS_V1_8) + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("function has wrong number of declared columns"), + errhint("To resolve the problem, update the \"pageinspect\" extension to the latest version."))); + + j = 0; + values[j++] = psprintf("%d", metad->btm_magic); + values[j++] = psprintf("%d", metad->btm_version); + values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_root); + values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_level); + values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_fastroot); + values[j++] = psprintf(INT64_FORMAT, (int64) metad->btm_fastlevel); + + /* + * Get values of extended metadata if available, use default values + * otherwise. Note that we rely on the assumption that btm_allequalimage + * is initialized to zero with indexes that were built on versions prior + * to Postgres 13 (just like _bt_metaversion()). + */ + if (metad->btm_version >= BTREE_NOVAC_VERSION) + { + values[j++] = psprintf(INT64_FORMAT, + (int64) metad->btm_last_cleanup_num_delpages); + values[j++] = psprintf("%f", metad->btm_last_cleanup_num_heap_tuples); + values[j++] = metad->btm_allequalimage ? "t" : "f"; + } + else + { + values[j++] = "0"; + values[j++] = "-1"; + values[j++] = "f"; + } + + tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc), + values); + + result = HeapTupleGetDatum(tuple); + + UnlockReleaseBuffer(buffer); + relation_close(rel, AccessShareLock); + + PG_RETURN_DATUM(result); +} diff --git a/contrib/pageinspect/expected/brin.out b/contrib/pageinspect/expected/brin.out new file mode 100644 index 0000000..098ddc2 --- /dev/null +++ b/contrib/pageinspect/expected/brin.out @@ -0,0 +1,92 @@ +CREATE TABLE test1 (a int, b text); +INSERT INTO test1 VALUES (1, 'one'); +CREATE INDEX test1_a_idx ON test1 USING brin (a); +SELECT brin_page_type(get_raw_page('test1_a_idx', 0)); + brin_page_type +---------------- + meta +(1 row) + +SELECT brin_page_type(get_raw_page('test1_a_idx', 1)); + brin_page_type +---------------- + revmap +(1 row) + +SELECT brin_page_type(get_raw_page('test1_a_idx', 2)); + brin_page_type +---------------- + regular +(1 row) + +SELECT * FROM brin_metapage_info(get_raw_page('test1_a_idx', 0)); + magic | version | pagesperrange | lastrevmappage +------------+---------+---------------+---------------- + 0xA8109CFA | 1 | 128 | 1 +(1 row) + +SELECT * FROM brin_metapage_info(get_raw_page('test1_a_idx', 1)); +ERROR: page is not a BRIN page of type "metapage" +DETAIL: Expected special type 0000f091, got 0000f092. +SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 0)) LIMIT 5; +ERROR: page is not a BRIN page of type "revmap" +DETAIL: Expected special type 0000f092, got 0000f091. +SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 1)) LIMIT 5; + pages +------- + (2,1) + (0,0) + (0,0) + (0,0) + (0,0) +(5 rows) + +SELECT * FROM brin_page_items(get_raw_page('test1_a_idx', 2), 'test1_a_idx') + ORDER BY blknum, attnum LIMIT 5; + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | f | f | f | {1 .. 1} +(1 row) + +-- Mask DETAIL messages as these are not portable across architectures. +\set VERBOSITY terse +-- Failures for non-BRIN index. +CREATE INDEX test1_a_btree ON test1 (a); +SELECT brin_page_items(get_raw_page('test1_a_btree', 0), 'test1_a_btree'); +ERROR: "test1_a_btree" is not a BRIN index +SELECT brin_page_items(get_raw_page('test1_a_btree', 0), 'test1_a_idx'); +ERROR: input page is not a valid BRIN page +-- Invalid special area size +SELECT brin_page_type(get_raw_page('test1', 0)); +ERROR: input page is not a valid BRIN page +SELECT * FROM brin_metapage_info(get_raw_page('test1', 0)); +ERROR: input page is not a valid BRIN page +SELECT * FROM brin_revmap_data(get_raw_page('test1', 0)); +ERROR: input page is not a valid BRIN page +\set VERBOSITY default +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT brin_page_type(decode(repeat('00', :block_size), 'hex')); + brin_page_type +---------------- + +(1 row) + +SELECT brin_page_items(decode(repeat('00', :block_size), 'hex'), 'test1_a_idx'); + brin_page_items +----------------- +(0 rows) + +SELECT brin_metapage_info(decode(repeat('00', :block_size), 'hex')); + brin_metapage_info +-------------------- + +(1 row) + +SELECT brin_revmap_data(decode(repeat('00', :block_size), 'hex')); + brin_revmap_data +------------------ + +(1 row) + +DROP TABLE test1; diff --git a/contrib/pageinspect/expected/btree.out b/contrib/pageinspect/expected/btree.out new file mode 100644 index 0000000..0aa5d73 --- /dev/null +++ b/contrib/pageinspect/expected/btree.out @@ -0,0 +1,221 @@ +CREATE TABLE test1 (a int8, b int4range); +INSERT INTO test1 VALUES (72057594037927937, '[0,1)'); +CREATE INDEX test1_a_idx ON test1 USING btree (a); +\x +SELECT * FROM bt_metap('test1_a_idx'); +-[ RECORD 1 ]-------------+------- +magic | 340322 +version | 4 +root | 1 +level | 0 +fastroot | 1 +fastlevel | 0 +last_cleanup_num_delpages | 0 +last_cleanup_num_tuples | -1 +allequalimage | t + +SELECT * FROM bt_page_stats('test1_a_idx', -1); +ERROR: invalid block number -1 +SELECT * FROM bt_page_stats('test1_a_idx', 0); +ERROR: block 0 is a meta page +SELECT * FROM bt_page_stats('test1_a_idx', 1); +-[ RECORD 1 ]-+----- +blkno | 1 +type | l +live_items | 1 +dead_items | 0 +avg_item_size | 16 +page_size | 8192 +free_size | 8128 +btpo_prev | 0 +btpo_next | 0 +btpo_level | 0 +btpo_flags | 3 + +SELECT * FROM bt_page_stats('test1_a_idx', 2); +ERROR: block number 2 is out of range +-- bt_multi_page_stats() function returns a set of records of page statistics. +CREATE TABLE test2 AS (SELECT generate_series(1, 1000)::int8 AS col1); +CREATE INDEX test2_col1_idx ON test2(col1); +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 0, 1); +ERROR: block 0 is a meta page +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 1, -1); +-[ RECORD 1 ]-+----- +blkno | 1 +type | l +live_items | 367 +dead_items | 0 +avg_item_size | 16 +page_size | 8192 +free_size | 808 +btpo_prev | 0 +btpo_next | 2 +btpo_level | 0 +btpo_flags | 1 +-[ RECORD 2 ]-+----- +blkno | 2 +type | l +live_items | 367 +dead_items | 0 +avg_item_size | 16 +page_size | 8192 +free_size | 808 +btpo_prev | 1 +btpo_next | 4 +btpo_level | 0 +btpo_flags | 1 +-[ RECORD 3 ]-+----- +blkno | 3 +type | r +live_items | 3 +dead_items | 0 +avg_item_size | 13 +page_size | 8192 +free_size | 8096 +btpo_prev | 0 +btpo_next | 0 +btpo_level | 1 +btpo_flags | 2 +-[ RECORD 4 ]-+----- +blkno | 4 +type | l +live_items | 268 +dead_items | 0 +avg_item_size | 16 +page_size | 8192 +free_size | 2788 +btpo_prev | 2 +btpo_next | 0 +btpo_level | 0 +btpo_flags | 1 + +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 1, 0); +(0 rows) + +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 1, 2); +-[ RECORD 1 ]-+----- +blkno | 1 +type | l +live_items | 367 +dead_items | 0 +avg_item_size | 16 +page_size | 8192 +free_size | 808 +btpo_prev | 0 +btpo_next | 2 +btpo_level | 0 +btpo_flags | 1 +-[ RECORD 2 ]-+----- +blkno | 2 +type | l +live_items | 367 +dead_items | 0 +avg_item_size | 16 +page_size | 8192 +free_size | 808 +btpo_prev | 1 +btpo_next | 4 +btpo_level | 0 +btpo_flags | 1 + +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 3, 2); +-[ RECORD 1 ]-+----- +blkno | 3 +type | r +live_items | 3 +dead_items | 0 +avg_item_size | 13 +page_size | 8192 +free_size | 8096 +btpo_prev | 0 +btpo_next | 0 +btpo_level | 1 +btpo_flags | 2 +-[ RECORD 2 ]-+----- +blkno | 4 +type | l +live_items | 268 +dead_items | 0 +avg_item_size | 16 +page_size | 8192 +free_size | 2788 +btpo_prev | 2 +btpo_next | 0 +btpo_level | 0 +btpo_flags | 1 + +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 7, 2); +ERROR: block number 7 is out of range +DROP TABLE test2; +SELECT * FROM bt_page_items('test1_a_idx', -1); +ERROR: invalid block number -1 +SELECT * FROM bt_page_items('test1_a_idx', 0); +ERROR: block 0 is a meta page +SELECT * FROM bt_page_items('test1_a_idx', 1); +-[ RECORD 1 ]----------------------- +itemoffset | 1 +ctid | (0,1) +itemlen | 16 +nulls | f +vars | f +data | 01 00 00 00 00 00 00 01 +dead | f +htid | (0,1) +tids | + +SELECT * FROM bt_page_items('test1_a_idx', 2); +ERROR: block number 2 is out of range +SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', -1)); +ERROR: invalid block number +SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 0)); +ERROR: block is a meta page +SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 1)); +-[ RECORD 1 ]----------------------- +itemoffset | 1 +ctid | (0,1) +itemlen | 16 +nulls | f +vars | f +data | 01 00 00 00 00 00 00 01 +dead | f +htid | (0,1) +tids | + +SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 2)); +ERROR: block number 2 is out of range for relation "test1_a_idx" +-- Failure when using a non-btree index. +CREATE INDEX test1_a_hash ON test1 USING hash(a); +SELECT bt_metap('test1_a_hash'); +ERROR: "test1_a_hash" is not a btree index +SELECT bt_page_stats('test1_a_hash', 0); +ERROR: "test1_a_hash" is not a btree index +SELECT bt_page_items('test1_a_hash', 0); +ERROR: "test1_a_hash" is not a btree index +SELECT bt_page_items(get_raw_page('test1_a_hash', 0)); +ERROR: block is a meta page +CREATE INDEX test1_b_gist ON test1 USING gist(b); +-- Special area of GiST is the same as btree, this complains about inconsistent +-- leaf data on the page. +SELECT bt_page_items(get_raw_page('test1_b_gist', 0)); +ERROR: block is not a valid btree leaf page +-- Several failure modes. +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse +-- invalid page size +SELECT bt_page_items('aaa'::bytea); +ERROR: invalid page size +-- invalid special area size +CREATE INDEX test1_a_brin ON test1 USING brin(a); +SELECT bt_page_items(get_raw_page('test1', 0)); +ERROR: input page is not a valid btree page +SELECT bt_page_items(get_raw_page('test1_a_brin', 0)); +ERROR: input page is not a valid btree page +\set VERBOSITY default +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT bt_page_items(decode(repeat('00', :block_size), 'hex')); +-[ RECORD 1 ]-+- +bt_page_items | + +DROP TABLE test1; diff --git a/contrib/pageinspect/expected/checksum.out b/contrib/pageinspect/expected/checksum.out new file mode 100644 index 0000000..a85388e --- /dev/null +++ b/contrib/pageinspect/expected/checksum.out @@ -0,0 +1,40 @@ +-- +-- Verify correct calculation of checksums +-- +-- Postgres' checksum algorithm produces different answers on little-endian +-- and big-endian machines. The results of this test also vary depending +-- on the configured block size. This test has several different expected +-- results files to handle the following possibilities: +-- +-- BLCKSZ end file +-- 8K LE checksum.out +-- 8K BE checksum_1.out +-- +-- In future we might provide additional expected-results files for other +-- block sizes, but there seems little point as long as so many other +-- test scripts also show false failures for non-default block sizes. +-- +-- This is to label the results files with blocksize: +SHOW block_size; + block_size +------------ + 8192 +(1 row) + +SHOW block_size \gset +-- Apply page_checksum() to some different data patterns and block numbers +SELECT blkno, + page_checksum(decode(repeat('01', :block_size), 'hex'), blkno) AS checksum_01, + page_checksum(decode(repeat('04', :block_size), 'hex'), blkno) AS checksum_04, + page_checksum(decode(repeat('ff', :block_size), 'hex'), blkno) AS checksum_ff, + page_checksum(decode(repeat('abcd', :block_size / 2), 'hex'), blkno) AS checksum_abcd, + page_checksum(decode(repeat('e6d6', :block_size / 2), 'hex'), blkno) AS checksum_e6d6, + page_checksum(decode(repeat('4a5e', :block_size / 2), 'hex'), blkno) AS checksum_4a5e + FROM generate_series(0, 100, 50) AS a (blkno); + blkno | checksum_01 | checksum_04 | checksum_ff | checksum_abcd | checksum_e6d6 | checksum_4a5e +-------+-------------+-------------+-------------+---------------+---------------+--------------- + 0 | 1175 | 28338 | 3612 | -30781 | -16269 | -27377 + 50 | 1225 | 28352 | 3598 | -30795 | -16251 | -27391 + 100 | 1139 | 28438 | 3648 | -30881 | -16305 | -27349 +(3 rows) + diff --git a/contrib/pageinspect/expected/checksum_1.out b/contrib/pageinspect/expected/checksum_1.out new file mode 100644 index 0000000..6fb1b1b --- /dev/null +++ b/contrib/pageinspect/expected/checksum_1.out @@ -0,0 +1,40 @@ +-- +-- Verify correct calculation of checksums +-- +-- Postgres' checksum algorithm produces different answers on little-endian +-- and big-endian machines. The results of this test also vary depending +-- on the configured block size. This test has several different expected +-- results files to handle the following possibilities: +-- +-- BLCKSZ end file +-- 8K LE checksum.out +-- 8K BE checksum_1.out +-- +-- In future we might provide additional expected-results files for other +-- block sizes, but there seems little point as long as so many other +-- test scripts also show false failures for non-default block sizes. +-- +-- This is to label the results files with blocksize: +SHOW block_size; + block_size +------------ + 8192 +(1 row) + +SHOW block_size \gset +-- Apply page_checksum() to some different data patterns and block numbers +SELECT blkno, + page_checksum(decode(repeat('01', :block_size), 'hex'), blkno) AS checksum_01, + page_checksum(decode(repeat('04', :block_size), 'hex'), blkno) AS checksum_04, + page_checksum(decode(repeat('ff', :block_size), 'hex'), blkno) AS checksum_ff, + page_checksum(decode(repeat('abcd', :block_size / 2), 'hex'), blkno) AS checksum_abcd, + page_checksum(decode(repeat('e6d6', :block_size / 2), 'hex'), blkno) AS checksum_e6d6, + page_checksum(decode(repeat('4a5e', :block_size / 2), 'hex'), blkno) AS checksum_4a5e + FROM generate_series(0, 100, 50) AS a (blkno); + blkno | checksum_01 | checksum_04 | checksum_ff | checksum_abcd | checksum_e6d6 | checksum_4a5e +-------+-------------+-------------+-------------+---------------+---------------+--------------- + 0 | -16327 | 8766 | -2722 | 13757 | -11485 | -31426 + 50 | -16281 | 8780 | -2708 | 13771 | -11503 | -31440 + 100 | -16235 | 8866 | -2758 | 13721 | -11577 | -31518 +(3 rows) + diff --git a/contrib/pageinspect/expected/gin.out b/contrib/pageinspect/expected/gin.out new file mode 100644 index 0000000..ff1da6a --- /dev/null +++ b/contrib/pageinspect/expected/gin.out @@ -0,0 +1,71 @@ +CREATE TABLE test1 (x int, y int[]); +INSERT INTO test1 VALUES (1, ARRAY[11, 111]); +CREATE INDEX test1_y_idx ON test1 USING gin (y) WITH (fastupdate = off); +\x +SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 0)); +-[ RECORD 1 ]----+----------- +pending_head | 4294967295 +pending_tail | 4294967295 +tail_free_size | 0 +n_pending_pages | 0 +n_pending_tuples | 0 +n_total_pages | 2 +n_entry_pages | 1 +n_data_pages | 0 +n_entries | 2 +version | 2 + +SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 1)); +ERROR: input page is not a GIN metapage +DETAIL: Flags 0002, expected 0008 +SELECT * FROM gin_page_opaque_info(get_raw_page('test1_y_idx', 1)); +-[ RECORD 1 ]--------- +rightlink | 4294967295 +maxoff | 0 +flags | {leaf} + +SELECT * FROM gin_leafpage_items(get_raw_page('test1_y_idx', 1)); +ERROR: input page is not a compressed GIN data leaf page +DETAIL: Flags 0002, expected 0083 +INSERT INTO test1 SELECT x, ARRAY[1,10] FROM generate_series(2,10000) x; +SELECT COUNT(*) > 0 +FROM gin_leafpage_items(get_raw_page('test1_y_idx', + (pg_relation_size('test1_y_idx') / + current_setting('block_size')::bigint)::int - 1)); +-[ RECORD 1 ] +?column? | t + +-- Failure with various modes. +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse +-- invalid page size +SELECT gin_leafpage_items('aaa'::bytea); +ERROR: invalid page size +SELECT gin_metapage_info('bbb'::bytea); +ERROR: invalid page size +SELECT gin_page_opaque_info('ccc'::bytea); +ERROR: invalid page size +-- invalid special area size +SELECT * FROM gin_metapage_info(get_raw_page('test1', 0)); +ERROR: input page is not a valid GIN metapage +SELECT * FROM gin_page_opaque_info(get_raw_page('test1', 0)); +ERROR: input page is not a valid GIN data leaf page +SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0)); +ERROR: input page is not a valid GIN data leaf page +\set VERBOSITY default +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex')); +-[ RECORD 1 ]------+- +gin_leafpage_items | + +SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex')); +-[ RECORD 1 ]-----+- +gin_metapage_info | + +SELECT gin_page_opaque_info(decode(repeat('00', :block_size), 'hex')); +-[ RECORD 1 ]--------+- +gin_page_opaque_info | + +DROP TABLE test1; diff --git a/contrib/pageinspect/expected/gist.out b/contrib/pageinspect/expected/gist.out new file mode 100644 index 0000000..d1adbab --- /dev/null +++ b/contrib/pageinspect/expected/gist.out @@ -0,0 +1,133 @@ +-- The gist_page_opaque_info() function prints the page's LSN. Normally, +-- that's constant 1 (GistBuildLSN) on every page of a freshly built GiST +-- index. But with wal_level=minimal, the whole relation is dumped to WAL at +-- the end of the transaction if it's smaller than wal_skip_threshold, which +-- updates the LSNs. Wrap the tests on gist_page_opaque_info() in the +-- same transaction with the CREATE INDEX so that we see the LSNs before +-- they are possibly overwritten at end of transaction. +BEGIN; +-- Create a test table and GiST index. +CREATE TABLE test_gist AS SELECT point(i,i) p, i::text t FROM + generate_series(1,1000) i; +CREATE INDEX test_gist_idx ON test_gist USING gist (p); +-- Page 0 is the root, the rest are leaf pages +SELECT * FROM gist_page_opaque_info(get_raw_page('test_gist_idx', 0)); + lsn | nsn | rightlink | flags +-----+-----+------------+------- + 0/1 | 0/0 | 4294967295 | {} +(1 row) + +SELECT * FROM gist_page_opaque_info(get_raw_page('test_gist_idx', 1)); + lsn | nsn | rightlink | flags +-----+-----+------------+-------- + 0/1 | 0/0 | 4294967295 | {leaf} +(1 row) + +SELECT * FROM gist_page_opaque_info(get_raw_page('test_gist_idx', 2)); + lsn | nsn | rightlink | flags +-----+-----+-----------+-------- + 0/1 | 0/0 | 1 | {leaf} +(1 row) + +COMMIT; +SELECT * FROM gist_page_items(get_raw_page('test_gist_idx', 0), 'test_gist_idx'); + itemoffset | ctid | itemlen | dead | keys +------------+-----------+---------+------+------------------------------- + 1 | (1,65535) | 40 | f | (p)=("(185,185),(1,1)") + 2 | (2,65535) | 40 | f | (p)=("(370,370),(186,186)") + 3 | (3,65535) | 40 | f | (p)=("(555,555),(371,371)") + 4 | (4,65535) | 40 | f | (p)=("(740,740),(556,556)") + 5 | (5,65535) | 40 | f | (p)=("(870,870),(741,741)") + 6 | (6,65535) | 40 | f | (p)=("(1000,1000),(871,871)") +(6 rows) + +SELECT * FROM gist_page_items(get_raw_page('test_gist_idx', 1), 'test_gist_idx') LIMIT 5; + itemoffset | ctid | itemlen | dead | keys +------------+-------+---------+------+--------------------- + 1 | (0,1) | 40 | f | (p)=("(1,1),(1,1)") + 2 | (0,2) | 40 | f | (p)=("(2,2),(2,2)") + 3 | (0,3) | 40 | f | (p)=("(3,3),(3,3)") + 4 | (0,4) | 40 | f | (p)=("(4,4),(4,4)") + 5 | (0,5) | 40 | f | (p)=("(5,5),(5,5)") +(5 rows) + +-- gist_page_items_bytea prints the raw key data as a bytea. The output of that is +-- platform-dependent (endianness), so omit the actual key data from the output. +SELECT itemoffset, ctid, itemlen FROM gist_page_items_bytea(get_raw_page('test_gist_idx', 0)); + itemoffset | ctid | itemlen +------------+-----------+--------- + 1 | (1,65535) | 40 + 2 | (2,65535) | 40 + 3 | (3,65535) | 40 + 4 | (4,65535) | 40 + 5 | (5,65535) | 40 + 6 | (6,65535) | 40 +(6 rows) + +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse +-- Failures with non-GiST index. +CREATE INDEX test_gist_btree on test_gist(t); +SELECT gist_page_items(get_raw_page('test_gist_btree', 0), 'test_gist_btree'); +ERROR: "test_gist_btree" is not a GiST index +SELECT gist_page_items(get_raw_page('test_gist_btree', 0), 'test_gist_idx'); +ERROR: input page is not a valid GiST page +-- Failure with various modes. +-- invalid page size +SELECT gist_page_items_bytea('aaa'::bytea); +ERROR: invalid page size +SELECT gist_page_items('aaa'::bytea, 'test_gist_idx'::regclass); +ERROR: invalid page size +SELECT gist_page_opaque_info('aaa'::bytea); +ERROR: invalid page size +-- invalid special area size +SELECT * FROM gist_page_opaque_info(get_raw_page('test_gist', 0)); +ERROR: input page is not a valid GiST page +SELECT gist_page_items_bytea(get_raw_page('test_gist', 0)); +ERROR: input page is not a valid GiST page +SELECT gist_page_items_bytea(get_raw_page('test_gist_btree', 0)); +ERROR: input page is not a valid GiST page +\set VERBOSITY default +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT gist_page_items_bytea(decode(repeat('00', :block_size), 'hex')); + gist_page_items_bytea +----------------------- +(0 rows) + +SELECT gist_page_items(decode(repeat('00', :block_size), 'hex'), 'test_gist_idx'::regclass); + gist_page_items +----------------- +(0 rows) + +SELECT gist_page_opaque_info(decode(repeat('00', :block_size), 'hex')); + gist_page_opaque_info +----------------------- + +(1 row) + +-- Test gist_page_items with included columns. +-- Non-leaf pages contain only the key attributes, and leaf pages contain +-- the included attributes. +ALTER TABLE test_gist ADD COLUMN i int DEFAULT NULL; +CREATE INDEX test_gist_idx_inc ON test_gist + USING gist (p) INCLUDE (t, i); +-- Mask the value of the key attribute to avoid alignment issues. +SELECT regexp_replace(keys, '\(p\)=\("(.*?)"\)', '(p)=("")') AS keys_nonleaf_1 + FROM gist_page_items(get_raw_page('test_gist_idx_inc', 0), 'test_gist_idx_inc') + WHERE itemoffset = 1; + keys_nonleaf_1 +---------------- + (p)=("") +(1 row) + +SELECT keys AS keys_leaf_1 + FROM gist_page_items(get_raw_page('test_gist_idx_inc', 1), 'test_gist_idx_inc') + WHERE itemoffset = 1; + keys_leaf_1 +------------------------------------------------------ + (p) INCLUDE (t, i)=("(1,1),(1,1)") INCLUDE (1, null) +(1 row) + +DROP TABLE test_gist; diff --git a/contrib/pageinspect/expected/hash.out b/contrib/pageinspect/expected/hash.out new file mode 100644 index 0000000..ea387a6 --- /dev/null +++ b/contrib/pageinspect/expected/hash.out @@ -0,0 +1,210 @@ +CREATE TABLE test_hash (a int, b text); +INSERT INTO test_hash VALUES (1, 'one'); +CREATE INDEX test_hash_a_idx ON test_hash USING hash (a); +CREATE TABLE test_hash_part (a int, b int) PARTITION BY RANGE (a); +CREATE INDEX test_hash_part_idx ON test_hash_part USING hash(b); +\x +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 0)); +-[ RECORD 1 ]--+--------- +hash_page_type | metapage + +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 1)); +-[ RECORD 1 ]--+------- +hash_page_type | bucket + +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 2)); +-[ RECORD 1 ]--+------- +hash_page_type | bucket + +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 3)); +-[ RECORD 1 ]--+------- +hash_page_type | bucket + +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 4)); +-[ RECORD 1 ]--+------- +hash_page_type | bucket + +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 5)); +-[ RECORD 1 ]--+------- +hash_page_type | bitmap + +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 6)); +ERROR: block number 6 is out of range for relation "test_hash_a_idx" +SELECT * FROM hash_bitmap_info('test_hash_a_idx', -1); +ERROR: invalid block number +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 0); +ERROR: invalid overflow block number 0 +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 1); +ERROR: invalid overflow block number 1 +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 2); +ERROR: invalid overflow block number 2 +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 3); +ERROR: invalid overflow block number 3 +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 4); +ERROR: invalid overflow block number 4 +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 5); +ERROR: invalid overflow block number 5 +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 6); +ERROR: block number 6 is out of range for relation "test_hash_a_idx" +SELECT * FROM hash_bitmap_info('test_hash_part_idx', 1); -- error +ERROR: "test_hash_part_idx" is not a hash index +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 0)); +-[ RECORD 1 ]-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +magic | 105121344 +version | 4 +ntuples | 1 +bsize | 8152 +bmsize | 4096 +bmshift | 15 +maxbucket | 3 +highmask | 7 +lowmask | 3 +ovflpoint | 2 +firstfree | 0 +nmaps | 1 +procid | 450 +spares | {0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} +mapp | {5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} + +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 1)); +ERROR: page is not a hash meta page +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 2)); +ERROR: page is not a hash meta page +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 3)); +ERROR: page is not a hash meta page +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 4)); +ERROR: page is not a hash meta page +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 5)); +ERROR: page is not a hash meta page +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 0)); +ERROR: page is not a hash bucket or overflow page +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 1)); +-[ RECORD 1 ]---+----------- +live_items | 0 +dead_items | 0 +page_size | 8192 +hasho_prevblkno | 3 +hasho_nextblkno | 4294967295 +hasho_bucket | 0 +hasho_flag | 2 +hasho_page_id | 65408 + +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 2)); +-[ RECORD 1 ]---+----------- +live_items | 0 +dead_items | 0 +page_size | 8192 +hasho_prevblkno | 3 +hasho_nextblkno | 4294967295 +hasho_bucket | 1 +hasho_flag | 2 +hasho_page_id | 65408 + +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 3)); +-[ RECORD 1 ]---+----------- +live_items | 1 +dead_items | 0 +page_size | 8192 +hasho_prevblkno | 3 +hasho_nextblkno | 4294967295 +hasho_bucket | 2 +hasho_flag | 2 +hasho_page_id | 65408 + +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 4)); +-[ RECORD 1 ]---+----------- +live_items | 0 +dead_items | 0 +page_size | 8192 +hasho_prevblkno | 3 +hasho_nextblkno | 4294967295 +hasho_bucket | 3 +hasho_flag | 2 +hasho_page_id | 65408 + +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 5)); +ERROR: page is not a hash bucket or overflow page +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 0)); +ERROR: page is not a hash bucket or overflow page +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 1)); +(0 rows) + +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 2)); +(0 rows) + +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 3)); +-[ RECORD 1 ]---------- +itemoffset | 1 +ctid | (0,1) +data | 2389907270 + +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 4)); +(0 rows) + +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 5)); +ERROR: page is not a hash bucket or overflow page +-- Failure with non-hash index +CREATE INDEX test_hash_a_btree ON test_hash USING btree (a); +SELECT hash_bitmap_info('test_hash_a_btree', 0); +ERROR: "test_hash_a_btree" is not a hash index +-- Failure with various modes. +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse +-- invalid page size +SELECT hash_metapage_info('aaa'::bytea); +ERROR: invalid page size +SELECT hash_page_items('bbb'::bytea); +ERROR: invalid page size +SELECT hash_page_stats('ccc'::bytea); +ERROR: invalid page size +SELECT hash_page_type('ddd'::bytea); +ERROR: invalid page size +-- invalid special area size +SELECT hash_metapage_info(get_raw_page('test_hash', 0)); +ERROR: input page is not a valid hash page +SELECT hash_page_items(get_raw_page('test_hash', 0)); +ERROR: input page is not a valid hash page +SELECT hash_page_stats(get_raw_page('test_hash', 0)); +ERROR: input page is not a valid hash page +SELECT hash_page_type(get_raw_page('test_hash', 0)); +ERROR: input page is not a valid hash page +\set VERBOSITY default +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT hash_metapage_info(decode(repeat('00', :block_size), 'hex')); +ERROR: page is not a hash meta page +SELECT hash_page_items(decode(repeat('00', :block_size), 'hex')); +ERROR: page is not a hash bucket or overflow page +SELECT hash_page_stats(decode(repeat('00', :block_size), 'hex')); +ERROR: page is not a hash bucket or overflow page +SELECT hash_page_type(decode(repeat('00', :block_size), 'hex')); +-[ RECORD 1 ]--+------- +hash_page_type | unused + +DROP TABLE test_hash; +DROP TABLE test_hash_part; diff --git a/contrib/pageinspect/expected/oldextversions.out b/contrib/pageinspect/expected/oldextversions.out new file mode 100644 index 0000000..f5c4b61 --- /dev/null +++ b/contrib/pageinspect/expected/oldextversions.out @@ -0,0 +1,56 @@ +-- test old extension version entry points +DROP EXTENSION pageinspect; +CREATE EXTENSION pageinspect VERSION '1.8'; +CREATE TABLE test1 (a int8, b text); +INSERT INTO test1 VALUES (72057594037927937, 'text'); +CREATE INDEX test1_a_idx ON test1 USING btree (a); +-- from page.sql +SELECT octet_length(get_raw_page('test1', 0)) AS main_0; + main_0 +-------- + 8192 +(1 row) + +SELECT octet_length(get_raw_page('test1', 'main', 0)) AS main_0; + main_0 +-------- + 8192 +(1 row) + +SELECT page_checksum(get_raw_page('test1', 0), 0) IS NOT NULL AS silly_checksum_test; + silly_checksum_test +--------------------- + t +(1 row) + +-- from btree.sql +SELECT * FROM bt_page_stats('test1_a_idx', 1); + blkno | type | live_items | dead_items | avg_item_size | page_size | free_size | btpo_prev | btpo_next | btpo | btpo_flags +-------+------+------------+------------+---------------+-----------+-----------+-----------+-----------+------+------------ + 1 | l | 1 | 0 | 16 | 8192 | 8128 | 0 | 0 | 0 | 3 +(1 row) + +SELECT * FROM bt_page_items('test1_a_idx', 1); + itemoffset | ctid | itemlen | nulls | vars | data | dead | htid | tids +------------+-------+---------+-------+------+-------------------------+------+-------+------ + 1 | (0,1) | 16 | f | f | 01 00 00 00 00 00 00 01 | f | (0,1) | +(1 row) + +-- page_header() uses int instead of smallint for lower, upper, special and +-- pagesize in pageinspect >= 1.10. +ALTER EXTENSION pageinspect UPDATE TO '1.9'; +\df page_header + List of functions + Schema | Name | Result data type | Argument data types | Type +--------+-------------+------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------ + public | page_header | record | page bytea, OUT lsn pg_lsn, OUT checksum smallint, OUT flags smallint, OUT lower smallint, OUT upper smallint, OUT special smallint, OUT pagesize smallint, OUT version smallint, OUT prune_xid xid | func +(1 row) + +SELECT pagesize, version FROM page_header(get_raw_page('test1', 0)); + pagesize | version +----------+--------- + 8192 | 4 +(1 row) + +DROP TABLE test1; +DROP EXTENSION pageinspect; diff --git a/contrib/pageinspect/expected/page.out b/contrib/pageinspect/expected/page.out new file mode 100644 index 0000000..80ddb45 --- /dev/null +++ b/contrib/pageinspect/expected/page.out @@ -0,0 +1,241 @@ +CREATE EXTENSION pageinspect; +-- Use a temp table so that effects of VACUUM are predictable +CREATE TEMP TABLE test1 (a int, b int); +INSERT INTO test1 VALUES (16777217, 131584); +VACUUM (DISABLE_PAGE_SKIPPING) test1; -- set up FSM +-- The page contents can vary, so just test that it can be read +-- successfully, but don't keep the output. +SELECT octet_length(get_raw_page('test1', 'main', 0)) AS main_0; + main_0 +-------- + 8192 +(1 row) + +SELECT octet_length(get_raw_page('test1', 'main', 1)) AS main_1; +ERROR: block number 1 is out of range for relation "test1" +SELECT octet_length(get_raw_page('test1', 'fsm', 0)) AS fsm_0; + fsm_0 +------- + 8192 +(1 row) + +SELECT octet_length(get_raw_page('test1', 'fsm', 1)) AS fsm_1; + fsm_1 +------- + 8192 +(1 row) + +SELECT octet_length(get_raw_page('test1', 'vm', 0)) AS vm_0; + vm_0 +------ + 8192 +(1 row) + +SELECT octet_length(get_raw_page('test1', 'vm', 1)) AS vm_1; +ERROR: block number 1 is out of range for relation "test1" +SELECT octet_length(get_raw_page('test1', 'main', -1)); +ERROR: invalid block number +SELECT octet_length(get_raw_page('xxx', 'main', 0)); +ERROR: relation "xxx" does not exist +SELECT octet_length(get_raw_page('test1', 'xxx', 0)); +ERROR: invalid fork name +HINT: Valid fork names are "main", "fsm", "vm", and "init". +SELECT get_raw_page('test1', 0) = get_raw_page('test1', 'main', 0); + ?column? +---------- + t +(1 row) + +SELECT pagesize, version FROM page_header(get_raw_page('test1', 0)); + pagesize | version +----------+--------- + 8192 | 4 +(1 row) + +SELECT page_checksum(get_raw_page('test1', 0), 0) IS NOT NULL AS silly_checksum_test; + silly_checksum_test +--------------------- + t +(1 row) + +SELECT page_checksum(get_raw_page('test1', 0), -1); +ERROR: invalid block number +SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bits) + FROM heap_page_items(get_raw_page('test1', 0)); + tuple_data_split +------------------------------- + {"\\x01000001","\\x00020200"} +(1 row) + +SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0)); + fsm_page_contents +------------------- + 0: 254 + + 1: 254 + + 3: 254 + + 7: 254 + + 15: 254 + + 31: 254 + + 63: 254 + + 127: 254 + + 255: 254 + + 511: 254 + + 1023: 254 + + 2047: 254 + + 4095: 254 + + fp_next_slot: 0 + + +(1 row) + +-- If we freeze the only tuple on test1, the infomask should +-- always be the same in all test runs. +VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test1; +SELECT t_infomask, t_infomask2, raw_flags, combined_flags +FROM heap_page_items(get_raw_page('test1', 0)), + LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2); + t_infomask | t_infomask2 | raw_flags | combined_flags +------------+-------------+-----------------------------------------------------------+-------------------- + 2816 | 2 | {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID,HEAP_XMAX_INVALID} | {HEAP_XMIN_FROZEN} +(1 row) + +-- tests for decoding of combined flags +-- HEAP_XMAX_SHR_LOCK = (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK) +SELECT * FROM heap_tuple_infomask_flags(x'0050'::int, 0); + raw_flags | combined_flags +---------------------------------------------+---------------------- + {HEAP_XMAX_KEYSHR_LOCK,HEAP_XMAX_EXCL_LOCK} | {HEAP_XMAX_SHR_LOCK} +(1 row) + +-- HEAP_XMIN_FROZEN = (HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID) +SELECT * FROM heap_tuple_infomask_flags(x'0300'::int, 0); + raw_flags | combined_flags +-----------------------------------------+-------------------- + {HEAP_XMIN_COMMITTED,HEAP_XMIN_INVALID} | {HEAP_XMIN_FROZEN} +(1 row) + +-- HEAP_MOVED = (HEAP_MOVED_IN | HEAP_MOVED_OFF) +SELECT * FROM heap_tuple_infomask_flags(x'C000'::int, 0); + raw_flags | combined_flags +--------------------------------+---------------- + {HEAP_MOVED_OFF,HEAP_MOVED_IN} | {HEAP_MOVED} +(1 row) + +SELECT * FROM heap_tuple_infomask_flags(x'C000'::int, 0); + raw_flags | combined_flags +--------------------------------+---------------- + {HEAP_MOVED_OFF,HEAP_MOVED_IN} | {HEAP_MOVED} +(1 row) + +-- test all flags of t_infomask and t_infomask2 +SELECT unnest(raw_flags) + FROM heap_tuple_infomask_flags(x'FFFF'::int, x'FFFF'::int) ORDER BY 1; + unnest +----------------------- + HEAP_COMBOCID + HEAP_HASEXTERNAL + HEAP_HASNULL + HEAP_HASOID_OLD + HEAP_HASVARWIDTH + HEAP_HOT_UPDATED + HEAP_KEYS_UPDATED + HEAP_MOVED_IN + HEAP_MOVED_OFF + HEAP_ONLY_TUPLE + HEAP_UPDATED + HEAP_XMAX_COMMITTED + HEAP_XMAX_EXCL_LOCK + HEAP_XMAX_INVALID + HEAP_XMAX_IS_MULTI + HEAP_XMAX_KEYSHR_LOCK + HEAP_XMAX_LOCK_ONLY + HEAP_XMIN_COMMITTED + HEAP_XMIN_INVALID +(19 rows) + +SELECT unnest(combined_flags) + FROM heap_tuple_infomask_flags(x'FFFF'::int, x'FFFF'::int) ORDER BY 1; + unnest +-------------------- + HEAP_MOVED + HEAP_XMAX_SHR_LOCK + HEAP_XMIN_FROZEN +(3 rows) + +-- no flags at all +SELECT * FROM heap_tuple_infomask_flags(0, 0); + raw_flags | combined_flags +-----------+---------------- + {} | {} +(1 row) + +-- no combined flags +SELECT * FROM heap_tuple_infomask_flags(x'0010'::int, 0); + raw_flags | combined_flags +-------------------------+---------------- + {HEAP_XMAX_KEYSHR_LOCK} | {} +(1 row) + +DROP TABLE test1; +-- check that using any of these functions with a partitioned table or index +-- would fail +create table test_partitioned (a int) partition by range (a); +create index test_partitioned_index on test_partitioned (a); +select get_raw_page('test_partitioned', 0); -- error about partitioned table +ERROR: cannot get raw page from relation "test_partitioned" +DETAIL: This operation is not supported for partitioned tables. +select get_raw_page('test_partitioned_index', 0); -- error about partitioned index +ERROR: cannot get raw page from relation "test_partitioned_index" +DETAIL: This operation is not supported for partitioned indexes. +-- a regular table which is a member of a partition set should work though +create table test_part1 partition of test_partitioned for values from ( 1 ) to (100); +select get_raw_page('test_part1', 0); -- get farther and error about empty table +ERROR: block number 0 is out of range for relation "test_part1" +drop table test_partitioned; +-- check null bitmap alignment for table whose number of attributes is multiple of 8 +create table test8 (f1 int, f2 int, f3 int, f4 int, f5 int, f6 int, f7 int, f8 int); +insert into test8(f1, f8) values (x'7f00007f'::int, 0); +select t_bits, t_data from heap_page_items(get_raw_page('test8', 0)); + t_bits | t_data +----------+-------------------- + 10000001 | \x7f00007f00000000 +(1 row) + +select tuple_data_split('test8'::regclass, t_data, t_infomask, t_infomask2, t_bits) + from heap_page_items(get_raw_page('test8', 0)); + tuple_data_split +------------------------------------------------------------- + {"\\x7f00007f",NULL,NULL,NULL,NULL,NULL,NULL,"\\x00000000"} +(1 row) + +drop table test8; +-- Failure with incorrect page size +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes. +\set VERBOSITY terse +SELECT fsm_page_contents('aaa'::bytea); +ERROR: invalid page size +SELECT page_checksum('bbb'::bytea, 0); +ERROR: invalid page size +SELECT page_header('ccc'::bytea); +ERROR: invalid page size +\set VERBOSITY default +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT fsm_page_contents(decode(repeat('00', :block_size), 'hex')); + fsm_page_contents +------------------- + +(1 row) + +SELECT page_header(decode(repeat('00', :block_size), 'hex')); + page_header +----------------------- + (0/0,0,0,0,0,0,0,0,0) +(1 row) + +SELECT page_checksum(decode(repeat('00', :block_size), 'hex'), 1); + page_checksum +--------------- + +(1 row) + diff --git a/contrib/pageinspect/fsmfuncs.c b/contrib/pageinspect/fsmfuncs.c new file mode 100644 index 0000000..23b192b --- /dev/null +++ b/contrib/pageinspect/fsmfuncs.c @@ -0,0 +1,65 @@ +/*------------------------------------------------------------------------- + * + * fsmfuncs.c + * Functions to investigate FSM pages + * + * These functions are restricted to superusers for the fear of introducing + * security holes if the input checking isn't as water-tight as it should. + * You'd need to be superuser to obtain a raw page image anyway, so + * there's hardly any use case for using these without superuser-rights + * anyway. + * + * Copyright (c) 2007-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pageinspect/fsmfuncs.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "pageinspect.h" +#include "storage/fsm_internals.h" +#include "utils/builtins.h" + +/* + * Dumps the contents of a FSM page. + */ +PG_FUNCTION_INFO_V1(fsm_page_contents); + +Datum +fsm_page_contents(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + StringInfoData sinfo; + Page page; + FSMPage fsmpage; + int i; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + page = get_page_from_raw(raw_page); + + if (PageIsNew(page)) + PG_RETURN_NULL(); + + fsmpage = (FSMPage) PageGetContents(page); + + initStringInfo(&sinfo); + + for (i = 0; i < NodesPerPage; i++) + { + if (fsmpage->fp_nodes[i] != 0) + appendStringInfo(&sinfo, "%d: %d\n", i, fsmpage->fp_nodes[i]); + } + appendStringInfo(&sinfo, "fp_next_slot: %d\n", fsmpage->fp_next_slot); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(sinfo.data, sinfo.len)); +} diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c new file mode 100644 index 0000000..0f84698 --- /dev/null +++ b/contrib/pageinspect/ginfuncs.c @@ -0,0 +1,285 @@ +/* + * ginfuncs.c + * Functions to investigate the content of GIN indexes + * + * Copyright (c) 2014-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pageinspect/ginfuncs.c + */ +#include "postgres.h" + +#include "access/gin.h" +#include "access/gin_private.h" +#include "access/htup_details.h" +#include "catalog/namespace.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "pageinspect.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/rel.h" + + +PG_FUNCTION_INFO_V1(gin_metapage_info); +PG_FUNCTION_INFO_V1(gin_page_opaque_info); +PG_FUNCTION_INFO_V1(gin_leafpage_items); + + +Datum +gin_metapage_info(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + TupleDesc tupdesc; + Page page; + GinPageOpaque opaq; + GinMetaPageData *metadata; + HeapTuple resultTuple; + Datum values[10]; + bool nulls[10]; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + page = get_page_from_raw(raw_page); + + if (PageIsNew(page)) + PG_RETURN_NULL(); + + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid GIN metapage"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(GinPageOpaqueData)), + (int) PageGetSpecialSize(page)))); + + opaq = GinPageGetOpaque(page); + + if (opaq->flags != GIN_META) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a GIN metapage"), + errdetail("Flags %04X, expected %04X", + opaq->flags, GIN_META))); + + /* 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"); + + metadata = GinPageGetMeta(page); + + memset(nulls, 0, sizeof(nulls)); + + values[0] = Int64GetDatum(metadata->head); + values[1] = Int64GetDatum(metadata->tail); + values[2] = Int32GetDatum(metadata->tailFreeSize); + values[3] = Int64GetDatum(metadata->nPendingPages); + values[4] = Int64GetDatum(metadata->nPendingHeapTuples); + + /* statistics, updated by VACUUM */ + values[5] = Int64GetDatum(metadata->nTotalPages); + values[6] = Int64GetDatum(metadata->nEntryPages); + values[7] = Int64GetDatum(metadata->nDataPages); + values[8] = Int64GetDatum(metadata->nEntries); + + values[9] = Int32GetDatum(metadata->ginVersion); + + /* Build and return the result tuple. */ + resultTuple = heap_form_tuple(tupdesc, values, nulls); + + return HeapTupleGetDatum(resultTuple); +} + + +Datum +gin_page_opaque_info(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + TupleDesc tupdesc; + Page page; + GinPageOpaque opaq; + HeapTuple resultTuple; + Datum values[3]; + bool nulls[3]; + Datum flags[16]; + int nflags = 0; + uint16 flagbits; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + page = get_page_from_raw(raw_page); + + if (PageIsNew(page)) + PG_RETURN_NULL(); + + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid GIN data leaf page"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(GinPageOpaqueData)), + (int) PageGetSpecialSize(page)))); + + opaq = GinPageGetOpaque(page); + + /* 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"); + + /* Convert the flags bitmask to an array of human-readable names */ + flagbits = opaq->flags; + if (flagbits & GIN_DATA) + flags[nflags++] = CStringGetTextDatum("data"); + if (flagbits & GIN_LEAF) + flags[nflags++] = CStringGetTextDatum("leaf"); + if (flagbits & GIN_DELETED) + flags[nflags++] = CStringGetTextDatum("deleted"); + if (flagbits & GIN_META) + flags[nflags++] = CStringGetTextDatum("meta"); + if (flagbits & GIN_LIST) + flags[nflags++] = CStringGetTextDatum("list"); + if (flagbits & GIN_LIST_FULLROW) + flags[nflags++] = CStringGetTextDatum("list_fullrow"); + if (flagbits & GIN_INCOMPLETE_SPLIT) + flags[nflags++] = CStringGetTextDatum("incomplete_split"); + if (flagbits & GIN_COMPRESSED) + flags[nflags++] = CStringGetTextDatum("compressed"); + flagbits &= ~(GIN_DATA | GIN_LEAF | GIN_DELETED | GIN_META | GIN_LIST | + GIN_LIST_FULLROW | GIN_INCOMPLETE_SPLIT | GIN_COMPRESSED); + if (flagbits) + { + /* any flags we don't recognize are printed in hex */ + flags[nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(flagbits)); + } + + memset(nulls, 0, sizeof(nulls)); + + values[0] = Int64GetDatum(opaq->rightlink); + values[1] = Int32GetDatum(opaq->maxoff); + values[2] = PointerGetDatum(construct_array_builtin(flags, nflags, TEXTOID)); + + /* Build and return the result tuple. */ + resultTuple = heap_form_tuple(tupdesc, values, nulls); + + return HeapTupleGetDatum(resultTuple); +} + +typedef struct gin_leafpage_items_state +{ + TupleDesc tupd; + GinPostingList *seg; + GinPostingList *lastseg; +} gin_leafpage_items_state; + +Datum +gin_leafpage_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + FuncCallContext *fctx; + gin_leafpage_items_state *inter_call_data; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + MemoryContext mctx; + Page page; + GinPageOpaque opaq; + + fctx = SRF_FIRSTCALL_INIT(); + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + page = get_page_from_raw(raw_page); + + if (PageIsNew(page)) + { + MemoryContextSwitchTo(mctx); + PG_RETURN_NULL(); + } + + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid GIN data leaf page"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(GinPageOpaqueData)), + (int) PageGetSpecialSize(page)))); + + opaq = GinPageGetOpaque(page); + if (opaq->flags != (GIN_DATA | GIN_LEAF | GIN_COMPRESSED)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a compressed GIN data leaf page"), + errdetail("Flags %04X, expected %04X", + opaq->flags, + (GIN_DATA | GIN_LEAF | GIN_COMPRESSED)))); + + inter_call_data = palloc(sizeof(gin_leafpage_items_state)); + + /* 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"); + + inter_call_data->tupd = tupdesc; + + inter_call_data->seg = GinDataLeafPageGetPostingList(page); + inter_call_data->lastseg = (GinPostingList *) + (((char *) inter_call_data->seg) + + GinDataLeafPageGetPostingListSize(page)); + + fctx->user_fctx = inter_call_data; + + MemoryContextSwitchTo(mctx); + } + + fctx = SRF_PERCALL_SETUP(); + inter_call_data = fctx->user_fctx; + + if (inter_call_data->seg != inter_call_data->lastseg) + { + GinPostingList *cur = inter_call_data->seg; + HeapTuple resultTuple; + Datum result; + Datum values[3]; + bool nulls[3]; + int ndecoded, + i; + ItemPointer tids; + Datum *tids_datum; + + memset(nulls, 0, sizeof(nulls)); + + values[0] = ItemPointerGetDatum(&cur->first); + values[1] = UInt16GetDatum(cur->nbytes); + + /* build an array of decoded item pointers */ + tids = ginPostingListDecode(cur, &ndecoded); + tids_datum = (Datum *) palloc(ndecoded * sizeof(Datum)); + for (i = 0; i < ndecoded; i++) + tids_datum[i] = ItemPointerGetDatum(&tids[i]); + values[2] = PointerGetDatum(construct_array_builtin(tids_datum, ndecoded, TIDOID)); + pfree(tids_datum); + pfree(tids); + + /* Build and return the result tuple. */ + resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls); + result = HeapTupleGetDatum(resultTuple); + + inter_call_data->seg = GinNextPostingListSegment(cur); + + SRF_RETURN_NEXT(fctx, result); + } + + SRF_RETURN_DONE(fctx); +} diff --git a/contrib/pageinspect/gistfuncs.c b/contrib/pageinspect/gistfuncs.c new file mode 100644 index 0000000..5512b00 --- /dev/null +++ b/contrib/pageinspect/gistfuncs.c @@ -0,0 +1,369 @@ +/* + * gistfuncs.c + * Functions to investigate the content of GiST indexes + * + * Copyright (c) 2014-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pageinspect/gistfuncs.c + */ +#include "postgres.h" + +#include "access/gist.h" +#include "access/gist_private.h" +#include "access/htup.h" +#include "access/relation.h" +#include "catalog/namespace.h" +#include "catalog/pg_am_d.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "pageinspect.h" +#include "storage/itemptr.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/pg_lsn.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" +#include "utils/varlena.h" + +PG_FUNCTION_INFO_V1(gist_page_opaque_info); +PG_FUNCTION_INFO_V1(gist_page_items); +PG_FUNCTION_INFO_V1(gist_page_items_bytea); + +#define IS_GIST(r) ((r)->rd_rel->relam == GIST_AM_OID) + + +static Page verify_gist_page(bytea *raw_page); + +/* + * Verify that the given bytea contains a GIST page or die in the attempt. + * A pointer to the page is returned. + */ +static Page +verify_gist_page(bytea *raw_page) +{ + Page page = get_page_from_raw(raw_page); + GISTPageOpaque opaq; + + if (PageIsNew(page)) + return page; + + /* verify the special space has the expected size */ + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "GiST"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(GISTPageOpaqueData)), + (int) PageGetSpecialSize(page)))); + + opaq = GistPageGetOpaque(page); + if (opaq->gist_page_id != GIST_PAGE_ID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "GiST"), + errdetail("Expected %08x, got %08x.", + GIST_PAGE_ID, + opaq->gist_page_id))); + + return page; +} + +Datum +gist_page_opaque_info(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + TupleDesc tupdesc; + Page page; + HeapTuple resultTuple; + Datum values[4]; + bool nulls[4]; + Datum flags[16]; + int nflags = 0; + uint16 flagbits; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + page = verify_gist_page(raw_page); + + if (PageIsNew(page)) + PG_RETURN_NULL(); + + /* 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"); + + /* Convert the flags bitmask to an array of human-readable names */ + flagbits = GistPageGetOpaque(page)->flags; + if (flagbits & F_LEAF) + flags[nflags++] = CStringGetTextDatum("leaf"); + if (flagbits & F_DELETED) + flags[nflags++] = CStringGetTextDatum("deleted"); + if (flagbits & F_TUPLES_DELETED) + flags[nflags++] = CStringGetTextDatum("tuples_deleted"); + if (flagbits & F_FOLLOW_RIGHT) + flags[nflags++] = CStringGetTextDatum("follow_right"); + if (flagbits & F_HAS_GARBAGE) + flags[nflags++] = CStringGetTextDatum("has_garbage"); + flagbits &= ~(F_LEAF | F_DELETED | F_TUPLES_DELETED | F_FOLLOW_RIGHT | F_HAS_GARBAGE); + if (flagbits) + { + /* any flags we don't recognize are printed in hex */ + flags[nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(flagbits)); + } + + memset(nulls, 0, sizeof(nulls)); + + values[0] = LSNGetDatum(PageGetLSN(page)); + values[1] = LSNGetDatum(GistPageGetNSN(page)); + values[2] = Int64GetDatum(GistPageGetOpaque(page)->rightlink); + values[3] = PointerGetDatum(construct_array_builtin(flags, nflags, TEXTOID)); + + /* Build and return the result tuple. */ + resultTuple = heap_form_tuple(tupdesc, values, nulls); + + return HeapTupleGetDatum(resultTuple); +} + +Datum +gist_page_items_bytea(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Page page; + OffsetNumber offset; + OffsetNumber maxoff = InvalidOffsetNumber; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + InitMaterializedSRF(fcinfo, 0); + + page = verify_gist_page(raw_page); + + if (PageIsNew(page)) + PG_RETURN_NULL(); + + /* Avoid bogus PageGetMaxOffsetNumber() call with deleted pages */ + if (GistPageIsDeleted(page)) + elog(NOTICE, "page is deleted"); + else + maxoff = PageGetMaxOffsetNumber(page); + + for (offset = FirstOffsetNumber; + offset <= maxoff; + offset++) + { + Datum values[5]; + bool nulls[5]; + ItemId id; + IndexTuple itup; + bytea *tuple_bytea; + int tuple_len; + + id = PageGetItemId(page, offset); + + if (!ItemIdIsValid(id)) + elog(ERROR, "invalid ItemId"); + + itup = (IndexTuple) PageGetItem(page, id); + tuple_len = IndexTupleSize(itup); + + memset(nulls, 0, sizeof(nulls)); + + values[0] = DatumGetInt16(offset); + values[1] = ItemPointerGetDatum(&itup->t_tid); + values[2] = Int32GetDatum((int) IndexTupleSize(itup)); + + tuple_bytea = (bytea *) palloc(tuple_len + VARHDRSZ); + SET_VARSIZE(tuple_bytea, tuple_len + VARHDRSZ); + memcpy(VARDATA(tuple_bytea), itup, tuple_len); + values[3] = BoolGetDatum(ItemIdIsDead(id)); + values[4] = PointerGetDatum(tuple_bytea); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} + +Datum +gist_page_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Oid indexRelid = PG_GETARG_OID(1); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Relation indexRel; + TupleDesc tupdesc; + Page page; + uint16 flagbits; + bits16 printflags = 0; + OffsetNumber offset; + OffsetNumber maxoff = InvalidOffsetNumber; + char *index_columns; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + InitMaterializedSRF(fcinfo, 0); + + /* Open the relation */ + indexRel = index_open(indexRelid, AccessShareLock); + + if (!IS_GIST(indexRel)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a %s index", + RelationGetRelationName(indexRel), "GiST"))); + + page = verify_gist_page(raw_page); + + if (PageIsNew(page)) + { + index_close(indexRel, AccessShareLock); + PG_RETURN_NULL(); + } + + flagbits = GistPageGetOpaque(page)->flags; + + /* + * Included attributes are added when dealing with leaf pages, discarded + * for non-leaf pages as these include only data for key attributes. + */ + printflags |= RULE_INDEXDEF_PRETTY; + if (flagbits & F_LEAF) + { + tupdesc = RelationGetDescr(indexRel); + } + else + { + tupdesc = CreateTupleDescCopy(RelationGetDescr(indexRel)); + tupdesc->natts = IndexRelationGetNumberOfKeyAttributes(indexRel); + printflags |= RULE_INDEXDEF_KEYS_ONLY; + } + + index_columns = pg_get_indexdef_columns_extended(indexRelid, + printflags); + + /* Avoid bogus PageGetMaxOffsetNumber() call with deleted pages */ + if (GistPageIsDeleted(page)) + elog(NOTICE, "page is deleted"); + else + maxoff = PageGetMaxOffsetNumber(page); + + for (offset = FirstOffsetNumber; + offset <= maxoff; + offset++) + { + Datum values[5]; + bool nulls[5]; + ItemId id; + IndexTuple itup; + Datum itup_values[INDEX_MAX_KEYS]; + bool itup_isnull[INDEX_MAX_KEYS]; + StringInfoData buf; + int i; + + id = PageGetItemId(page, offset); + + if (!ItemIdIsValid(id)) + elog(ERROR, "invalid ItemId"); + + itup = (IndexTuple) PageGetItem(page, id); + + index_deform_tuple(itup, tupdesc, + itup_values, itup_isnull); + + memset(nulls, 0, sizeof(nulls)); + + values[0] = DatumGetInt16(offset); + values[1] = ItemPointerGetDatum(&itup->t_tid); + values[2] = Int32GetDatum((int) IndexTupleSize(itup)); + values[3] = BoolGetDatum(ItemIdIsDead(id)); + + if (index_columns) + { + initStringInfo(&buf); + appendStringInfo(&buf, "(%s)=(", index_columns); + + /* Most of this is copied from record_out(). */ + for (i = 0; i < tupdesc->natts; i++) + { + char *value; + char *tmp; + bool nq = false; + + if (itup_isnull[i]) + value = "null"; + else + { + Oid foutoid; + bool typisvarlena; + Oid typoid; + + typoid = tupdesc->attrs[i].atttypid; + getTypeOutputInfo(typoid, &foutoid, &typisvarlena); + value = OidOutputFunctionCall(foutoid, itup_values[i]); + } + + if (i == IndexRelationGetNumberOfKeyAttributes(indexRel)) + appendStringInfoString(&buf, ") INCLUDE ("); + else if (i > 0) + appendStringInfoString(&buf, ", "); + + /* Check whether we need double quotes for this value */ + nq = (value[0] == '\0'); /* force quotes for empty string */ + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\' || + ch == '(' || ch == ')' || ch == ',' || + isspace((unsigned char) ch)) + { + nq = true; + break; + } + } + + /* And emit the string */ + if (nq) + appendStringInfoCharMacro(&buf, '"'); + for (tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\') + appendStringInfoCharMacro(&buf, ch); + appendStringInfoCharMacro(&buf, ch); + } + if (nq) + appendStringInfoCharMacro(&buf, '"'); + } + + appendStringInfoChar(&buf, ')'); + + values[4] = CStringGetTextDatum(buf.data); + nulls[4] = false; + } + else + { + values[4] = (Datum) 0; + nulls[4] = true; + } + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + relation_close(indexRel, AccessShareLock); + + return (Datum) 0; +} diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c new file mode 100644 index 0000000..5c38ec1 --- /dev/null +++ b/contrib/pageinspect/hashfuncs.c @@ -0,0 +1,570 @@ +/* + * hashfuncs.c + * Functions to investigate the content of HASH indexes + * + * Copyright (c) 2017-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pageinspect/hashfuncs.c + */ + +#include "postgres.h" + +#include "access/hash.h" +#include "access/htup_details.h" +#include "access/relation.h" +#include "catalog/pg_am.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "pageinspect.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/rel.h" + +PG_FUNCTION_INFO_V1(hash_page_type); +PG_FUNCTION_INFO_V1(hash_page_stats); +PG_FUNCTION_INFO_V1(hash_page_items); +PG_FUNCTION_INFO_V1(hash_bitmap_info); +PG_FUNCTION_INFO_V1(hash_metapage_info); + +#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX) +#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID) + +/* ------------------------------------------------ + * structure for single hash page statistics + * ------------------------------------------------ + */ +typedef struct HashPageStat +{ + int live_items; + int dead_items; + int page_size; + int free_size; + + /* opaque data */ + BlockNumber hasho_prevblkno; + BlockNumber hasho_nextblkno; + Bucket hasho_bucket; + uint16 hasho_flag; + uint16 hasho_page_id; +} HashPageStat; + + +/* + * Verify that the given bytea contains a HASH page, or die in the attempt. + * A pointer to a palloc'd, properly aligned copy of the page is returned. + */ +static Page +verify_hash_page(bytea *raw_page, int flags) +{ + Page page = get_page_from_raw(raw_page); + int pagetype = LH_UNUSED_PAGE; + + /* Treat new pages as unused. */ + if (!PageIsNew(page)) + { + HashPageOpaque pageopaque; + + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(HashPageOpaqueData))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "hash"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(HashPageOpaqueData)), + (int) PageGetSpecialSize(page)))); + + pageopaque = HashPageGetOpaque(page); + if (pageopaque->hasho_page_id != HASHO_PAGE_ID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid %s page", "hash"), + errdetail("Expected %08x, got %08x.", + HASHO_PAGE_ID, pageopaque->hasho_page_id))); + + pagetype = pageopaque->hasho_flag & LH_PAGE_TYPE; + } + + /* Check that page type is sane. */ + if (pagetype != LH_OVERFLOW_PAGE && pagetype != LH_BUCKET_PAGE && + pagetype != LH_BITMAP_PAGE && pagetype != LH_META_PAGE && + pagetype != LH_UNUSED_PAGE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid hash page type %08x", pagetype))); + + /* If requested, verify page type. */ + if (flags != 0 && (pagetype & flags) == 0) + { + switch (flags) + { + case LH_META_PAGE: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("page is not a hash meta page"))); + break; + case LH_BUCKET_PAGE | LH_OVERFLOW_PAGE: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("page is not a hash bucket or overflow page"))); + break; + case LH_OVERFLOW_PAGE: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("page is not a hash overflow page"))); + break; + default: + elog(ERROR, + "hash page of type %08x not in mask %08x", + pagetype, flags); + break; + } + } + + /* + * If it is the metapage, also verify magic number and version. + */ + if (pagetype == LH_META_PAGE) + { + HashMetaPage metap = HashPageGetMeta(page); + + if (metap->hashm_magic != HASH_MAGIC) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("invalid magic number for metadata"), + errdetail("Expected 0x%08x, got 0x%08x.", + HASH_MAGIC, metap->hashm_magic))); + + if (metap->hashm_version != HASH_VERSION) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("invalid version for metadata"), + errdetail("Expected %d, got %d.", + HASH_VERSION, metap->hashm_version))); + } + + return page; +} + +/* ------------------------------------------------- + * GetHashPageStatistics() + * + * Collect statistics of single hash page + * ------------------------------------------------- + */ +static void +GetHashPageStatistics(Page page, HashPageStat *stat) +{ + OffsetNumber maxoff = PageGetMaxOffsetNumber(page); + HashPageOpaque opaque = HashPageGetOpaque(page); + int off; + + stat->dead_items = stat->live_items = 0; + stat->page_size = PageGetPageSize(page); + + /* hash page opaque data */ + stat->hasho_prevblkno = opaque->hasho_prevblkno; + stat->hasho_nextblkno = opaque->hasho_nextblkno; + stat->hasho_bucket = opaque->hasho_bucket; + stat->hasho_flag = opaque->hasho_flag; + stat->hasho_page_id = opaque->hasho_page_id; + + /* count live and dead tuples, and free space */ + for (off = FirstOffsetNumber; off <= maxoff; off++) + { + ItemId id = PageGetItemId(page, off); + + if (!ItemIdIsDead(id)) + stat->live_items++; + else + stat->dead_items++; + } + stat->free_size = PageGetFreeSpace(page); +} + +/* --------------------------------------------------- + * hash_page_type() + * + * Usage: SELECT hash_page_type(get_raw_page('con_hash_index', 1)); + * --------------------------------------------------- + */ +Datum +hash_page_type(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Page page; + HashPageOpaque opaque; + int pagetype; + const char *type; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + page = verify_hash_page(raw_page, 0); + + if (PageIsNew(page)) + type = "unused"; + else + { + opaque = HashPageGetOpaque(page); + + /* page type (flags) */ + pagetype = opaque->hasho_flag & LH_PAGE_TYPE; + if (pagetype == LH_META_PAGE) + type = "metapage"; + else if (pagetype == LH_OVERFLOW_PAGE) + type = "overflow"; + else if (pagetype == LH_BUCKET_PAGE) + type = "bucket"; + else if (pagetype == LH_BITMAP_PAGE) + type = "bitmap"; + else + type = "unused"; + } + + PG_RETURN_TEXT_P(cstring_to_text(type)); +} + +/* --------------------------------------------------- + * hash_page_stats() + * + * Usage: SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 1)); + * --------------------------------------------------- + */ +Datum +hash_page_stats(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Page page; + int j; + Datum values[9]; + bool nulls[9] = {0}; + HashPageStat stat; + HeapTuple tuple; + TupleDesc tupleDesc; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); + + /* keep compiler quiet */ + stat.hasho_prevblkno = stat.hasho_nextblkno = InvalidBlockNumber; + stat.hasho_flag = stat.hasho_page_id = stat.free_size = 0; + + GetHashPageStatistics(page, &stat); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + tupleDesc = BlessTupleDesc(tupleDesc); + + j = 0; + values[j++] = Int32GetDatum(stat.live_items); + values[j++] = Int32GetDatum(stat.dead_items); + values[j++] = Int32GetDatum(stat.page_size); + values[j++] = Int32GetDatum(stat.free_size); + values[j++] = Int64GetDatum((int64) stat.hasho_prevblkno); + values[j++] = Int64GetDatum((int64) stat.hasho_nextblkno); + values[j++] = Int64GetDatum((int64) stat.hasho_bucket); + values[j++] = Int32GetDatum((int32) stat.hasho_flag); + values[j++] = Int32GetDatum((int32) stat.hasho_page_id); + + tuple = heap_form_tuple(tupleDesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); +} + +/* + * cross-call data structure for SRF + */ +struct user_args +{ + Page page; + OffsetNumber offset; +}; + +/*------------------------------------------------------- + * hash_page_items() + * + * Get IndexTupleData set in a hash page + * + * Usage: SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1)); + *------------------------------------------------------- + */ +Datum +hash_page_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Page page; + Datum result; + Datum values[3]; + bool nulls[3] = {0}; + uint32 hashkey; + HeapTuple tuple; + FuncCallContext *fctx; + MemoryContext mctx; + struct user_args *uargs; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupleDesc; + + fctx = SRF_FIRSTCALL_INIT(); + + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE); + + uargs = palloc(sizeof(struct user_args)); + + uargs->page = page; + + uargs->offset = FirstOffsetNumber; + + fctx->max_calls = PageGetMaxOffsetNumber(uargs->page); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + tupleDesc = BlessTupleDesc(tupleDesc); + + fctx->attinmeta = TupleDescGetAttInMetadata(tupleDesc); + + fctx->user_fctx = uargs; + + MemoryContextSwitchTo(mctx); + } + + fctx = SRF_PERCALL_SETUP(); + uargs = fctx->user_fctx; + + if (fctx->call_cntr < fctx->max_calls) + { + ItemId id; + IndexTuple itup; + int j; + + id = PageGetItemId(uargs->page, uargs->offset); + + if (!ItemIdIsValid(id)) + elog(ERROR, "invalid ItemId"); + + itup = (IndexTuple) PageGetItem(uargs->page, id); + + j = 0; + values[j++] = Int32GetDatum((int32) uargs->offset); + values[j++] = PointerGetDatum(&itup->t_tid); + + hashkey = _hash_get_indextuple_hashkey(itup); + values[j] = Int64GetDatum((int64) hashkey); + + tuple = heap_form_tuple(fctx->attinmeta->tupdesc, values, nulls); + result = HeapTupleGetDatum(tuple); + + uargs->offset = uargs->offset + 1; + + SRF_RETURN_NEXT(fctx, result); + } + + SRF_RETURN_DONE(fctx); +} + +/* ------------------------------------------------ + * hash_bitmap_info() + * + * Get bitmap information for a particular overflow page + * + * Usage: SELECT * FROM hash_bitmap_info('con_hash_index'::regclass, 5); + * ------------------------------------------------ + */ +Datum +hash_bitmap_info(PG_FUNCTION_ARGS) +{ + Oid indexRelid = PG_GETARG_OID(0); + int64 ovflblkno = PG_GETARG_INT64(1); + HashMetaPage metap; + Buffer metabuf, + mapbuf; + BlockNumber bitmapblkno; + Page mappage; + bool bit = false; + TupleDesc tupleDesc; + Relation indexRel; + uint32 ovflbitno; + int32 bitmappage, + bitmapbit; + HeapTuple tuple; + int i, + j; + Datum values[3]; + bool nulls[3] = {0}; + uint32 *freep; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + indexRel = relation_open(indexRelid, AccessShareLock); + + if (!IS_INDEX(indexRel) || !IS_HASH(indexRel)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a %s index", + RelationGetRelationName(indexRel), "hash"))); + + if (RELATION_IS_OTHER_TEMP(indexRel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary tables of other sessions"))); + + if (ovflblkno < 0 || ovflblkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number"))); + + if (ovflblkno >= RelationGetNumberOfBlocks(indexRel)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("block number %lld is out of range for relation \"%s\"", + (long long int) ovflblkno, RelationGetRelationName(indexRel)))); + + /* Read the metapage so we can determine which bitmap page to use */ + metabuf = _hash_getbuf(indexRel, HASH_METAPAGE, HASH_READ, LH_META_PAGE); + metap = HashPageGetMeta(BufferGetPage(metabuf)); + + /* + * Reject attempt to read the bit for a metapage or bitmap page; this is + * only meaningful for overflow pages. + */ + if (ovflblkno == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid overflow block number %u", + (BlockNumber) ovflblkno))); + for (i = 0; i < metap->hashm_nmaps; i++) + if (metap->hashm_mapp[i] == ovflblkno) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid overflow block number %u", + (BlockNumber) ovflblkno))); + + /* + * Identify overflow bit number. This will error out for primary bucket + * pages, and we've already rejected the metapage and bitmap pages above. + */ + ovflbitno = _hash_ovflblkno_to_bitno(metap, (BlockNumber) ovflblkno); + + bitmappage = ovflbitno >> BMPG_SHIFT(metap); + bitmapbit = ovflbitno & BMPG_MASK(metap); + + if (bitmappage >= metap->hashm_nmaps) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid overflow block number %u", + (BlockNumber) ovflblkno))); + + bitmapblkno = metap->hashm_mapp[bitmappage]; + + _hash_relbuf(indexRel, metabuf); + + /* Check the status of bitmap bit for overflow page */ + mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE); + mappage = BufferGetPage(mapbuf); + freep = HashPageGetBitmap(mappage); + + bit = ISSET(freep, bitmapbit) != 0; + + _hash_relbuf(indexRel, mapbuf); + index_close(indexRel, AccessShareLock); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + tupleDesc = BlessTupleDesc(tupleDesc); + + j = 0; + values[j++] = Int64GetDatum((int64) bitmapblkno); + values[j++] = Int32GetDatum(bitmapbit); + values[j++] = BoolGetDatum(bit); + + tuple = heap_form_tuple(tupleDesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); +} + +/* ------------------------------------------------ + * hash_metapage_info() + * + * Get the meta-page information for a hash index + * + * Usage: SELECT * FROM hash_metapage_info(get_raw_page('con_hash_index', 0)) + * ------------------------------------------------ + */ +Datum +hash_metapage_info(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Page page; + HashMetaPageData *metad; + TupleDesc tupleDesc; + HeapTuple tuple; + int i, + j; + Datum values[16]; + bool nulls[16] = {0}; + Datum spares[HASH_MAX_SPLITPOINTS]; + Datum mapp[HASH_MAX_BITMAPS]; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + page = verify_hash_page(raw_page, LH_META_PAGE); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + tupleDesc = BlessTupleDesc(tupleDesc); + + metad = HashPageGetMeta(page); + + j = 0; + values[j++] = Int64GetDatum((int64) metad->hashm_magic); + values[j++] = Int64GetDatum((int64) metad->hashm_version); + values[j++] = Float8GetDatum(metad->hashm_ntuples); + values[j++] = Int32GetDatum((int32) metad->hashm_ffactor); + values[j++] = Int32GetDatum((int32) metad->hashm_bsize); + values[j++] = Int32GetDatum((int32) metad->hashm_bmsize); + values[j++] = Int32GetDatum((int32) metad->hashm_bmshift); + values[j++] = Int64GetDatum((int64) metad->hashm_maxbucket); + values[j++] = Int64GetDatum((int64) metad->hashm_highmask); + values[j++] = Int64GetDatum((int64) metad->hashm_lowmask); + values[j++] = Int64GetDatum((int64) metad->hashm_ovflpoint); + values[j++] = Int64GetDatum((int64) metad->hashm_firstfree); + values[j++] = Int64GetDatum((int64) metad->hashm_nmaps); + values[j++] = ObjectIdGetDatum((Oid) metad->hashm_procid); + + for (i = 0; i < HASH_MAX_SPLITPOINTS; i++) + spares[i] = Int64GetDatum((int64) metad->hashm_spares[i]); + values[j++] = PointerGetDatum(construct_array_builtin(spares, HASH_MAX_SPLITPOINTS, INT8OID)); + + for (i = 0; i < HASH_MAX_BITMAPS; i++) + mapp[i] = Int64GetDatum((int64) metad->hashm_mapp[i]); + values[j++] = PointerGetDatum(construct_array_builtin(mapp, HASH_MAX_BITMAPS, INT8OID)); + + tuple = heap_form_tuple(tupleDesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); +} diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c new file mode 100644 index 0000000..0f02525 --- /dev/null +++ b/contrib/pageinspect/heapfuncs.c @@ -0,0 +1,618 @@ +/*------------------------------------------------------------------------- + * + * heapfuncs.c + * Functions to investigate heap pages + * + * We check the input to these functions for corrupt pointers etc. that + * might cause crashes, but at the same time we try to print out as much + * information as possible, even if it's nonsense. That's because if a + * page is corrupt, we don't know why and how exactly it is corrupt, so we + * let the user judge it. + * + * These functions are restricted to superusers for the fear of introducing + * security holes if the input checking isn't as water-tight as it should be. + * You'd need to be superuser to obtain a raw page image anyway, so + * there's hardly any use case for using these without superuser-rights + * anyway. + * + * Copyright (c) 2007-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pageinspect/heapfuncs.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/relation.h" +#include "catalog/pg_am_d.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "pageinspect.h" +#include "port/pg_bitutils.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/rel.h" + +/* + * It's not supported to create tuples with oids anymore, but when pg_upgrade + * was used to upgrade from an older version, tuples might still have an + * oid. Seems worthwhile to display that. + */ +#define HeapTupleHeaderGetOidOld(tup) \ +( \ + ((tup)->t_infomask & HEAP_HASOID_OLD) ? \ + *((Oid *) ((char *)(tup) + (tup)->t_hoff - sizeof(Oid))) \ + : \ + InvalidOid \ +) + + +/* + * bits_to_text + * + * Converts a bits8-array of 'len' bits to a human-readable + * c-string representation. + */ +static char * +bits_to_text(bits8 *bits, int len) +{ + int i; + char *str; + + str = palloc(len + 1); + + for (i = 0; i < len; i++) + str[i] = (bits[(i / 8)] & (1 << (i % 8))) ? '1' : '0'; + + str[i] = '\0'; + + return str; +} + + +/* + * text_to_bits + * + * Converts a c-string representation of bits into a bits8-array. This is + * the reverse operation of previous routine. + */ +static bits8 * +text_to_bits(char *str, int len) +{ + bits8 *bits; + int off = 0; + char byte = 0; + + bits = palloc(len + 1); + + while (off < len) + { + if (off % 8 == 0) + byte = 0; + + if ((str[off] == '0') || (str[off] == '1')) + byte = byte | ((str[off] - '0') << off % 8); + else + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid character \"%.*s\" in t_bits string", + pg_mblen(str + off), str + off))); + + if (off % 8 == 7) + bits[off / 8] = byte; + + off++; + } + + return bits; +} + +/* + * heap_page_items + * + * Allows inspection of line pointers and tuple headers of a heap page. + */ +PG_FUNCTION_INFO_V1(heap_page_items); + +typedef struct heap_page_items_state +{ + TupleDesc tupd; + Page page; + uint16 offset; +} heap_page_items_state; + +Datum +heap_page_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + heap_page_items_state *inter_call_data = NULL; + FuncCallContext *fctx; + int raw_page_size; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + raw_page_size = VARSIZE(raw_page) - VARHDRSZ; + + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + MemoryContext mctx; + + if (raw_page_size < SizeOfPageHeaderData) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page too small (%d bytes)", raw_page_size))); + + fctx = SRF_FIRSTCALL_INIT(); + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + inter_call_data = palloc(sizeof(heap_page_items_state)); + + /* 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"); + + inter_call_data->tupd = tupdesc; + + inter_call_data->offset = FirstOffsetNumber; + inter_call_data->page = VARDATA(raw_page); + + fctx->max_calls = PageGetMaxOffsetNumber(inter_call_data->page); + fctx->user_fctx = inter_call_data; + + MemoryContextSwitchTo(mctx); + } + + fctx = SRF_PERCALL_SETUP(); + inter_call_data = fctx->user_fctx; + + if (fctx->call_cntr < fctx->max_calls) + { + Page page = inter_call_data->page; + HeapTuple resultTuple; + Datum result; + ItemId id; + Datum values[14]; + bool nulls[14]; + uint16 lp_offset; + uint16 lp_flags; + uint16 lp_len; + + memset(nulls, 0, sizeof(nulls)); + + /* Extract information from the line pointer */ + + id = PageGetItemId(page, inter_call_data->offset); + + lp_offset = ItemIdGetOffset(id); + lp_flags = ItemIdGetFlags(id); + lp_len = ItemIdGetLength(id); + + values[0] = UInt16GetDatum(inter_call_data->offset); + values[1] = UInt16GetDatum(lp_offset); + values[2] = UInt16GetDatum(lp_flags); + values[3] = UInt16GetDatum(lp_len); + + /* + * We do just enough validity checking to make sure we don't reference + * data outside the page passed to us. The page could be corrupt in + * many other ways, but at least we won't crash. + */ + if (ItemIdHasStorage(id) && + lp_len >= MinHeapTupleSize && + lp_offset == MAXALIGN(lp_offset) && + lp_offset + lp_len <= raw_page_size) + { + HeapTupleHeader tuphdr; + bytea *tuple_data_bytea; + int tuple_data_len; + + /* Extract information from the tuple header */ + + tuphdr = (HeapTupleHeader) PageGetItem(page, id); + + values[4] = UInt32GetDatum(HeapTupleHeaderGetRawXmin(tuphdr)); + values[5] = UInt32GetDatum(HeapTupleHeaderGetRawXmax(tuphdr)); + /* shared with xvac */ + values[6] = UInt32GetDatum(HeapTupleHeaderGetRawCommandId(tuphdr)); + values[7] = PointerGetDatum(&tuphdr->t_ctid); + values[8] = UInt32GetDatum(tuphdr->t_infomask2); + values[9] = UInt32GetDatum(tuphdr->t_infomask); + values[10] = UInt8GetDatum(tuphdr->t_hoff); + + /* Copy raw tuple data into bytea attribute */ + tuple_data_len = lp_len - tuphdr->t_hoff; + tuple_data_bytea = (bytea *) palloc(tuple_data_len + VARHDRSZ); + SET_VARSIZE(tuple_data_bytea, tuple_data_len + VARHDRSZ); + memcpy(VARDATA(tuple_data_bytea), (char *) tuphdr + tuphdr->t_hoff, + tuple_data_len); + values[13] = PointerGetDatum(tuple_data_bytea); + + /* + * We already checked that the item is completely within the raw + * page passed to us, with the length given in the line pointer. + * Let's check that t_hoff doesn't point over lp_len, before using + * it to access t_bits and oid. + */ + if (tuphdr->t_hoff >= SizeofHeapTupleHeader && + tuphdr->t_hoff <= lp_len && + tuphdr->t_hoff == MAXALIGN(tuphdr->t_hoff)) + { + if (tuphdr->t_infomask & HEAP_HASNULL) + { + int bits_len; + + bits_len = + BITMAPLEN(HeapTupleHeaderGetNatts(tuphdr)) * BITS_PER_BYTE; + values[11] = CStringGetTextDatum(bits_to_text(tuphdr->t_bits, bits_len)); + } + else + nulls[11] = true; + + if (tuphdr->t_infomask & HEAP_HASOID_OLD) + values[12] = HeapTupleHeaderGetOidOld(tuphdr); + else + nulls[12] = true; + } + else + { + nulls[11] = true; + nulls[12] = true; + } + } + else + { + /* + * The line pointer is not used, or it's invalid. Set the rest of + * the fields to NULL + */ + int i; + + for (i = 4; i <= 13; i++) + nulls[i] = true; + } + + /* Build and return the result tuple. */ + resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls); + result = HeapTupleGetDatum(resultTuple); + + inter_call_data->offset++; + + SRF_RETURN_NEXT(fctx, result); + } + else + SRF_RETURN_DONE(fctx); +} + +/* + * tuple_data_split_internal + * + * Split raw tuple data taken directly from a page into an array of bytea + * elements. This routine does a lookup on NULL values and creates array + * elements accordingly. This is a reimplementation of nocachegetattr() + * in heaptuple.c simplified for educational purposes. + */ +static Datum +tuple_data_split_internal(Oid relid, char *tupdata, + uint16 tupdata_len, uint16 t_infomask, + uint16 t_infomask2, bits8 *t_bits, + bool do_detoast) +{ + ArrayBuildState *raw_attrs; + int nattrs; + int i; + int off = 0; + Relation rel; + TupleDesc tupdesc; + + /* Get tuple descriptor from relation OID */ + rel = relation_open(relid, AccessShareLock); + tupdesc = RelationGetDescr(rel); + + raw_attrs = initArrayResult(BYTEAOID, CurrentMemoryContext, false); + nattrs = tupdesc->natts; + + if (rel->rd_rel->relam != HEAP_TABLE_AM_OID) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only heap AM is supported"))); + + if (nattrs < (t_infomask2 & HEAP_NATTS_MASK)) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("number of attributes in tuple header is greater than number of attributes in tuple descriptor"))); + + for (i = 0; i < nattrs; i++) + { + Form_pg_attribute attr; + bool is_null; + bytea *attr_data = NULL; + + attr = TupleDescAttr(tupdesc, i); + + /* + * Tuple header can specify fewer attributes than tuple descriptor as + * ALTER TABLE ADD COLUMN without DEFAULT keyword does not actually + * change tuples in pages, so attributes with numbers greater than + * (t_infomask2 & HEAP_NATTS_MASK) should be treated as NULL. + */ + if (i >= (t_infomask2 & HEAP_NATTS_MASK)) + is_null = true; + else + is_null = (t_infomask & HEAP_HASNULL) && att_isnull(i, t_bits); + + if (!is_null) + { + int len; + + if (attr->attlen == -1) + { + off = att_align_pointer(off, attr->attalign, -1, + tupdata + off); + + /* + * As VARSIZE_ANY throws an exception if it can't properly + * detect the type of external storage in macros VARTAG_SIZE, + * this check is repeated to have a nicer error handling. + */ + if (VARATT_IS_EXTERNAL(tupdata + off) && + !VARATT_IS_EXTERNAL_ONDISK(tupdata + off) && + !VARATT_IS_EXTERNAL_INDIRECT(tupdata + off)) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("first byte of varlena attribute is incorrect for attribute %d", i))); + + len = VARSIZE_ANY(tupdata + off); + } + else + { + off = att_align_nominal(off, attr->attalign); + len = attr->attlen; + } + + if (tupdata_len < off + len) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("unexpected end of tuple data"))); + + if (attr->attlen == -1 && do_detoast) + attr_data = pg_detoast_datum_copy((struct varlena *) (tupdata + off)); + else + { + attr_data = (bytea *) palloc(len + VARHDRSZ); + SET_VARSIZE(attr_data, len + VARHDRSZ); + memcpy(VARDATA(attr_data), tupdata + off, len); + } + + off = att_addlength_pointer(off, attr->attlen, + tupdata + off); + } + + raw_attrs = accumArrayResult(raw_attrs, PointerGetDatum(attr_data), + is_null, BYTEAOID, CurrentMemoryContext); + if (attr_data) + pfree(attr_data); + } + + if (tupdata_len != off) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("end of tuple reached without looking at all its data"))); + + relation_close(rel, AccessShareLock); + + return makeArrayResult(raw_attrs, CurrentMemoryContext); +} + +/* + * tuple_data_split + * + * Split raw tuple data taken directly from page into distinct elements + * taking into account null values. + */ +PG_FUNCTION_INFO_V1(tuple_data_split); + +Datum +tuple_data_split(PG_FUNCTION_ARGS) +{ + Oid relid; + bytea *raw_data; + uint16 t_infomask; + uint16 t_infomask2; + char *t_bits_str; + bool do_detoast = false; + bits8 *t_bits = NULL; + Datum res; + + relid = PG_GETARG_OID(0); + raw_data = PG_ARGISNULL(1) ? NULL : PG_GETARG_BYTEA_P(1); + t_infomask = PG_GETARG_INT16(2); + t_infomask2 = PG_GETARG_INT16(3); + t_bits_str = PG_ARGISNULL(4) ? NULL : + text_to_cstring(PG_GETARG_TEXT_PP(4)); + + if (PG_NARGS() >= 6) + do_detoast = PG_GETARG_BOOL(5); + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + if (!raw_data) + PG_RETURN_NULL(); + + /* + * Convert t_bits string back to the bits8 array as represented in the + * tuple header. + */ + if (t_infomask & HEAP_HASNULL) + { + size_t bits_str_len; + size_t bits_len; + + bits_len = BITMAPLEN(t_infomask2 & HEAP_NATTS_MASK) * BITS_PER_BYTE; + if (!t_bits_str) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("t_bits string must not be NULL"))); + + bits_str_len = strlen(t_bits_str); + if (bits_len != bits_str_len) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("unexpected length of t_bits string: %zu, expected %zu", + bits_str_len, bits_len))); + + /* do the conversion */ + t_bits = text_to_bits(t_bits_str, bits_str_len); + } + else + { + if (t_bits_str) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("t_bits string is expected to be NULL, but instead it is %zu bytes long", + strlen(t_bits_str)))); + } + + /* Split tuple data */ + res = tuple_data_split_internal(relid, (char *) raw_data + VARHDRSZ, + VARSIZE(raw_data) - VARHDRSZ, + t_infomask, t_infomask2, t_bits, + do_detoast); + + if (t_bits) + pfree(t_bits); + + PG_RETURN_DATUM(res); +} + +/* + * heap_tuple_infomask_flags + * + * Decode into a human-readable format t_infomask and t_infomask2 associated + * to a tuple. All the flags are described in access/htup_details.h. + */ +PG_FUNCTION_INFO_V1(heap_tuple_infomask_flags); + +Datum +heap_tuple_infomask_flags(PG_FUNCTION_ARGS) +{ +#define HEAP_TUPLE_INFOMASK_COLS 2 + Datum values[HEAP_TUPLE_INFOMASK_COLS] = {0}; + bool nulls[HEAP_TUPLE_INFOMASK_COLS] = {0}; + uint16 t_infomask = PG_GETARG_INT16(0); + uint16 t_infomask2 = PG_GETARG_INT16(1); + int cnt = 0; + ArrayType *a; + int bitcnt; + Datum *flags; + TupleDesc tupdesc; + HeapTuple tuple; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + /* 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"); + + bitcnt = pg_popcount((const char *) &t_infomask, sizeof(uint16)) + + pg_popcount((const char *) &t_infomask2, sizeof(uint16)); + + /* If no flags, return a set of empty arrays */ + if (bitcnt <= 0) + { + values[0] = PointerGetDatum(construct_empty_array(TEXTOID)); + values[1] = PointerGetDatum(construct_empty_array(TEXTOID)); + tuple = heap_form_tuple(tupdesc, values, nulls); + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); + } + + /* build set of raw flags */ + flags = (Datum *) palloc0(sizeof(Datum) * bitcnt); + + /* decode t_infomask */ + if ((t_infomask & HEAP_HASNULL) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_HASNULL"); + if ((t_infomask & HEAP_HASVARWIDTH) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_HASVARWIDTH"); + if ((t_infomask & HEAP_HASEXTERNAL) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_HASEXTERNAL"); + if ((t_infomask & HEAP_HASOID_OLD) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_HASOID_OLD"); + if ((t_infomask & HEAP_XMAX_KEYSHR_LOCK) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_XMAX_KEYSHR_LOCK"); + if ((t_infomask & HEAP_COMBOCID) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_COMBOCID"); + if ((t_infomask & HEAP_XMAX_EXCL_LOCK) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_XMAX_EXCL_LOCK"); + if ((t_infomask & HEAP_XMAX_LOCK_ONLY) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_XMAX_LOCK_ONLY"); + if ((t_infomask & HEAP_XMIN_COMMITTED) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_XMIN_COMMITTED"); + if ((t_infomask & HEAP_XMIN_INVALID) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_XMIN_INVALID"); + if ((t_infomask & HEAP_XMAX_COMMITTED) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_XMAX_COMMITTED"); + if ((t_infomask & HEAP_XMAX_INVALID) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_XMAX_INVALID"); + if ((t_infomask & HEAP_XMAX_IS_MULTI) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_XMAX_IS_MULTI"); + if ((t_infomask & HEAP_UPDATED) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_UPDATED"); + if ((t_infomask & HEAP_MOVED_OFF) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_MOVED_OFF"); + if ((t_infomask & HEAP_MOVED_IN) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_MOVED_IN"); + + /* decode t_infomask2 */ + if ((t_infomask2 & HEAP_KEYS_UPDATED) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_KEYS_UPDATED"); + if ((t_infomask2 & HEAP_HOT_UPDATED) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_HOT_UPDATED"); + if ((t_infomask2 & HEAP_ONLY_TUPLE) != 0) + flags[cnt++] = CStringGetTextDatum("HEAP_ONLY_TUPLE"); + + /* build value */ + Assert(cnt <= bitcnt); + a = construct_array_builtin(flags, cnt, TEXTOID); + values[0] = PointerGetDatum(a); + + /* + * Build set of combined flags. Use the same array as previously, this + * keeps the code simple. + */ + cnt = 0; + MemSet(flags, 0, sizeof(Datum) * bitcnt); + + /* decode combined masks of t_infomask */ + if ((t_infomask & HEAP_XMAX_SHR_LOCK) == HEAP_XMAX_SHR_LOCK) + flags[cnt++] = CStringGetTextDatum("HEAP_XMAX_SHR_LOCK"); + if ((t_infomask & HEAP_XMIN_FROZEN) == HEAP_XMIN_FROZEN) + flags[cnt++] = CStringGetTextDatum("HEAP_XMIN_FROZEN"); + if ((t_infomask & HEAP_MOVED) == HEAP_MOVED) + flags[cnt++] = CStringGetTextDatum("HEAP_MOVED"); + + /* Build an empty array if there are no combined flags */ + if (cnt == 0) + a = construct_empty_array(TEXTOID); + else + a = construct_array_builtin(flags, cnt, TEXTOID); + pfree(flags); + values[1] = PointerGetDatum(a); + + /* Returns the record as Datum */ + tuple = heap_form_tuple(tupdesc, values, nulls); + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); +} diff --git a/contrib/pageinspect/meson.build b/contrib/pageinspect/meson.build new file mode 100644 index 0000000..4dde4e7 --- /dev/null +++ b/contrib/pageinspect/meson.build @@ -0,0 +1,60 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pageinspect_sources = files( + 'brinfuncs.c', + 'btreefuncs.c', + 'fsmfuncs.c', + 'ginfuncs.c', + 'gistfuncs.c', + 'hashfuncs.c', + 'heapfuncs.c', + 'rawpage.c', +) + +if host_system == 'windows' + pageinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pageinspect', + '--FILEDESC', 'pageinspect - functions to inspect contents of database pages',]) +endif + +pageinspect = shared_module('pageinspect', + pageinspect_sources, + kwargs: contrib_mod_args, +) +contrib_targets += pageinspect + +install_data( + 'pageinspect--1.0--1.1.sql', + 'pageinspect--1.1--1.2.sql', + 'pageinspect--1.2--1.3.sql', + 'pageinspect--1.3--1.4.sql', + 'pageinspect--1.4--1.5.sql', + 'pageinspect--1.5--1.6.sql', + 'pageinspect--1.5.sql', + 'pageinspect--1.6--1.7.sql', + 'pageinspect--1.7--1.8.sql', + 'pageinspect--1.8--1.9.sql', + 'pageinspect--1.9--1.10.sql', + 'pageinspect--1.10--1.11.sql', + 'pageinspect--1.11--1.12.sql', + 'pageinspect.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'pageinspect', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'page', + 'btree', + 'brin', + 'gin', + 'gist', + 'hash', + 'checksum', + 'oldextversions', + ], + }, +} diff --git a/contrib/pageinspect/pageinspect--1.0--1.1.sql b/contrib/pageinspect/pageinspect--1.0--1.1.sql new file mode 100644 index 0000000..0e2c3f4 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.0--1.1.sql @@ -0,0 +1,18 @@ +/* contrib/pageinspect/pageinspect--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.1'" to load this file. \quit + +DROP FUNCTION page_header(bytea); +CREATE FUNCTION page_header(IN page bytea, + OUT lsn text, + OUT checksum smallint, + OUT flags smallint, + OUT lower smallint, + OUT upper smallint, + OUT special smallint, + OUT pagesize smallint, + OUT version smallint, + OUT prune_xid xid) +AS 'MODULE_PATHNAME', 'page_header' +LANGUAGE C STRICT; diff --git a/contrib/pageinspect/pageinspect--1.1--1.2.sql b/contrib/pageinspect/pageinspect--1.1--1.2.sql new file mode 100644 index 0000000..31ffbb0 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.1--1.2.sql @@ -0,0 +1,18 @@ +/* contrib/pageinspect/pageinspect--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.2'" to load this file. \quit + +DROP FUNCTION page_header(bytea); +CREATE FUNCTION page_header(IN page bytea, + OUT lsn pg_lsn, + OUT checksum smallint, + OUT flags smallint, + OUT lower smallint, + OUT upper smallint, + OUT special smallint, + OUT pagesize smallint, + OUT version smallint, + OUT prune_xid xid) +AS 'MODULE_PATHNAME', 'page_header' +LANGUAGE C STRICT; diff --git a/contrib/pageinspect/pageinspect--1.10--1.11.sql b/contrib/pageinspect/pageinspect--1.10--1.11.sql new file mode 100644 index 0000000..8fa5e10 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.10--1.11.sql @@ -0,0 +1,28 @@ +/* contrib/pageinspect/pageinspect--1.10--1.11.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.11'" to load this file. \quit + +-- +-- Functions that fetch relation pages must be PARALLEL RESTRICTED, +-- not PARALLEL SAFE, otherwise they will fail when run on a +-- temporary table in a parallel worker process. +-- + +ALTER FUNCTION get_raw_page(text, int8) PARALLEL RESTRICTED; +ALTER FUNCTION get_raw_page(text, text, int8) PARALLEL RESTRICTED; +-- tuple_data_split must be restricted because it may fetch TOAST data. +ALTER FUNCTION tuple_data_split(oid, bytea, integer, integer, text) PARALLEL RESTRICTED; +ALTER FUNCTION tuple_data_split(oid, bytea, integer, integer, text, bool) PARALLEL RESTRICTED; +-- heap_page_item_attrs must be restricted because it calls tuple_data_split. +ALTER FUNCTION heap_page_item_attrs(bytea, regclass, bool) PARALLEL RESTRICTED; +ALTER FUNCTION heap_page_item_attrs(bytea, regclass) PARALLEL RESTRICTED; +ALTER FUNCTION bt_metap(text) PARALLEL RESTRICTED; +ALTER FUNCTION bt_page_stats(text, int8) PARALLEL RESTRICTED; +ALTER FUNCTION bt_page_items(text, int8) PARALLEL RESTRICTED; +ALTER FUNCTION hash_bitmap_info(regclass, int8) PARALLEL RESTRICTED; +-- brin_page_items might be parallel safe, because it seems to touch +-- only index metadata, but I don't think there's a point in risking it. +-- Likewise for gist_page_items. +ALTER FUNCTION brin_page_items(bytea, regclass) PARALLEL RESTRICTED; +ALTER FUNCTION gist_page_items(bytea, regclass) PARALLEL RESTRICTED; diff --git a/contrib/pageinspect/pageinspect--1.11--1.12.sql b/contrib/pageinspect/pageinspect--1.11--1.12.sql new file mode 100644 index 0000000..a20d67a --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.11--1.12.sql @@ -0,0 +1,40 @@ +/* contrib/pageinspect/pageinspect--1.11--1.12.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.12'" to load this file. \quit + +-- +-- bt_multi_page_stats() +-- +CREATE FUNCTION bt_multi_page_stats(IN relname text, IN blkno int8, IN blk_count int8, + OUT blkno int8, + OUT type "char", + OUT live_items int4, + OUT dead_items int4, + OUT avg_item_size int4, + OUT page_size int4, + OUT free_size int4, + OUT btpo_prev int8, + OUT btpo_next int8, + OUT btpo_level int8, + OUT btpo_flags int4) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'bt_multi_page_stats' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +-- +-- add information about BRIN empty ranges +-- +DROP FUNCTION brin_page_items(IN page bytea, IN index_oid regclass); +CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass, + OUT itemoffset int, + OUT blknum int8, + OUT attnum int, + OUT allnulls bool, + OUT hasnulls bool, + OUT placeholder bool, + OUT empty bool, + OUT value text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'brin_page_items' +LANGUAGE C STRICT PARALLEL RESTRICTED; diff --git a/contrib/pageinspect/pageinspect--1.2--1.3.sql b/contrib/pageinspect/pageinspect--1.2--1.3.sql new file mode 100644 index 0000000..9c55a6e --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.2--1.3.sql @@ -0,0 +1,82 @@ +/* contrib/pageinspect/pageinspect--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.3'" to load this file. \quit + +-- +-- brin_page_type() +-- +CREATE FUNCTION brin_page_type(IN page bytea) +RETURNS text +AS 'MODULE_PATHNAME', 'brin_page_type' +LANGUAGE C STRICT; + +-- +-- brin_metapage_info() +-- +CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text, + OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint) +AS 'MODULE_PATHNAME', 'brin_metapage_info' +LANGUAGE C STRICT; + +-- +-- brin_revmap_data() +-- +CREATE FUNCTION brin_revmap_data(IN page bytea, + OUT pages tid) +RETURNS SETOF tid +AS 'MODULE_PATHNAME', 'brin_revmap_data' +LANGUAGE C STRICT; + +-- +-- brin_page_items() +-- +CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass, + OUT itemoffset int, + OUT blknum int, + OUT attnum int, + OUT allnulls bool, + OUT hasnulls bool, + OUT placeholder bool, + OUT value text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'brin_page_items' +LANGUAGE C STRICT; + +-- +-- gin_metapage_info() +-- +CREATE FUNCTION gin_metapage_info(IN page bytea, + OUT pending_head bigint, + OUT pending_tail bigint, + OUT tail_free_size int4, + OUT n_pending_pages bigint, + OUT n_pending_tuples bigint, + OUT n_total_pages bigint, + OUT n_entry_pages bigint, + OUT n_data_pages bigint, + OUT n_entries bigint, + OUT version int4) +AS 'MODULE_PATHNAME', 'gin_metapage_info' +LANGUAGE C STRICT; + +-- +-- gin_page_opaque_info() +-- +CREATE FUNCTION gin_page_opaque_info(IN page bytea, + OUT rightlink bigint, + OUT maxoff int4, + OUT flags text[]) +AS 'MODULE_PATHNAME', 'gin_page_opaque_info' +LANGUAGE C STRICT; + +-- +-- gin_leafpage_items() +-- +CREATE FUNCTION gin_leafpage_items(IN page bytea, + OUT first_tid tid, + OUT nbytes int2, + OUT tids tid[]) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'gin_leafpage_items' +LANGUAGE C STRICT; diff --git a/contrib/pageinspect/pageinspect--1.3--1.4.sql b/contrib/pageinspect/pageinspect--1.3--1.4.sql new file mode 100644 index 0000000..86e0dfa --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.3--1.4.sql @@ -0,0 +1,118 @@ +/* contrib/pageinspect/pageinspect--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.4'" to load this file. \quit + +-- +-- heap_page_items() +-- +DROP FUNCTION heap_page_items(bytea); +CREATE FUNCTION heap_page_items(IN page bytea, + OUT lp smallint, + OUT lp_off smallint, + OUT lp_flags smallint, + OUT lp_len smallint, + OUT t_xmin xid, + OUT t_xmax xid, + OUT t_field3 int4, + OUT t_ctid tid, + OUT t_infomask2 integer, + OUT t_infomask integer, + OUT t_hoff smallint, + OUT t_bits text, + OUT t_oid oid, + OUT t_data bytea) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'heap_page_items' +LANGUAGE C STRICT; + +-- +-- tuple_data_split() +-- +CREATE FUNCTION tuple_data_split(rel_oid oid, + t_data bytea, + t_infomask integer, + t_infomask2 integer, + t_bits text) +RETURNS bytea[] +AS 'MODULE_PATHNAME','tuple_data_split' +LANGUAGE C; + +CREATE FUNCTION tuple_data_split(rel_oid oid, + t_data bytea, + t_infomask integer, + t_infomask2 integer, + t_bits text, + do_detoast bool) +RETURNS bytea[] +AS 'MODULE_PATHNAME','tuple_data_split' +LANGUAGE C; + +-- +-- heap_page_item_attrs() +-- +CREATE FUNCTION heap_page_item_attrs( + IN page bytea, + IN rel_oid regclass, + IN do_detoast bool, + OUT lp smallint, + OUT lp_off smallint, + OUT lp_flags smallint, + OUT lp_len smallint, + OUT t_xmin xid, + OUT t_xmax xid, + OUT t_field3 int4, + OUT t_ctid tid, + OUT t_infomask2 integer, + OUT t_infomask integer, + OUT t_hoff smallint, + OUT t_bits text, + OUT t_oid oid, + OUT t_attrs bytea[] + ) +RETURNS SETOF record AS $$ +SELECT lp, + lp_off, + lp_flags, + lp_len, + t_xmin, + t_xmax, + t_field3, + t_ctid, + t_infomask2, + t_infomask, + t_hoff, + t_bits, + t_oid, + tuple_data_split( + rel_oid, + t_data, + t_infomask, + t_infomask2, + t_bits, + do_detoast) + AS t_attrs + FROM heap_page_items(page); +$$ LANGUAGE SQL; + +CREATE FUNCTION heap_page_item_attrs( + IN page bytea, + IN rel_oid regclass, + OUT lp smallint, + OUT lp_off smallint, + OUT lp_flags smallint, + OUT lp_len smallint, + OUT t_xmin xid, + OUT t_xmax xid, + OUT t_field3 int4, + OUT t_ctid tid, + OUT t_infomask2 integer, + OUT t_infomask integer, + OUT t_hoff smallint, + OUT t_bits text, + OUT t_oid oid, + OUT t_attrs bytea[] + ) +RETURNS SETOF record AS $$ +SELECT * from heap_page_item_attrs(page, rel_oid, false); +$$ LANGUAGE SQL; diff --git a/contrib/pageinspect/pageinspect--1.4--1.5.sql b/contrib/pageinspect/pageinspect--1.4--1.5.sql new file mode 100644 index 0000000..7ed7b54 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.4--1.5.sql @@ -0,0 +1,24 @@ +/* contrib/pageinspect/pageinspect--1.4--1.5.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.5'" to load this file. \quit + +ALTER FUNCTION get_raw_page(text, int4) PARALLEL SAFE; +ALTER FUNCTION get_raw_page(text, text, int4) PARALLEL SAFE; +ALTER FUNCTION page_header(bytea) PARALLEL SAFE; +ALTER FUNCTION heap_page_items(bytea) PARALLEL SAFE; +ALTER FUNCTION tuple_data_split(oid, bytea, integer, integer, text) PARALLEL SAFE; +ALTER FUNCTION tuple_data_split(oid, bytea, integer, integer, text, bool) PARALLEL SAFE; +ALTER FUNCTION heap_page_item_attrs(bytea, regclass, bool) PARALLEL SAFE; +ALTER FUNCTION heap_page_item_attrs(bytea, regclass) PARALLEL SAFE; +ALTER FUNCTION bt_metap(text) PARALLEL SAFE; +ALTER FUNCTION bt_page_stats(text, int4) PARALLEL SAFE; +ALTER FUNCTION bt_page_items(text, int4) PARALLEL SAFE; +ALTER FUNCTION brin_page_type(bytea) PARALLEL SAFE; +ALTER FUNCTION brin_metapage_info(bytea) PARALLEL SAFE; +ALTER FUNCTION brin_revmap_data(bytea) PARALLEL SAFE; +ALTER FUNCTION brin_page_items(bytea, regclass) PARALLEL SAFE; +ALTER FUNCTION fsm_page_contents(bytea) PARALLEL SAFE; +ALTER FUNCTION gin_metapage_info(bytea) PARALLEL SAFE; +ALTER FUNCTION gin_page_opaque_info(bytea) PARALLEL SAFE; +ALTER FUNCTION gin_leafpage_items(bytea) PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect--1.5--1.6.sql b/contrib/pageinspect/pageinspect--1.5--1.6.sql new file mode 100644 index 0000000..c435628 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.5--1.6.sql @@ -0,0 +1,99 @@ +/* contrib/pageinspect/pageinspect--1.5--1.6.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.6'" to load this file. \quit + +-- +-- HASH functions +-- + +-- +-- hash_page_type() +-- +CREATE FUNCTION hash_page_type(IN page bytea) +RETURNS text +AS 'MODULE_PATHNAME', 'hash_page_type' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- hash_page_stats() +-- +CREATE FUNCTION hash_page_stats(IN page bytea, + OUT live_items int4, + OUT dead_items int4, + OUT page_size int4, + OUT free_size int4, + OUT hasho_prevblkno int8, + OUT hasho_nextblkno int8, + OUT hasho_bucket int8, + OUT hasho_flag int4, + OUT hasho_page_id int4) +AS 'MODULE_PATHNAME', 'hash_page_stats' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- hash_page_items() +-- +CREATE FUNCTION hash_page_items(IN page bytea, + OUT itemoffset int4, + OUT ctid tid, + OUT data int8) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'hash_page_items' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- hash_bitmap_info() +-- +CREATE FUNCTION hash_bitmap_info(IN index_oid regclass, IN blkno int8, + OUT bitmapblkno int8, + OUT bitmapbit int4, + OUT bitstatus bool) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'hash_bitmap_info' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- hash_metapage_info() +-- +CREATE FUNCTION hash_metapage_info(IN page bytea, + OUT magic int8, + OUT version int8, + OUT ntuples double precision, + OUT ffactor int4, + OUT bsize int4, + OUT bmsize int4, + OUT bmshift int4, + OUT maxbucket int8, + OUT highmask int8, + OUT lowmask int8, + OUT ovflpoint int8, + OUT firstfree int8, + OUT nmaps int8, + OUT procid oid, + OUT spares int8[], + OUT mapp int8[]) +AS 'MODULE_PATHNAME', 'hash_metapage_info' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- page_checksum() +-- +CREATE FUNCTION page_checksum(IN page bytea, IN blkno int4) +RETURNS smallint +AS 'MODULE_PATHNAME', 'page_checksum' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- bt_page_items_bytea() +-- +CREATE FUNCTION bt_page_items(IN page bytea, + OUT itemoffset smallint, + OUT ctid tid, + OUT itemlen smallint, + OUT nulls bool, + OUT vars bool, + OUT data text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'bt_page_items_bytea' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect--1.5.sql b/contrib/pageinspect/pageinspect--1.5.sql new file mode 100644 index 0000000..1e40c3c --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.5.sql @@ -0,0 +1,279 @@ +/* contrib/pageinspect/pageinspect--1.5.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pageinspect" to load this file. \quit + +-- +-- get_raw_page() +-- +CREATE FUNCTION get_raw_page(text, int4) +RETURNS bytea +AS 'MODULE_PATHNAME', 'get_raw_page' +LANGUAGE C STRICT PARALLEL SAFE; + +CREATE FUNCTION get_raw_page(text, text, int4) +RETURNS bytea +AS 'MODULE_PATHNAME', 'get_raw_page_fork' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- page_header() +-- +CREATE FUNCTION page_header(IN page bytea, + OUT lsn pg_lsn, + OUT checksum smallint, + OUT flags smallint, + OUT lower smallint, + OUT upper smallint, + OUT special smallint, + OUT pagesize smallint, + OUT version smallint, + OUT prune_xid xid) +AS 'MODULE_PATHNAME', 'page_header' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- heap_page_items() +-- +CREATE FUNCTION heap_page_items(IN page bytea, + OUT lp smallint, + OUT lp_off smallint, + OUT lp_flags smallint, + OUT lp_len smallint, + OUT t_xmin xid, + OUT t_xmax xid, + OUT t_field3 int4, + OUT t_ctid tid, + OUT t_infomask2 integer, + OUT t_infomask integer, + OUT t_hoff smallint, + OUT t_bits text, + OUT t_oid oid, + OUT t_data bytea) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'heap_page_items' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- tuple_data_split() +-- +CREATE FUNCTION tuple_data_split(rel_oid oid, + t_data bytea, + t_infomask integer, + t_infomask2 integer, + t_bits text) +RETURNS bytea[] +AS 'MODULE_PATHNAME','tuple_data_split' +LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION tuple_data_split(rel_oid oid, + t_data bytea, + t_infomask integer, + t_infomask2 integer, + t_bits text, + do_detoast bool) +RETURNS bytea[] +AS 'MODULE_PATHNAME','tuple_data_split' +LANGUAGE C PARALLEL SAFE; + +-- +-- heap_page_item_attrs() +-- +CREATE FUNCTION heap_page_item_attrs( + IN page bytea, + IN rel_oid regclass, + IN do_detoast bool, + OUT lp smallint, + OUT lp_off smallint, + OUT lp_flags smallint, + OUT lp_len smallint, + OUT t_xmin xid, + OUT t_xmax xid, + OUT t_field3 int4, + OUT t_ctid tid, + OUT t_infomask2 integer, + OUT t_infomask integer, + OUT t_hoff smallint, + OUT t_bits text, + OUT t_oid oid, + OUT t_attrs bytea[] + ) +RETURNS SETOF record AS $$ +SELECT lp, + lp_off, + lp_flags, + lp_len, + t_xmin, + t_xmax, + t_field3, + t_ctid, + t_infomask2, + t_infomask, + t_hoff, + t_bits, + t_oid, + tuple_data_split( + rel_oid, + t_data, + t_infomask, + t_infomask2, + t_bits, + do_detoast) + AS t_attrs + FROM heap_page_items(page); +$$ LANGUAGE SQL PARALLEL SAFE; + +CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass, + OUT lp smallint, + OUT lp_off smallint, + OUT lp_flags smallint, + OUT lp_len smallint, + OUT t_xmin xid, + OUT t_xmax xid, + OUT t_field3 int4, + OUT t_ctid tid, + OUT t_infomask2 integer, + OUT t_infomask integer, + OUT t_hoff smallint, + OUT t_bits text, + OUT t_oid oid, + OUT t_attrs bytea[] + ) +RETURNS SETOF record AS $$ +SELECT * from heap_page_item_attrs(page, rel_oid, false); +$$ LANGUAGE SQL PARALLEL SAFE; + +-- +-- bt_metap() +-- +CREATE FUNCTION bt_metap(IN relname text, + OUT magic int4, + OUT version int4, + OUT root int4, + OUT level int4, + OUT fastroot int4, + OUT fastlevel int4) +AS 'MODULE_PATHNAME', 'bt_metap' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- bt_page_stats() +-- +CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int4, + OUT blkno int4, + OUT type "char", + OUT live_items int4, + OUT dead_items int4, + OUT avg_item_size int4, + OUT page_size int4, + OUT free_size int4, + OUT btpo_prev int4, + OUT btpo_next int4, + OUT btpo int4, + OUT btpo_flags int4) +AS 'MODULE_PATHNAME', 'bt_page_stats' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- bt_page_items() +-- +CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4, + OUT itemoffset smallint, + OUT ctid tid, + OUT itemlen smallint, + OUT nulls bool, + OUT vars bool, + OUT data text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'bt_page_items' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- brin_page_type() +-- +CREATE FUNCTION brin_page_type(IN page bytea) +RETURNS text +AS 'MODULE_PATHNAME', 'brin_page_type' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- brin_metapage_info() +-- +CREATE FUNCTION brin_metapage_info(IN page bytea, OUT magic text, + OUT version integer, OUT pagesperrange integer, OUT lastrevmappage bigint) +AS 'MODULE_PATHNAME', 'brin_metapage_info' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- brin_revmap_data() +-- +CREATE FUNCTION brin_revmap_data(IN page bytea, + OUT pages tid) +RETURNS SETOF tid +AS 'MODULE_PATHNAME', 'brin_revmap_data' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- brin_page_items() +-- +CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass, + OUT itemoffset int, + OUT blknum int, + OUT attnum int, + OUT allnulls bool, + OUT hasnulls bool, + OUT placeholder bool, + OUT value text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'brin_page_items' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- fsm_page_contents() +-- +CREATE FUNCTION fsm_page_contents(IN page bytea) +RETURNS text +AS 'MODULE_PATHNAME', 'fsm_page_contents' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- GIN functions +-- + +-- +-- gin_metapage_info() +-- +CREATE FUNCTION gin_metapage_info(IN page bytea, + OUT pending_head bigint, + OUT pending_tail bigint, + OUT tail_free_size int4, + OUT n_pending_pages bigint, + OUT n_pending_tuples bigint, + OUT n_total_pages bigint, + OUT n_entry_pages bigint, + OUT n_data_pages bigint, + OUT n_entries bigint, + OUT version int4) +AS 'MODULE_PATHNAME', 'gin_metapage_info' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- gin_page_opaque_info() +-- +CREATE FUNCTION gin_page_opaque_info(IN page bytea, + OUT rightlink bigint, + OUT maxoff int4, + OUT flags text[]) +AS 'MODULE_PATHNAME', 'gin_page_opaque_info' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- gin_leafpage_items() +-- +CREATE FUNCTION gin_leafpage_items(IN page bytea, + OUT first_tid tid, + OUT nbytes int2, + OUT tids tid[]) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'gin_leafpage_items' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect--1.6--1.7.sql b/contrib/pageinspect/pageinspect--1.6--1.7.sql new file mode 100644 index 0000000..2433a21 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.6--1.7.sql @@ -0,0 +1,26 @@ +/* contrib/pageinspect/pageinspect--1.6--1.7.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.7'" to load this file. \quit + +-- +-- bt_metap() +-- +DROP FUNCTION bt_metap(IN relname text, + OUT magic int4, + OUT version int4, + OUT root int4, + OUT level int4, + OUT fastroot int4, + OUT fastlevel int4); +CREATE FUNCTION bt_metap(IN relname text, + OUT magic int4, + OUT version int4, + OUT root int4, + OUT level int4, + OUT fastroot int4, + OUT fastlevel int4, + OUT oldest_xact int4, + OUT last_cleanup_num_tuples real) +AS 'MODULE_PATHNAME', 'bt_metap' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect--1.7--1.8.sql b/contrib/pageinspect/pageinspect--1.7--1.8.sql new file mode 100644 index 0000000..45031a0 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.7--1.8.sql @@ -0,0 +1,69 @@ +/* contrib/pageinspect/pageinspect--1.7--1.8.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.8'" to load this file. \quit + +-- +-- heap_tuple_infomask_flags() +-- +CREATE FUNCTION heap_tuple_infomask_flags( + t_infomask integer, + t_infomask2 integer, + raw_flags OUT text[], + combined_flags OUT text[]) +RETURNS record +AS 'MODULE_PATHNAME', 'heap_tuple_infomask_flags' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- bt_metap() +-- +DROP FUNCTION bt_metap(text); +CREATE FUNCTION bt_metap(IN relname text, + OUT magic int4, + OUT version int4, + OUT root int8, + OUT level int8, + OUT fastroot int8, + OUT fastlevel int8, + OUT oldest_xact xid, + OUT last_cleanup_num_tuples float8, + OUT allequalimage boolean) +AS 'MODULE_PATHNAME', 'bt_metap' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- bt_page_items(text, int4) +-- +DROP FUNCTION bt_page_items(text, int4); +CREATE FUNCTION bt_page_items(IN relname text, IN blkno int4, + OUT itemoffset smallint, + OUT ctid tid, + OUT itemlen smallint, + OUT nulls bool, + OUT vars bool, + OUT data text, + OUT dead boolean, + OUT htid tid, + OUT tids tid[]) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'bt_page_items' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- bt_page_items(bytea) +-- +DROP FUNCTION bt_page_items(bytea); +CREATE FUNCTION bt_page_items(IN page bytea, + OUT itemoffset smallint, + OUT ctid tid, + OUT itemlen smallint, + OUT nulls bool, + OUT vars bool, + OUT data text, + OUT dead boolean, + OUT htid tid, + OUT tids tid[]) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'bt_page_items_bytea' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect--1.8--1.9.sql b/contrib/pageinspect/pageinspect--1.8--1.9.sql new file mode 100644 index 0000000..be89a64 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.8--1.9.sql @@ -0,0 +1,137 @@ +/* contrib/pageinspect/pageinspect--1.8--1.9.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.9'" to load this file. \quit + +-- +-- gist_page_opaque_info() +-- +CREATE FUNCTION gist_page_opaque_info(IN page bytea, + OUT lsn pg_lsn, + OUT nsn pg_lsn, + OUT rightlink bigint, + OUT flags text[]) +AS 'MODULE_PATHNAME', 'gist_page_opaque_info' +LANGUAGE C STRICT PARALLEL SAFE; + + +-- +-- gist_page_items_bytea() +-- +CREATE FUNCTION gist_page_items_bytea(IN page bytea, + OUT itemoffset smallint, + OUT ctid tid, + OUT itemlen smallint, + OUT dead boolean, + OUT key_data bytea) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'gist_page_items_bytea' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- gist_page_items() +-- +CREATE FUNCTION gist_page_items(IN page bytea, + IN index_oid regclass, + OUT itemoffset smallint, + OUT ctid tid, + OUT itemlen smallint, + OUT dead boolean, + OUT keys text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'gist_page_items' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- get_raw_page() +-- +DROP FUNCTION get_raw_page(text, int4); +CREATE FUNCTION get_raw_page(text, int8) +RETURNS bytea +AS 'MODULE_PATHNAME', 'get_raw_page_1_9' +LANGUAGE C STRICT PARALLEL SAFE; + +DROP FUNCTION get_raw_page(text, text, int4); +CREATE FUNCTION get_raw_page(text, text, int8) +RETURNS bytea +AS 'MODULE_PATHNAME', 'get_raw_page_fork_1_9' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- page_checksum() +-- +DROP FUNCTION page_checksum(IN page bytea, IN blkno int4); +CREATE FUNCTION page_checksum(IN page bytea, IN blkno int8) +RETURNS smallint +AS 'MODULE_PATHNAME', 'page_checksum_1_9' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- bt_metap() +-- +DROP FUNCTION bt_metap(text); +CREATE FUNCTION bt_metap(IN relname text, + OUT magic int4, + OUT version int4, + OUT root int8, + OUT level int8, + OUT fastroot int8, + OUT fastlevel int8, + OUT last_cleanup_num_delpages int8, + OUT last_cleanup_num_tuples float8, + OUT allequalimage boolean) +AS 'MODULE_PATHNAME', 'bt_metap' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- bt_page_stats() +-- +DROP FUNCTION bt_page_stats(text, int4); +CREATE FUNCTION bt_page_stats(IN relname text, IN blkno int8, + OUT blkno int8, + OUT type "char", + OUT live_items int4, + OUT dead_items int4, + OUT avg_item_size int4, + OUT page_size int4, + OUT free_size int4, + OUT btpo_prev int8, + OUT btpo_next int8, + OUT btpo_level int8, + OUT btpo_flags int4) +AS 'MODULE_PATHNAME', 'bt_page_stats_1_9' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- bt_page_items() +-- +DROP FUNCTION bt_page_items(text, int4); +CREATE FUNCTION bt_page_items(IN relname text, IN blkno int8, + OUT itemoffset smallint, + OUT ctid tid, + OUT itemlen smallint, + OUT nulls bool, + OUT vars bool, + OUT data text, + OUT dead boolean, + OUT htid tid, + OUT tids tid[]) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'bt_page_items_1_9' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- brin_page_items() +-- +DROP FUNCTION brin_page_items(IN page bytea, IN index_oid regclass); +CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass, + OUT itemoffset int, + OUT blknum int8, + OUT attnum int, + OUT allnulls bool, + OUT hasnulls bool, + OUT placeholder bool, + OUT value text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'brin_page_items' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect--1.9--1.10.sql b/contrib/pageinspect/pageinspect--1.9--1.10.sql new file mode 100644 index 0000000..8dd9a27 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.9--1.10.sql @@ -0,0 +1,21 @@ +/* contrib/pageinspect/pageinspect--1.9--1.10.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.10'" to load this file. \quit + +-- +-- page_header() +-- +DROP FUNCTION page_header(IN page bytea); +CREATE FUNCTION page_header(IN page bytea, + OUT lsn pg_lsn, + OUT checksum smallint, + OUT flags smallint, + OUT lower int, + OUT upper int, + OUT special int, + OUT pagesize int, + OUT version smallint, + OUT prune_xid xid) +AS 'MODULE_PATHNAME', 'page_header' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control new file mode 100644 index 0000000..b2804e9 --- /dev/null +++ b/contrib/pageinspect/pageinspect.control @@ -0,0 +1,5 @@ +# pageinspect extension +comment = 'inspect the contents of database pages at a low level' +default_version = '1.12' +module_pathname = '$libdir/pageinspect' +relocatable = true diff --git a/contrib/pageinspect/pageinspect.h b/contrib/pageinspect/pageinspect.h new file mode 100644 index 0000000..3f0fe7e --- /dev/null +++ b/contrib/pageinspect/pageinspect.h @@ -0,0 +1,30 @@ +/*------------------------------------------------------------------------- + * + * pageinspect.h + * Common functions for pageinspect. + * + * Copyright (c) 2017-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pageinspect/pageinspect.h + * + *------------------------------------------------------------------------- + */ +#ifndef _PAGEINSPECT_H_ +#define _PAGEINSPECT_H_ + +#include "storage/bufpage.h" + +/* + * Extension version number, for supporting older extension versions' objects + */ +enum pageinspect_version +{ + PAGEINSPECT_V1_8, + PAGEINSPECT_V1_9, +}; + +/* in rawpage.c */ +extern Page get_page_from_raw(bytea *raw_page); + +#endif /* _PAGEINSPECT_H_ */ diff --git a/contrib/pageinspect/rawpage.c b/contrib/pageinspect/rawpage.c new file mode 100644 index 0000000..b25a63c --- /dev/null +++ b/contrib/pageinspect/rawpage.c @@ -0,0 +1,376 @@ +/*------------------------------------------------------------------------- + * + * rawpage.c + * Functions to extract a raw page as bytea and inspect it + * + * Access-method specific inspection functions are in separate files. + * + * Copyright (c) 2007-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pageinspect/rawpage.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/relation.h" +#include "catalog/namespace.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "pageinspect.h" +#include "storage/bufmgr.h" +#include "storage/checksum.h" +#include "utils/builtins.h" +#include "utils/pg_lsn.h" +#include "utils/rel.h" +#include "utils/varlena.h" + +PG_MODULE_MAGIC; + +static bytea *get_raw_page_internal(text *relname, ForkNumber forknum, + BlockNumber blkno); + + +/* + * get_raw_page + * + * Returns a copy of a page from shared buffers as a bytea + */ +PG_FUNCTION_INFO_V1(get_raw_page_1_9); + +Datum +get_raw_page_1_9(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + int64 blkno = PG_GETARG_INT64(1); + bytea *raw_page; + + if (blkno < 0 || blkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number"))); + + raw_page = get_raw_page_internal(relname, MAIN_FORKNUM, blkno); + + PG_RETURN_BYTEA_P(raw_page); +} + +/* + * entry point for old extension version + */ +PG_FUNCTION_INFO_V1(get_raw_page); + +Datum +get_raw_page(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + uint32 blkno = PG_GETARG_UINT32(1); + bytea *raw_page; + + /* + * We don't normally bother to check the number of arguments to a C + * function, but here it's needed for safety because early 8.4 beta + * releases mistakenly redefined get_raw_page() as taking three arguments. + */ + if (PG_NARGS() != 2) + ereport(ERROR, + (errmsg("wrong number of arguments to get_raw_page()"), + errhint("Run the updated pageinspect.sql script."))); + + raw_page = get_raw_page_internal(relname, MAIN_FORKNUM, blkno); + + PG_RETURN_BYTEA_P(raw_page); +} + +/* + * get_raw_page_fork + * + * Same, for any fork + */ +PG_FUNCTION_INFO_V1(get_raw_page_fork_1_9); + +Datum +get_raw_page_fork_1_9(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + text *forkname = PG_GETARG_TEXT_PP(1); + int64 blkno = PG_GETARG_INT64(2); + bytea *raw_page; + ForkNumber forknum; + + forknum = forkname_to_number(text_to_cstring(forkname)); + + if (blkno < 0 || blkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number"))); + + raw_page = get_raw_page_internal(relname, forknum, blkno); + + PG_RETURN_BYTEA_P(raw_page); +} + +/* + * Entry point for old extension version + */ +PG_FUNCTION_INFO_V1(get_raw_page_fork); + +Datum +get_raw_page_fork(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + text *forkname = PG_GETARG_TEXT_PP(1); + uint32 blkno = PG_GETARG_UINT32(2); + bytea *raw_page; + ForkNumber forknum; + + forknum = forkname_to_number(text_to_cstring(forkname)); + + raw_page = get_raw_page_internal(relname, forknum, blkno); + + PG_RETURN_BYTEA_P(raw_page); +} + +/* + * workhorse + */ +static bytea * +get_raw_page_internal(text *relname, ForkNumber forknum, BlockNumber blkno) +{ + bytea *raw_page; + RangeVar *relrv; + Relation rel; + char *raw_page_data; + Buffer buf; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot get raw page from relation \"%s\"", + RelationGetRelationName(rel)), + errdetail_relkind_not_supported(rel->rd_rel->relkind))); + + /* + * Reject attempts to read non-local temporary relations; we would be + * likely to get wrong data since we have no visibility into the owning + * session's local buffers. + */ + if (RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary tables of other sessions"))); + + if (blkno >= RelationGetNumberOfBlocksInFork(rel, forknum)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("block number %u is out of range for relation \"%s\"", + blkno, RelationGetRelationName(rel)))); + + /* Initialize buffer to copy to */ + raw_page = (bytea *) palloc(BLCKSZ + VARHDRSZ); + SET_VARSIZE(raw_page, BLCKSZ + VARHDRSZ); + raw_page_data = VARDATA(raw_page); + + /* Take a verbatim copy of the page */ + + buf = ReadBufferExtended(rel, forknum, blkno, RBM_NORMAL, NULL); + LockBuffer(buf, BUFFER_LOCK_SHARE); + + memcpy(raw_page_data, BufferGetPage(buf), BLCKSZ); + + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + ReleaseBuffer(buf); + + relation_close(rel, AccessShareLock); + + return raw_page; +} + + +/* + * get_page_from_raw + * + * Get a palloc'd, maxalign'ed page image from the result of get_raw_page() + * + * On machines with MAXALIGN = 8, the payload of a bytea is not maxaligned, + * since it will start 4 bytes into a palloc'd value. On alignment-picky + * machines, this will cause failures in accesses to 8-byte-wide values + * within the page. We don't need to worry if accessing only 4-byte or + * smaller fields, but when examining a struct that contains 8-byte fields, + * use this function for safety. + */ +Page +get_page_from_raw(bytea *raw_page) +{ + Page page; + int raw_page_size; + + raw_page_size = VARSIZE_ANY_EXHDR(raw_page); + + if (raw_page_size != BLCKSZ) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid page size"), + errdetail("Expected %d bytes, got %d.", + BLCKSZ, raw_page_size))); + + page = palloc(raw_page_size); + + memcpy(page, VARDATA_ANY(raw_page), raw_page_size); + + return page; +} + + +/* + * page_header + * + * Allows inspection of page header fields of a raw page + */ + +PG_FUNCTION_INFO_V1(page_header); + +Datum +page_header(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + + TupleDesc tupdesc; + + Datum result; + HeapTuple tuple; + Datum values[9]; + bool nulls[9]; + + Page page; + PageHeader pageheader; + XLogRecPtr lsn; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + page = get_page_from_raw(raw_page); + pageheader = (PageHeader) page; + + /* 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"); + + /* Extract information from the page header */ + + lsn = PageGetLSN(page); + + /* pageinspect >= 1.2 uses pg_lsn instead of text for the LSN field. */ + if (TupleDescAttr(tupdesc, 0)->atttypid == TEXTOID) + { + char lsnchar[64]; + + snprintf(lsnchar, sizeof(lsnchar), "%X/%X", LSN_FORMAT_ARGS(lsn)); + values[0] = CStringGetTextDatum(lsnchar); + } + else + values[0] = LSNGetDatum(lsn); + values[1] = UInt16GetDatum(pageheader->pd_checksum); + values[2] = UInt16GetDatum(pageheader->pd_flags); + + /* pageinspect >= 1.10 uses int4 instead of int2 for those fields */ + switch (TupleDescAttr(tupdesc, 3)->atttypid) + { + case INT2OID: + Assert(TupleDescAttr(tupdesc, 4)->atttypid == INT2OID && + TupleDescAttr(tupdesc, 5)->atttypid == INT2OID && + TupleDescAttr(tupdesc, 6)->atttypid == INT2OID); + values[3] = UInt16GetDatum(pageheader->pd_lower); + values[4] = UInt16GetDatum(pageheader->pd_upper); + values[5] = UInt16GetDatum(pageheader->pd_special); + values[6] = UInt16GetDatum(PageGetPageSize(page)); + break; + case INT4OID: + Assert(TupleDescAttr(tupdesc, 4)->atttypid == INT4OID && + TupleDescAttr(tupdesc, 5)->atttypid == INT4OID && + TupleDescAttr(tupdesc, 6)->atttypid == INT4OID); + values[3] = Int32GetDatum(pageheader->pd_lower); + values[4] = Int32GetDatum(pageheader->pd_upper); + values[5] = Int32GetDatum(pageheader->pd_special); + values[6] = Int32GetDatum(PageGetPageSize(page)); + break; + default: + elog(ERROR, "incorrect output types"); + break; + } + + values[7] = UInt16GetDatum(PageGetPageLayoutVersion(page)); + values[8] = TransactionIdGetDatum(pageheader->pd_prune_xid); + + /* Build and return the tuple. */ + + memset(nulls, 0, sizeof(nulls)); + + tuple = heap_form_tuple(tupdesc, values, nulls); + result = HeapTupleGetDatum(tuple); + + PG_RETURN_DATUM(result); +} + +/* + * page_checksum + * + * Compute checksum of a raw page + */ + +PG_FUNCTION_INFO_V1(page_checksum_1_9); +PG_FUNCTION_INFO_V1(page_checksum); + +static Datum +page_checksum_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + int64 blkno = (ext_version == PAGEINSPECT_V1_8 ? PG_GETARG_UINT32(1) : PG_GETARG_INT64(1)); + Page page; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions"))); + + if (blkno < 0 || blkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number"))); + + page = get_page_from_raw(raw_page); + + if (PageIsNew(page)) + PG_RETURN_NULL(); + + PG_RETURN_INT16(pg_checksum_page((char *) page, blkno)); +} + +Datum +page_checksum_1_9(PG_FUNCTION_ARGS) +{ + return page_checksum_internal(fcinfo, PAGEINSPECT_V1_9); +} + +/* + * Entry point for old extension version + */ +Datum +page_checksum(PG_FUNCTION_ARGS) +{ + return page_checksum_internal(fcinfo, PAGEINSPECT_V1_8); +} diff --git a/contrib/pageinspect/sql/brin.sql b/contrib/pageinspect/sql/brin.sql new file mode 100644 index 0000000..96b4645 --- /dev/null +++ b/contrib/pageinspect/sql/brin.sql @@ -0,0 +1,39 @@ +CREATE TABLE test1 (a int, b text); +INSERT INTO test1 VALUES (1, 'one'); +CREATE INDEX test1_a_idx ON test1 USING brin (a); + +SELECT brin_page_type(get_raw_page('test1_a_idx', 0)); +SELECT brin_page_type(get_raw_page('test1_a_idx', 1)); +SELECT brin_page_type(get_raw_page('test1_a_idx', 2)); + +SELECT * FROM brin_metapage_info(get_raw_page('test1_a_idx', 0)); +SELECT * FROM brin_metapage_info(get_raw_page('test1_a_idx', 1)); + +SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 0)) LIMIT 5; +SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 1)) LIMIT 5; + +SELECT * FROM brin_page_items(get_raw_page('test1_a_idx', 2), 'test1_a_idx') + ORDER BY blknum, attnum LIMIT 5; + +-- Mask DETAIL messages as these are not portable across architectures. +\set VERBOSITY terse + +-- Failures for non-BRIN index. +CREATE INDEX test1_a_btree ON test1 (a); +SELECT brin_page_items(get_raw_page('test1_a_btree', 0), 'test1_a_btree'); +SELECT brin_page_items(get_raw_page('test1_a_btree', 0), 'test1_a_idx'); + +-- Invalid special area size +SELECT brin_page_type(get_raw_page('test1', 0)); +SELECT * FROM brin_metapage_info(get_raw_page('test1', 0)); +SELECT * FROM brin_revmap_data(get_raw_page('test1', 0)); +\set VERBOSITY default + +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT brin_page_type(decode(repeat('00', :block_size), 'hex')); +SELECT brin_page_items(decode(repeat('00', :block_size), 'hex'), 'test1_a_idx'); +SELECT brin_metapage_info(decode(repeat('00', :block_size), 'hex')); +SELECT brin_revmap_data(decode(repeat('00', :block_size), 'hex')); + +DROP TABLE test1; diff --git a/contrib/pageinspect/sql/btree.sql b/contrib/pageinspect/sql/btree.sql new file mode 100644 index 0000000..102ebde --- /dev/null +++ b/contrib/pageinspect/sql/btree.sql @@ -0,0 +1,62 @@ +CREATE TABLE test1 (a int8, b int4range); +INSERT INTO test1 VALUES (72057594037927937, '[0,1)'); +CREATE INDEX test1_a_idx ON test1 USING btree (a); + +\x + +SELECT * FROM bt_metap('test1_a_idx'); + +SELECT * FROM bt_page_stats('test1_a_idx', -1); +SELECT * FROM bt_page_stats('test1_a_idx', 0); +SELECT * FROM bt_page_stats('test1_a_idx', 1); +SELECT * FROM bt_page_stats('test1_a_idx', 2); + +-- bt_multi_page_stats() function returns a set of records of page statistics. +CREATE TABLE test2 AS (SELECT generate_series(1, 1000)::int8 AS col1); +CREATE INDEX test2_col1_idx ON test2(col1); +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 0, 1); +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 1, -1); +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 1, 0); +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 1, 2); +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 3, 2); +SELECT * FROM bt_multi_page_stats('test2_col1_idx', 7, 2); +DROP TABLE test2; + +SELECT * FROM bt_page_items('test1_a_idx', -1); +SELECT * FROM bt_page_items('test1_a_idx', 0); +SELECT * FROM bt_page_items('test1_a_idx', 1); +SELECT * FROM bt_page_items('test1_a_idx', 2); + +SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', -1)); +SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 0)); +SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 1)); +SELECT * FROM bt_page_items(get_raw_page('test1_a_idx', 2)); + +-- Failure when using a non-btree index. +CREATE INDEX test1_a_hash ON test1 USING hash(a); +SELECT bt_metap('test1_a_hash'); +SELECT bt_page_stats('test1_a_hash', 0); +SELECT bt_page_items('test1_a_hash', 0); +SELECT bt_page_items(get_raw_page('test1_a_hash', 0)); +CREATE INDEX test1_b_gist ON test1 USING gist(b); +-- Special area of GiST is the same as btree, this complains about inconsistent +-- leaf data on the page. +SELECT bt_page_items(get_raw_page('test1_b_gist', 0)); + +-- Several failure modes. +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse +-- invalid page size +SELECT bt_page_items('aaa'::bytea); +-- invalid special area size +CREATE INDEX test1_a_brin ON test1 USING brin(a); +SELECT bt_page_items(get_raw_page('test1', 0)); +SELECT bt_page_items(get_raw_page('test1_a_brin', 0)); +\set VERBOSITY default + +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT bt_page_items(decode(repeat('00', :block_size), 'hex')); + +DROP TABLE test1; diff --git a/contrib/pageinspect/sql/checksum.sql b/contrib/pageinspect/sql/checksum.sql new file mode 100644 index 0000000..b877db0 --- /dev/null +++ b/contrib/pageinspect/sql/checksum.sql @@ -0,0 +1,31 @@ +-- +-- Verify correct calculation of checksums +-- +-- Postgres' checksum algorithm produces different answers on little-endian +-- and big-endian machines. The results of this test also vary depending +-- on the configured block size. This test has several different expected +-- results files to handle the following possibilities: +-- +-- BLCKSZ end file +-- 8K LE checksum.out +-- 8K BE checksum_1.out +-- +-- In future we might provide additional expected-results files for other +-- block sizes, but there seems little point as long as so many other +-- test scripts also show false failures for non-default block sizes. +-- + +-- This is to label the results files with blocksize: +SHOW block_size; + +SHOW block_size \gset + +-- Apply page_checksum() to some different data patterns and block numbers +SELECT blkno, + page_checksum(decode(repeat('01', :block_size), 'hex'), blkno) AS checksum_01, + page_checksum(decode(repeat('04', :block_size), 'hex'), blkno) AS checksum_04, + page_checksum(decode(repeat('ff', :block_size), 'hex'), blkno) AS checksum_ff, + page_checksum(decode(repeat('abcd', :block_size / 2), 'hex'), blkno) AS checksum_abcd, + page_checksum(decode(repeat('e6d6', :block_size / 2), 'hex'), blkno) AS checksum_e6d6, + page_checksum(decode(repeat('4a5e', :block_size / 2), 'hex'), blkno) AS checksum_4a5e + FROM generate_series(0, 100, 50) AS a (blkno); diff --git a/contrib/pageinspect/sql/gin.sql b/contrib/pageinspect/sql/gin.sql new file mode 100644 index 0000000..b57466d --- /dev/null +++ b/contrib/pageinspect/sql/gin.sql @@ -0,0 +1,41 @@ +CREATE TABLE test1 (x int, y int[]); +INSERT INTO test1 VALUES (1, ARRAY[11, 111]); +CREATE INDEX test1_y_idx ON test1 USING gin (y) WITH (fastupdate = off); + +\x + +SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 0)); +SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 1)); + +SELECT * FROM gin_page_opaque_info(get_raw_page('test1_y_idx', 1)); + +SELECT * FROM gin_leafpage_items(get_raw_page('test1_y_idx', 1)); + +INSERT INTO test1 SELECT x, ARRAY[1,10] FROM generate_series(2,10000) x; + +SELECT COUNT(*) > 0 +FROM gin_leafpage_items(get_raw_page('test1_y_idx', + (pg_relation_size('test1_y_idx') / + current_setting('block_size')::bigint)::int - 1)); + +-- Failure with various modes. +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse +-- invalid page size +SELECT gin_leafpage_items('aaa'::bytea); +SELECT gin_metapage_info('bbb'::bytea); +SELECT gin_page_opaque_info('ccc'::bytea); +-- invalid special area size +SELECT * FROM gin_metapage_info(get_raw_page('test1', 0)); +SELECT * FROM gin_page_opaque_info(get_raw_page('test1', 0)); +SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0)); +\set VERBOSITY default + +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex')); +SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex')); +SELECT gin_page_opaque_info(decode(repeat('00', :block_size), 'hex')); + +DROP TABLE test1; diff --git a/contrib/pageinspect/sql/gist.sql b/contrib/pageinspect/sql/gist.sql new file mode 100644 index 0000000..d263542 --- /dev/null +++ b/contrib/pageinspect/sql/gist.sql @@ -0,0 +1,69 @@ +-- The gist_page_opaque_info() function prints the page's LSN. Normally, +-- that's constant 1 (GistBuildLSN) on every page of a freshly built GiST +-- index. But with wal_level=minimal, the whole relation is dumped to WAL at +-- the end of the transaction if it's smaller than wal_skip_threshold, which +-- updates the LSNs. Wrap the tests on gist_page_opaque_info() in the +-- same transaction with the CREATE INDEX so that we see the LSNs before +-- they are possibly overwritten at end of transaction. +BEGIN; + +-- Create a test table and GiST index. +CREATE TABLE test_gist AS SELECT point(i,i) p, i::text t FROM + generate_series(1,1000) i; +CREATE INDEX test_gist_idx ON test_gist USING gist (p); + +-- Page 0 is the root, the rest are leaf pages +SELECT * FROM gist_page_opaque_info(get_raw_page('test_gist_idx', 0)); +SELECT * FROM gist_page_opaque_info(get_raw_page('test_gist_idx', 1)); +SELECT * FROM gist_page_opaque_info(get_raw_page('test_gist_idx', 2)); + +COMMIT; + +SELECT * FROM gist_page_items(get_raw_page('test_gist_idx', 0), 'test_gist_idx'); +SELECT * FROM gist_page_items(get_raw_page('test_gist_idx', 1), 'test_gist_idx') LIMIT 5; + +-- gist_page_items_bytea prints the raw key data as a bytea. The output of that is +-- platform-dependent (endianness), so omit the actual key data from the output. +SELECT itemoffset, ctid, itemlen FROM gist_page_items_bytea(get_raw_page('test_gist_idx', 0)); + +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse + +-- Failures with non-GiST index. +CREATE INDEX test_gist_btree on test_gist(t); +SELECT gist_page_items(get_raw_page('test_gist_btree', 0), 'test_gist_btree'); +SELECT gist_page_items(get_raw_page('test_gist_btree', 0), 'test_gist_idx'); + +-- Failure with various modes. +-- invalid page size +SELECT gist_page_items_bytea('aaa'::bytea); +SELECT gist_page_items('aaa'::bytea, 'test_gist_idx'::regclass); +SELECT gist_page_opaque_info('aaa'::bytea); +-- invalid special area size +SELECT * FROM gist_page_opaque_info(get_raw_page('test_gist', 0)); +SELECT gist_page_items_bytea(get_raw_page('test_gist', 0)); +SELECT gist_page_items_bytea(get_raw_page('test_gist_btree', 0)); +\set VERBOSITY default + +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT gist_page_items_bytea(decode(repeat('00', :block_size), 'hex')); +SELECT gist_page_items(decode(repeat('00', :block_size), 'hex'), 'test_gist_idx'::regclass); +SELECT gist_page_opaque_info(decode(repeat('00', :block_size), 'hex')); + +-- Test gist_page_items with included columns. +-- Non-leaf pages contain only the key attributes, and leaf pages contain +-- the included attributes. +ALTER TABLE test_gist ADD COLUMN i int DEFAULT NULL; +CREATE INDEX test_gist_idx_inc ON test_gist + USING gist (p) INCLUDE (t, i); +-- Mask the value of the key attribute to avoid alignment issues. +SELECT regexp_replace(keys, '\(p\)=\("(.*?)"\)', '(p)=("")') AS keys_nonleaf_1 + FROM gist_page_items(get_raw_page('test_gist_idx_inc', 0), 'test_gist_idx_inc') + WHERE itemoffset = 1; +SELECT keys AS keys_leaf_1 + FROM gist_page_items(get_raw_page('test_gist_idx_inc', 1), 'test_gist_idx_inc') + WHERE itemoffset = 1; + +DROP TABLE test_gist; diff --git a/contrib/pageinspect/sql/hash.sql b/contrib/pageinspect/sql/hash.sql new file mode 100644 index 0000000..e4b9e97 --- /dev/null +++ b/contrib/pageinspect/sql/hash.sql @@ -0,0 +1,113 @@ +CREATE TABLE test_hash (a int, b text); +INSERT INTO test_hash VALUES (1, 'one'); +CREATE INDEX test_hash_a_idx ON test_hash USING hash (a); + +CREATE TABLE test_hash_part (a int, b int) PARTITION BY RANGE (a); +CREATE INDEX test_hash_part_idx ON test_hash_part USING hash(b); + +\x + +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 0)); +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 1)); +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 2)); +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 3)); +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 4)); +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 5)); +SELECT hash_page_type(get_raw_page('test_hash_a_idx', 6)); + + +SELECT * FROM hash_bitmap_info('test_hash_a_idx', -1); +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 0); +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 1); +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 2); +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 3); +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 4); +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 5); +SELECT * FROM hash_bitmap_info('test_hash_a_idx', 6); +SELECT * FROM hash_bitmap_info('test_hash_part_idx', 1); -- error + + +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 0)); + +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 1)); + +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 2)); + +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 3)); + +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 4)); + +SELECT magic, version, ntuples, bsize, bmsize, bmshift, maxbucket, highmask, +lowmask, ovflpoint, firstfree, nmaps, procid, spares, mapp FROM +hash_metapage_info(get_raw_page('test_hash_a_idx', 5)); + +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 0)); + +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 1)); + +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 2)); + +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 3)); + +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 4)); + +SELECT live_items, dead_items, page_size, hasho_prevblkno, hasho_nextblkno, +hasho_bucket, hasho_flag, hasho_page_id FROM +hash_page_stats(get_raw_page('test_hash_a_idx', 5)); + +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 0)); +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 1)); +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 2)); +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 3)); +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 4)); +SELECT * FROM hash_page_items(get_raw_page('test_hash_a_idx', 5)); + +-- Failure with non-hash index +CREATE INDEX test_hash_a_btree ON test_hash USING btree (a); +SELECT hash_bitmap_info('test_hash_a_btree', 0); + +-- Failure with various modes. +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse +-- invalid page size +SELECT hash_metapage_info('aaa'::bytea); +SELECT hash_page_items('bbb'::bytea); +SELECT hash_page_stats('ccc'::bytea); +SELECT hash_page_type('ddd'::bytea); +-- invalid special area size +SELECT hash_metapage_info(get_raw_page('test_hash', 0)); +SELECT hash_page_items(get_raw_page('test_hash', 0)); +SELECT hash_page_stats(get_raw_page('test_hash', 0)); +SELECT hash_page_type(get_raw_page('test_hash', 0)); +\set VERBOSITY default + +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT hash_metapage_info(decode(repeat('00', :block_size), 'hex')); +SELECT hash_page_items(decode(repeat('00', :block_size), 'hex')); +SELECT hash_page_stats(decode(repeat('00', :block_size), 'hex')); +SELECT hash_page_type(decode(repeat('00', :block_size), 'hex')); + +DROP TABLE test_hash; +DROP TABLE test_hash_part; diff --git a/contrib/pageinspect/sql/oldextversions.sql b/contrib/pageinspect/sql/oldextversions.sql new file mode 100644 index 0000000..9f95349 --- /dev/null +++ b/contrib/pageinspect/sql/oldextversions.sql @@ -0,0 +1,26 @@ +-- test old extension version entry points + +DROP EXTENSION pageinspect; +CREATE EXTENSION pageinspect VERSION '1.8'; + +CREATE TABLE test1 (a int8, b text); +INSERT INTO test1 VALUES (72057594037927937, 'text'); +CREATE INDEX test1_a_idx ON test1 USING btree (a); + +-- from page.sql +SELECT octet_length(get_raw_page('test1', 0)) AS main_0; +SELECT octet_length(get_raw_page('test1', 'main', 0)) AS main_0; +SELECT page_checksum(get_raw_page('test1', 0), 0) IS NOT NULL AS silly_checksum_test; + +-- from btree.sql +SELECT * FROM bt_page_stats('test1_a_idx', 1); +SELECT * FROM bt_page_items('test1_a_idx', 1); + +-- page_header() uses int instead of smallint for lower, upper, special and +-- pagesize in pageinspect >= 1.10. +ALTER EXTENSION pageinspect UPDATE TO '1.9'; +\df page_header +SELECT pagesize, version FROM page_header(get_raw_page('test1', 0)); + +DROP TABLE test1; +DROP EXTENSION pageinspect; diff --git a/contrib/pageinspect/sql/page.sql b/contrib/pageinspect/sql/page.sql new file mode 100644 index 0000000..5bff568 --- /dev/null +++ b/contrib/pageinspect/sql/page.sql @@ -0,0 +1,100 @@ +CREATE EXTENSION pageinspect; + +-- Use a temp table so that effects of VACUUM are predictable +CREATE TEMP TABLE test1 (a int, b int); +INSERT INTO test1 VALUES (16777217, 131584); + +VACUUM (DISABLE_PAGE_SKIPPING) test1; -- set up FSM + +-- The page contents can vary, so just test that it can be read +-- successfully, but don't keep the output. + +SELECT octet_length(get_raw_page('test1', 'main', 0)) AS main_0; +SELECT octet_length(get_raw_page('test1', 'main', 1)) AS main_1; + +SELECT octet_length(get_raw_page('test1', 'fsm', 0)) AS fsm_0; +SELECT octet_length(get_raw_page('test1', 'fsm', 1)) AS fsm_1; + +SELECT octet_length(get_raw_page('test1', 'vm', 0)) AS vm_0; +SELECT octet_length(get_raw_page('test1', 'vm', 1)) AS vm_1; + +SELECT octet_length(get_raw_page('test1', 'main', -1)); +SELECT octet_length(get_raw_page('xxx', 'main', 0)); +SELECT octet_length(get_raw_page('test1', 'xxx', 0)); + +SELECT get_raw_page('test1', 0) = get_raw_page('test1', 'main', 0); + +SELECT pagesize, version FROM page_header(get_raw_page('test1', 0)); + +SELECT page_checksum(get_raw_page('test1', 0), 0) IS NOT NULL AS silly_checksum_test; +SELECT page_checksum(get_raw_page('test1', 0), -1); + +SELECT tuple_data_split('test1'::regclass, t_data, t_infomask, t_infomask2, t_bits) + FROM heap_page_items(get_raw_page('test1', 0)); + +SELECT * FROM fsm_page_contents(get_raw_page('test1', 'fsm', 0)); + +-- If we freeze the only tuple on test1, the infomask should +-- always be the same in all test runs. +VACUUM (FREEZE, DISABLE_PAGE_SKIPPING) test1; + +SELECT t_infomask, t_infomask2, raw_flags, combined_flags +FROM heap_page_items(get_raw_page('test1', 0)), + LATERAL heap_tuple_infomask_flags(t_infomask, t_infomask2); + +-- tests for decoding of combined flags +-- HEAP_XMAX_SHR_LOCK = (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK) +SELECT * FROM heap_tuple_infomask_flags(x'0050'::int, 0); +-- HEAP_XMIN_FROZEN = (HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID) +SELECT * FROM heap_tuple_infomask_flags(x'0300'::int, 0); +-- HEAP_MOVED = (HEAP_MOVED_IN | HEAP_MOVED_OFF) +SELECT * FROM heap_tuple_infomask_flags(x'C000'::int, 0); +SELECT * FROM heap_tuple_infomask_flags(x'C000'::int, 0); + +-- test all flags of t_infomask and t_infomask2 +SELECT unnest(raw_flags) + FROM heap_tuple_infomask_flags(x'FFFF'::int, x'FFFF'::int) ORDER BY 1; +SELECT unnest(combined_flags) + FROM heap_tuple_infomask_flags(x'FFFF'::int, x'FFFF'::int) ORDER BY 1; + +-- no flags at all +SELECT * FROM heap_tuple_infomask_flags(0, 0); +-- no combined flags +SELECT * FROM heap_tuple_infomask_flags(x'0010'::int, 0); + +DROP TABLE test1; + +-- check that using any of these functions with a partitioned table or index +-- would fail +create table test_partitioned (a int) partition by range (a); +create index test_partitioned_index on test_partitioned (a); +select get_raw_page('test_partitioned', 0); -- error about partitioned table +select get_raw_page('test_partitioned_index', 0); -- error about partitioned index + +-- a regular table which is a member of a partition set should work though +create table test_part1 partition of test_partitioned for values from ( 1 ) to (100); +select get_raw_page('test_part1', 0); -- get farther and error about empty table +drop table test_partitioned; + +-- check null bitmap alignment for table whose number of attributes is multiple of 8 +create table test8 (f1 int, f2 int, f3 int, f4 int, f5 int, f6 int, f7 int, f8 int); +insert into test8(f1, f8) values (x'7f00007f'::int, 0); +select t_bits, t_data from heap_page_items(get_raw_page('test8', 0)); +select tuple_data_split('test8'::regclass, t_data, t_infomask, t_infomask2, t_bits) + from heap_page_items(get_raw_page('test8', 0)); +drop table test8; + +-- Failure with incorrect page size +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes. +\set VERBOSITY terse +SELECT fsm_page_contents('aaa'::bytea); +SELECT page_checksum('bbb'::bytea, 0); +SELECT page_header('ccc'::bytea); +\set VERBOSITY default + +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT fsm_page_contents(decode(repeat('00', :block_size), 'hex')); +SELECT page_header(decode(repeat('00', :block_size), 'hex')); +SELECT page_checksum(decode(repeat('00', :block_size), 'hex'), 1); diff --git a/contrib/passwordcheck/.gitignore b/contrib/passwordcheck/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/passwordcheck/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/passwordcheck/Makefile b/contrib/passwordcheck/Makefile new file mode 100644 index 0000000..006735a --- /dev/null +++ b/contrib/passwordcheck/Makefile @@ -0,0 +1,24 @@ +# contrib/passwordcheck/Makefile + +MODULE_big = passwordcheck +OBJS = \ + $(WIN32RES) \ + passwordcheck.o +PGFILEDESC = "passwordcheck - strengthen user password checks" + +# uncomment the following two lines to enable cracklib support +# PG_CPPFLAGS = -DUSE_CRACKLIB '-DCRACKLIB_DICTPATH="/usr/lib/cracklib_dict"' +# SHLIB_LINK = -lcrack + +REGRESS = passwordcheck + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/passwordcheck +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/passwordcheck/expected/passwordcheck.out b/contrib/passwordcheck/expected/passwordcheck.out new file mode 100644 index 0000000..2027681 --- /dev/null +++ b/contrib/passwordcheck/expected/passwordcheck.out @@ -0,0 +1,19 @@ +LOAD 'passwordcheck'; +CREATE USER regress_passwordcheck_user1; +-- ok +ALTER USER regress_passwordcheck_user1 PASSWORD 'a_nice_long_password'; +-- error: too short +ALTER USER regress_passwordcheck_user1 PASSWORD 'tooshrt'; +ERROR: password is too short +-- error: contains user name +ALTER USER regress_passwordcheck_user1 PASSWORD 'xyzregress_passwordcheck_user1'; +ERROR: password must not contain user name +-- error: contains only letters +ALTER USER regress_passwordcheck_user1 PASSWORD 'alessnicelongpassword'; +ERROR: password must contain both letters and nonletters +-- encrypted ok (password is "secret") +ALTER USER regress_passwordcheck_user1 PASSWORD 'md592350e12ac34e52dd598f90893bb3ae7'; +-- error: password is user name +ALTER USER regress_passwordcheck_user1 PASSWORD 'md507a112732ed9f2087fa90b192d44e358'; +ERROR: password must not equal user name +DROP USER regress_passwordcheck_user1; diff --git a/contrib/passwordcheck/meson.build b/contrib/passwordcheck/meson.build new file mode 100644 index 0000000..8bde1de --- /dev/null +++ b/contrib/passwordcheck/meson.build @@ -0,0 +1,38 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +passwordcheck_sources = files( + 'passwordcheck.c', +) + +passwordcheck_c_args = [] +passwordcheck_deps = [] + +# uncomment the following two lines to enable cracklib support +# passwordcheck_c_args += ['-DUSE_CRACKLIB', '-DCRACKLIB_DICTPATH="/usr/lib/cracklib_dict"'] +# passwordcheck_deps += [cc.find_library('crack')] + +if host_system == 'windows' + passwordcheck_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'passwordcheck', + '--FILEDESC', 'passwordcheck - strengthen user password checks',]) +endif + +passwordcheck = shared_module('passwordcheck', + passwordcheck_sources, + c_args: passwordcheck_c_args, + kwargs: contrib_mod_args + { + 'dependencies': contrib_mod_args.get('dependencies') + passwordcheck_deps, + } +) +contrib_targets += passwordcheck + +tests += { + 'name': 'passwordcheck', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'passwordcheck', + ], + }, +} diff --git a/contrib/passwordcheck/passwordcheck.c b/contrib/passwordcheck/passwordcheck.c new file mode 100644 index 0000000..ae4a0ab --- /dev/null +++ b/contrib/passwordcheck/passwordcheck.c @@ -0,0 +1,148 @@ +/*------------------------------------------------------------------------- + * + * passwordcheck.c + * + * + * Copyright (c) 2009-2023, PostgreSQL Global Development Group + * + * Author: Laurenz Albe + * + * IDENTIFICATION + * contrib/passwordcheck/passwordcheck.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#ifdef USE_CRACKLIB +#include +#endif + +#include "commands/user.h" +#include "fmgr.h" +#include "libpq/crypt.h" + +PG_MODULE_MAGIC; + +/* Saved hook value in case of unload */ +static check_password_hook_type prev_check_password_hook = NULL; + +/* passwords shorter than this will be rejected */ +#define MIN_PWD_LENGTH 8 + +/* + * check_password + * + * performs checks on an encrypted or unencrypted password + * ereport's if not acceptable + * + * username: name of role being created or changed + * password: new password (possibly already encrypted) + * password_type: PASSWORD_TYPE_* code, to indicate if the password is + * in plaintext or encrypted form. + * validuntil_time: password expiration time, as a timestamptz Datum + * validuntil_null: true if password expiration time is NULL + * + * This sample implementation doesn't pay any attention to the password + * expiration time, but you might wish to insist that it be non-null and + * not too far in the future. + */ +static void +check_password(const char *username, + const char *shadow_pass, + PasswordType password_type, + Datum validuntil_time, + bool validuntil_null) +{ + if (prev_check_password_hook) + prev_check_password_hook(username, shadow_pass, + password_type, validuntil_time, + validuntil_null); + + if (password_type != PASSWORD_TYPE_PLAINTEXT) + { + /* + * Unfortunately we cannot perform exhaustive checks on encrypted + * passwords - we are restricted to guessing. (Alternatively, we could + * insist on the password being presented non-encrypted, but that has + * its own security disadvantages.) + * + * We only check for username = password. + */ + const char *logdetail = NULL; + + if (plain_crypt_verify(username, shadow_pass, username, &logdetail) == STATUS_OK) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must not equal user name"))); + } + else + { + /* + * For unencrypted passwords we can perform better checks + */ + const char *password = shadow_pass; + int pwdlen = strlen(password); + int i; + bool pwd_has_letter, + pwd_has_nonletter; +#ifdef USE_CRACKLIB + const char *reason; +#endif + + /* enforce minimum length */ + if (pwdlen < MIN_PWD_LENGTH) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password is too short"))); + + /* check if the password contains the username */ + if (strstr(password, username)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must not contain user name"))); + + /* check if the password contains both letters and non-letters */ + pwd_has_letter = false; + pwd_has_nonletter = false; + for (i = 0; i < pwdlen; i++) + { + /* + * isalpha() does not work for multibyte encodings but let's + * consider non-ASCII characters non-letters + */ + if (isalpha((unsigned char) password[i])) + pwd_has_letter = true; + else + pwd_has_nonletter = true; + } + if (!pwd_has_letter || !pwd_has_nonletter) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password must contain both letters and nonletters"))); + +#ifdef USE_CRACKLIB + /* call cracklib to check password */ + if ((reason = FascistCheck(password, CRACKLIB_DICTPATH))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("password is easily cracked"), + errdetail_log("cracklib diagnostic: %s", reason))); +#endif + } + + /* all checks passed, password is ok */ +} + +/* + * Module initialization function + */ +void +_PG_init(void) +{ + /* activate password checks when the module is loaded */ + prev_check_password_hook = check_password_hook; + check_password_hook = check_password; +} diff --git a/contrib/passwordcheck/sql/passwordcheck.sql b/contrib/passwordcheck/sql/passwordcheck.sql new file mode 100644 index 0000000..1fbd6b0 --- /dev/null +++ b/contrib/passwordcheck/sql/passwordcheck.sql @@ -0,0 +1,23 @@ +LOAD 'passwordcheck'; + +CREATE USER regress_passwordcheck_user1; + +-- ok +ALTER USER regress_passwordcheck_user1 PASSWORD 'a_nice_long_password'; + +-- error: too short +ALTER USER regress_passwordcheck_user1 PASSWORD 'tooshrt'; + +-- error: contains user name +ALTER USER regress_passwordcheck_user1 PASSWORD 'xyzregress_passwordcheck_user1'; + +-- error: contains only letters +ALTER USER regress_passwordcheck_user1 PASSWORD 'alessnicelongpassword'; + +-- encrypted ok (password is "secret") +ALTER USER regress_passwordcheck_user1 PASSWORD 'md592350e12ac34e52dd598f90893bb3ae7'; + +-- error: password is user name +ALTER USER regress_passwordcheck_user1 PASSWORD 'md507a112732ed9f2087fa90b192d44e358'; + +DROP USER regress_passwordcheck_user1; diff --git a/contrib/pg_buffercache/.gitignore b/contrib/pg_buffercache/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pg_buffercache/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pg_buffercache/Makefile b/contrib/pg_buffercache/Makefile new file mode 100644 index 0000000..d6b58d4 --- /dev/null +++ b/contrib/pg_buffercache/Makefile @@ -0,0 +1,25 @@ +# contrib/pg_buffercache/Makefile + +MODULE_big = pg_buffercache +OBJS = \ + $(WIN32RES) \ + pg_buffercache_pages.o + +EXTENSION = pg_buffercache +DATA = pg_buffercache--1.2.sql pg_buffercache--1.2--1.3.sql \ + pg_buffercache--1.1--1.2.sql pg_buffercache--1.0--1.1.sql \ + pg_buffercache--1.3--1.4.sql +PGFILEDESC = "pg_buffercache - monitoring of shared buffer cache in real-time" + +REGRESS = pg_buffercache + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pg_buffercache +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pg_buffercache/expected/pg_buffercache.out b/contrib/pg_buffercache/expected/pg_buffercache.out new file mode 100644 index 0000000..b745dc6 --- /dev/null +++ b/contrib/pg_buffercache/expected/pg_buffercache.out @@ -0,0 +1,57 @@ +CREATE EXTENSION pg_buffercache; +select count(*) = (select setting::bigint + from pg_settings + where name = 'shared_buffers') +from pg_buffercache; + ?column? +---------- + t +(1 row) + +select buffers_used + buffers_unused > 0, + buffers_dirty <= buffers_used, + buffers_pinned <= buffers_used +from pg_buffercache_summary(); + ?column? | ?column? | ?column? +----------+----------+---------- + t | t | t +(1 row) + +SELECT count(*) > 0 FROM pg_buffercache_usage_counts() WHERE buffers >= 0; + ?column? +---------- + t +(1 row) + +-- Check that the functions / views can't be accessed by default. To avoid +-- having to create a dedicated user, use the pg_database_owner pseudo-role. +SET ROLE pg_database_owner; +SELECT * FROM pg_buffercache; +ERROR: permission denied for view pg_buffercache +SELECT * FROM pg_buffercache_pages() AS p (wrong int); +ERROR: permission denied for function pg_buffercache_pages +SELECT * FROM pg_buffercache_summary(); +ERROR: permission denied for function pg_buffercache_summary +SELECT * FROM pg_buffercache_usage_counts(); +ERROR: permission denied for function pg_buffercache_usage_counts +RESET role; +-- Check that pg_monitor is allowed to query view / function +SET ROLE pg_monitor; +SELECT count(*) > 0 FROM pg_buffercache; + ?column? +---------- + t +(1 row) + +SELECT buffers_used + buffers_unused > 0 FROM pg_buffercache_summary(); + ?column? +---------- + t +(1 row) + +SELECT count(*) > 0 FROM pg_buffercache_usage_counts(); + ?column? +---------- + t +(1 row) + diff --git a/contrib/pg_buffercache/meson.build b/contrib/pg_buffercache/meson.build new file mode 100644 index 0000000..c51edf3 --- /dev/null +++ b/contrib/pg_buffercache/meson.build @@ -0,0 +1,38 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pg_buffercache_sources = files( + 'pg_buffercache_pages.c', +) + +if host_system == 'windows' + pg_buffercache_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pg_buffercache', + '--FILEDESC', 'pg_buffercache - monitoring of shared buffer cache in real-time',]) +endif + +pg_buffercache = shared_module('pg_buffercache', + pg_buffercache_sources, + kwargs: contrib_mod_args, +) +contrib_targets += pg_buffercache + +install_data( + 'pg_buffercache--1.0--1.1.sql', + 'pg_buffercache--1.1--1.2.sql', + 'pg_buffercache--1.2--1.3.sql', + 'pg_buffercache--1.2.sql', + 'pg_buffercache--1.3--1.4.sql', + 'pg_buffercache.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'pg_buffercache', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'pg_buffercache', + ], + }, +} diff --git a/contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql b/contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql new file mode 100644 index 0000000..54d02f5 --- /dev/null +++ b/contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql @@ -0,0 +1,11 @@ +/* contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.1'" to load this file. \quit + +-- Upgrade view to 1.1. format +CREATE OR REPLACE VIEW pg_buffercache AS + SELECT P.* FROM pg_buffercache_pages() AS P + (bufferid integer, relfilenode oid, reltablespace oid, reldatabase oid, + relforknumber int2, relblocknumber int8, isdirty bool, usagecount int2, + pinning_backends int4); diff --git a/contrib/pg_buffercache/pg_buffercache--1.1--1.2.sql b/contrib/pg_buffercache/pg_buffercache--1.1--1.2.sql new file mode 100644 index 0000000..5997aad --- /dev/null +++ b/contrib/pg_buffercache/pg_buffercache--1.1--1.2.sql @@ -0,0 +1,6 @@ +/* contrib/pg_buffercache/pg_buffercache--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.2'" to load this file. \quit + +ALTER FUNCTION pg_buffercache_pages() PARALLEL SAFE; diff --git a/contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql b/contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql new file mode 100644 index 0000000..b37ef01 --- /dev/null +++ b/contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql @@ -0,0 +1,7 @@ +/* contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.3'" to load this file. \quit + +GRANT EXECUTE ON FUNCTION pg_buffercache_pages() TO pg_monitor; +GRANT SELECT ON pg_buffercache TO pg_monitor; diff --git a/contrib/pg_buffercache/pg_buffercache--1.2.sql b/contrib/pg_buffercache/pg_buffercache--1.2.sql new file mode 100644 index 0000000..6ee5d84 --- /dev/null +++ b/contrib/pg_buffercache/pg_buffercache--1.2.sql @@ -0,0 +1,21 @@ +/* contrib/pg_buffercache/pg_buffercache--1.2.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_buffercache" to load this file. \quit + +-- Register the function. +CREATE FUNCTION pg_buffercache_pages() +RETURNS SETOF RECORD +AS 'MODULE_PATHNAME', 'pg_buffercache_pages' +LANGUAGE C PARALLEL SAFE; + +-- Create a view for convenient access. +CREATE VIEW pg_buffercache AS + SELECT P.* FROM pg_buffercache_pages() AS P + (bufferid integer, relfilenode oid, reltablespace oid, reldatabase oid, + relforknumber int2, relblocknumber int8, isdirty bool, usagecount int2, + pinning_backends int4); + +-- Don't want these to be available to public. +REVOKE ALL ON FUNCTION pg_buffercache_pages() FROM PUBLIC; +REVOKE ALL ON pg_buffercache FROM PUBLIC; diff --git a/contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql b/contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql new file mode 100644 index 0000000..d5aebf3 --- /dev/null +++ b/contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql @@ -0,0 +1,28 @@ +/* contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.4'" to load this file. \quit + +CREATE FUNCTION pg_buffercache_summary( + OUT buffers_used int4, + OUT buffers_unused int4, + OUT buffers_dirty int4, + OUT buffers_pinned int4, + OUT usagecount_avg float8) +AS 'MODULE_PATHNAME', 'pg_buffercache_summary' +LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION pg_buffercache_usage_counts( + OUT usage_count int4, + OUT buffers int4, + OUT dirty int4, + OUT pinned int4) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_buffercache_usage_counts' +LANGUAGE C PARALLEL SAFE; + +-- Don't want these to be available to public. +REVOKE ALL ON FUNCTION pg_buffercache_summary() FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_buffercache_summary() TO pg_monitor; +REVOKE ALL ON FUNCTION pg_buffercache_usage_counts() FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_buffercache_usage_counts() TO pg_monitor; diff --git a/contrib/pg_buffercache/pg_buffercache.control b/contrib/pg_buffercache/pg_buffercache.control new file mode 100644 index 0000000..a82ae5f --- /dev/null +++ b/contrib/pg_buffercache/pg_buffercache.control @@ -0,0 +1,5 @@ +# pg_buffercache extension +comment = 'examine the shared buffer cache' +default_version = '1.4' +module_pathname = '$libdir/pg_buffercache' +relocatable = true diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c new file mode 100644 index 0000000..3316732 --- /dev/null +++ b/contrib/pg_buffercache/pg_buffercache_pages.c @@ -0,0 +1,349 @@ +/*------------------------------------------------------------------------- + * + * pg_buffercache_pages.c + * display some contents of the buffer cache + * + * contrib/pg_buffercache/pg_buffercache_pages.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "storage/buf_internals.h" +#include "storage/bufmgr.h" + + +#define NUM_BUFFERCACHE_PAGES_MIN_ELEM 8 +#define NUM_BUFFERCACHE_PAGES_ELEM 9 +#define NUM_BUFFERCACHE_SUMMARY_ELEM 5 +#define NUM_BUFFERCACHE_USAGE_COUNTS_ELEM 4 + +PG_MODULE_MAGIC; + +/* + * Record structure holding the to be exposed cache data. + */ +typedef struct +{ + uint32 bufferid; + RelFileNumber relfilenumber; + Oid reltablespace; + Oid reldatabase; + ForkNumber forknum; + BlockNumber blocknum; + bool isvalid; + bool isdirty; + uint16 usagecount; + + /* + * An int32 is sufficiently large, as MAX_BACKENDS prevents a buffer from + * being pinned by too many backends and each backend will only pin once + * because of bufmgr.c's PrivateRefCount infrastructure. + */ + int32 pinning_backends; +} BufferCachePagesRec; + + +/* + * Function context for data persisting over repeated calls. + */ +typedef struct +{ + TupleDesc tupdesc; + BufferCachePagesRec *record; +} BufferCachePagesContext; + + +/* + * Function returning data from the shared buffer cache - buffer number, + * relation node/tablespace/database/blocknum and dirty indicator. + */ +PG_FUNCTION_INFO_V1(pg_buffercache_pages); +PG_FUNCTION_INFO_V1(pg_buffercache_summary); +PG_FUNCTION_INFO_V1(pg_buffercache_usage_counts); + +Datum +pg_buffercache_pages(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + Datum result; + MemoryContext oldcontext; + BufferCachePagesContext *fctx; /* User function context. */ + TupleDesc tupledesc; + TupleDesc expected_tupledesc; + HeapTuple tuple; + + if (SRF_IS_FIRSTCALL()) + { + int i; + + funcctx = SRF_FIRSTCALL_INIT(); + + /* Switch context when allocating stuff to be used in later calls */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* Create a user function context for cross-call persistence */ + fctx = (BufferCachePagesContext *) palloc(sizeof(BufferCachePagesContext)); + + /* + * To smoothly support upgrades from version 1.0 of this extension + * transparently handle the (non-)existence of the pinning_backends + * column. We unfortunately have to get the result type for that... - + * we can't use the result type determined by the function definition + * without potentially crashing when somebody uses the old (or even + * wrong) function definition though. + */ + if (get_call_result_type(fcinfo, NULL, &expected_tupledesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (expected_tupledesc->natts < NUM_BUFFERCACHE_PAGES_MIN_ELEM || + expected_tupledesc->natts > NUM_BUFFERCACHE_PAGES_ELEM) + elog(ERROR, "incorrect number of output arguments"); + + /* Construct a tuple descriptor for the result rows. */ + tupledesc = CreateTemplateTupleDesc(expected_tupledesc->natts); + TupleDescInitEntry(tupledesc, (AttrNumber) 1, "bufferid", + INT4OID, -1, 0); + TupleDescInitEntry(tupledesc, (AttrNumber) 2, "relfilenode", + OIDOID, -1, 0); + TupleDescInitEntry(tupledesc, (AttrNumber) 3, "reltablespace", + OIDOID, -1, 0); + TupleDescInitEntry(tupledesc, (AttrNumber) 4, "reldatabase", + OIDOID, -1, 0); + TupleDescInitEntry(tupledesc, (AttrNumber) 5, "relforknumber", + INT2OID, -1, 0); + TupleDescInitEntry(tupledesc, (AttrNumber) 6, "relblocknumber", + INT8OID, -1, 0); + TupleDescInitEntry(tupledesc, (AttrNumber) 7, "isdirty", + BOOLOID, -1, 0); + TupleDescInitEntry(tupledesc, (AttrNumber) 8, "usage_count", + INT2OID, -1, 0); + + if (expected_tupledesc->natts == NUM_BUFFERCACHE_PAGES_ELEM) + TupleDescInitEntry(tupledesc, (AttrNumber) 9, "pinning_backends", + INT4OID, -1, 0); + + fctx->tupdesc = BlessTupleDesc(tupledesc); + + /* Allocate NBuffers worth of BufferCachePagesRec records. */ + fctx->record = (BufferCachePagesRec *) + MemoryContextAllocHuge(CurrentMemoryContext, + sizeof(BufferCachePagesRec) * NBuffers); + + /* Set max calls and remember the user function context. */ + funcctx->max_calls = NBuffers; + funcctx->user_fctx = fctx; + + /* Return to original context when allocating transient memory */ + MemoryContextSwitchTo(oldcontext); + + /* + * Scan through all the buffers, saving the relevant fields in the + * fctx->record structure. + * + * We don't hold the partition locks, so we don't get a consistent + * snapshot across all buffers, but we do grab the buffer header + * locks, so the information of each buffer is self-consistent. + */ + for (i = 0; i < NBuffers; i++) + { + BufferDesc *bufHdr; + uint32 buf_state; + + bufHdr = GetBufferDescriptor(i); + /* Lock each buffer header before inspecting. */ + buf_state = LockBufHdr(bufHdr); + + fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr); + fctx->record[i].relfilenumber = BufTagGetRelNumber(&bufHdr->tag); + fctx->record[i].reltablespace = bufHdr->tag.spcOid; + fctx->record[i].reldatabase = bufHdr->tag.dbOid; + fctx->record[i].forknum = BufTagGetForkNum(&bufHdr->tag); + fctx->record[i].blocknum = bufHdr->tag.blockNum; + fctx->record[i].usagecount = BUF_STATE_GET_USAGECOUNT(buf_state); + fctx->record[i].pinning_backends = BUF_STATE_GET_REFCOUNT(buf_state); + + if (buf_state & BM_DIRTY) + fctx->record[i].isdirty = true; + else + fctx->record[i].isdirty = false; + + /* Note if the buffer is valid, and has storage created */ + if ((buf_state & BM_VALID) && (buf_state & BM_TAG_VALID)) + fctx->record[i].isvalid = true; + else + fctx->record[i].isvalid = false; + + UnlockBufHdr(bufHdr, buf_state); + } + } + + funcctx = SRF_PERCALL_SETUP(); + + /* Get the saved state */ + fctx = funcctx->user_fctx; + + if (funcctx->call_cntr < funcctx->max_calls) + { + uint32 i = funcctx->call_cntr; + Datum values[NUM_BUFFERCACHE_PAGES_ELEM]; + bool nulls[NUM_BUFFERCACHE_PAGES_ELEM]; + + values[0] = Int32GetDatum(fctx->record[i].bufferid); + nulls[0] = false; + + /* + * Set all fields except the bufferid to null if the buffer is unused + * or not valid. + */ + if (fctx->record[i].blocknum == InvalidBlockNumber || + fctx->record[i].isvalid == false) + { + nulls[1] = true; + nulls[2] = true; + nulls[3] = true; + nulls[4] = true; + nulls[5] = true; + nulls[6] = true; + nulls[7] = true; + /* unused for v1.0 callers, but the array is always long enough */ + nulls[8] = true; + } + else + { + values[1] = ObjectIdGetDatum(fctx->record[i].relfilenumber); + nulls[1] = false; + values[2] = ObjectIdGetDatum(fctx->record[i].reltablespace); + nulls[2] = false; + values[3] = ObjectIdGetDatum(fctx->record[i].reldatabase); + nulls[3] = false; + values[4] = ObjectIdGetDatum(fctx->record[i].forknum); + nulls[4] = false; + values[5] = Int64GetDatum((int64) fctx->record[i].blocknum); + nulls[5] = false; + values[6] = BoolGetDatum(fctx->record[i].isdirty); + nulls[6] = false; + values[7] = Int16GetDatum(fctx->record[i].usagecount); + nulls[7] = false; + /* unused for v1.0 callers, but the array is always long enough */ + values[8] = Int32GetDatum(fctx->record[i].pinning_backends); + nulls[8] = false; + } + + /* Build and return the tuple. */ + tuple = heap_form_tuple(fctx->tupdesc, values, nulls); + result = HeapTupleGetDatum(tuple); + + SRF_RETURN_NEXT(funcctx, result); + } + else + SRF_RETURN_DONE(funcctx); +} + +Datum +pg_buffercache_summary(PG_FUNCTION_ARGS) +{ + Datum result; + TupleDesc tupledesc; + HeapTuple tuple; + Datum values[NUM_BUFFERCACHE_SUMMARY_ELEM]; + bool nulls[NUM_BUFFERCACHE_SUMMARY_ELEM]; + + int32 buffers_used = 0; + int32 buffers_unused = 0; + int32 buffers_dirty = 0; + int32 buffers_pinned = 0; + int64 usagecount_total = 0; + + if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + for (int i = 0; i < NBuffers; i++) + { + BufferDesc *bufHdr; + uint32 buf_state; + + /* + * This function summarizes the state of all headers. Locking the + * buffer headers wouldn't provide an improved result as the state of + * the buffer can still change after we release the lock and it'd + * noticeably increase the cost of the function. + */ + bufHdr = GetBufferDescriptor(i); + buf_state = pg_atomic_read_u32(&bufHdr->state); + + if (buf_state & BM_VALID) + { + buffers_used++; + usagecount_total += BUF_STATE_GET_USAGECOUNT(buf_state); + + if (buf_state & BM_DIRTY) + buffers_dirty++; + } + else + buffers_unused++; + + if (BUF_STATE_GET_REFCOUNT(buf_state) > 0) + buffers_pinned++; + } + + memset(nulls, 0, sizeof(nulls)); + values[0] = Int32GetDatum(buffers_used); + values[1] = Int32GetDatum(buffers_unused); + values[2] = Int32GetDatum(buffers_dirty); + values[3] = Int32GetDatum(buffers_pinned); + + if (buffers_used != 0) + values[4] = Float8GetDatum((double) usagecount_total / buffers_used); + else + nulls[4] = true; + + /* Build and return the tuple. */ + tuple = heap_form_tuple(tupledesc, values, nulls); + result = HeapTupleGetDatum(tuple); + + PG_RETURN_DATUM(result); +} + +Datum +pg_buffercache_usage_counts(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + int usage_counts[BM_MAX_USAGE_COUNT + 1] = {0}; + int dirty[BM_MAX_USAGE_COUNT + 1] = {0}; + int pinned[BM_MAX_USAGE_COUNT + 1] = {0}; + Datum values[NUM_BUFFERCACHE_USAGE_COUNTS_ELEM]; + bool nulls[NUM_BUFFERCACHE_USAGE_COUNTS_ELEM] = {0}; + + InitMaterializedSRF(fcinfo, 0); + + for (int i = 0; i < NBuffers; i++) + { + BufferDesc *bufHdr = GetBufferDescriptor(i); + uint32 buf_state = pg_atomic_read_u32(&bufHdr->state); + int usage_count; + + usage_count = BUF_STATE_GET_USAGECOUNT(buf_state); + usage_counts[usage_count]++; + + if (buf_state & BM_DIRTY) + dirty[usage_count]++; + + if (BUF_STATE_GET_REFCOUNT(buf_state) > 0) + pinned[usage_count]++; + } + + for (int i = 0; i < BM_MAX_USAGE_COUNT + 1; i++) + { + values[0] = Int32GetDatum(i); + values[1] = Int32GetDatum(usage_counts[i]); + values[2] = Int32GetDatum(dirty[i]); + values[3] = Int32GetDatum(pinned[i]); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} diff --git a/contrib/pg_buffercache/sql/pg_buffercache.sql b/contrib/pg_buffercache/sql/pg_buffercache.sql new file mode 100644 index 0000000..944fbb1 --- /dev/null +++ b/contrib/pg_buffercache/sql/pg_buffercache.sql @@ -0,0 +1,28 @@ +CREATE EXTENSION pg_buffercache; + +select count(*) = (select setting::bigint + from pg_settings + where name = 'shared_buffers') +from pg_buffercache; + +select buffers_used + buffers_unused > 0, + buffers_dirty <= buffers_used, + buffers_pinned <= buffers_used +from pg_buffercache_summary(); + +SELECT count(*) > 0 FROM pg_buffercache_usage_counts() WHERE buffers >= 0; + +-- Check that the functions / views can't be accessed by default. To avoid +-- having to create a dedicated user, use the pg_database_owner pseudo-role. +SET ROLE pg_database_owner; +SELECT * FROM pg_buffercache; +SELECT * FROM pg_buffercache_pages() AS p (wrong int); +SELECT * FROM pg_buffercache_summary(); +SELECT * FROM pg_buffercache_usage_counts(); +RESET role; + +-- Check that pg_monitor is allowed to query view / function +SET ROLE pg_monitor; +SELECT count(*) > 0 FROM pg_buffercache; +SELECT buffers_used + buffers_unused > 0 FROM pg_buffercache_summary(); +SELECT count(*) > 0 FROM pg_buffercache_usage_counts(); diff --git a/contrib/pg_freespacemap/.gitignore b/contrib/pg_freespacemap/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pg_freespacemap/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pg_freespacemap/Makefile b/contrib/pg_freespacemap/Makefile new file mode 100644 index 0000000..b48e4b2 --- /dev/null +++ b/contrib/pg_freespacemap/Makefile @@ -0,0 +1,29 @@ +# contrib/pg_freespacemap/Makefile + +MODULE_big = pg_freespacemap +OBJS = \ + $(WIN32RES) \ + pg_freespacemap.o + +EXTENSION = pg_freespacemap +DATA = pg_freespacemap--1.1.sql pg_freespacemap--1.1--1.2.sql \ + pg_freespacemap--1.0--1.1.sql +PGFILEDESC = "pg_freespacemap - monitoring of free space map" + +REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_freespacemap/pg_freespacemap.conf +REGRESS = pg_freespacemap + +# Disabled because these tests require "autovacuum=off", 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 = contrib/pg_freespacemap +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pg_freespacemap/expected/pg_freespacemap.out b/contrib/pg_freespacemap/expected/pg_freespacemap.out new file mode 100644 index 0000000..eb574c2 --- /dev/null +++ b/contrib/pg_freespacemap/expected/pg_freespacemap.out @@ -0,0 +1,85 @@ +CREATE EXTENSION pg_freespacemap; +CREATE TABLE freespace_tab (c1 int) WITH (autovacuum_enabled = off); +CREATE INDEX freespace_brin ON freespace_tab USING brin (c1); +CREATE INDEX freespace_btree ON freespace_tab USING btree (c1); +CREATE INDEX freespace_hash ON freespace_tab USING hash (c1); +-- report all the sizes of the FSMs for all the relation blocks. +WITH rel AS (SELECT oid::regclass AS id FROM pg_class WHERE relname ~ 'freespace') + SELECT rel.id, fsm.blkno, (fsm.avail > 0) AS is_avail + FROM rel, LATERAL pg_freespace(rel.id) AS fsm + ORDER BY 1, 2; + id | blkno | is_avail +-----------------+-------+---------- + freespace_brin | 0 | f + freespace_brin | 1 | f + freespace_brin | 2 | t + freespace_btree | 0 | f + freespace_hash | 0 | f + freespace_hash | 1 | f + freespace_hash | 2 | f + freespace_hash | 3 | f + freespace_hash | 4 | f + freespace_hash | 5 | f + freespace_hash | 6 | f + freespace_hash | 7 | f + freespace_hash | 8 | f + freespace_hash | 9 | f +(14 rows) + +INSERT INTO freespace_tab VALUES (1); +VACUUM freespace_tab; +WITH rel AS (SELECT oid::regclass AS id FROM pg_class WHERE relname ~ 'freespace') + SELECT rel.id, fsm.blkno, (fsm.avail > 0) AS is_avail + FROM rel, LATERAL pg_freespace(rel.id) AS fsm + ORDER BY 1, 2; + id | blkno | is_avail +-----------------+-------+---------- + freespace_tab | 0 | t + freespace_brin | 0 | f + freespace_brin | 1 | f + freespace_brin | 2 | t + freespace_btree | 0 | f + freespace_btree | 1 | f + freespace_hash | 0 | f + freespace_hash | 1 | f + freespace_hash | 2 | f + freespace_hash | 3 | f + freespace_hash | 4 | f + freespace_hash | 5 | f + freespace_hash | 6 | f + freespace_hash | 7 | f + freespace_hash | 8 | f + freespace_hash | 9 | f +(16 rows) + +DELETE FROM freespace_tab; +VACUUM freespace_tab; +WITH rel AS (SELECT oid::regclass AS id FROM pg_class WHERE relname ~ 'freespace') + SELECT rel.id, fsm.blkno, (fsm.avail > 0) AS is_avail + FROM rel, LATERAL pg_freespace(rel.id) AS fsm + ORDER BY 1, 2; + id | blkno | is_avail +-----------------+-------+---------- + freespace_brin | 0 | f + freespace_brin | 1 | f + freespace_brin | 2 | t + freespace_btree | 0 | f + freespace_btree | 1 | f + freespace_hash | 0 | f + freespace_hash | 1 | f + freespace_hash | 2 | f + freespace_hash | 3 | f + freespace_hash | 4 | f + freespace_hash | 5 | f + freespace_hash | 6 | f + freespace_hash | 7 | f + freespace_hash | 8 | f + freespace_hash | 9 | f +(15 rows) + +-- failures with incorrect block number +SELECT * FROM pg_freespace('freespace_tab', -1); +ERROR: invalid block number +SELECT * FROM pg_freespace('freespace_tab', 4294967295); +ERROR: invalid block number +DROP TABLE freespace_tab; diff --git a/contrib/pg_freespacemap/meson.build b/contrib/pg_freespacemap/meson.build new file mode 100644 index 0000000..a91dc45 --- /dev/null +++ b/contrib/pg_freespacemap/meson.build @@ -0,0 +1,42 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pg_freespacemap_sources = files( + 'pg_freespacemap.c', +) + +if host_system == 'windows' + pg_freespacemap_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pg_freespacemap', + '--FILEDESC', 'pg_freespacemap - monitoring of free space map',]) +endif + +pg_freespacemap = shared_module('pg_freespacemap', + pg_freespacemap_sources, + kwargs: contrib_mod_args, +) +contrib_targets += pg_freespacemap + +install_data( + 'pg_freespacemap--1.0--1.1.sql', + 'pg_freespacemap--1.1--1.2.sql', + 'pg_freespacemap--1.1.sql', + 'pg_freespacemap.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'pg_freespacemap', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'pg_freespacemap', + ], + 'regress_args': [ + '--temp-config', files('pg_freespacemap.conf') + ], + # Disabled because these tests require "autovacuum=off", which + # typical runningcheck users do not have (e.g. buildfarm clients). + 'runningcheck': false, + }, +} diff --git a/contrib/pg_freespacemap/pg_freespacemap--1.0--1.1.sql b/contrib/pg_freespacemap/pg_freespacemap--1.0--1.1.sql new file mode 100644 index 0000000..52d6576 --- /dev/null +++ b/contrib/pg_freespacemap/pg_freespacemap--1.0--1.1.sql @@ -0,0 +1,7 @@ +/* contrib/pg_freespacemap/pg_freespacemap--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_freespacemap UPDATE TO '1.1'" to load this file. \quit + +ALTER FUNCTION pg_freespace(regclass, bigint) PARALLEL SAFE; +ALTER FUNCTION pg_freespace(regclass) PARALLEL SAFE; diff --git a/contrib/pg_freespacemap/pg_freespacemap--1.1--1.2.sql b/contrib/pg_freespacemap/pg_freespacemap--1.1--1.2.sql new file mode 100644 index 0000000..f558def --- /dev/null +++ b/contrib/pg_freespacemap/pg_freespacemap--1.1--1.2.sql @@ -0,0 +1,7 @@ +/* contrib/pg_freespacemap/pg_freespacemap--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_freespacemap UPDATE TO '1.2'" to load this file. \quit + +GRANT EXECUTE ON FUNCTION pg_freespace(regclass, bigint) TO pg_stat_scan_tables; +GRANT EXECUTE ON FUNCTION pg_freespace(regclass) TO pg_stat_scan_tables; diff --git a/contrib/pg_freespacemap/pg_freespacemap--1.1.sql b/contrib/pg_freespacemap/pg_freespacemap--1.1.sql new file mode 100644 index 0000000..e1b8242 --- /dev/null +++ b/contrib/pg_freespacemap/pg_freespacemap--1.1.sql @@ -0,0 +1,25 @@ +/* contrib/pg_freespacemap/pg_freespacemap--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_freespacemap" to load this file. \quit + +-- Register the C function. +CREATE FUNCTION pg_freespace(regclass, bigint) +RETURNS int2 +AS 'MODULE_PATHNAME', 'pg_freespace' +LANGUAGE C STRICT PARALLEL SAFE; + +-- pg_freespace shows the recorded space avail at each block in a relation +CREATE FUNCTION + pg_freespace(rel regclass, blkno OUT bigint, avail OUT int2) +RETURNS SETOF RECORD +AS $$ + SELECT blkno, pg_freespace($1, blkno) AS avail + FROM generate_series(0, pg_relation_size($1) / current_setting('block_size')::bigint - 1) AS blkno; +$$ +LANGUAGE SQL PARALLEL SAFE; + + +-- Don't want these to be available to public. +REVOKE ALL ON FUNCTION pg_freespace(regclass, bigint) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_freespace(regclass) FROM PUBLIC; diff --git a/contrib/pg_freespacemap/pg_freespacemap.c b/contrib/pg_freespacemap/pg_freespacemap.c new file mode 100644 index 0000000..b82cab2 --- /dev/null +++ b/contrib/pg_freespacemap/pg_freespacemap.c @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------- + * + * pg_freespacemap.c + * display contents of a free space map + * + * contrib/pg_freespacemap/pg_freespacemap.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/relation.h" +#include "funcapi.h" +#include "storage/freespace.h" + +PG_MODULE_MAGIC; + +/* + * Returns the amount of free space on a given page, according to the + * free space map. + */ +PG_FUNCTION_INFO_V1(pg_freespace); + +Datum +pg_freespace(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int64 blkno = PG_GETARG_INT64(1); + int16 freespace; + Relation rel; + + rel = relation_open(relid, AccessShareLock); + + if (blkno < 0 || blkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number"))); + + freespace = GetRecordedFreeSpace(rel, blkno); + + relation_close(rel, AccessShareLock); + PG_RETURN_INT16(freespace); +} diff --git a/contrib/pg_freespacemap/pg_freespacemap.conf b/contrib/pg_freespacemap/pg_freespacemap.conf new file mode 100644 index 0000000..96b1ed8 --- /dev/null +++ b/contrib/pg_freespacemap/pg_freespacemap.conf @@ -0,0 +1 @@ +autovacuum = off diff --git a/contrib/pg_freespacemap/pg_freespacemap.control b/contrib/pg_freespacemap/pg_freespacemap.control new file mode 100644 index 0000000..ac8fc50 --- /dev/null +++ b/contrib/pg_freespacemap/pg_freespacemap.control @@ -0,0 +1,5 @@ +# pg_freespacemap extension +comment = 'examine the free space map (FSM)' +default_version = '1.2' +module_pathname = '$libdir/pg_freespacemap' +relocatable = true diff --git a/contrib/pg_freespacemap/sql/pg_freespacemap.sql b/contrib/pg_freespacemap/sql/pg_freespacemap.sql new file mode 100644 index 0000000..06275d8 --- /dev/null +++ b/contrib/pg_freespacemap/sql/pg_freespacemap.sql @@ -0,0 +1,32 @@ +CREATE EXTENSION pg_freespacemap; + +CREATE TABLE freespace_tab (c1 int) WITH (autovacuum_enabled = off); +CREATE INDEX freespace_brin ON freespace_tab USING brin (c1); +CREATE INDEX freespace_btree ON freespace_tab USING btree (c1); +CREATE INDEX freespace_hash ON freespace_tab USING hash (c1); + +-- report all the sizes of the FSMs for all the relation blocks. +WITH rel AS (SELECT oid::regclass AS id FROM pg_class WHERE relname ~ 'freespace') + SELECT rel.id, fsm.blkno, (fsm.avail > 0) AS is_avail + FROM rel, LATERAL pg_freespace(rel.id) AS fsm + ORDER BY 1, 2; + +INSERT INTO freespace_tab VALUES (1); +VACUUM freespace_tab; +WITH rel AS (SELECT oid::regclass AS id FROM pg_class WHERE relname ~ 'freespace') + SELECT rel.id, fsm.blkno, (fsm.avail > 0) AS is_avail + FROM rel, LATERAL pg_freespace(rel.id) AS fsm + ORDER BY 1, 2; + +DELETE FROM freespace_tab; +VACUUM freespace_tab; +WITH rel AS (SELECT oid::regclass AS id FROM pg_class WHERE relname ~ 'freespace') + SELECT rel.id, fsm.blkno, (fsm.avail > 0) AS is_avail + FROM rel, LATERAL pg_freespace(rel.id) AS fsm + ORDER BY 1, 2; + +-- failures with incorrect block number +SELECT * FROM pg_freespace('freespace_tab', -1); +SELECT * FROM pg_freespace('freespace_tab', 4294967295); + +DROP TABLE freespace_tab; diff --git a/contrib/pg_prewarm/.gitignore b/contrib/pg_prewarm/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pg_prewarm/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pg_prewarm/Makefile b/contrib/pg_prewarm/Makefile new file mode 100644 index 0000000..9cfde8c --- /dev/null +++ b/contrib/pg_prewarm/Makefile @@ -0,0 +1,24 @@ +# contrib/pg_prewarm/Makefile + +MODULE_big = pg_prewarm +OBJS = \ + $(WIN32RES) \ + autoprewarm.o \ + pg_prewarm.o + +EXTENSION = pg_prewarm +DATA = pg_prewarm--1.1--1.2.sql pg_prewarm--1.1.sql pg_prewarm--1.0--1.1.sql +PGFILEDESC = "pg_prewarm - preload relation data into system buffer cache" + +TAP_TESTS = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pg_prewarm +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c new file mode 100644 index 0000000..9383544 --- /dev/null +++ b/contrib/pg_prewarm/autoprewarm.c @@ -0,0 +1,920 @@ +/*------------------------------------------------------------------------- + * + * autoprewarm.c + * Periodically dump information about the blocks present in + * shared_buffers, and reload them on server restart. + * + * Due to locking considerations, we can't actually begin prewarming + * until the server reaches a consistent state. We need the catalogs + * to be consistent so that we can figure out which relation to lock, + * and we need to lock the relations so that we don't try to prewarm + * pages from a relation that is in the process of being dropped. + * + * While prewarming, autoprewarm will use two workers. There's a + * leader worker that reads and sorts the list of blocks to be + * prewarmed and then launches a per-database worker for each + * relevant database in turn. The former keeps running after the + * initial prewarm is complete to update the dump file periodically. + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pg_prewarm/autoprewarm.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include + +#include "access/relation.h" +#include "access/xact.h" +#include "catalog/pg_class.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "postmaster/bgworker.h" +#include "postmaster/interrupt.h" +#include "storage/buf_internals.h" +#include "storage/dsm.h" +#include "storage/fd.h" +#include "storage/ipc.h" +#include "storage/latch.h" +#include "storage/lwlock.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/shmem.h" +#include "storage/smgr.h" +#include "tcop/tcopprot.h" +#include "utils/acl.h" +#include "utils/datetime.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/relfilenumbermap.h" +#include "utils/resowner.h" + +#define AUTOPREWARM_FILE "autoprewarm.blocks" + +/* Metadata for each block we dump. */ +typedef struct BlockInfoRecord +{ + Oid database; + Oid tablespace; + RelFileNumber filenumber; + ForkNumber forknum; + BlockNumber blocknum; +} BlockInfoRecord; + +/* Shared state information for autoprewarm bgworker. */ +typedef struct AutoPrewarmSharedState +{ + LWLock lock; /* mutual exclusion */ + pid_t bgworker_pid; /* for main bgworker */ + pid_t pid_using_dumpfile; /* for autoprewarm or block dump */ + + /* Following items are for communication with per-database worker */ + dsm_handle block_info_handle; + Oid database; + int prewarm_start_idx; + int prewarm_stop_idx; + int prewarmed_blocks; +} AutoPrewarmSharedState; + +PGDLLEXPORT void autoprewarm_main(Datum main_arg); +PGDLLEXPORT void autoprewarm_database_main(Datum main_arg); + +PG_FUNCTION_INFO_V1(autoprewarm_start_worker); +PG_FUNCTION_INFO_V1(autoprewarm_dump_now); + +static void apw_load_buffers(void); +static int apw_dump_now(bool is_bgworker, bool dump_unlogged); +static void apw_start_leader_worker(void); +static void apw_start_database_worker(void); +static bool apw_init_shmem(void); +static void apw_detach_shmem(int code, Datum arg); +static int apw_compare_blockinfo(const void *p, const void *q); +static void autoprewarm_shmem_request(void); +static shmem_request_hook_type prev_shmem_request_hook = NULL; + +/* Pointer to shared-memory state. */ +static AutoPrewarmSharedState *apw_state = NULL; + +/* GUC variables. */ +static bool autoprewarm = true; /* start worker? */ +static int autoprewarm_interval = 300; /* dump interval */ + +/* + * Module load callback. + */ +void +_PG_init(void) +{ + DefineCustomIntVariable("pg_prewarm.autoprewarm_interval", + "Sets the interval between dumps of shared buffers", + "If set to zero, time-based dumping is disabled.", + &autoprewarm_interval, + 300, + 0, INT_MAX / 1000, + PGC_SIGHUP, + GUC_UNIT_S, + NULL, + NULL, + NULL); + + if (!process_shared_preload_libraries_in_progress) + return; + + /* can't define PGC_POSTMASTER variable after startup */ + DefineCustomBoolVariable("pg_prewarm.autoprewarm", + "Starts the autoprewarm worker.", + NULL, + &autoprewarm, + true, + PGC_POSTMASTER, + 0, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("pg_prewarm"); + + prev_shmem_request_hook = shmem_request_hook; + shmem_request_hook = autoprewarm_shmem_request; + + /* Register autoprewarm worker, if enabled. */ + if (autoprewarm) + apw_start_leader_worker(); +} + +/* + * Requests any additional shared memory required for autoprewarm. + */ +static void +autoprewarm_shmem_request(void) +{ + if (prev_shmem_request_hook) + prev_shmem_request_hook(); + + RequestAddinShmemSpace(MAXALIGN(sizeof(AutoPrewarmSharedState))); +} + +/* + * Main entry point for the leader autoprewarm process. Per-database workers + * have a separate entry point. + */ +void +autoprewarm_main(Datum main_arg) +{ + bool first_time = true; + bool final_dump_allowed = true; + TimestampTz last_dump_time = 0; + + /* Establish signal handlers; once that's done, unblock signals. */ + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + BackgroundWorkerUnblockSignals(); + + /* Create (if necessary) and attach to our shared memory area. */ + if (apw_init_shmem()) + first_time = false; + + /* Set on-detach hook so that our PID will be cleared on exit. */ + on_shmem_exit(apw_detach_shmem, 0); + + /* + * Store our PID in the shared memory area --- unless there's already + * another worker running, in which case just exit. + */ + LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE); + if (apw_state->bgworker_pid != InvalidPid) + { + LWLockRelease(&apw_state->lock); + ereport(LOG, + (errmsg("autoprewarm worker is already running under PID %d", + (int) apw_state->bgworker_pid))); + return; + } + apw_state->bgworker_pid = MyProcPid; + LWLockRelease(&apw_state->lock); + + /* + * Preload buffers from the dump file only if we just created the shared + * memory region. Otherwise, it's either already been done or shouldn't + * be done - e.g. because the old dump file has been overwritten since the + * server was started. + * + * There's not much point in performing a dump immediately after we finish + * preloading; so, if we do end up preloading, consider the last dump time + * to be equal to the current time. + * + * If apw_load_buffers() is terminated early by a shutdown request, + * prevent dumping out our state below the loop, because we'd effectively + * just truncate the saved state to however much we'd managed to preload. + */ + if (first_time) + { + apw_load_buffers(); + final_dump_allowed = !ShutdownRequestPending; + last_dump_time = GetCurrentTimestamp(); + } + + /* Periodically dump buffers until terminated. */ + while (!ShutdownRequestPending) + { + /* In case of a SIGHUP, just reload the configuration. */ + if (ConfigReloadPending) + { + ConfigReloadPending = false; + ProcessConfigFile(PGC_SIGHUP); + } + + if (autoprewarm_interval <= 0) + { + /* We're only dumping at shutdown, so just wait forever. */ + (void) WaitLatch(MyLatch, + WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, + -1L, + PG_WAIT_EXTENSION); + } + else + { + TimestampTz next_dump_time; + long delay_in_ms; + + /* Compute the next dump time. */ + next_dump_time = + TimestampTzPlusMilliseconds(last_dump_time, + autoprewarm_interval * 1000); + delay_in_ms = + TimestampDifferenceMilliseconds(GetCurrentTimestamp(), + next_dump_time); + + /* Perform a dump if it's time. */ + if (delay_in_ms <= 0) + { + last_dump_time = GetCurrentTimestamp(); + apw_dump_now(true, false); + continue; + } + + /* Sleep until the next dump time. */ + (void) WaitLatch(MyLatch, + WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, + delay_in_ms, + PG_WAIT_EXTENSION); + } + + /* Reset the latch, loop. */ + ResetLatch(MyLatch); + } + + /* + * Dump one last time. We assume this is probably the result of a system + * shutdown, although it's possible that we've merely been terminated. + */ + if (final_dump_allowed) + apw_dump_now(true, true); +} + +/* + * Read the dump file and launch per-database workers one at a time to + * prewarm the buffers found there. + */ +static void +apw_load_buffers(void) +{ + FILE *file = NULL; + int num_elements, + i; + BlockInfoRecord *blkinfo; + dsm_segment *seg; + + /* + * Skip the prewarm if the dump file is in use; otherwise, prevent any + * other process from writing it while we're using it. + */ + LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE); + if (apw_state->pid_using_dumpfile == InvalidPid) + apw_state->pid_using_dumpfile = MyProcPid; + else + { + LWLockRelease(&apw_state->lock); + ereport(LOG, + (errmsg("skipping prewarm because block dump file is being written by PID %d", + (int) apw_state->pid_using_dumpfile))); + return; + } + LWLockRelease(&apw_state->lock); + + /* + * Open the block dump file. Exit quietly if it doesn't exist, but report + * any other error. + */ + file = AllocateFile(AUTOPREWARM_FILE, "r"); + if (!file) + { + if (errno == ENOENT) + { + LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE); + apw_state->pid_using_dumpfile = InvalidPid; + LWLockRelease(&apw_state->lock); + return; /* No file to load. */ + } + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", + AUTOPREWARM_FILE))); + } + + /* First line of the file is a record count. */ + if (fscanf(file, "<<%d>>\n", &num_elements) != 1) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read from file \"%s\": %m", + AUTOPREWARM_FILE))); + + /* Allocate a dynamic shared memory segment to store the record data. */ + seg = dsm_create(sizeof(BlockInfoRecord) * num_elements, 0); + blkinfo = (BlockInfoRecord *) dsm_segment_address(seg); + + /* Read records, one per line. */ + for (i = 0; i < num_elements; i++) + { + unsigned forknum; + + if (fscanf(file, "%u,%u,%u,%u,%u\n", &blkinfo[i].database, + &blkinfo[i].tablespace, &blkinfo[i].filenumber, + &forknum, &blkinfo[i].blocknum) != 5) + ereport(ERROR, + (errmsg("autoprewarm block dump file is corrupted at line %d", + i + 1))); + blkinfo[i].forknum = forknum; + } + + FreeFile(file); + + /* Sort the blocks to be loaded. */ + pg_qsort(blkinfo, num_elements, sizeof(BlockInfoRecord), + apw_compare_blockinfo); + + /* Populate shared memory state. */ + apw_state->block_info_handle = dsm_segment_handle(seg); + apw_state->prewarm_start_idx = apw_state->prewarm_stop_idx = 0; + apw_state->prewarmed_blocks = 0; + + /* Get the info position of the first block of the next database. */ + while (apw_state->prewarm_start_idx < num_elements) + { + int j = apw_state->prewarm_start_idx; + Oid current_db = blkinfo[j].database; + + /* + * Advance the prewarm_stop_idx to the first BlockInfoRecord that does + * not belong to this database. + */ + j++; + while (j < num_elements) + { + if (current_db != blkinfo[j].database) + { + /* + * Combine BlockInfoRecords for global objects with those of + * the database. + */ + if (current_db != InvalidOid) + break; + current_db = blkinfo[j].database; + } + + j++; + } + + /* + * If we reach this point with current_db == InvalidOid, then only + * BlockInfoRecords belonging to global objects exist. We can't + * prewarm without a database connection, so just bail out. + */ + if (current_db == InvalidOid) + break; + + /* Configure stop point and database for next per-database worker. */ + apw_state->prewarm_stop_idx = j; + apw_state->database = current_db; + Assert(apw_state->prewarm_start_idx < apw_state->prewarm_stop_idx); + + /* If we've run out of free buffers, don't launch another worker. */ + if (!have_free_buffer()) + break; + + /* + * Likewise, don't launch if we've already been told to shut down. + * (The launch would fail anyway, but we might as well skip it.) + */ + if (ShutdownRequestPending) + break; + + /* + * Start a per-database worker to load blocks for this database; this + * function will return once the per-database worker exits. + */ + apw_start_database_worker(); + + /* Prepare for next database. */ + apw_state->prewarm_start_idx = apw_state->prewarm_stop_idx; + } + + /* Clean up. */ + dsm_detach(seg); + LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE); + apw_state->block_info_handle = DSM_HANDLE_INVALID; + apw_state->pid_using_dumpfile = InvalidPid; + LWLockRelease(&apw_state->lock); + + /* Report our success, if we were able to finish. */ + if (!ShutdownRequestPending) + ereport(LOG, + (errmsg("autoprewarm successfully prewarmed %d of %d previously-loaded blocks", + apw_state->prewarmed_blocks, num_elements))); +} + +/* + * Prewarm all blocks for one database (and possibly also global objects, if + * those got grouped with this database). + */ +void +autoprewarm_database_main(Datum main_arg) +{ + int pos; + BlockInfoRecord *block_info; + Relation rel = NULL; + BlockNumber nblocks = 0; + BlockInfoRecord *old_blk = NULL; + dsm_segment *seg; + + /* Establish signal handlers; once that's done, unblock signals. */ + pqsignal(SIGTERM, die); + BackgroundWorkerUnblockSignals(); + + /* Connect to correct database and get block information. */ + apw_init_shmem(); + seg = dsm_attach(apw_state->block_info_handle); + if (seg == NULL) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("could not map dynamic shared memory segment"))); + BackgroundWorkerInitializeConnectionByOid(apw_state->database, InvalidOid, 0); + block_info = (BlockInfoRecord *) dsm_segment_address(seg); + pos = apw_state->prewarm_start_idx; + + /* + * Loop until we run out of blocks to prewarm or until we run out of free + * buffers. + */ + while (pos < apw_state->prewarm_stop_idx && have_free_buffer()) + { + BlockInfoRecord *blk = &block_info[pos++]; + Buffer buf; + + CHECK_FOR_INTERRUPTS(); + + /* + * Quit if we've reached records for another database. If previous + * blocks are of some global objects, then continue pre-warming. + */ + if (old_blk != NULL && old_blk->database != blk->database && + old_blk->database != 0) + break; + + /* + * As soon as we encounter a block of a new relation, close the old + * relation. Note that rel will be NULL if try_relation_open failed + * previously; in that case, there is nothing to close. + */ + if (old_blk != NULL && old_blk->filenumber != blk->filenumber && + rel != NULL) + { + relation_close(rel, AccessShareLock); + rel = NULL; + CommitTransactionCommand(); + } + + /* + * Try to open each new relation, but only once, when we first + * encounter it. If it's been dropped, skip the associated blocks. + */ + if (old_blk == NULL || old_blk->filenumber != blk->filenumber) + { + Oid reloid; + + Assert(rel == NULL); + StartTransactionCommand(); + reloid = RelidByRelfilenumber(blk->tablespace, blk->filenumber); + if (OidIsValid(reloid)) + rel = try_relation_open(reloid, AccessShareLock); + + if (!rel) + CommitTransactionCommand(); + } + if (!rel) + { + old_blk = blk; + continue; + } + + /* Once per fork, check for fork existence and size. */ + if (old_blk == NULL || + old_blk->filenumber != blk->filenumber || + old_blk->forknum != blk->forknum) + { + /* + * smgrexists is not safe for illegal forknum, hence check whether + * the passed forknum is valid before using it in smgrexists. + */ + if (blk->forknum > InvalidForkNumber && + blk->forknum <= MAX_FORKNUM && + smgrexists(RelationGetSmgr(rel), blk->forknum)) + nblocks = RelationGetNumberOfBlocksInFork(rel, blk->forknum); + else + nblocks = 0; + } + + /* Check whether blocknum is valid and within fork file size. */ + if (blk->blocknum >= nblocks) + { + /* Move to next forknum. */ + old_blk = blk; + continue; + } + + /* Prewarm buffer. */ + buf = ReadBufferExtended(rel, blk->forknum, blk->blocknum, RBM_NORMAL, + NULL); + if (BufferIsValid(buf)) + { + apw_state->prewarmed_blocks++; + ReleaseBuffer(buf); + } + + old_blk = blk; + } + + dsm_detach(seg); + + /* Release lock on previous relation. */ + if (rel) + { + relation_close(rel, AccessShareLock); + CommitTransactionCommand(); + } +} + +/* + * Dump information on blocks in shared buffers. We use a text format here + * so that it's easy to understand and even change the file contents if + * necessary. + * Returns the number of blocks dumped. + */ +static int +apw_dump_now(bool is_bgworker, bool dump_unlogged) +{ + int num_blocks; + int i; + int ret; + BlockInfoRecord *block_info_array; + BufferDesc *bufHdr; + FILE *file; + char transient_dump_file_path[MAXPGPATH]; + pid_t pid; + + LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE); + pid = apw_state->pid_using_dumpfile; + if (apw_state->pid_using_dumpfile == InvalidPid) + apw_state->pid_using_dumpfile = MyProcPid; + LWLockRelease(&apw_state->lock); + + if (pid != InvalidPid) + { + if (!is_bgworker) + ereport(ERROR, + (errmsg("could not perform block dump because dump file is being used by PID %d", + (int) apw_state->pid_using_dumpfile))); + + ereport(LOG, + (errmsg("skipping block dump because it is already being performed by PID %d", + (int) apw_state->pid_using_dumpfile))); + return 0; + } + + block_info_array = + (BlockInfoRecord *) palloc(sizeof(BlockInfoRecord) * NBuffers); + + for (num_blocks = 0, i = 0; i < NBuffers; i++) + { + uint32 buf_state; + + CHECK_FOR_INTERRUPTS(); + + bufHdr = GetBufferDescriptor(i); + + /* Lock each buffer header before inspecting. */ + buf_state = LockBufHdr(bufHdr); + + /* + * Unlogged tables will be automatically truncated after a crash or + * unclean shutdown. In such cases we need not prewarm them. Dump them + * only if requested by caller. + */ + if (buf_state & BM_TAG_VALID && + ((buf_state & BM_PERMANENT) || dump_unlogged)) + { + block_info_array[num_blocks].database = bufHdr->tag.dbOid; + block_info_array[num_blocks].tablespace = bufHdr->tag.spcOid; + block_info_array[num_blocks].filenumber = + BufTagGetRelNumber(&bufHdr->tag); + block_info_array[num_blocks].forknum = + BufTagGetForkNum(&bufHdr->tag); + block_info_array[num_blocks].blocknum = bufHdr->tag.blockNum; + ++num_blocks; + } + + UnlockBufHdr(bufHdr, buf_state); + } + + snprintf(transient_dump_file_path, MAXPGPATH, "%s.tmp", AUTOPREWARM_FILE); + file = AllocateFile(transient_dump_file_path, "w"); + if (!file) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\": %m", + transient_dump_file_path))); + + ret = fprintf(file, "<<%d>>\n", num_blocks); + if (ret < 0) + { + int save_errno = errno; + + FreeFile(file); + unlink(transient_dump_file_path); + errno = save_errno; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + transient_dump_file_path))); + } + + for (i = 0; i < num_blocks; i++) + { + CHECK_FOR_INTERRUPTS(); + + ret = fprintf(file, "%u,%u,%u,%u,%u\n", + block_info_array[i].database, + block_info_array[i].tablespace, + block_info_array[i].filenumber, + (uint32) block_info_array[i].forknum, + block_info_array[i].blocknum); + if (ret < 0) + { + int save_errno = errno; + + FreeFile(file); + unlink(transient_dump_file_path); + errno = save_errno; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not write to file \"%s\": %m", + transient_dump_file_path))); + } + } + + pfree(block_info_array); + + /* + * Rename transient_dump_file_path to AUTOPREWARM_FILE to make things + * permanent. + */ + ret = FreeFile(file); + if (ret != 0) + { + int save_errno = errno; + + unlink(transient_dump_file_path); + errno = save_errno; + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not close file \"%s\": %m", + transient_dump_file_path))); + } + + (void) durable_rename(transient_dump_file_path, AUTOPREWARM_FILE, ERROR); + apw_state->pid_using_dumpfile = InvalidPid; + + ereport(DEBUG1, + (errmsg_internal("wrote block details for %d blocks", num_blocks))); + return num_blocks; +} + +/* + * SQL-callable function to launch autoprewarm. + */ +Datum +autoprewarm_start_worker(PG_FUNCTION_ARGS) +{ + pid_t pid; + + if (!autoprewarm) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("autoprewarm is disabled"))); + + apw_init_shmem(); + LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE); + pid = apw_state->bgworker_pid; + LWLockRelease(&apw_state->lock); + + if (pid != InvalidPid) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("autoprewarm worker is already running under PID %d", + (int) pid))); + + apw_start_leader_worker(); + + PG_RETURN_VOID(); +} + +/* + * SQL-callable function to perform an immediate block dump. + * + * Note: this is declared to return int8, as insurance against some + * very distant day when we might make NBuffers wider than int. + */ +Datum +autoprewarm_dump_now(PG_FUNCTION_ARGS) +{ + int num_blocks; + + apw_init_shmem(); + + PG_ENSURE_ERROR_CLEANUP(apw_detach_shmem, 0); + { + num_blocks = apw_dump_now(false, true); + } + PG_END_ENSURE_ERROR_CLEANUP(apw_detach_shmem, 0); + + PG_RETURN_INT64((int64) num_blocks); +} + +/* + * Allocate and initialize autoprewarm related shared memory, if not already + * done, and set up backend-local pointer to that state. Returns true if an + * existing shared memory segment was found. + */ +static bool +apw_init_shmem(void) +{ + bool found; + + LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); + apw_state = ShmemInitStruct("autoprewarm", + sizeof(AutoPrewarmSharedState), + &found); + if (!found) + { + /* First time through ... */ + LWLockInitialize(&apw_state->lock, LWLockNewTrancheId()); + apw_state->bgworker_pid = InvalidPid; + apw_state->pid_using_dumpfile = InvalidPid; + } + LWLockRelease(AddinShmemInitLock); + + LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm"); + + return found; +} + +/* + * Clear our PID from autoprewarm shared state. + */ +static void +apw_detach_shmem(int code, Datum arg) +{ + LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE); + if (apw_state->pid_using_dumpfile == MyProcPid) + apw_state->pid_using_dumpfile = InvalidPid; + if (apw_state->bgworker_pid == MyProcPid) + apw_state->bgworker_pid = InvalidPid; + LWLockRelease(&apw_state->lock); +} + +/* + * Start autoprewarm leader worker process. + */ +static void +apw_start_leader_worker(void) +{ + BackgroundWorker worker; + BackgroundWorkerHandle *handle; + BgwHandleStatus status; + pid_t pid; + + MemSet(&worker, 0, sizeof(BackgroundWorker)); + worker.bgw_flags = BGWORKER_SHMEM_ACCESS; + worker.bgw_start_time = BgWorkerStart_ConsistentState; + strcpy(worker.bgw_library_name, "pg_prewarm"); + strcpy(worker.bgw_function_name, "autoprewarm_main"); + strcpy(worker.bgw_name, "autoprewarm leader"); + strcpy(worker.bgw_type, "autoprewarm leader"); + + if (process_shared_preload_libraries_in_progress) + { + RegisterBackgroundWorker(&worker); + return; + } + + /* must set notify PID to wait for startup */ + worker.bgw_notify_pid = MyProcPid; + + if (!RegisterDynamicBackgroundWorker(&worker, &handle)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("could not register background process"), + errhint("You may need to increase max_worker_processes."))); + + status = WaitForBackgroundWorkerStartup(handle, &pid); + if (status != BGWH_STARTED) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("could not start background process"), + errhint("More details may be available in the server log."))); +} + +/* + * Start autoprewarm per-database worker process. + */ +static void +apw_start_database_worker(void) +{ + BackgroundWorker worker; + BackgroundWorkerHandle *handle; + + MemSet(&worker, 0, sizeof(BackgroundWorker)); + worker.bgw_flags = + BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; + worker.bgw_start_time = BgWorkerStart_ConsistentState; + worker.bgw_restart_time = BGW_NEVER_RESTART; + strcpy(worker.bgw_library_name, "pg_prewarm"); + strcpy(worker.bgw_function_name, "autoprewarm_database_main"); + strcpy(worker.bgw_name, "autoprewarm worker"); + strcpy(worker.bgw_type, "autoprewarm worker"); + + /* must set notify PID to wait for shutdown */ + worker.bgw_notify_pid = MyProcPid; + + if (!RegisterDynamicBackgroundWorker(&worker, &handle)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_RESOURCES), + errmsg("registering dynamic bgworker autoprewarm failed"), + errhint("Consider increasing configuration parameter \"max_worker_processes\"."))); + + /* + * Ignore return value; if it fails, postmaster has died, but we have + * checks for that elsewhere. + */ + WaitForBackgroundWorkerShutdown(handle); +} + +/* Compare member elements to check whether they are not equal. */ +#define cmp_member_elem(fld) \ +do { \ + if (a->fld < b->fld) \ + return -1; \ + else if (a->fld > b->fld) \ + return 1; \ +} while(0) + +/* + * apw_compare_blockinfo + * + * We depend on all records for a particular database being consecutive + * in the dump file; each per-database worker will preload blocks until + * it sees a block for some other database. Sorting by tablespace, + * filenumber, forknum, and blocknum isn't critical for correctness, but + * helps us get a sequential I/O pattern. + */ +static int +apw_compare_blockinfo(const void *p, const void *q) +{ + const BlockInfoRecord *a = (const BlockInfoRecord *) p; + const BlockInfoRecord *b = (const BlockInfoRecord *) q; + + cmp_member_elem(database); + cmp_member_elem(tablespace); + cmp_member_elem(filenumber); + cmp_member_elem(forknum); + cmp_member_elem(blocknum); + + return 0; +} diff --git a/contrib/pg_prewarm/meson.build b/contrib/pg_prewarm/meson.build new file mode 100644 index 0000000..b6ff9d2 --- /dev/null +++ b/contrib/pg_prewarm/meson.build @@ -0,0 +1,37 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pg_prewarm_sources = files( + 'autoprewarm.c', + 'pg_prewarm.c', +) + +if host_system == 'windows' + pg_prewarm_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pg_prewarm', + '--FILEDESC', 'pg_prewarm - preload relation data into system buffer cache',]) +endif + +pg_prewarm = shared_module('pg_prewarm', + pg_prewarm_sources, + kwargs: contrib_mod_args, +) +contrib_targets += pg_prewarm + +install_data( + 'pg_prewarm--1.0--1.1.sql', + 'pg_prewarm--1.1--1.2.sql', + 'pg_prewarm--1.1.sql', + 'pg_prewarm.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'pg_prewarm', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_basic.pl', + ], + }, +} diff --git a/contrib/pg_prewarm/pg_prewarm--1.0--1.1.sql b/contrib/pg_prewarm/pg_prewarm--1.0--1.1.sql new file mode 100644 index 0000000..9966054 --- /dev/null +++ b/contrib/pg_prewarm/pg_prewarm--1.0--1.1.sql @@ -0,0 +1,6 @@ +/* contrib/pg_prewarm/pg_prewarm--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_prewarm UPDATE TO '1.1'" to load this file. \quit + +ALTER FUNCTION pg_prewarm(regclass, text, text, int8, int8) PARALLEL SAFE; diff --git a/contrib/pg_prewarm/pg_prewarm--1.1--1.2.sql b/contrib/pg_prewarm/pg_prewarm--1.1--1.2.sql new file mode 100644 index 0000000..2381c06 --- /dev/null +++ b/contrib/pg_prewarm/pg_prewarm--1.1--1.2.sql @@ -0,0 +1,14 @@ +/* contrib/pg_prewarm/pg_prewarm--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_prewarm UPDATE TO '1.2'" to load this file. \quit + +CREATE FUNCTION autoprewarm_start_worker() +RETURNS VOID STRICT +AS 'MODULE_PATHNAME', 'autoprewarm_start_worker' +LANGUAGE C; + +CREATE FUNCTION autoprewarm_dump_now() +RETURNS pg_catalog.int8 STRICT +AS 'MODULE_PATHNAME', 'autoprewarm_dump_now' +LANGUAGE C; diff --git a/contrib/pg_prewarm/pg_prewarm--1.1.sql b/contrib/pg_prewarm/pg_prewarm--1.1.sql new file mode 100644 index 0000000..b150895 --- /dev/null +++ b/contrib/pg_prewarm/pg_prewarm--1.1.sql @@ -0,0 +1,14 @@ +/* contrib/pg_prewarm/pg_prewarm--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_prewarm" to load this file. \quit + +-- Register the function. +CREATE FUNCTION pg_prewarm(regclass, + mode text default 'buffer', + fork text default 'main', + first_block int8 default null, + last_block int8 default null) +RETURNS int8 +AS 'MODULE_PATHNAME', 'pg_prewarm' +LANGUAGE C PARALLEL SAFE; diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c new file mode 100644 index 0000000..e464d0d --- /dev/null +++ b/contrib/pg_prewarm/pg_prewarm.c @@ -0,0 +1,204 @@ +/*------------------------------------------------------------------------- + * + * pg_prewarm.c + * prewarming utilities + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pg_prewarm/pg_prewarm.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include +#include + +#include "access/relation.h" +#include "fmgr.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/smgr.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(pg_prewarm); + +typedef enum +{ + PREWARM_PREFETCH, + PREWARM_READ, + PREWARM_BUFFER +} PrewarmType; + +static PGIOAlignedBlock blockbuffer; + +/* + * pg_prewarm(regclass, mode text, fork text, + * first_block int8, last_block int8) + * + * The first argument is the relation to be prewarmed; the second controls + * how prewarming is done; legal options are 'prefetch', 'read', and 'buffer'. + * The third is the name of the relation fork to be prewarmed. The fourth + * and fifth arguments specify the first and last block to be prewarmed. + * If the fourth argument is NULL, it will be taken as 0; if the fifth argument + * is NULL, it will be taken as the number of blocks in the relation. The + * return value is the number of blocks successfully prewarmed. + */ +Datum +pg_prewarm(PG_FUNCTION_ARGS) +{ + Oid relOid; + text *forkName; + text *type; + int64 first_block; + int64 last_block; + int64 nblocks; + int64 blocks_done = 0; + int64 block; + Relation rel; + ForkNumber forkNumber; + char *forkString; + char *ttype; + PrewarmType ptype; + AclResult aclresult; + + /* Basic sanity checking. */ + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("relation cannot be null"))); + relOid = PG_GETARG_OID(0); + if (PG_ARGISNULL(1)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("prewarm type cannot be null"))); + type = PG_GETARG_TEXT_PP(1); + ttype = text_to_cstring(type); + if (strcmp(ttype, "prefetch") == 0) + ptype = PREWARM_PREFETCH; + else if (strcmp(ttype, "read") == 0) + ptype = PREWARM_READ; + else if (strcmp(ttype, "buffer") == 0) + ptype = PREWARM_BUFFER; + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid prewarm type"), + errhint("Valid prewarm types are \"prefetch\", \"read\", and \"buffer\"."))); + PG_RETURN_INT64(0); /* Placate compiler. */ + } + if (PG_ARGISNULL(2)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("relation fork cannot be null"))); + forkName = PG_GETARG_TEXT_PP(2); + forkString = text_to_cstring(forkName); + forkNumber = forkname_to_number(forkString); + + /* Open relation and check privileges. */ + rel = relation_open(relOid, AccessShareLock); + aclresult = pg_class_aclcheck(relOid, GetUserId(), ACL_SELECT); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), get_rel_name(relOid)); + + /* Check that the fork exists. */ + if (!smgrexists(RelationGetSmgr(rel), forkNumber)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("fork \"%s\" does not exist for this relation", + forkString))); + + /* Validate block numbers, or handle nulls. */ + nblocks = RelationGetNumberOfBlocksInFork(rel, forkNumber); + if (PG_ARGISNULL(3)) + first_block = 0; + else + { + first_block = PG_GETARG_INT64(3); + if (first_block < 0 || first_block >= nblocks) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("starting block number must be between 0 and %lld", + (long long) (nblocks - 1)))); + } + if (PG_ARGISNULL(4)) + last_block = nblocks - 1; + else + { + last_block = PG_GETARG_INT64(4); + if (last_block < 0 || last_block >= nblocks) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ending block number must be between 0 and %lld", + (long long) (nblocks - 1)))); + } + + /* Now we're ready to do the real work. */ + if (ptype == PREWARM_PREFETCH) + { +#ifdef USE_PREFETCH + + /* + * In prefetch mode, we just hint the OS to read the blocks, but we + * don't know whether it really does it, and we don't wait for it to + * finish. + * + * It would probably be better to pass our prefetch requests in chunks + * of a megabyte or maybe even a whole segment at a time, but there's + * no practical way to do that at present without a gross modularity + * violation, so we just do this. + */ + for (block = first_block; block <= last_block; ++block) + { + CHECK_FOR_INTERRUPTS(); + PrefetchBuffer(rel, forkNumber, block); + ++blocks_done; + } +#else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("prefetch is not supported by this build"))); +#endif + } + else if (ptype == PREWARM_READ) + { + /* + * In read mode, we actually read the blocks, but not into shared + * buffers. This is more portable than prefetch mode (it works + * everywhere) and is synchronous. + */ + for (block = first_block; block <= last_block; ++block) + { + CHECK_FOR_INTERRUPTS(); + smgrread(RelationGetSmgr(rel), forkNumber, block, blockbuffer.data); + ++blocks_done; + } + } + else if (ptype == PREWARM_BUFFER) + { + /* + * In buffer mode, we actually pull the data into shared_buffers. + */ + for (block = first_block; block <= last_block; ++block) + { + Buffer buf; + + CHECK_FOR_INTERRUPTS(); + buf = ReadBufferExtended(rel, forkNumber, block, RBM_NORMAL, NULL); + ReleaseBuffer(buf); + ++blocks_done; + } + } + + /* Close relation, release lock. */ + relation_close(rel, AccessShareLock); + + PG_RETURN_INT64(blocks_done); +} diff --git a/contrib/pg_prewarm/pg_prewarm.control b/contrib/pg_prewarm/pg_prewarm.control new file mode 100644 index 0000000..40e3add --- /dev/null +++ b/contrib/pg_prewarm/pg_prewarm.control @@ -0,0 +1,5 @@ +# pg_prewarm extension +comment = 'prewarm relation data' +default_version = '1.2' +module_pathname = '$libdir/pg_prewarm' +relocatable = true diff --git a/contrib/pg_prewarm/t/001_basic.pl b/contrib/pg_prewarm/t/001_basic.pl new file mode 100644 index 0000000..6b7c869 --- /dev/null +++ b/contrib/pg_prewarm/t/001_basic.pl @@ -0,0 +1,58 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + + +my $node = PostgreSQL::Test::Cluster->new('main'); + +$node->init; +$node->append_conf( + 'postgresql.conf', + qq{shared_preload_libraries = 'pg_prewarm' + pg_prewarm.autoprewarm = true + pg_prewarm.autoprewarm_interval = 0}); +$node->start; + +# setup +$node->safe_psql("postgres", + "CREATE EXTENSION pg_prewarm;\n" + . "CREATE TABLE test(c1 int);\n" + . "INSERT INTO test SELECT generate_series(1, 100);"); + +# test read mode +my $result = + $node->safe_psql("postgres", "SELECT pg_prewarm('test', 'read');"); +like($result, qr/^[1-9][0-9]*$/, 'read mode succeeded'); + +# test buffer_mode +$result = + $node->safe_psql("postgres", "SELECT pg_prewarm('test', 'buffer');"); +like($result, qr/^[1-9][0-9]*$/, 'buffer mode succeeded'); + +# prefetch mode might or might not be available +my ($cmdret, $stdout, $stderr) = + $node->psql("postgres", "SELECT pg_prewarm('test', 'prefetch');"); +ok( ( $stdout =~ qr/^[1-9][0-9]*$/ + or $stderr =~ qr/prefetch is not supported by this build/), + 'prefetch mode succeeded'); + +# test autoprewarm_dump_now() +$result = $node->safe_psql("postgres", "SELECT autoprewarm_dump_now();"); +like($result, qr/^[1-9][0-9]*$/, 'autoprewarm_dump_now succeeded'); + +# restart, to verify that auto prewarm actually works +$node->restart; + +$node->wait_for_log( + "autoprewarm successfully prewarmed [1-9][0-9]* of [0-9]+ previously-loaded blocks" +); + +$node->stop; + +done_testing(); diff --git a/contrib/pg_stat_statements/.gitignore b/contrib/pg_stat_statements/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pg_stat_statements/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pg_stat_statements/Makefile b/contrib/pg_stat_statements/Makefile new file mode 100644 index 0000000..5578a9d --- /dev/null +++ b/contrib/pg_stat_statements/Makefile @@ -0,0 +1,35 @@ +# contrib/pg_stat_statements/Makefile + +MODULE_big = pg_stat_statements +OBJS = \ + $(WIN32RES) \ + pg_stat_statements.o + +EXTENSION = pg_stat_statements +DATA = pg_stat_statements--1.4.sql \ + pg_stat_statements--1.9--1.10.sql pg_stat_statements--1.8--1.9.sql \ + pg_stat_statements--1.7--1.8.sql pg_stat_statements--1.6--1.7.sql \ + pg_stat_statements--1.5--1.6.sql pg_stat_statements--1.4--1.5.sql \ + pg_stat_statements--1.3--1.4.sql pg_stat_statements--1.2--1.3.sql \ + pg_stat_statements--1.1--1.2.sql pg_stat_statements--1.0--1.1.sql +PGFILEDESC = "pg_stat_statements - execution statistics of SQL statements" + +LDFLAGS_SL += $(filter -lm, $(LIBS)) + +REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_stat_statements/pg_stat_statements.conf +REGRESS = select dml cursors utility level_tracking planning \ + user_activity wal cleanup oldextversions +# Disabled because these tests require "shared_preload_libraries=pg_stat_statements", +# 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 = contrib/pg_stat_statements +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pg_stat_statements/expected/cleanup.out b/contrib/pg_stat_statements/expected/cleanup.out new file mode 100644 index 0000000..36bec35 --- /dev/null +++ b/contrib/pg_stat_statements/expected/cleanup.out @@ -0,0 +1 @@ +DROP EXTENSION pg_stat_statements; diff --git a/contrib/pg_stat_statements/expected/cursors.out b/contrib/pg_stat_statements/expected/cursors.out new file mode 100644 index 0000000..46375ea --- /dev/null +++ b/contrib/pg_stat_statements/expected/cursors.out @@ -0,0 +1,70 @@ +-- +-- Cursors +-- +-- These tests require track_utility to be enabled. +SET pg_stat_statements.track_utility = TRUE; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- DECLARE +-- SELECT is normalized. +DECLARE cursor_stats_1 CURSOR WITH HOLD FOR SELECT 1; +CLOSE cursor_stats_1; +DECLARE cursor_stats_1 CURSOR WITH HOLD FOR SELECT 2; +CLOSE cursor_stats_1; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+------------------------------------------------------- + 2 | 0 | CLOSE cursor_stats_1 + 2 | 0 | DECLARE cursor_stats_1 CURSOR WITH HOLD FOR SELECT $1 + 1 | 1 | SELECT pg_stat_statements_reset() +(3 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- FETCH +BEGIN; +DECLARE cursor_stats_1 CURSOR WITH HOLD FOR SELECT 2; +DECLARE cursor_stats_2 CURSOR WITH HOLD FOR SELECT 3; +FETCH 1 IN cursor_stats_1; + ?column? +---------- + 2 +(1 row) + +FETCH 1 IN cursor_stats_2; + ?column? +---------- + 3 +(1 row) + +CLOSE cursor_stats_1; +CLOSE cursor_stats_2; +COMMIT; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+------------------------------------------------------- + 1 | 0 | BEGIN + 1 | 0 | CLOSE cursor_stats_1 + 1 | 0 | CLOSE cursor_stats_2 + 1 | 0 | COMMIT + 1 | 0 | DECLARE cursor_stats_1 CURSOR WITH HOLD FOR SELECT $1 + 1 | 0 | DECLARE cursor_stats_2 CURSOR WITH HOLD FOR SELECT $1 + 1 | 1 | FETCH 1 IN cursor_stats_1 + 1 | 1 | FETCH 1 IN cursor_stats_2 + 1 | 1 | SELECT pg_stat_statements_reset() +(9 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + diff --git a/contrib/pg_stat_statements/expected/dml.out b/contrib/pg_stat_statements/expected/dml.out new file mode 100644 index 0000000..ede47a7 --- /dev/null +++ b/contrib/pg_stat_statements/expected/dml.out @@ -0,0 +1,174 @@ +-- +-- DMLs on test table +-- +SET pg_stat_statements.track_utility = FALSE; +CREATE TEMP TABLE pgss_dml_tab (a int, b char(20)); +INSERT INTO pgss_dml_tab VALUES(generate_series(1, 10), 'aaa'); +UPDATE pgss_dml_tab SET b = 'bbb' WHERE a > 7; +DELETE FROM pgss_dml_tab WHERE a > 9; +-- explicit transaction +BEGIN; +UPDATE pgss_dml_tab SET b = '111' WHERE a = 1 ; +COMMIT; +BEGIN \; +UPDATE pgss_dml_tab SET b = '222' WHERE a = 2 \; +COMMIT ; +UPDATE pgss_dml_tab SET b = '333' WHERE a = 3 \; +UPDATE pgss_dml_tab SET b = '444' WHERE a = 4 ; +BEGIN \; +UPDATE pgss_dml_tab SET b = '555' WHERE a = 5 \; +UPDATE pgss_dml_tab SET b = '666' WHERE a = 6 \; +COMMIT ; +-- many INSERT values +INSERT INTO pgss_dml_tab (a, b) VALUES (1, 'a'), (2, 'b'), (3, 'c'); +-- SELECT with constants +SELECT * FROM pgss_dml_tab WHERE a > 5 ORDER BY a ; + a | b +---+---------------------- + 6 | 666 + 7 | aaa + 8 | bbb + 9 | bbb +(4 rows) + +SELECT * + FROM pgss_dml_tab + WHERE a > 9 + ORDER BY a ; + a | b +---+--- +(0 rows) + +-- these two need to be done on a different table +-- SELECT without constants +SELECT * FROM pgss_dml_tab ORDER BY a; + a | b +---+---------------------- + 1 | a + 1 | 111 + 2 | b + 2 | 222 + 3 | c + 3 | 333 + 4 | 444 + 5 | 555 + 6 | 666 + 7 | aaa + 8 | bbb + 9 | bbb +(12 rows) + +-- SELECT with IN clause +SELECT * FROM pgss_dml_tab WHERE a IN (1, 2, 3, 4, 5); + a | b +---+---------------------- + 1 | 111 + 2 | 222 + 3 | 333 + 4 | 444 + 5 | 555 + 1 | a + 2 | b + 3 | c +(8 rows) + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+--------------------------------------------------------------------- + 1 | 1 | DELETE FROM pgss_dml_tab WHERE a > $1 + 1 | 3 | INSERT INTO pgss_dml_tab (a, b) VALUES ($1, $2), ($3, $4), ($5, $6) + 1 | 10 | INSERT INTO pgss_dml_tab VALUES(generate_series($1, $2), $3) + 1 | 12 | SELECT * FROM pgss_dml_tab ORDER BY a + 2 | 4 | SELECT * FROM pgss_dml_tab WHERE a > $1 ORDER BY a + 1 | 8 | SELECT * FROM pgss_dml_tab WHERE a IN ($1, $2, $3, $4, $5) + 1 | 1 | SELECT pg_stat_statements_reset() + 1 | 0 | SET pg_stat_statements.track_utility = FALSE + 6 | 6 | UPDATE pgss_dml_tab SET b = $1 WHERE a = $2 + 1 | 3 | UPDATE pgss_dml_tab SET b = $1 WHERE a > $2 +(10 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- MERGE +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN MATCHED THEN UPDATE SET b = st.b || st.a::text; +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN MATCHED THEN UPDATE SET b = pgss_dml_tab.b || st.a::text; +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN MATCHED AND length(st.b) > 1 THEN UPDATE SET b = pgss_dml_tab.b || st.a::text; +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + WHEN NOT MATCHED THEN INSERT (a, b) VALUES (0, NULL); +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + WHEN NOT MATCHED THEN INSERT VALUES (0, NULL); -- same as above +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + WHEN NOT MATCHED THEN INSERT (b, a) VALUES (NULL, 0); +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + WHEN NOT MATCHED THEN INSERT (a) VALUES (0); +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN MATCHED THEN DELETE; +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN MATCHED THEN DO NOTHING; +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN NOT MATCHED THEN DO NOTHING; +DROP TABLE pgss_dml_tab; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+----------------------------------------------------------------------------------------- + 1 | 6 | MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= $1)+ + | | WHEN MATCHED AND length(st.b) > $2 THEN UPDATE SET b = pgss_dml_tab.b || st.a::text + 1 | 6 | MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= $1)+ + | | WHEN MATCHED THEN DELETE + 1 | 0 | MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= $1)+ + | | WHEN MATCHED THEN DO NOTHING + 1 | 6 | MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= $1)+ + | | WHEN MATCHED THEN UPDATE SET b = pgss_dml_tab.b || st.a::text + 1 | 6 | MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= $1)+ + | | WHEN MATCHED THEN UPDATE SET b = st.b || st.a::text + 1 | 0 | MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= $1)+ + | | WHEN NOT MATCHED THEN DO NOTHING + 1 | 0 | MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + + | | WHEN NOT MATCHED THEN INSERT (a) VALUES ($1) + 2 | 0 | MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + + | | WHEN NOT MATCHED THEN INSERT (a, b) VALUES ($1, $2) + 1 | 0 | MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + + | | WHEN NOT MATCHED THEN INSERT (b, a) VALUES ($1, $2) + 1 | 1 | SELECT pg_stat_statements_reset() +(10 rows) + +-- check that [temp] table relation extensions are tracked as writes +CREATE TABLE pgss_extend_tab (a int, b text); +CREATE TEMP TABLE pgss_extend_temp_tab (a int, b text); +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +INSERT INTO pgss_extend_tab (a, b) SELECT generate_series(1, 1000), 'something'; +INSERT INTO pgss_extend_temp_tab (a, b) SELECT generate_series(1, 1000), 'something'; +WITH sizes AS ( + SELECT + pg_relation_size('pgss_extend_tab') / current_setting('block_size')::int8 AS rel_size, + pg_relation_size('pgss_extend_temp_tab') / current_setting('block_size')::int8 AS temp_rel_size +) +SELECT + SUM(local_blks_written) >= (SELECT temp_rel_size FROM sizes) AS temp_written_ok, + SUM(local_blks_dirtied) >= (SELECT temp_rel_size FROM sizes) AS temp_dirtied_ok, + SUM(shared_blks_written) >= (SELECT rel_size FROM sizes) AS written_ok, + SUM(shared_blks_dirtied) >= (SELECT rel_size FROM sizes) AS dirtied_ok +FROM pg_stat_statements; + temp_written_ok | temp_dirtied_ok | written_ok | dirtied_ok +-----------------+-----------------+------------+------------ + t | t | t | t +(1 row) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + diff --git a/contrib/pg_stat_statements/expected/level_tracking.out b/contrib/pg_stat_statements/expected/level_tracking.out new file mode 100644 index 0000000..d924c87 --- /dev/null +++ b/contrib/pg_stat_statements/expected/level_tracking.out @@ -0,0 +1,210 @@ +-- +-- Statement level tracking +-- +SET pg_stat_statements.track_utility = TRUE; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- DO block - top-level tracking. +CREATE TABLE stats_track_tab (x int); +SET pg_stat_statements.track = 'top'; +DELETE FROM stats_track_tab; +DO $$ +BEGIN + DELETE FROM stats_track_tab; +END; +$$ LANGUAGE plpgsql; +SELECT toplevel, calls, query FROM pg_stat_statements + WHERE query LIKE '%DELETE%' ORDER BY query COLLATE "C", toplevel; + toplevel | calls | query +----------+-------+-------------------------------- + t | 1 | DELETE FROM stats_track_tab + t | 1 | DO $$ + + | | BEGIN + + | | DELETE FROM stats_track_tab;+ + | | END; + + | | $$ LANGUAGE plpgsql +(2 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- DO block - all-level tracking. +SET pg_stat_statements.track = 'all'; +DELETE FROM stats_track_tab; +DO $$ +BEGIN + DELETE FROM stats_track_tab; +END; $$; +DO LANGUAGE plpgsql $$ +BEGIN + -- this is a SELECT + PERFORM 'hello world'::TEXT; +END; $$; +SELECT toplevel, calls, query FROM pg_stat_statements + ORDER BY query COLLATE "C", toplevel; + toplevel | calls | query +----------+-------+-------------------------------------- + f | 1 | DELETE FROM stats_track_tab + t | 1 | DELETE FROM stats_track_tab + t | 1 | DO $$ + + | | BEGIN + + | | DELETE FROM stats_track_tab; + + | | END; $$ + t | 1 | DO LANGUAGE plpgsql $$ + + | | BEGIN + + | | -- this is a SELECT + + | | PERFORM 'hello world'::TEXT; + + | | END; $$ + f | 1 | SELECT $1::TEXT + t | 1 | SELECT pg_stat_statements_reset() + t | 1 | SET pg_stat_statements.track = 'all' +(7 rows) + +-- PL/pgSQL function - top-level tracking. +SET pg_stat_statements.track = 'top'; +SET pg_stat_statements.track_utility = FALSE; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +CREATE FUNCTION PLUS_TWO(i INTEGER) RETURNS INTEGER AS $$ +DECLARE + r INTEGER; +BEGIN + SELECT (i + 1 + 1.0)::INTEGER INTO r; + RETURN r; +END; $$ LANGUAGE plpgsql; +SELECT PLUS_TWO(3); + plus_two +---------- + 5 +(1 row) + +SELECT PLUS_TWO(7); + plus_two +---------- + 9 +(1 row) + +-- SQL function --- use LIMIT to keep it from being inlined +CREATE FUNCTION PLUS_ONE(i INTEGER) RETURNS INTEGER AS +$$ SELECT (i + 1.0)::INTEGER LIMIT 1 $$ LANGUAGE SQL; +SELECT PLUS_ONE(8); + plus_one +---------- + 9 +(1 row) + +SELECT PLUS_ONE(10); + plus_one +---------- + 11 +(1 row) + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+----------------------------------- + 2 | 2 | SELECT PLUS_ONE($1) + 2 | 2 | SELECT PLUS_TWO($1) + 1 | 1 | SELECT pg_stat_statements_reset() +(3 rows) + +-- PL/pgSQL function - all-level tracking. +SET pg_stat_statements.track = 'all'; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- we drop and recreate the functions to avoid any caching funnies +DROP FUNCTION PLUS_ONE(INTEGER); +DROP FUNCTION PLUS_TWO(INTEGER); +-- PL/pgSQL function +CREATE FUNCTION PLUS_TWO(i INTEGER) RETURNS INTEGER AS $$ +DECLARE + r INTEGER; +BEGIN + SELECT (i + 1 + 1.0)::INTEGER INTO r; + RETURN r; +END; $$ LANGUAGE plpgsql; +SELECT PLUS_TWO(-1); + plus_two +---------- + 1 +(1 row) + +SELECT PLUS_TWO(2); + plus_two +---------- + 4 +(1 row) + +-- SQL function --- use LIMIT to keep it from being inlined +CREATE FUNCTION PLUS_ONE(i INTEGER) RETURNS INTEGER AS +$$ SELECT (i + 1.0)::INTEGER LIMIT 1 $$ LANGUAGE SQL; +SELECT PLUS_ONE(3); + plus_one +---------- + 4 +(1 row) + +SELECT PLUS_ONE(1); + plus_one +---------- + 2 +(1 row) + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+----------------------------------- + 2 | 2 | SELECT (i + $2 + $3)::INTEGER + 2 | 2 | SELECT (i + $2)::INTEGER LIMIT $3 + 2 | 2 | SELECT PLUS_ONE($1) + 2 | 2 | SELECT PLUS_TWO($1) + 1 | 1 | SELECT pg_stat_statements_reset() +(5 rows) + +DROP FUNCTION PLUS_ONE(INTEGER); +-- +-- pg_stat_statements.track = none +-- +SET pg_stat_statements.track = 'none'; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT 1 AS "one"; + one +----- + 1 +(1 row) + +SELECT 1 + 1 AS "two"; + two +----- + 2 +(1 row) + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+------- +(0 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + diff --git a/contrib/pg_stat_statements/expected/oldextversions.out b/contrib/pg_stat_statements/expected/oldextversions.out new file mode 100644 index 0000000..efb2049 --- /dev/null +++ b/contrib/pg_stat_statements/expected/oldextversions.out @@ -0,0 +1,253 @@ +-- test old extension version entry points +CREATE EXTENSION pg_stat_statements WITH VERSION '1.4'; +-- Execution of pg_stat_statements_reset() is granted only to +-- superusers in 1.4, so this fails. +SET SESSION AUTHORIZATION pg_read_all_stats; +SELECT pg_stat_statements_reset(); +ERROR: permission denied for function pg_stat_statements_reset +RESET SESSION AUTHORIZATION; +AlTER EXTENSION pg_stat_statements UPDATE TO '1.5'; +-- Execution of pg_stat_statements_reset() should be granted to +-- pg_read_all_stats now, so this works. +SET SESSION AUTHORIZATION pg_read_all_stats; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +RESET SESSION AUTHORIZATION; +-- In 1.6, it got restricted back to superusers. +AlTER EXTENSION pg_stat_statements UPDATE TO '1.6'; +SET SESSION AUTHORIZATION pg_read_all_stats; +SELECT pg_stat_statements_reset(); +ERROR: permission denied for function pg_stat_statements_reset +RESET SESSION AUTHORIZATION; +SELECT pg_get_functiondef('pg_stat_statements_reset'::regproc); + pg_get_functiondef +------------------------------------------------------------------------------- + CREATE OR REPLACE FUNCTION public.pg_stat_statements_reset() + + RETURNS void + + LANGUAGE c + + PARALLEL SAFE + + AS '$libdir/pg_stat_statements', $function$pg_stat_statements_reset$function$+ + +(1 row) + +-- New function for pg_stat_statements_reset introduced, still +-- restricted for non-superusers. +AlTER EXTENSION pg_stat_statements UPDATE TO '1.7'; +SET SESSION AUTHORIZATION pg_read_all_stats; +SELECT pg_stat_statements_reset(); +ERROR: permission denied for function pg_stat_statements_reset +RESET SESSION AUTHORIZATION; +SELECT pg_get_functiondef('pg_stat_statements_reset'::regproc); + pg_get_functiondef +-------------------------------------------------------------------------------------------------------------------------------- + CREATE OR REPLACE FUNCTION public.pg_stat_statements_reset(userid oid DEFAULT 0, dbid oid DEFAULT 0, queryid bigint DEFAULT 0)+ + RETURNS void + + LANGUAGE c + + PARALLEL SAFE STRICT + + AS '$libdir/pg_stat_statements', $function$pg_stat_statements_reset_1_7$function$ + + +(1 row) + +\d pg_stat_statements + View "public.pg_stat_statements" + Column | Type | Collation | Nullable | Default +---------------------+------------------+-----------+----------+--------- + userid | oid | | | + dbid | oid | | | + queryid | bigint | | | + query | text | | | + calls | bigint | | | + total_time | double precision | | | + min_time | double precision | | | + max_time | double precision | | | + mean_time | double precision | | | + stddev_time | double precision | | | + rows | bigint | | | + shared_blks_hit | bigint | | | + shared_blks_read | bigint | | | + shared_blks_dirtied | bigint | | | + shared_blks_written | bigint | | | + local_blks_hit | bigint | | | + local_blks_read | bigint | | | + local_blks_dirtied | bigint | | | + local_blks_written | bigint | | | + temp_blks_read | bigint | | | + temp_blks_written | bigint | | | + blk_read_time | double precision | | | + blk_write_time | double precision | | | + +SELECT count(*) > 0 AS has_data FROM pg_stat_statements; + has_data +---------- + t +(1 row) + +-- New functions and views for pg_stat_statements in 1.8 +AlTER EXTENSION pg_stat_statements UPDATE TO '1.8'; +\d pg_stat_statements + View "public.pg_stat_statements" + Column | Type | Collation | Nullable | Default +---------------------+------------------+-----------+----------+--------- + userid | oid | | | + dbid | oid | | | + queryid | bigint | | | + query | text | | | + plans | bigint | | | + total_plan_time | double precision | | | + min_plan_time | double precision | | | + max_plan_time | double precision | | | + mean_plan_time | double precision | | | + stddev_plan_time | double precision | | | + calls | bigint | | | + total_exec_time | double precision | | | + min_exec_time | double precision | | | + max_exec_time | double precision | | | + mean_exec_time | double precision | | | + stddev_exec_time | double precision | | | + rows | bigint | | | + shared_blks_hit | bigint | | | + shared_blks_read | bigint | | | + shared_blks_dirtied | bigint | | | + shared_blks_written | bigint | | | + local_blks_hit | bigint | | | + local_blks_read | bigint | | | + local_blks_dirtied | bigint | | | + local_blks_written | bigint | | | + temp_blks_read | bigint | | | + temp_blks_written | bigint | | | + blk_read_time | double precision | | | + blk_write_time | double precision | | | + wal_records | bigint | | | + wal_fpi | bigint | | | + wal_bytes | numeric | | | + +SELECT pg_get_functiondef('pg_stat_statements_reset'::regproc); + pg_get_functiondef +-------------------------------------------------------------------------------------------------------------------------------- + CREATE OR REPLACE FUNCTION public.pg_stat_statements_reset(userid oid DEFAULT 0, dbid oid DEFAULT 0, queryid bigint DEFAULT 0)+ + RETURNS void + + LANGUAGE c + + PARALLEL SAFE STRICT + + AS '$libdir/pg_stat_statements', $function$pg_stat_statements_reset_1_7$function$ + + +(1 row) + +-- New function pg_stat_statement_info, and new function +-- and view for pg_stat_statements introduced in 1.9 +AlTER EXTENSION pg_stat_statements UPDATE TO '1.9'; +SELECT pg_get_functiondef('pg_stat_statements_info'::regproc); + pg_get_functiondef +------------------------------------------------------------------------------------------------------------------------- + CREATE OR REPLACE FUNCTION public.pg_stat_statements_info(OUT dealloc bigint, OUT stats_reset timestamp with time zone)+ + RETURNS record + + LANGUAGE c + + PARALLEL SAFE STRICT + + AS '$libdir/pg_stat_statements', $function$pg_stat_statements_info$function$ + + +(1 row) + +\d pg_stat_statements + View "public.pg_stat_statements" + Column | Type | Collation | Nullable | Default +---------------------+------------------+-----------+----------+--------- + userid | oid | | | + dbid | oid | | | + toplevel | boolean | | | + queryid | bigint | | | + query | text | | | + plans | bigint | | | + total_plan_time | double precision | | | + min_plan_time | double precision | | | + max_plan_time | double precision | | | + mean_plan_time | double precision | | | + stddev_plan_time | double precision | | | + calls | bigint | | | + total_exec_time | double precision | | | + min_exec_time | double precision | | | + max_exec_time | double precision | | | + mean_exec_time | double precision | | | + stddev_exec_time | double precision | | | + rows | bigint | | | + shared_blks_hit | bigint | | | + shared_blks_read | bigint | | | + shared_blks_dirtied | bigint | | | + shared_blks_written | bigint | | | + local_blks_hit | bigint | | | + local_blks_read | bigint | | | + local_blks_dirtied | bigint | | | + local_blks_written | bigint | | | + temp_blks_read | bigint | | | + temp_blks_written | bigint | | | + blk_read_time | double precision | | | + blk_write_time | double precision | | | + wal_records | bigint | | | + wal_fpi | bigint | | | + wal_bytes | numeric | | | + +SELECT count(*) > 0 AS has_data FROM pg_stat_statements; + has_data +---------- + t +(1 row) + +-- New functions and views for pg_stat_statements in 1.10 +AlTER EXTENSION pg_stat_statements UPDATE TO '1.10'; +\d pg_stat_statements + View "public.pg_stat_statements" + Column | Type | Collation | Nullable | Default +------------------------+------------------+-----------+----------+--------- + userid | oid | | | + dbid | oid | | | + toplevel | boolean | | | + queryid | bigint | | | + query | text | | | + plans | bigint | | | + total_plan_time | double precision | | | + min_plan_time | double precision | | | + max_plan_time | double precision | | | + mean_plan_time | double precision | | | + stddev_plan_time | double precision | | | + calls | bigint | | | + total_exec_time | double precision | | | + min_exec_time | double precision | | | + max_exec_time | double precision | | | + mean_exec_time | double precision | | | + stddev_exec_time | double precision | | | + rows | bigint | | | + shared_blks_hit | bigint | | | + shared_blks_read | bigint | | | + shared_blks_dirtied | bigint | | | + shared_blks_written | bigint | | | + local_blks_hit | bigint | | | + local_blks_read | bigint | | | + local_blks_dirtied | bigint | | | + local_blks_written | bigint | | | + temp_blks_read | bigint | | | + temp_blks_written | bigint | | | + blk_read_time | double precision | | | + blk_write_time | double precision | | | + temp_blk_read_time | double precision | | | + temp_blk_write_time | double precision | | | + wal_records | bigint | | | + wal_fpi | bigint | | | + wal_bytes | numeric | | | + jit_functions | bigint | | | + jit_generation_time | double precision | | | + jit_inlining_count | bigint | | | + jit_inlining_time | double precision | | | + jit_optimization_count | bigint | | | + jit_optimization_time | double precision | | | + jit_emission_count | bigint | | | + jit_emission_time | double precision | | | + +SELECT count(*) > 0 AS has_data FROM pg_stat_statements; + has_data +---------- + t +(1 row) + +DROP EXTENSION pg_stat_statements; diff --git a/contrib/pg_stat_statements/expected/planning.out b/contrib/pg_stat_statements/expected/planning.out new file mode 100644 index 0000000..c3561dd --- /dev/null +++ b/contrib/pg_stat_statements/expected/planning.out @@ -0,0 +1,88 @@ +-- +-- Information related to planning +-- +-- These tests require track_planning to be enabled. +SET pg_stat_statements.track_planning = TRUE; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- +-- [re]plan counting +-- +CREATE TABLE stats_plan_test (); +PREPARE prep1 AS SELECT COUNT(*) FROM stats_plan_test; +EXECUTE prep1; + count +------- + 0 +(1 row) + +EXECUTE prep1; + count +------- + 0 +(1 row) + +EXECUTE prep1; + count +------- + 0 +(1 row) + +ALTER TABLE stats_plan_test ADD COLUMN x int; +EXECUTE prep1; + count +------- + 0 +(1 row) + +SELECT 42; + ?column? +---------- + 42 +(1 row) + +SELECT 42; + ?column? +---------- + 42 +(1 row) + +SELECT 42; + ?column? +---------- + 42 +(1 row) + +SELECT plans, calls, rows, query FROM pg_stat_statements + WHERE query NOT LIKE 'PREPARE%' ORDER BY query COLLATE "C"; + plans | calls | rows | query +-------+-------+------+---------------------------------------------------------- + 0 | 1 | 0 | ALTER TABLE stats_plan_test ADD COLUMN x int + 0 | 1 | 0 | CREATE TABLE stats_plan_test () + 3 | 3 | 3 | SELECT $1 + 0 | 1 | 1 | SELECT pg_stat_statements_reset() + 1 | 0 | 0 | SELECT plans, calls, rows, query FROM pg_stat_statements+ + | | | WHERE query NOT LIKE $1 ORDER BY query COLLATE "C" +(5 rows) + +-- for the prepared statement we expect at least one replan, but cache +-- invalidations could force more +SELECT plans >= 2 AND plans <= calls AS plans_ok, calls, rows, query FROM pg_stat_statements + WHERE query LIKE 'PREPARE%' ORDER BY query COLLATE "C"; + plans_ok | calls | rows | query +----------+-------+------+------------------------------------------------------- + t | 4 | 4 | PREPARE prep1 AS SELECT COUNT(*) FROM stats_plan_test +(1 row) + +-- Cleanup +DROP TABLE stats_plan_test; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + diff --git a/contrib/pg_stat_statements/expected/select.out b/contrib/pg_stat_statements/expected/select.out new file mode 100644 index 0000000..972539b --- /dev/null +++ b/contrib/pg_stat_statements/expected/select.out @@ -0,0 +1,414 @@ +-- +-- SELECT statements +-- +CREATE EXTENSION pg_stat_statements; +SET pg_stat_statements.track_utility = FALSE; +SET pg_stat_statements.track_planning = TRUE; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- +-- simple and compound statements +-- +SELECT 1 AS "int"; + int +----- + 1 +(1 row) + +SELECT 'hello' + -- multiline + AS "text"; + text +------- + hello +(1 row) + +SELECT 'world' AS "text"; + text +------- + world +(1 row) + +-- transaction +BEGIN; +SELECT 1 AS "int"; + int +----- + 1 +(1 row) + +SELECT 'hello' AS "text"; + text +------- + hello +(1 row) + +COMMIT; +-- compound transaction +BEGIN \; +SELECT 2.0 AS "float" \; +SELECT 'world' AS "text" \; +COMMIT; + float +------- + 2.0 +(1 row) + + text +------- + world +(1 row) + +-- compound with empty statements and spurious leading spacing +\;\; SELECT 3 + 3 \;\;\; SELECT ' ' || ' !' \;\; SELECT 1 + 4 \;; + ?column? +---------- + 6 +(1 row) + + ?column? +---------- + ! +(1 row) + + ?column? +---------- + 5 +(1 row) + +-- non ;-terminated statements +SELECT 1 + 1 + 1 AS "add" \gset +SELECT :add + 1 + 1 AS "add" \; +SELECT :add + 1 + 1 AS "add" \gset + add +----- + 5 +(1 row) + +-- set operator +SELECT 1 AS i UNION SELECT 2 ORDER BY i; + i +--- + 1 + 2 +(2 rows) + +-- ? operator +select '{"a":1, "b":2}'::jsonb ? 'b'; + ?column? +---------- + t +(1 row) + +-- cte +WITH t(f) AS ( + VALUES (1.0), (2.0) +) + SELECT f FROM t ORDER BY f; + f +----- + 1.0 + 2.0 +(2 rows) + +-- prepared statement with parameter +PREPARE pgss_test (int) AS SELECT $1, 'test' LIMIT 1; +EXECUTE pgss_test(1); + ?column? | ?column? +----------+---------- + 1 | test +(1 row) + +DEALLOCATE pgss_test; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+------------------------------------------------------------------------------ + 1 | 1 | PREPARE pgss_test (int) AS SELECT $1, $2 LIMIT $3 + 4 | 4 | SELECT $1 + + | | -- multiline + + | | AS "text" + 2 | 2 | SELECT $1 + $2 + 3 | 3 | SELECT $1 + $2 + $3 AS "add" + 1 | 1 | SELECT $1 AS "float" + 2 | 2 | SELECT $1 AS "int" + 1 | 2 | SELECT $1 AS i UNION SELECT $2 ORDER BY i + 1 | 1 | SELECT $1 || $2 + 0 | 0 | SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C" + 1 | 1 | SELECT pg_stat_statements_reset() + 1 | 2 | WITH t(f) AS ( + + | | VALUES ($1), ($2) + + | | ) + + | | SELECT f FROM t ORDER BY f + 1 | 1 | select $1::jsonb ? $2 +(12 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- +-- queries with locking clauses +-- +CREATE TABLE pgss_a (id integer PRIMARY KEY); +CREATE TABLE pgss_b (id integer PRIMARY KEY, a_id integer REFERENCES pgss_a); +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- control query +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id; + id | id | a_id +----+----+------ +(0 rows) + +-- test range tables +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_a; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_a, pgss_b; -- matches plain "FOR UPDATE" + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b, pgss_a; + id | id | a_id +----+----+------ +(0 rows) + +-- test strengths +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR NO KEY UPDATE; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR SHARE; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR KEY SHARE; + id | id | a_id +----+----+------ +(0 rows) + +-- test wait policies +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE NOWAIT; + id | id | a_id +----+----+------ +(0 rows) + +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE SKIP LOCKED; + id | id | a_id +----+----+------ +(0 rows) + +SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | query +-------+------------------------------------------------------------------------------------------ + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR KEY SHARE + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR NO KEY UPDATE + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR SHARE + 2 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE NOWAIT + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_a + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b, pgss_a + 1 | SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE SKIP LOCKED + 0 | SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C" + 1 | SELECT pg_stat_statements_reset() +(12 rows) + +DROP TABLE pgss_a, pgss_b CASCADE; +-- +-- access to pg_stat_statements_info view +-- +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT dealloc FROM pg_stat_statements_info; + dealloc +--------- + 0 +(1 row) + +-- FROM [ONLY] +CREATE TABLE tbl_inh(id integer); +CREATE TABLE tbl_inh_1() INHERITS (tbl_inh); +INSERT INTO tbl_inh_1 SELECT 1; +SELECT * FROM tbl_inh; + id +---- + 1 +(1 row) + +SELECT * FROM ONLY tbl_inh; + id +---- +(0 rows) + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%FROM%tbl_inh%'; + count +------- + 2 +(1 row) + +-- WITH TIES +CREATE TABLE limitoption AS SELECT 0 AS val FROM generate_series(1, 10); +SELECT * +FROM limitoption +WHERE val < 2 +ORDER BY val +FETCH FIRST 2 ROWS WITH TIES; + val +----- + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +(10 rows) + +SELECT * +FROM limitoption +WHERE val < 2 +ORDER BY val +FETCH FIRST 2 ROW ONLY; + val +----- + 0 + 0 +(2 rows) + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%FETCH FIRST%'; + count +------- + 2 +(1 row) + +-- GROUP BY [DISTINCT] +SELECT a, b, c +FROM (VALUES (1, 2, 3), (4, NULL, 6), (7, 8, 9)) AS t (a, b, c) +GROUP BY ROLLUP(a, b), rollup(a, c) +ORDER BY a, b, c; + a | b | c +---+---+--- + 1 | 2 | 3 + 1 | 2 | + 1 | 2 | + 1 | | 3 + 1 | | 3 + 1 | | + 1 | | + 1 | | + 4 | | 6 + 4 | | 6 + 4 | | 6 + 4 | | + 4 | | + 4 | | + 4 | | + 4 | | + 7 | 8 | 9 + 7 | 8 | + 7 | 8 | + 7 | | 9 + 7 | | 9 + 7 | | + 7 | | + 7 | | + | | +(25 rows) + +SELECT a, b, c +FROM (VALUES (1, 2, 3), (4, NULL, 6), (7, 8, 9)) AS t (a, b, c) +GROUP BY DISTINCT ROLLUP(a, b), rollup(a, c) +ORDER BY a, b, c; + a | b | c +---+---+--- + 1 | 2 | 3 + 1 | 2 | + 1 | | 3 + 1 | | + 4 | | 6 + 4 | | 6 + 4 | | + 4 | | + 7 | 8 | 9 + 7 | 8 | + 7 | | 9 + 7 | | + | | +(13 rows) + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%GROUP BY%ROLLUP%'; + count +------- + 2 +(1 row) + +-- GROUPING SET agglevelsup +SELECT ( + SELECT ( + SELECT GROUPING(a,b) FROM (VALUES (1)) v2(c) + ) FROM (VALUES (1,2)) v1(a,b) GROUP BY (a,b) +) FROM (VALUES(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); + grouping +---------- + 0 + 0 + 0 +(3 rows) + +SELECT ( + SELECT ( + SELECT GROUPING(e,f) FROM (VALUES (1)) v2(c) + ) FROM (VALUES (1,2)) v1(a,b) GROUP BY (a,b) +) FROM (VALUES(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); + grouping +---------- + 3 + 0 + 1 +(3 rows) + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%SELECT GROUPING%'; + count +------- + 2 +(1 row) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + diff --git a/contrib/pg_stat_statements/expected/user_activity.out b/contrib/pg_stat_statements/expected/user_activity.out new file mode 100644 index 0000000..f3c6b6a --- /dev/null +++ b/contrib/pg_stat_statements/expected/user_activity.out @@ -0,0 +1,205 @@ +-- +-- Track user activity and reset them +-- +SET pg_stat_statements.track_utility = TRUE; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +CREATE ROLE regress_stats_user1; +CREATE ROLE regress_stats_user2; +SET ROLE regress_stats_user1; +SELECT 1 AS "ONE"; + ONE +----- + 1 +(1 row) + +SELECT 1+1 AS "TWO"; + TWO +----- + 2 +(1 row) + +RESET ROLE; +SET ROLE regress_stats_user2; +SELECT 1 AS "ONE"; + ONE +----- + 1 +(1 row) + +SELECT 1+1 AS "TWO"; + TWO +----- + 2 +(1 row) + +RESET ROLE; +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +-----------------------------------+-------+------ + CREATE ROLE regress_stats_user1 | 1 | 0 + CREATE ROLE regress_stats_user2 | 1 | 0 + RESET ROLE | 2 | 0 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT pg_stat_statements_reset() | 1 | 1 + SET ROLE regress_stats_user1 | 1 | 0 + SET ROLE regress_stats_user2 | 1 | 0 +(10 rows) + +-- +-- Don't reset anything if any of the parameter is NULL +-- +SELECT pg_stat_statements_reset(NULL); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +------------------------------------------------------------------------------+-------+------ + CREATE ROLE regress_stats_user1 | 1 | 0 + CREATE ROLE regress_stats_user2 | 1 | 0 + RESET ROLE | 2 | 0 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT pg_stat_statements_reset($1) | 1 | 1 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 1 | 10 + SET ROLE regress_stats_user1 | 1 | 0 + SET ROLE regress_stats_user2 | 1 | 0 +(12 rows) + +-- +-- remove query ('SELECT $1+$2 AS "TWO"') executed by regress_stats_user2 +-- in the current_database +-- +SELECT pg_stat_statements_reset( + (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user2'), + (SELECT d.oid FROM pg_database As d where datname = current_database()), + (SELECT s.queryid FROM pg_stat_statements AS s + WHERE s.query = 'SELECT $1+$2 AS "TWO"' LIMIT 1)); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +----------------------------------------------------------------------------------+-------+------ + CREATE ROLE regress_stats_user1 | 1 | 0 + CREATE ROLE regress_stats_user2 | 1 | 0 + RESET ROLE | 2 | 0 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1 AS "ONE" | 1 | 1 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT pg_stat_statements_reset( +| 1 | 1 + (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = $1), +| | + (SELECT d.oid FROM pg_database As d where datname = current_database()),+| | + (SELECT s.queryid FROM pg_stat_statements AS s +| | + WHERE s.query = $2 LIMIT $3)) | | + SELECT pg_stat_statements_reset($1) | 1 | 1 + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 2 | 22 + SET ROLE regress_stats_user1 | 1 | 0 + SET ROLE regress_stats_user2 | 1 | 0 +(12 rows) + +-- +-- remove query ('SELECT $1 AS "ONE"') executed by two users +-- +SELECT pg_stat_statements_reset(0,0,s.queryid) + FROM pg_stat_statements AS s WHERE s.query = 'SELECT $1 AS "ONE"'; + pg_stat_statements_reset +-------------------------- + + +(2 rows) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +----------------------------------------------------------------------------------+-------+------ + CREATE ROLE regress_stats_user1 | 1 | 0 + CREATE ROLE regress_stats_user2 | 1 | 0 + RESET ROLE | 2 | 0 + SELECT $1+$2 AS "TWO" | 1 | 1 + SELECT pg_stat_statements_reset( +| 1 | 1 + (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = $1), +| | + (SELECT d.oid FROM pg_database As d where datname = current_database()),+| | + (SELECT s.queryid FROM pg_stat_statements AS s +| | + WHERE s.query = $2 LIMIT $3)) | | + SELECT pg_stat_statements_reset($1) | 1 | 1 + SELECT pg_stat_statements_reset($1,$2,s.queryid) +| 1 | 2 + FROM pg_stat_statements AS s WHERE s.query = $3 | | + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 3 | 34 + SET ROLE regress_stats_user1 | 1 | 0 + SET ROLE regress_stats_user2 | 1 | 0 +(11 rows) + +-- +-- remove query of a user (regress_stats_user1) +-- +SELECT pg_stat_statements_reset(r.oid) + FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user1'; + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +----------------------------------------------------------------------------------+-------+------ + CREATE ROLE regress_stats_user1 | 1 | 0 + CREATE ROLE regress_stats_user2 | 1 | 0 + RESET ROLE | 2 | 0 + SELECT pg_stat_statements_reset( +| 1 | 1 + (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = $1), +| | + (SELECT d.oid FROM pg_database As d where datname = current_database()),+| | + (SELECT s.queryid FROM pg_stat_statements AS s +| | + WHERE s.query = $2 LIMIT $3)) | | + SELECT pg_stat_statements_reset($1) | 1 | 1 + SELECT pg_stat_statements_reset($1,$2,s.queryid) +| 1 | 2 + FROM pg_stat_statements AS s WHERE s.query = $3 | | + SELECT pg_stat_statements_reset() | 1 | 1 + SELECT pg_stat_statements_reset(r.oid) +| 1 | 1 + FROM pg_roles AS r WHERE r.rolname = $1 | | + SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" | 4 | 45 + SET ROLE regress_stats_user2 | 1 | 0 +(10 rows) + +-- +-- reset all +-- +SELECT pg_stat_statements_reset(0,0,0); + pg_stat_statements_reset +-------------------------- + +(1 row) + +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows +----------------------------------------+-------+------ + SELECT pg_stat_statements_reset(0,0,0) | 1 | 1 +(1 row) + +-- +-- cleanup +-- +DROP ROLE regress_stats_user1; +DROP ROLE regress_stats_user2; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + diff --git a/contrib/pg_stat_statements/expected/utility.out b/contrib/pg_stat_statements/expected/utility.out new file mode 100644 index 0000000..5a3804e --- /dev/null +++ b/contrib/pg_stat_statements/expected/utility.out @@ -0,0 +1,535 @@ +-- +-- Utility commands +-- +-- These tests require track_utility to be enabled. +SET pg_stat_statements.track_utility = TRUE; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- Tables, indexes, triggers +CREATE TEMP TABLE tab_stats (a int, b char(20)); +CREATE INDEX index_stats ON tab_stats(b, (b || 'data1'), (b || 'data2')) WHERE a > 0; +ALTER TABLE tab_stats ALTER COLUMN b set default 'a'; +ALTER TABLE tab_stats ALTER COLUMN b TYPE text USING 'data' || b; +ALTER TABLE tab_stats ADD CONSTRAINT a_nonzero CHECK (a <> 0); +DROP TABLE tab_stats \; +DROP TABLE IF EXISTS tab_stats \; +-- This DROP query uses two different strings, still they count as one entry. +DROP TABLE IF EXISTS tab_stats \; +Drop Table If Exists tab_stats \; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +NOTICE: table "tab_stats" does not exist, skipping +NOTICE: table "tab_stats" does not exist, skipping +NOTICE: table "tab_stats" does not exist, skipping + calls | rows | query +-------+------+-------------------------------------------------------------------------------------- + 1 | 0 | ALTER TABLE tab_stats ADD CONSTRAINT a_nonzero CHECK (a <> 0) + 1 | 0 | ALTER TABLE tab_stats ALTER COLUMN b TYPE text USING 'data' || b + 1 | 0 | ALTER TABLE tab_stats ALTER COLUMN b set default 'a' + 1 | 0 | CREATE INDEX index_stats ON tab_stats(b, (b || 'data1'), (b || 'data2')) WHERE a > 0 + 1 | 0 | CREATE TEMP TABLE tab_stats (a int, b char(20)) + 3 | 0 | DROP TABLE IF EXISTS tab_stats + 1 | 0 | DROP TABLE tab_stats + 1 | 1 | SELECT pg_stat_statements_reset() +(8 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- Partitions +CREATE TABLE pt_stats (a int, b int) PARTITION BY range (a); +CREATE TABLE pt_stats1 (a int, b int); +ALTER TABLE pt_stats ATTACH PARTITION pt_stats1 FOR VALUES FROM (0) TO (100); +CREATE TABLE pt_stats2 PARTITION OF pt_stats FOR VALUES FROM (100) TO (200); +CREATE INDEX pt_stats_index ON ONLY pt_stats (a); +CREATE INDEX pt_stats2_index ON ONLY pt_stats2 (a); +ALTER INDEX pt_stats_index ATTACH PARTITION pt_stats2_index; +DROP TABLE pt_stats; +-- Views +CREATE VIEW view_stats AS SELECT 1::int AS a, 2::int AS b; +ALTER VIEW view_stats ALTER COLUMN a SET DEFAULT 2; +DROP VIEW view_stats; +-- Foreign tables +CREATE FOREIGN DATA WRAPPER wrapper_stats; +CREATE SERVER server_stats FOREIGN DATA WRAPPER wrapper_stats; +CREATE FOREIGN TABLE foreign_stats (a int) SERVER server_stats; +ALTER FOREIGN TABLE foreign_stats ADD COLUMN b integer DEFAULT 1; +ALTER FOREIGN TABLE foreign_stats ADD CONSTRAINT b_nonzero CHECK (b <> 0); +DROP FOREIGN TABLE foreign_stats; +DROP SERVER server_stats; +DROP FOREIGN DATA WRAPPER wrapper_stats; +-- Functions +CREATE FUNCTION func_stats(a text DEFAULT 'a_data', b text DEFAULT lower('b_data')) + RETURNS text AS $$ SELECT $1::text || '_' || $2::text; $$ LANGUAGE SQL; +DROP FUNCTION func_stats; +-- Rules +CREATE TABLE tab_rule_stats (a int, b int); +CREATE TABLE tab_rule_stats_2 (a int, b int, c int, d int); +CREATE RULE rules_stats AS ON INSERT TO tab_rule_stats DO INSTEAD + INSERT INTO tab_rule_stats_2 VALUES(new.*, 1, 2); +DROP RULE rules_stats ON tab_rule_stats; +DROP TABLE tab_rule_stats, tab_rule_stats_2; +-- Types +CREATE TYPE stats_type as (f1 numeric(35, 6), f2 numeric(35, 2)); +DROP TYPE stats_type; +-- Triggers +CREATE TABLE trigger_tab_stats (a int, b int); +CREATE FUNCTION trigger_func_stats () RETURNS trigger LANGUAGE plpgsql + AS $$ BEGIN return OLD; end; $$; +CREATE TRIGGER trigger_tab_stats + AFTER UPDATE ON trigger_tab_stats + FOR EACH ROW WHEN (OLD.a < 0 AND OLD.b < 1 AND true) + EXECUTE FUNCTION trigger_func_stats(); +DROP TABLE trigger_tab_stats; +-- Policies +CREATE TABLE tab_policy_stats (a int, b int); +CREATE POLICY policy_stats ON tab_policy_stats USING (a = 5) WITH CHECK (b < 5); +DROP TABLE tab_policy_stats; +-- Statistics +CREATE TABLE tab_expr_stats (a int, b int); +CREATE STATISTICS tab_expr_stats_1 (mcv) ON a, (2*a), (3*b) FROM tab_expr_stats; +DROP TABLE tab_expr_stats; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+------------------------------------------------------------------------------------- + 1 | 0 | ALTER FOREIGN TABLE foreign_stats ADD COLUMN b integer DEFAULT 1 + 1 | 0 | ALTER FOREIGN TABLE foreign_stats ADD CONSTRAINT b_nonzero CHECK (b <> 0) + 1 | 0 | ALTER INDEX pt_stats_index ATTACH PARTITION pt_stats2_index + 1 | 0 | ALTER TABLE pt_stats ATTACH PARTITION pt_stats1 FOR VALUES FROM (0) TO (100) + 1 | 0 | ALTER VIEW view_stats ALTER COLUMN a SET DEFAULT 2 + 1 | 0 | CREATE FOREIGN DATA WRAPPER wrapper_stats + 1 | 0 | CREATE FOREIGN TABLE foreign_stats (a int) SERVER server_stats + 1 | 0 | CREATE FUNCTION func_stats(a text DEFAULT 'a_data', b text DEFAULT lower('b_data'))+ + | | RETURNS text AS $$ SELECT $1::text || '_' || $2::text; $$ LANGUAGE SQL + 1 | 0 | CREATE FUNCTION trigger_func_stats () RETURNS trigger LANGUAGE plpgsql + + | | AS $$ BEGIN return OLD; end; $$ + 1 | 0 | CREATE INDEX pt_stats2_index ON ONLY pt_stats2 (a) + 1 | 0 | CREATE INDEX pt_stats_index ON ONLY pt_stats (a) + 1 | 0 | CREATE POLICY policy_stats ON tab_policy_stats USING (a = 5) WITH CHECK (b < 5) + 1 | 0 | CREATE RULE rules_stats AS ON INSERT TO tab_rule_stats DO INSTEAD + + | | INSERT INTO tab_rule_stats_2 VALUES(new.*, 1, 2) + 1 | 0 | CREATE SERVER server_stats FOREIGN DATA WRAPPER wrapper_stats + 1 | 0 | CREATE STATISTICS tab_expr_stats_1 (mcv) ON a, (2*a), (3*b) FROM tab_expr_stats + 1 | 0 | CREATE TABLE pt_stats (a int, b int) PARTITION BY range (a) + 1 | 0 | CREATE TABLE pt_stats1 (a int, b int) + 1 | 0 | CREATE TABLE pt_stats2 PARTITION OF pt_stats FOR VALUES FROM (100) TO (200) + 1 | 0 | CREATE TABLE tab_expr_stats (a int, b int) + 1 | 0 | CREATE TABLE tab_policy_stats (a int, b int) + 1 | 0 | CREATE TABLE tab_rule_stats (a int, b int) + 1 | 0 | CREATE TABLE tab_rule_stats_2 (a int, b int, c int, d int) + 1 | 0 | CREATE TABLE trigger_tab_stats (a int, b int) + 1 | 0 | CREATE TRIGGER trigger_tab_stats + + | | AFTER UPDATE ON trigger_tab_stats + + | | FOR EACH ROW WHEN (OLD.a < 0 AND OLD.b < 1 AND true) + + | | EXECUTE FUNCTION trigger_func_stats() + 1 | 0 | CREATE TYPE stats_type as (f1 numeric(35, 6), f2 numeric(35, 2)) + 1 | 0 | CREATE VIEW view_stats AS SELECT 1::int AS a, 2::int AS b + 1 | 0 | DROP FOREIGN DATA WRAPPER wrapper_stats + 1 | 0 | DROP FOREIGN TABLE foreign_stats + 1 | 0 | DROP FUNCTION func_stats + 1 | 0 | DROP RULE rules_stats ON tab_rule_stats + 1 | 0 | DROP SERVER server_stats + 1 | 0 | DROP TABLE pt_stats + 1 | 0 | DROP TABLE tab_expr_stats + 1 | 0 | DROP TABLE tab_policy_stats + 1 | 0 | DROP TABLE tab_rule_stats, tab_rule_stats_2 + 1 | 0 | DROP TABLE trigger_tab_stats + 1 | 0 | DROP TYPE stats_type + 1 | 0 | DROP VIEW view_stats + 1 | 1 | SELECT pg_stat_statements_reset() +(39 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- Transaction statements +BEGIN; +ABORT; +BEGIN; +ROLLBACK; +-- WORK +BEGIN WORK; +COMMIT WORK; +BEGIN WORK; +ABORT WORK; +-- TRANSACTION +BEGIN TRANSACTION; +COMMIT TRANSACTION; +BEGIN TRANSACTION; +ABORT TRANSACTION; +-- More isolation levels +BEGIN TRANSACTION DEFERRABLE; +COMMIT TRANSACTION AND NO CHAIN; +BEGIN ISOLATION LEVEL SERIALIZABLE; +COMMIT; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +COMMIT; +-- List of A_Const nodes, same lists. +BEGIN TRANSACTION READ ONLY, READ WRITE, DEFERRABLE, NOT DEFERRABLE; +COMMIT; +BEGIN TRANSACTION NOT DEFERRABLE, READ ONLY, READ WRITE, DEFERRABLE; +COMMIT; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+--------------------------------------------------------------------- + 4 | 0 | ABORT + 6 | 0 | BEGIN + 2 | 0 | BEGIN ISOLATION LEVEL SERIALIZABLE + 1 | 0 | BEGIN TRANSACTION DEFERRABLE + 1 | 0 | BEGIN TRANSACTION NOT DEFERRABLE, READ ONLY, READ WRITE, DEFERRABLE + 1 | 0 | BEGIN TRANSACTION READ ONLY, READ WRITE, DEFERRABLE, NOT DEFERRABLE + 7 | 0 | COMMIT WORK + 1 | 1 | SELECT pg_stat_statements_reset() +(8 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- EXPLAIN statements +-- A Query is used, normalized by the query jumbling. +EXPLAIN (costs off) SELECT 1; + QUERY PLAN +------------ + Result +(1 row) + +EXPLAIN (costs off) SELECT 2; + QUERY PLAN +------------ + Result +(1 row) + +EXPLAIN (costs off) SELECT a FROM generate_series(1,10) AS tab(a) WHERE a = 3; + QUERY PLAN +-------------------------------------- + Function Scan on generate_series tab + Filter: (a = 3) +(2 rows) + +EXPLAIN (costs off) SELECT a FROM generate_series(1,10) AS tab(a) WHERE a = 7; + QUERY PLAN +-------------------------------------- + Function Scan on generate_series tab + Filter: (a = 7) +(2 rows) + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+--------------------------------------------------------------------------------- + 2 | 0 | EXPLAIN (costs off) SELECT $1 + 2 | 0 | EXPLAIN (costs off) SELECT a FROM generate_series($1,$2) AS tab(a) WHERE a = $3 + 1 | 1 | SELECT pg_stat_statements_reset() +(3 rows) + +-- CALL +CREATE OR REPLACE PROCEDURE sum_one(i int) AS $$ +DECLARE + r int; +BEGIN + SELECT (i + i)::int INTO r; +END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE PROCEDURE sum_two(i int, j int) AS $$ +DECLARE + r int; +BEGIN + SELECT (i + j)::int INTO r; +END; $$ LANGUAGE plpgsql; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +CALL sum_one(3); +CALL sum_one(199); +CALL sum_two(1,1); +CALL sum_two(1,2); +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+----------------------------------- + 1 | 0 | CALL sum_one(199) + 1 | 0 | CALL sum_one(3) + 1 | 0 | CALL sum_two(1,1) + 1 | 0 | CALL sum_two(1,2) + 1 | 1 | SELECT pg_stat_statements_reset() +(5 rows) + +-- COPY +CREATE TABLE copy_stats (a int, b int); +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- Some queries with A_Const nodes. +COPY (SELECT 1) TO STDOUT; +1 +COPY (SELECT 2) TO STDOUT; +2 +COPY (INSERT INTO copy_stats VALUES (1, 1) RETURNING *) TO STDOUT; +1 1 +COPY (INSERT INTO copy_stats VALUES (2, 2) RETURNING *) TO STDOUT; +2 2 +COPY (UPDATE copy_stats SET b = b + 1 RETURNING *) TO STDOUT; +1 2 +2 3 +COPY (UPDATE copy_stats SET b = b + 2 RETURNING *) TO STDOUT; +1 4 +2 5 +COPY (DELETE FROM copy_stats WHERE a = 1 RETURNING *) TO STDOUT; +1 4 +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+------------------------------------------------------------------- + 1 | 1 | COPY (DELETE FROM copy_stats WHERE a = 1 RETURNING *) TO STDOUT + 1 | 1 | COPY (INSERT INTO copy_stats VALUES (1, 1) RETURNING *) TO STDOUT + 1 | 1 | COPY (INSERT INTO copy_stats VALUES (2, 2) RETURNING *) TO STDOUT + 1 | 1 | COPY (SELECT 1) TO STDOUT + 1 | 1 | COPY (SELECT 2) TO STDOUT + 1 | 2 | COPY (UPDATE copy_stats SET b = b + 1 RETURNING *) TO STDOUT + 1 | 2 | COPY (UPDATE copy_stats SET b = b + 2 RETURNING *) TO STDOUT + 1 | 1 | SELECT pg_stat_statements_reset() +(8 rows) + +DROP TABLE copy_stats; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- CREATE TABLE AS +-- SELECT queries are normalized, creating matching query IDs. +CREATE TABLE ctas_stats_1 AS SELECT 1 AS a; +DROP TABLE ctas_stats_1; +CREATE TABLE ctas_stats_1 AS SELECT 2 AS a; +DROP TABLE ctas_stats_1; +CREATE TABLE ctas_stats_2 AS + SELECT a AS col1, 2::int AS col2 + FROM generate_series(1, 10) AS tab(a) WHERE a < 5 AND a > 2; +DROP TABLE ctas_stats_2; +CREATE TABLE ctas_stats_2 AS + SELECT a AS col1, 4::int AS col2 + FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 1; +DROP TABLE ctas_stats_2; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+-------------------------------------------------------------------- + 2 | 2 | CREATE TABLE ctas_stats_1 AS SELECT $1 AS a + 2 | 4 | CREATE TABLE ctas_stats_2 AS + + | | SELECT a AS col1, $1::int AS col2 + + | | FROM generate_series($2, $3) AS tab(a) WHERE a < $4 AND a > $5 + 2 | 0 | DROP TABLE ctas_stats_1 + 2 | 0 | DROP TABLE ctas_stats_2 + 1 | 1 | SELECT pg_stat_statements_reset() +(5 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- CREATE MATERIALIZED VIEW +-- SELECT queries are normalized, creating matching query IDs. +CREATE MATERIALIZED VIEW matview_stats_1 AS + SELECT a AS col1, 2::int AS col2 + FROM generate_series(1, 10) AS tab(a) WHERE a < 5 AND a > 2; +DROP MATERIALIZED VIEW matview_stats_1; +CREATE MATERIALIZED VIEW matview_stats_1 AS + SELECT a AS col1, 4::int AS col2 + FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 3; +DROP MATERIALIZED VIEW matview_stats_1; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+-------------------------------------------------------------------- + 2 | 2 | CREATE MATERIALIZED VIEW matview_stats_1 AS + + | | SELECT a AS col1, $1::int AS col2 + + | | FROM generate_series($2, $3) AS tab(a) WHERE a < $4 AND a > $5 + 2 | 0 | DROP MATERIALIZED VIEW matview_stats_1 + 1 | 1 | SELECT pg_stat_statements_reset() +(3 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- CREATE VIEW +CREATE VIEW view_stats_1 AS + SELECT a AS col1, 2::int AS col2 + FROM generate_series(1, 10) AS tab(a) WHERE a < 5 AND a > 2; +DROP VIEW view_stats_1; +CREATE VIEW view_stats_1 AS + SELECT a AS col1, 4::int AS col2 + FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 3; +DROP VIEW view_stats_1; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+----------------------------------------------------------------- + 1 | 0 | CREATE VIEW view_stats_1 AS + + | | SELECT a AS col1, 2::int AS col2 + + | | FROM generate_series(1, 10) AS tab(a) WHERE a < 5 AND a > 2 + 1 | 0 | CREATE VIEW view_stats_1 AS + + | | SELECT a AS col1, 4::int AS col2 + + | | FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 3 + 2 | 0 | DROP VIEW view_stats_1 + 1 | 1 | SELECT pg_stat_statements_reset() +(4 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- Domains +CREATE DOMAIN domain_stats AS int CHECK (VALUE > 0); +ALTER DOMAIN domain_stats SET DEFAULT '3'; +ALTER DOMAIN domain_stats ADD CONSTRAINT higher_than_one CHECK (VALUE > 1); +DROP DOMAIN domain_stats; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+---------------------------------------------------------------------------- + 1 | 0 | ALTER DOMAIN domain_stats ADD CONSTRAINT higher_than_one CHECK (VALUE > 1) + 1 | 0 | ALTER DOMAIN domain_stats SET DEFAULT '3' + 1 | 0 | CREATE DOMAIN domain_stats AS int CHECK (VALUE > 0) + 1 | 0 | DROP DOMAIN domain_stats + 1 | 1 | SELECT pg_stat_statements_reset() +(5 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- SET statements. +-- These use two different strings, still they count as one entry. +SET work_mem = '1MB'; +Set work_mem = '1MB'; +SET work_mem = '2MB'; +RESET work_mem; +SET enable_seqscan = off; +SET enable_seqscan = on; +RESET enable_seqscan; +-- SET TRANSACTION ISOLATION +BEGIN; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +COMMIT; +-- SET SESSION CHARACTERISTICS +SET SESSION SESSION AUTHORIZATION DEFAULT; +RESET SESSION AUTHORIZATION; +BEGIN; +SET LOCAL SESSION AUTHORIZATION DEFAULT; +RESET SESSION AUTHORIZATION; +COMMIT; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+------------------------------------------------- + 2 | 0 | BEGIN + 2 | 0 | COMMIT + 2 | 0 | RESET SESSION AUTHORIZATION + 1 | 0 | RESET enable_seqscan + 1 | 0 | RESET work_mem + 1 | 1 | SELECT pg_stat_statements_reset() + 1 | 0 | SET LOCAL SESSION AUTHORIZATION DEFAULT + 1 | 0 | SET SESSION SESSION AUTHORIZATION DEFAULT + 1 | 0 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED + 1 | 0 | SET TRANSACTION ISOLATION LEVEL REPEATABLE READ + 1 | 0 | SET TRANSACTION ISOLATION LEVEL SERIALIZABLE + 1 | 0 | SET enable_seqscan = off + 1 | 0 | SET enable_seqscan = on + 2 | 0 | SET work_mem = '1MB' + 1 | 0 | SET work_mem = '2MB' +(15 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + +-- +-- Track the total number of rows retrieved or affected by the utility +-- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW, +-- REFRESH MATERIALIZED VIEW and SELECT INTO +-- +CREATE TABLE pgss_ctas AS SELECT a, 'ctas' b FROM generate_series(1, 10) a; +SELECT generate_series(1, 10) c INTO pgss_select_into; +COPY pgss_ctas (a, b) FROM STDIN; +CREATE MATERIALIZED VIEW pgss_matv AS SELECT * FROM pgss_ctas; +REFRESH MATERIALIZED VIEW pgss_matv; +BEGIN; +DECLARE pgss_cursor CURSOR FOR SELECT * FROM pgss_matv; +FETCH NEXT pgss_cursor; + a | b +---+------ + 1 | ctas +(1 row) + +FETCH FORWARD 5 pgss_cursor; + a | b +---+------ + 2 | ctas + 3 | ctas + 4 | ctas + 5 | ctas + 6 | ctas +(5 rows) + +FETCH FORWARD ALL pgss_cursor; + a | b +----+------ + 7 | ctas + 8 | ctas + 9 | ctas + 10 | ctas + 11 | copy + 12 | copy + 13 | copy +(7 rows) + +COMMIT; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + calls | rows | query +-------+------+------------------------------------------------------------------------- + 1 | 0 | BEGIN + 1 | 0 | COMMIT + 1 | 3 | COPY pgss_ctas (a, b) FROM STDIN + 1 | 13 | CREATE MATERIALIZED VIEW pgss_matv AS SELECT * FROM pgss_ctas + 1 | 10 | CREATE TABLE pgss_ctas AS SELECT a, $1 b FROM generate_series($2, $3) a + 1 | 0 | DECLARE pgss_cursor CURSOR FOR SELECT * FROM pgss_matv + 1 | 5 | FETCH FORWARD 5 pgss_cursor + 1 | 7 | FETCH FORWARD ALL pgss_cursor + 1 | 1 | FETCH NEXT pgss_cursor + 1 | 13 | REFRESH MATERIALIZED VIEW pgss_matv + 1 | 10 | SELECT generate_series($1, $2) c INTO pgss_select_into + 1 | 1 | SELECT pg_stat_statements_reset() +(12 rows) + +DROP MATERIALIZED VIEW pgss_matv; +DROP TABLE pgss_ctas; +DROP TABLE pgss_select_into; +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + diff --git a/contrib/pg_stat_statements/expected/wal.out b/contrib/pg_stat_statements/expected/wal.out new file mode 100644 index 0000000..9896ba2 --- /dev/null +++ b/contrib/pg_stat_statements/expected/wal.out @@ -0,0 +1,30 @@ +-- +-- Validate WAL generation metrics +-- +SET pg_stat_statements.track_utility = FALSE; +CREATE TABLE pgss_wal_tab (a int, b char(20)); +INSERT INTO pgss_wal_tab VALUES(generate_series(1, 10), 'aaa'); +UPDATE pgss_wal_tab SET b = 'bbb' WHERE a > 7; +DELETE FROM pgss_wal_tab WHERE a > 9; +DROP TABLE pgss_wal_tab; +-- Check WAL is generated for the above statements +SELECT query, calls, rows, +wal_bytes > 0 as wal_bytes_generated, +wal_records > 0 as wal_records_generated, +wal_records >= rows as wal_records_ge_rows +FROM pg_stat_statements ORDER BY query COLLATE "C"; + query | calls | rows | wal_bytes_generated | wal_records_generated | wal_records_ge_rows +--------------------------------------------------------------+-------+------+---------------------+-----------------------+--------------------- + DELETE FROM pgss_wal_tab WHERE a > $1 | 1 | 1 | t | t | t + INSERT INTO pgss_wal_tab VALUES(generate_series($1, $2), $3) | 1 | 10 | t | t | t + SELECT pg_stat_statements_reset() | 1 | 1 | f | f | f + SET pg_stat_statements.track_utility = FALSE | 1 | 0 | f | f | t + UPDATE pgss_wal_tab SET b = $1 WHERE a > $2 | 1 | 3 | t | t | t +(5 rows) + +SELECT pg_stat_statements_reset(); + pg_stat_statements_reset +-------------------------- + +(1 row) + diff --git a/contrib/pg_stat_statements/meson.build b/contrib/pg_stat_statements/meson.build new file mode 100644 index 0000000..3e3062a --- /dev/null +++ b/contrib/pg_stat_statements/meson.build @@ -0,0 +1,60 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pg_stat_statements_sources = files( + 'pg_stat_statements.c', +) + +if host_system == 'windows' + pg_stat_statements_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pg_stat_statements', + '--FILEDESC', 'pg_stat_statements - execution statistics of SQL statements',]) +endif + +pg_stat_statements = shared_module('pg_stat_statements', + pg_stat_statements_sources, + kwargs: contrib_mod_args + { + 'dependencies': contrib_mod_args['dependencies'], + }, +) +contrib_targets += pg_stat_statements + +install_data( + 'pg_stat_statements.control', + 'pg_stat_statements--1.4.sql', + 'pg_stat_statements--1.9--1.10.sql', + 'pg_stat_statements--1.8--1.9.sql', + 'pg_stat_statements--1.7--1.8.sql', + 'pg_stat_statements--1.6--1.7.sql', + 'pg_stat_statements--1.5--1.6.sql', + 'pg_stat_statements--1.4--1.5.sql', + 'pg_stat_statements--1.3--1.4.sql', + 'pg_stat_statements--1.2--1.3.sql', + 'pg_stat_statements--1.1--1.2.sql', + 'pg_stat_statements--1.0--1.1.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'pg_stat_statements', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'select', + 'dml', + 'cursors', + 'utility', + 'level_tracking', + 'planning', + 'user_activity', + 'wal', + 'cleanup', + 'oldextversions', + ], + 'regress_args': ['--temp-config', files('pg_stat_statements.conf')], + # Disabled because these tests require + # "shared_preload_libraries=pg_stat_statements", which typical + # runningcheck users do not have (e.g. buildfarm clients). + 'runningcheck': false, + }, +} diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.0--1.1.sql b/contrib/pg_stat_statements/pg_stat_statements--1.0--1.1.sql new file mode 100644 index 0000000..5be281e --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.0--1.1.sql @@ -0,0 +1,42 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.1'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements( + OUT userid oid, + OUT dbid oid, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.1--1.2.sql b/contrib/pg_stat_statements/pg_stat_statements--1.1--1.2.sql new file mode 100644 index 0000000..74ae438 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.1--1.2.sql @@ -0,0 +1,43 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.2'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_2' +LANGUAGE C STRICT VOLATILE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.2--1.3.sql b/contrib/pg_stat_statements/pg_stat_statements--1.2--1.3.sql new file mode 100644 index 0000000..a56f151 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.2--1.3.sql @@ -0,0 +1,47 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.3'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_3' +LANGUAGE C STRICT VOLATILE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.3--1.4.sql b/contrib/pg_stat_statements/pg_stat_statements--1.3--1.4.sql new file mode 100644 index 0000000..ae70c1f --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.3--1.4.sql @@ -0,0 +1,7 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.4'" to load this file. \quit + +ALTER FUNCTION pg_stat_statements_reset() PARALLEL SAFE; +ALTER FUNCTION pg_stat_statements(boolean) PARALLEL SAFE; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.4--1.5.sql b/contrib/pg_stat_statements/pg_stat_statements--1.4--1.5.sql new file mode 100644 index 0000000..9c76122 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.4--1.5.sql @@ -0,0 +1,6 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.4--1.5.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.5'" to load this file. \quit + +GRANT EXECUTE ON FUNCTION pg_stat_statements_reset() TO pg_read_all_stats; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.4.sql b/contrib/pg_stat_statements/pg_stat_statements--1.4.sql new file mode 100644 index 0000000..58cdf60 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.4.sql @@ -0,0 +1,48 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.4.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_stat_statements" to load this file. \quit + +-- Register functions. +CREATE FUNCTION pg_stat_statements_reset() +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT calls int8, + OUT total_time float8, + OUT min_time float8, + OUT max_time float8, + OUT mean_time float8, + OUT stddev_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_3' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- Register a view on the function for ease of use. +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_statements_reset() FROM PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.5--1.6.sql b/contrib/pg_stat_statements/pg_stat_statements--1.5--1.6.sql new file mode 100644 index 0000000..4f8c7f7 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.5--1.6.sql @@ -0,0 +1,7 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.5--1.6.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.6'" to load this file. \quit + +-- Execution is only allowed for superusers, fixing issue with 1.5. +REVOKE EXECUTE ON FUNCTION pg_stat_statements_reset() FROM pg_read_all_stats; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.6--1.7.sql b/contrib/pg_stat_statements/pg_stat_statements--1.6--1.7.sql new file mode 100644 index 0000000..6fc3fed --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.6--1.7.sql @@ -0,0 +1,22 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.6--1.7.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.7'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements_reset(); + +/* Then we can drop them */ +DROP FUNCTION pg_stat_statements_reset(); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements_reset(IN userid Oid DEFAULT 0, + IN dbid Oid DEFAULT 0, + IN queryid bigint DEFAULT 0 +) +RETURNS void +AS 'MODULE_PATHNAME', 'pg_stat_statements_reset_1_7' +LANGUAGE C STRICT PARALLEL SAFE; + +-- Don't want this to be available to non-superusers. +REVOKE ALL ON FUNCTION pg_stat_statements_reset(Oid, Oid, bigint) FROM PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql b/contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql new file mode 100644 index 0000000..0f63f08 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql @@ -0,0 +1,56 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.7--1.8.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.8'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_8' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql b/contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql new file mode 100644 index 0000000..c45223f --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql @@ -0,0 +1,71 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.8--1.9.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.9'" to load this file. \quit + +--- Define pg_stat_statements_info +CREATE FUNCTION pg_stat_statements_info( + OUT dealloc bigint, + OUT stats_reset timestamp with time zone +) +RETURNS record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements_info AS + SELECT * FROM pg_stat_statements_info(); + +GRANT SELECT ON pg_stat_statements_info TO PUBLIC; + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_9' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements--1.9--1.10.sql b/contrib/pg_stat_statements/pg_stat_statements--1.9--1.10.sql new file mode 100644 index 0000000..811813c --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements--1.9--1.10.sql @@ -0,0 +1,67 @@ +/* contrib/pg_stat_statements/pg_stat_statements--1.9--1.10.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_stat_statements UPDATE TO '1.10'" to load this file. \quit + +/* First we have to remove them from the extension */ +ALTER EXTENSION pg_stat_statements DROP VIEW pg_stat_statements; +ALTER EXTENSION pg_stat_statements DROP FUNCTION pg_stat_statements(boolean); + +/* Then we can drop them */ +DROP VIEW pg_stat_statements; +DROP FUNCTION pg_stat_statements(boolean); + +/* Now redefine */ +CREATE FUNCTION pg_stat_statements(IN showtext boolean, + OUT userid oid, + OUT dbid oid, + OUT toplevel bool, + OUT queryid bigint, + OUT query text, + OUT plans int8, + OUT total_plan_time float8, + OUT min_plan_time float8, + OUT max_plan_time float8, + OUT mean_plan_time float8, + OUT stddev_plan_time float8, + OUT calls int8, + OUT total_exec_time float8, + OUT min_exec_time float8, + OUT max_exec_time float8, + OUT mean_exec_time float8, + OUT stddev_exec_time float8, + OUT rows int8, + OUT shared_blks_hit int8, + OUT shared_blks_read int8, + OUT shared_blks_dirtied int8, + OUT shared_blks_written int8, + OUT local_blks_hit int8, + OUT local_blks_read int8, + OUT local_blks_dirtied int8, + OUT local_blks_written int8, + OUT temp_blks_read int8, + OUT temp_blks_written int8, + OUT blk_read_time float8, + OUT blk_write_time float8, + OUT temp_blk_read_time float8, + OUT temp_blk_write_time float8, + OUT wal_records int8, + OUT wal_fpi int8, + OUT wal_bytes numeric, + OUT jit_functions int8, + OUT jit_generation_time float8, + OUT jit_inlining_count int8, + OUT jit_inlining_time float8, + OUT jit_optimization_count int8, + OUT jit_optimization_time float8, + OUT jit_emission_count int8, + OUT jit_emission_time float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_stat_statements_1_10' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +CREATE VIEW pg_stat_statements AS + SELECT * FROM pg_stat_statements(true); + +GRANT SELECT ON pg_stat_statements TO PUBLIC; diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c new file mode 100644 index 0000000..55b957d --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -0,0 +1,2877 @@ +/*------------------------------------------------------------------------- + * + * pg_stat_statements.c + * Track statement planning and execution times as well as resource + * usage across a whole database cluster. + * + * Execution costs are totaled for each distinct source query, and kept in + * a shared hashtable. (We track only as many distinct queries as will fit + * in the designated amount of shared memory.) + * + * Starting in Postgres 9.2, this module normalized query entries. As of + * Postgres 14, the normalization is done by the core if compute_query_id is + * enabled, or optionally by third-party modules. + * + * To facilitate presenting entries to users, we create "representative" query + * strings in which constants are replaced with parameter symbols ($n), to + * make it clearer what a normalized entry can represent. To save on shared + * memory, and to avoid having to truncate oversized query strings, we store + * these strings in a temporary external query-texts file. Offsets into this + * file are kept in shared memory. + * + * Note about locking issues: to create or delete an entry in the shared + * hashtable, one must hold pgss->lock exclusively. Modifying any field + * in an entry except the counters requires the same. To look up an entry, + * one must hold the lock shared. To read or update the counters within + * an entry, one must hold the lock shared or exclusive (so the entry doesn't + * disappear!) and also take the entry's mutex spinlock. + * The shared state variable pgss->extent (the next free spot in the external + * query-text file) should be accessed only while holding either the + * pgss->mutex spinlock, or exclusive lock on pgss->lock. We use the mutex to + * allow reserving file space while holding only shared lock on pgss->lock. + * Rewriting the entire external query-text file, eg for garbage collection, + * requires holding pgss->lock exclusively; this allows individual entries + * in the file to be read or written while holding only shared lock. + * + * + * Copyright (c) 2008-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pg_stat_statements/pg_stat_statements.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include +#include +#include + +#include "access/parallel.h" +#include "catalog/pg_authid.h" +#include "common/hashfn.h" +#include "executor/instrument.h" +#include "funcapi.h" +#include "jit/jit.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "nodes/queryjumble.h" +#include "optimizer/planner.h" +#include "parser/analyze.h" +#include "parser/parsetree.h" +#include "parser/scanner.h" +#include "parser/scansup.h" +#include "pgstat.h" +#include "storage/fd.h" +#include "storage/ipc.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" +#include "storage/spin.h" +#include "tcop/utility.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/timestamp.h" + +PG_MODULE_MAGIC; + +/* Location of permanent stats file (valid when database is shut down) */ +#define PGSS_DUMP_FILE PGSTAT_STAT_PERMANENT_DIRECTORY "/pg_stat_statements.stat" + +/* + * Location of external query text file. + */ +#define PGSS_TEXT_FILE PG_STAT_TMP_DIR "/pgss_query_texts.stat" + +/* Magic number identifying the stats file format */ +static const uint32 PGSS_FILE_HEADER = 0x20220408; + +/* PostgreSQL major version number, changes in which invalidate all entries */ +static const uint32 PGSS_PG_MAJOR_VERSION = PG_VERSION_NUM / 100; + +/* XXX: Should USAGE_EXEC reflect execution time and/or buffer usage? */ +#define USAGE_EXEC(duration) (1.0) +#define USAGE_INIT (1.0) /* including initial planning */ +#define ASSUMED_MEDIAN_INIT (10.0) /* initial assumed median usage */ +#define ASSUMED_LENGTH_INIT 1024 /* initial assumed mean query length */ +#define USAGE_DECREASE_FACTOR (0.99) /* decreased every entry_dealloc */ +#define STICKY_DECREASE_FACTOR (0.50) /* factor for sticky entries */ +#define USAGE_DEALLOC_PERCENT 5 /* free this % of entries at once */ +#define IS_STICKY(c) ((c.calls[PGSS_PLAN] + c.calls[PGSS_EXEC]) == 0) + +/* + * Utility statements that pgss_ProcessUtility and pgss_post_parse_analyze + * ignores. + */ +#define PGSS_HANDLED_UTILITY(n) (!IsA(n, ExecuteStmt) && \ + !IsA(n, PrepareStmt) && \ + !IsA(n, DeallocateStmt)) + +/* + * Extension version number, for supporting older extension versions' objects + */ +typedef enum pgssVersion +{ + PGSS_V1_0 = 0, + PGSS_V1_1, + PGSS_V1_2, + PGSS_V1_3, + PGSS_V1_8, + PGSS_V1_9, + PGSS_V1_10 +} pgssVersion; + +typedef enum pgssStoreKind +{ + PGSS_INVALID = -1, + + /* + * PGSS_PLAN and PGSS_EXEC must be respectively 0 and 1 as they're used to + * reference the underlying values in the arrays in the Counters struct, + * and this order is required in pg_stat_statements_internal(). + */ + PGSS_PLAN = 0, + PGSS_EXEC, + + PGSS_NUMKIND /* Must be last value of this enum */ +} pgssStoreKind; + +/* + * Hashtable key that defines the identity of a hashtable entry. We separate + * queries by user and by database even if they are otherwise identical. + * + * If you add a new key to this struct, make sure to teach pgss_store() to + * zero the padding bytes. Otherwise, things will break, because pgss_hash is + * created using HASH_BLOBS, and thus tag_hash is used to hash this. + + */ +typedef struct pgssHashKey +{ + Oid userid; /* user OID */ + Oid dbid; /* database OID */ + uint64 queryid; /* query identifier */ + bool toplevel; /* query executed at top level */ +} pgssHashKey; + +/* + * The actual stats counters kept within pgssEntry. + */ +typedef struct Counters +{ + int64 calls[PGSS_NUMKIND]; /* # of times planned/executed */ + double total_time[PGSS_NUMKIND]; /* total planning/execution time, + * in msec */ + double min_time[PGSS_NUMKIND]; /* minimum planning/execution time in + * msec */ + double max_time[PGSS_NUMKIND]; /* maximum planning/execution time in + * msec */ + double mean_time[PGSS_NUMKIND]; /* mean planning/execution time in + * msec */ + double sum_var_time[PGSS_NUMKIND]; /* sum of variances in + * planning/execution time in msec */ + int64 rows; /* total # of retrieved or affected rows */ + int64 shared_blks_hit; /* # of shared buffer hits */ + int64 shared_blks_read; /* # of shared disk blocks read */ + int64 shared_blks_dirtied; /* # of shared disk blocks dirtied */ + int64 shared_blks_written; /* # of shared disk blocks written */ + int64 local_blks_hit; /* # of local buffer hits */ + int64 local_blks_read; /* # of local disk blocks read */ + int64 local_blks_dirtied; /* # of local disk blocks dirtied */ + int64 local_blks_written; /* # of local disk blocks written */ + int64 temp_blks_read; /* # of temp blocks read */ + int64 temp_blks_written; /* # of temp blocks written */ + double blk_read_time; /* time spent reading blocks, in msec */ + double blk_write_time; /* time spent writing blocks, in msec */ + double temp_blk_read_time; /* time spent reading temp blocks, in msec */ + double temp_blk_write_time; /* time spent writing temp blocks, in + * msec */ + double usage; /* usage factor */ + int64 wal_records; /* # of WAL records generated */ + int64 wal_fpi; /* # of WAL full page images generated */ + uint64 wal_bytes; /* total amount of WAL generated in bytes */ + int64 jit_functions; /* total number of JIT functions emitted */ + double jit_generation_time; /* total time to generate jit code */ + int64 jit_inlining_count; /* number of times inlining time has been + * > 0 */ + double jit_inlining_time; /* total time to inline jit code */ + int64 jit_optimization_count; /* number of times optimization time + * has been > 0 */ + double jit_optimization_time; /* total time to optimize jit code */ + int64 jit_emission_count; /* number of times emission time has been + * > 0 */ + double jit_emission_time; /* total time to emit jit code */ +} Counters; + +/* + * Global statistics for pg_stat_statements + */ +typedef struct pgssGlobalStats +{ + int64 dealloc; /* # of times entries were deallocated */ + TimestampTz stats_reset; /* timestamp with all stats reset */ +} pgssGlobalStats; + +/* + * Statistics per statement + * + * Note: in event of a failure in garbage collection of the query text file, + * we reset query_offset to zero and query_len to -1. This will be seen as + * an invalid state by qtext_fetch(). + */ +typedef struct pgssEntry +{ + pgssHashKey key; /* hash key of entry - MUST BE FIRST */ + Counters counters; /* the statistics for this query */ + Size query_offset; /* query text offset in external file */ + int query_len; /* # of valid bytes in query string, or -1 */ + int encoding; /* query text encoding */ + slock_t mutex; /* protects the counters only */ +} pgssEntry; + +/* + * Global shared state + */ +typedef struct pgssSharedState +{ + LWLock *lock; /* protects hashtable search/modification */ + double cur_median_usage; /* current median usage in hashtable */ + Size mean_query_len; /* current mean entry text length */ + slock_t mutex; /* protects following fields only: */ + Size extent; /* current extent of query file */ + int n_writers; /* number of active writers to query file */ + int gc_count; /* query file garbage collection cycle count */ + pgssGlobalStats stats; /* global statistics for pgss */ +} pgssSharedState; + +/*---- Local variables ----*/ + +/* Current nesting depth of ExecutorRun+ProcessUtility calls */ +static int exec_nested_level = 0; + +/* Current nesting depth of planner calls */ +static int plan_nested_level = 0; + +/* Saved hook values in case of unload */ +static shmem_request_hook_type prev_shmem_request_hook = NULL; +static shmem_startup_hook_type prev_shmem_startup_hook = NULL; +static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL; +static planner_hook_type prev_planner_hook = NULL; +static ExecutorStart_hook_type prev_ExecutorStart = NULL; +static ExecutorRun_hook_type prev_ExecutorRun = NULL; +static ExecutorFinish_hook_type prev_ExecutorFinish = NULL; +static ExecutorEnd_hook_type prev_ExecutorEnd = NULL; +static ProcessUtility_hook_type prev_ProcessUtility = NULL; + +/* Links to shared memory state */ +static pgssSharedState *pgss = NULL; +static HTAB *pgss_hash = NULL; + +/*---- GUC variables ----*/ + +typedef enum +{ + PGSS_TRACK_NONE, /* track no statements */ + PGSS_TRACK_TOP, /* only top level statements */ + PGSS_TRACK_ALL /* all statements, including nested ones */ +} PGSSTrackLevel; + +static const struct config_enum_entry track_options[] = +{ + {"none", PGSS_TRACK_NONE, false}, + {"top", PGSS_TRACK_TOP, false}, + {"all", PGSS_TRACK_ALL, false}, + {NULL, 0, false} +}; + +static int pgss_max = 5000; /* max # statements to track */ +static int pgss_track = PGSS_TRACK_TOP; /* tracking level */ +static bool pgss_track_utility = true; /* whether to track utility commands */ +static bool pgss_track_planning = false; /* whether to track planning + * duration */ +static bool pgss_save = true; /* whether to save stats across shutdown */ + + +#define pgss_enabled(level) \ + (!IsParallelWorker() && \ + (pgss_track == PGSS_TRACK_ALL || \ + (pgss_track == PGSS_TRACK_TOP && (level) == 0))) + +#define record_gc_qtexts() \ + do { \ + volatile pgssSharedState *s = (volatile pgssSharedState *) pgss; \ + SpinLockAcquire(&s->mutex); \ + s->gc_count++; \ + SpinLockRelease(&s->mutex); \ + } while(0) + +/*---- Function declarations ----*/ + +PG_FUNCTION_INFO_V1(pg_stat_statements_reset); +PG_FUNCTION_INFO_V1(pg_stat_statements_reset_1_7); +PG_FUNCTION_INFO_V1(pg_stat_statements_1_2); +PG_FUNCTION_INFO_V1(pg_stat_statements_1_3); +PG_FUNCTION_INFO_V1(pg_stat_statements_1_8); +PG_FUNCTION_INFO_V1(pg_stat_statements_1_9); +PG_FUNCTION_INFO_V1(pg_stat_statements_1_10); +PG_FUNCTION_INFO_V1(pg_stat_statements); +PG_FUNCTION_INFO_V1(pg_stat_statements_info); + +static void pgss_shmem_request(void); +static void pgss_shmem_startup(void); +static void pgss_shmem_shutdown(int code, Datum arg); +static void pgss_post_parse_analyze(ParseState *pstate, Query *query, + JumbleState *jstate); +static PlannedStmt *pgss_planner(Query *parse, + const char *query_string, + int cursorOptions, + ParamListInfo boundParams); +static void pgss_ExecutorStart(QueryDesc *queryDesc, int eflags); +static void pgss_ExecutorRun(QueryDesc *queryDesc, + ScanDirection direction, + uint64 count, bool execute_once); +static void pgss_ExecutorFinish(QueryDesc *queryDesc); +static void pgss_ExecutorEnd(QueryDesc *queryDesc); +static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, QueryCompletion *qc); +static void pgss_store(const char *query, uint64 queryId, + int query_location, int query_len, + pgssStoreKind kind, + double total_time, uint64 rows, + const BufferUsage *bufusage, + const WalUsage *walusage, + const struct JitInstrumentation *jitusage, + JumbleState *jstate); +static void pg_stat_statements_internal(FunctionCallInfo fcinfo, + pgssVersion api_version, + bool showtext); +static Size pgss_memsize(void); +static pgssEntry *entry_alloc(pgssHashKey *key, Size query_offset, int query_len, + int encoding, bool sticky); +static void entry_dealloc(void); +static bool qtext_store(const char *query, int query_len, + Size *query_offset, int *gc_count); +static char *qtext_load_file(Size *buffer_size); +static char *qtext_fetch(Size query_offset, int query_len, + char *buffer, Size buffer_size); +static bool need_gc_qtexts(void); +static void gc_qtexts(void); +static void entry_reset(Oid userid, Oid dbid, uint64 queryid); +static char *generate_normalized_query(JumbleState *jstate, const char *query, + int query_loc, int *query_len_p); +static void fill_in_constant_lengths(JumbleState *jstate, const char *query, + int query_loc); +static int comp_location(const void *a, const void *b); + + +/* + * Module load callback + */ +void +_PG_init(void) +{ + /* + * In order to create our shared memory area, we have to be loaded via + * shared_preload_libraries. If not, fall out without hooking into any of + * the main system. (We don't throw error here because it seems useful to + * allow the pg_stat_statements functions to be created even when the + * module isn't active. The functions must protect themselves against + * being called then, however.) + */ + if (!process_shared_preload_libraries_in_progress) + return; + + /* + * Inform the postmaster that we want to enable query_id calculation if + * compute_query_id is set to auto. + */ + EnableQueryId(); + + /* + * Define (or redefine) custom GUC variables. + */ + DefineCustomIntVariable("pg_stat_statements.max", + "Sets the maximum number of statements tracked by pg_stat_statements.", + NULL, + &pgss_max, + 5000, + 100, + INT_MAX / 2, + PGC_POSTMASTER, + 0, + NULL, + NULL, + NULL); + + DefineCustomEnumVariable("pg_stat_statements.track", + "Selects which statements are tracked by pg_stat_statements.", + NULL, + &pgss_track, + PGSS_TRACK_TOP, + track_options, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("pg_stat_statements.track_utility", + "Selects whether utility commands are tracked by pg_stat_statements.", + NULL, + &pgss_track_utility, + true, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("pg_stat_statements.track_planning", + "Selects whether planning duration is tracked by pg_stat_statements.", + NULL, + &pgss_track_planning, + false, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + + DefineCustomBoolVariable("pg_stat_statements.save", + "Save pg_stat_statements statistics across server shutdowns.", + NULL, + &pgss_save, + true, + PGC_SIGHUP, + 0, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("pg_stat_statements"); + + /* + * Install hooks. + */ + prev_shmem_request_hook = shmem_request_hook; + shmem_request_hook = pgss_shmem_request; + prev_shmem_startup_hook = shmem_startup_hook; + shmem_startup_hook = pgss_shmem_startup; + prev_post_parse_analyze_hook = post_parse_analyze_hook; + post_parse_analyze_hook = pgss_post_parse_analyze; + prev_planner_hook = planner_hook; + planner_hook = pgss_planner; + prev_ExecutorStart = ExecutorStart_hook; + ExecutorStart_hook = pgss_ExecutorStart; + prev_ExecutorRun = ExecutorRun_hook; + ExecutorRun_hook = pgss_ExecutorRun; + prev_ExecutorFinish = ExecutorFinish_hook; + ExecutorFinish_hook = pgss_ExecutorFinish; + prev_ExecutorEnd = ExecutorEnd_hook; + ExecutorEnd_hook = pgss_ExecutorEnd; + prev_ProcessUtility = ProcessUtility_hook; + ProcessUtility_hook = pgss_ProcessUtility; +} + +/* + * shmem_request hook: request additional shared resources. We'll allocate or + * attach to the shared resources in pgss_shmem_startup(). + */ +static void +pgss_shmem_request(void) +{ + if (prev_shmem_request_hook) + prev_shmem_request_hook(); + + RequestAddinShmemSpace(pgss_memsize()); + RequestNamedLWLockTranche("pg_stat_statements", 1); +} + +/* + * shmem_startup hook: allocate or attach to shared memory, + * then load any pre-existing statistics from file. + * Also create and load the query-texts file, which is expected to exist + * (even if empty) while the module is enabled. + */ +static void +pgss_shmem_startup(void) +{ + bool found; + HASHCTL info; + FILE *file = NULL; + FILE *qfile = NULL; + uint32 header; + int32 num; + int32 pgver; + int32 i; + int buffer_size; + char *buffer = NULL; + + if (prev_shmem_startup_hook) + prev_shmem_startup_hook(); + + /* reset in case this is a restart within the postmaster */ + pgss = NULL; + pgss_hash = NULL; + + /* + * Create or attach to the shared memory state, including hash table + */ + LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); + + pgss = ShmemInitStruct("pg_stat_statements", + sizeof(pgssSharedState), + &found); + + if (!found) + { + /* First time through ... */ + pgss->lock = &(GetNamedLWLockTranche("pg_stat_statements"))->lock; + pgss->cur_median_usage = ASSUMED_MEDIAN_INIT; + pgss->mean_query_len = ASSUMED_LENGTH_INIT; + SpinLockInit(&pgss->mutex); + pgss->extent = 0; + pgss->n_writers = 0; + pgss->gc_count = 0; + pgss->stats.dealloc = 0; + pgss->stats.stats_reset = GetCurrentTimestamp(); + } + + info.keysize = sizeof(pgssHashKey); + info.entrysize = sizeof(pgssEntry); + pgss_hash = ShmemInitHash("pg_stat_statements hash", + pgss_max, pgss_max, + &info, + HASH_ELEM | HASH_BLOBS); + + LWLockRelease(AddinShmemInitLock); + + /* + * If we're in the postmaster (or a standalone backend...), set up a shmem + * exit hook to dump the statistics to disk. + */ + if (!IsUnderPostmaster) + on_shmem_exit(pgss_shmem_shutdown, (Datum) 0); + + /* + * Done if some other process already completed our initialization. + */ + if (found) + return; + + /* + * Note: we don't bother with locks here, because there should be no other + * processes running when this code is reached. + */ + + /* Unlink query text file possibly left over from crash */ + unlink(PGSS_TEXT_FILE); + + /* Allocate new query text temp file */ + qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W); + if (qfile == NULL) + goto write_error; + + /* + * If we were told not to load old statistics, we're done. (Note we do + * not try to unlink any old dump file in this case. This seems a bit + * questionable but it's the historical behavior.) + */ + if (!pgss_save) + { + FreeFile(qfile); + return; + } + + /* + * Attempt to load old statistics from the dump file. + */ + file = AllocateFile(PGSS_DUMP_FILE, PG_BINARY_R); + if (file == NULL) + { + if (errno != ENOENT) + goto read_error; + /* No existing persisted stats file, so we're done */ + FreeFile(qfile); + return; + } + + buffer_size = 2048; + buffer = (char *) palloc(buffer_size); + + if (fread(&header, sizeof(uint32), 1, file) != 1 || + fread(&pgver, sizeof(uint32), 1, file) != 1 || + fread(&num, sizeof(int32), 1, file) != 1) + goto read_error; + + if (header != PGSS_FILE_HEADER || + pgver != PGSS_PG_MAJOR_VERSION) + goto data_error; + + for (i = 0; i < num; i++) + { + pgssEntry temp; + pgssEntry *entry; + Size query_offset; + + if (fread(&temp, sizeof(pgssEntry), 1, file) != 1) + goto read_error; + + /* Encoding is the only field we can easily sanity-check */ + if (!PG_VALID_BE_ENCODING(temp.encoding)) + goto data_error; + + /* Resize buffer as needed */ + if (temp.query_len >= buffer_size) + { + buffer_size = Max(buffer_size * 2, temp.query_len + 1); + buffer = repalloc(buffer, buffer_size); + } + + if (fread(buffer, 1, temp.query_len + 1, file) != temp.query_len + 1) + goto read_error; + + /* Should have a trailing null, but let's make sure */ + buffer[temp.query_len] = '\0'; + + /* Skip loading "sticky" entries */ + if (IS_STICKY(temp.counters)) + continue; + + /* Store the query text */ + query_offset = pgss->extent; + if (fwrite(buffer, 1, temp.query_len + 1, qfile) != temp.query_len + 1) + goto write_error; + pgss->extent += temp.query_len + 1; + + /* make the hashtable entry (discards old entries if too many) */ + entry = entry_alloc(&temp.key, query_offset, temp.query_len, + temp.encoding, + false); + + /* copy in the actual stats */ + entry->counters = temp.counters; + } + + /* Read global statistics for pg_stat_statements */ + if (fread(&pgss->stats, sizeof(pgssGlobalStats), 1, file) != 1) + goto read_error; + + pfree(buffer); + FreeFile(file); + FreeFile(qfile); + + /* + * Remove the persisted stats file so it's not included in + * backups/replication standbys, etc. A new file will be written on next + * shutdown. + * + * Note: it's okay if the PGSS_TEXT_FILE is included in a basebackup, + * because we remove that file on startup; it acts inversely to + * PGSS_DUMP_FILE, in that it is only supposed to be around when the + * server is running, whereas PGSS_DUMP_FILE is only supposed to be around + * when the server is not running. Leaving the file creates no danger of + * a newly restored database having a spurious record of execution costs, + * which is what we're really concerned about here. + */ + unlink(PGSS_DUMP_FILE); + + return; + +read_error: + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", + PGSS_DUMP_FILE))); + goto fail; +data_error: + ereport(LOG, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("ignoring invalid data in file \"%s\"", + PGSS_DUMP_FILE))); + goto fail; +write_error: + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write file \"%s\": %m", + PGSS_TEXT_FILE))); +fail: + if (buffer) + pfree(buffer); + if (file) + FreeFile(file); + if (qfile) + FreeFile(qfile); + /* If possible, throw away the bogus file; ignore any error */ + unlink(PGSS_DUMP_FILE); + + /* + * Don't unlink PGSS_TEXT_FILE here; it should always be around while the + * server is running with pg_stat_statements enabled + */ +} + +/* + * shmem_shutdown hook: Dump statistics into file. + * + * Note: we don't bother with acquiring lock, because there should be no + * other processes running when this is called. + */ +static void +pgss_shmem_shutdown(int code, Datum arg) +{ + FILE *file; + char *qbuffer = NULL; + Size qbuffer_size = 0; + HASH_SEQ_STATUS hash_seq; + int32 num_entries; + pgssEntry *entry; + + /* Don't try to dump during a crash. */ + if (code) + return; + + /* Safety check ... shouldn't get here unless shmem is set up. */ + if (!pgss || !pgss_hash) + return; + + /* Don't dump if told not to. */ + if (!pgss_save) + return; + + file = AllocateFile(PGSS_DUMP_FILE ".tmp", PG_BINARY_W); + if (file == NULL) + goto error; + + if (fwrite(&PGSS_FILE_HEADER, sizeof(uint32), 1, file) != 1) + goto error; + if (fwrite(&PGSS_PG_MAJOR_VERSION, sizeof(uint32), 1, file) != 1) + goto error; + num_entries = hash_get_num_entries(pgss_hash); + if (fwrite(&num_entries, sizeof(int32), 1, file) != 1) + goto error; + + qbuffer = qtext_load_file(&qbuffer_size); + if (qbuffer == NULL) + goto error; + + /* + * When serializing to disk, we store query texts immediately after their + * entry data. Any orphaned query texts are thereby excluded. + */ + hash_seq_init(&hash_seq, pgss_hash); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + int len = entry->query_len; + char *qstr = qtext_fetch(entry->query_offset, len, + qbuffer, qbuffer_size); + + if (qstr == NULL) + continue; /* Ignore any entries with bogus texts */ + + if (fwrite(entry, sizeof(pgssEntry), 1, file) != 1 || + fwrite(qstr, 1, len + 1, file) != len + 1) + { + /* note: we assume hash_seq_term won't change errno */ + hash_seq_term(&hash_seq); + goto error; + } + } + + /* Dump global statistics for pg_stat_statements */ + if (fwrite(&pgss->stats, sizeof(pgssGlobalStats), 1, file) != 1) + goto error; + + free(qbuffer); + qbuffer = NULL; + + if (FreeFile(file)) + { + file = NULL; + goto error; + } + + /* + * Rename file into place, so we atomically replace any old one. + */ + (void) durable_rename(PGSS_DUMP_FILE ".tmp", PGSS_DUMP_FILE, LOG); + + /* Unlink query-texts file; it's not needed while shutdown */ + unlink(PGSS_TEXT_FILE); + + return; + +error: + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write file \"%s\": %m", + PGSS_DUMP_FILE ".tmp"))); + free(qbuffer); + if (file) + FreeFile(file); + unlink(PGSS_DUMP_FILE ".tmp"); + unlink(PGSS_TEXT_FILE); +} + +/* + * Post-parse-analysis hook: mark query with a queryId + */ +static void +pgss_post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate) +{ + if (prev_post_parse_analyze_hook) + prev_post_parse_analyze_hook(pstate, query, jstate); + + /* Safety check... */ + if (!pgss || !pgss_hash || !pgss_enabled(exec_nested_level)) + return; + + /* + * Clear queryId for prepared statements related utility, as those will + * inherit from the underlying statement's one (except DEALLOCATE which is + * entirely untracked). + */ + if (query->utilityStmt) + { + if (pgss_track_utility && !PGSS_HANDLED_UTILITY(query->utilityStmt)) + { + query->queryId = UINT64CONST(0); + return; + } + } + + /* + * If query jumbling were able to identify any ignorable constants, we + * immediately create a hash table entry for the query, so that we can + * record the normalized form of the query string. If there were no such + * constants, the normalized string would be the same as the query text + * anyway, so there's no need for an early entry. + */ + if (jstate && jstate->clocations_count > 0) + pgss_store(pstate->p_sourcetext, + query->queryId, + query->stmt_location, + query->stmt_len, + PGSS_INVALID, + 0, + 0, + NULL, + NULL, + NULL, + jstate); +} + +/* + * Planner hook: forward to regular planner, but measure planning time + * if needed. + */ +static PlannedStmt * +pgss_planner(Query *parse, + const char *query_string, + int cursorOptions, + ParamListInfo boundParams) +{ + PlannedStmt *result; + + /* + * We can't process the query if no query_string is provided, as + * pgss_store needs it. We also ignore query without queryid, as it would + * be treated as a utility statement, which may not be the case. + * + * Note that planner_hook can be called from the planner itself, so we + * have a specific nesting level for the planner. However, utility + * commands containing optimizable statements can also call the planner, + * same for regular DML (for instance for underlying foreign key queries). + * So testing the planner nesting level only is not enough to detect real + * top level planner call. + */ + if (pgss_enabled(plan_nested_level + exec_nested_level) + && pgss_track_planning && query_string + && parse->queryId != UINT64CONST(0)) + { + instr_time start; + instr_time duration; + BufferUsage bufusage_start, + bufusage; + WalUsage walusage_start, + walusage; + + /* We need to track buffer usage as the planner can access them. */ + bufusage_start = pgBufferUsage; + + /* + * Similarly the planner could write some WAL records in some cases + * (e.g. setting a hint bit with those being WAL-logged) + */ + walusage_start = pgWalUsage; + INSTR_TIME_SET_CURRENT(start); + + plan_nested_level++; + PG_TRY(); + { + if (prev_planner_hook) + result = prev_planner_hook(parse, query_string, cursorOptions, + boundParams); + else + result = standard_planner(parse, query_string, cursorOptions, + boundParams); + } + PG_FINALLY(); + { + plan_nested_level--; + } + PG_END_TRY(); + + INSTR_TIME_SET_CURRENT(duration); + INSTR_TIME_SUBTRACT(duration, start); + + /* calc differences of buffer counters. */ + memset(&bufusage, 0, sizeof(BufferUsage)); + BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start); + + /* calc differences of WAL counters. */ + memset(&walusage, 0, sizeof(WalUsage)); + WalUsageAccumDiff(&walusage, &pgWalUsage, &walusage_start); + + pgss_store(query_string, + parse->queryId, + parse->stmt_location, + parse->stmt_len, + PGSS_PLAN, + INSTR_TIME_GET_MILLISEC(duration), + 0, + &bufusage, + &walusage, + NULL, + NULL); + } + else + { + if (prev_planner_hook) + result = prev_planner_hook(parse, query_string, cursorOptions, + boundParams); + else + result = standard_planner(parse, query_string, cursorOptions, + boundParams); + } + + return result; +} + +/* + * ExecutorStart hook: start up tracking if needed + */ +static void +pgss_ExecutorStart(QueryDesc *queryDesc, int eflags) +{ + if (prev_ExecutorStart) + prev_ExecutorStart(queryDesc, eflags); + else + standard_ExecutorStart(queryDesc, eflags); + + /* + * If query has queryId zero, don't track it. This prevents double + * counting of optimizable statements that are directly contained in + * utility statements. + */ + if (pgss_enabled(exec_nested_level) && queryDesc->plannedstmt->queryId != UINT64CONST(0)) + { + /* + * Set up to track total elapsed time in ExecutorRun. Make sure the + * space is allocated in the per-query context so it will go away at + * ExecutorEnd. + */ + if (queryDesc->totaltime == NULL) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt); + queryDesc->totaltime = InstrAlloc(1, INSTRUMENT_ALL, false); + MemoryContextSwitchTo(oldcxt); + } + } +} + +/* + * ExecutorRun hook: all we need do is track nesting depth + */ +static void +pgss_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count, + bool execute_once) +{ + exec_nested_level++; + PG_TRY(); + { + if (prev_ExecutorRun) + prev_ExecutorRun(queryDesc, direction, count, execute_once); + else + standard_ExecutorRun(queryDesc, direction, count, execute_once); + } + PG_FINALLY(); + { + exec_nested_level--; + } + PG_END_TRY(); +} + +/* + * ExecutorFinish hook: all we need do is track nesting depth + */ +static void +pgss_ExecutorFinish(QueryDesc *queryDesc) +{ + exec_nested_level++; + PG_TRY(); + { + if (prev_ExecutorFinish) + prev_ExecutorFinish(queryDesc); + else + standard_ExecutorFinish(queryDesc); + } + PG_FINALLY(); + { + exec_nested_level--; + } + PG_END_TRY(); +} + +/* + * ExecutorEnd hook: store results if needed + */ +static void +pgss_ExecutorEnd(QueryDesc *queryDesc) +{ + uint64 queryId = queryDesc->plannedstmt->queryId; + + if (queryId != UINT64CONST(0) && queryDesc->totaltime && + pgss_enabled(exec_nested_level)) + { + /* + * Make sure stats accumulation is done. (Note: it's okay if several + * levels of hook all do this.) + */ + InstrEndLoop(queryDesc->totaltime); + + pgss_store(queryDesc->sourceText, + queryId, + queryDesc->plannedstmt->stmt_location, + queryDesc->plannedstmt->stmt_len, + PGSS_EXEC, + queryDesc->totaltime->total * 1000.0, /* convert to msec */ + queryDesc->estate->es_total_processed, + &queryDesc->totaltime->bufusage, + &queryDesc->totaltime->walusage, + queryDesc->estate->es_jit ? &queryDesc->estate->es_jit->instr : NULL, + NULL); + } + + if (prev_ExecutorEnd) + prev_ExecutorEnd(queryDesc); + else + standard_ExecutorEnd(queryDesc); +} + +/* + * ProcessUtility hook + */ +static void +pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, QueryEnvironment *queryEnv, + DestReceiver *dest, QueryCompletion *qc) +{ + Node *parsetree = pstmt->utilityStmt; + uint64 saved_queryId = pstmt->queryId; + int saved_stmt_location = pstmt->stmt_location; + int saved_stmt_len = pstmt->stmt_len; + + /* + * Force utility statements to get queryId zero. We do this even in cases + * where the statement contains an optimizable statement for which a + * queryId could be derived (such as EXPLAIN or DECLARE CURSOR). For such + * cases, runtime control will first go through ProcessUtility and then + * the executor, and we don't want the executor hooks to do anything, + * since we are already measuring the statement's costs at the utility + * level. + * + * Note that this is only done if pg_stat_statements is enabled and + * configured to track utility statements, in the unlikely possibility + * that user configured another extension to handle utility statements + * only. + */ + if (pgss_enabled(exec_nested_level) && pgss_track_utility) + pstmt->queryId = UINT64CONST(0); + + /* + * If it's an EXECUTE statement, we don't track it and don't increment the + * nesting level. This allows the cycles to be charged to the underlying + * PREPARE instead (by the Executor hooks), which is much more useful. + * + * We also don't track execution of PREPARE. If we did, we would get one + * hash table entry for the PREPARE (with hash calculated from the query + * string), and then a different one with the same query string (but hash + * calculated from the query tree) would be used to accumulate costs of + * ensuing EXECUTEs. This would be confusing, and inconsistent with other + * cases where planning time is not included at all. + * + * Likewise, we don't track execution of DEALLOCATE. + */ + if (pgss_track_utility && pgss_enabled(exec_nested_level) && + PGSS_HANDLED_UTILITY(parsetree)) + { + instr_time start; + instr_time duration; + uint64 rows; + BufferUsage bufusage_start, + bufusage; + WalUsage walusage_start, + walusage; + + bufusage_start = pgBufferUsage; + walusage_start = pgWalUsage; + INSTR_TIME_SET_CURRENT(start); + + exec_nested_level++; + PG_TRY(); + { + if (prev_ProcessUtility) + prev_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + else + standard_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + } + PG_FINALLY(); + { + exec_nested_level--; + } + PG_END_TRY(); + + /* + * CAUTION: do not access the *pstmt data structure again below here. + * If it was a ROLLBACK or similar, that data structure may have been + * freed. We must copy everything we still need into local variables, + * which we did above. + * + * For the same reason, we can't risk restoring pstmt->queryId to its + * former value, which'd otherwise be a good idea. + */ + + INSTR_TIME_SET_CURRENT(duration); + INSTR_TIME_SUBTRACT(duration, start); + + /* + * Track the total number of rows retrieved or affected by the utility + * statements of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED + * VIEW, REFRESH MATERIALIZED VIEW and SELECT INTO. + */ + rows = (qc && (qc->commandTag == CMDTAG_COPY || + qc->commandTag == CMDTAG_FETCH || + qc->commandTag == CMDTAG_SELECT || + qc->commandTag == CMDTAG_REFRESH_MATERIALIZED_VIEW)) ? + qc->nprocessed : 0; + + /* calc differences of buffer counters. */ + memset(&bufusage, 0, sizeof(BufferUsage)); + BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start); + + /* calc differences of WAL counters. */ + memset(&walusage, 0, sizeof(WalUsage)); + WalUsageAccumDiff(&walusage, &pgWalUsage, &walusage_start); + + pgss_store(queryString, + saved_queryId, + saved_stmt_location, + saved_stmt_len, + PGSS_EXEC, + INSTR_TIME_GET_MILLISEC(duration), + rows, + &bufusage, + &walusage, + NULL, + NULL); + } + else + { + if (prev_ProcessUtility) + prev_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + else + standard_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + } +} + +/* + * Store some statistics for a statement. + * + * If jstate is not NULL then we're trying to create an entry for which + * we have no statistics as yet; we just want to record the normalized + * query string. total_time, rows, bufusage and walusage are ignored in this + * case. + * + * If kind is PGSS_PLAN or PGSS_EXEC, its value is used as the array position + * for the arrays in the Counters field. + */ +static void +pgss_store(const char *query, uint64 queryId, + int query_location, int query_len, + pgssStoreKind kind, + double total_time, uint64 rows, + const BufferUsage *bufusage, + const WalUsage *walusage, + const struct JitInstrumentation *jitusage, + JumbleState *jstate) +{ + pgssHashKey key; + pgssEntry *entry; + char *norm_query = NULL; + int encoding = GetDatabaseEncoding(); + + Assert(query != NULL); + + /* Safety check... */ + if (!pgss || !pgss_hash) + return; + + /* + * Nothing to do if compute_query_id isn't enabled and no other module + * computed a query identifier. + */ + if (queryId == UINT64CONST(0)) + return; + + /* + * Confine our attention to the relevant part of the string, if the query + * is a portion of a multi-statement source string, and update query + * location and length if needed. + */ + query = CleanQuerytext(query, &query_location, &query_len); + + /* Set up key for hashtable search */ + + /* clear padding */ + memset(&key, 0, sizeof(pgssHashKey)); + + key.userid = GetUserId(); + key.dbid = MyDatabaseId; + key.queryid = queryId; + key.toplevel = (exec_nested_level == 0); + + /* Lookup the hash table entry with shared lock. */ + LWLockAcquire(pgss->lock, LW_SHARED); + + entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL); + + /* Create new entry, if not present */ + if (!entry) + { + Size query_offset; + int gc_count; + bool stored; + bool do_gc; + + /* + * Create a new, normalized query string if caller asked. We don't + * need to hold the lock while doing this work. (Note: in any case, + * it's possible that someone else creates a duplicate hashtable entry + * in the interval where we don't hold the lock below. That case is + * handled by entry_alloc.) + */ + if (jstate) + { + LWLockRelease(pgss->lock); + norm_query = generate_normalized_query(jstate, query, + query_location, + &query_len); + LWLockAcquire(pgss->lock, LW_SHARED); + } + + /* Append new query text to file with only shared lock held */ + stored = qtext_store(norm_query ? norm_query : query, query_len, + &query_offset, &gc_count); + + /* + * Determine whether we need to garbage collect external query texts + * while the shared lock is still held. This micro-optimization + * avoids taking the time to decide this while holding exclusive lock. + */ + do_gc = need_gc_qtexts(); + + /* Need exclusive lock to make a new hashtable entry - promote */ + LWLockRelease(pgss->lock); + LWLockAcquire(pgss->lock, LW_EXCLUSIVE); + + /* + * A garbage collection may have occurred while we weren't holding the + * lock. In the unlikely event that this happens, the query text we + * stored above will have been garbage collected, so write it again. + * This should be infrequent enough that doing it while holding + * exclusive lock isn't a performance problem. + */ + if (!stored || pgss->gc_count != gc_count) + stored = qtext_store(norm_query ? norm_query : query, query_len, + &query_offset, NULL); + + /* If we failed to write to the text file, give up */ + if (!stored) + goto done; + + /* OK to create a new hashtable entry */ + entry = entry_alloc(&key, query_offset, query_len, encoding, + jstate != NULL); + + /* If needed, perform garbage collection while exclusive lock held */ + if (do_gc) + gc_qtexts(); + } + + /* Increment the counts, except when jstate is not NULL */ + if (!jstate) + { + /* + * Grab the spinlock while updating the counters (see comment about + * locking rules at the head of the file) + */ + volatile pgssEntry *e = (volatile pgssEntry *) entry; + + Assert(kind == PGSS_PLAN || kind == PGSS_EXEC); + + SpinLockAcquire(&e->mutex); + + /* "Unstick" entry if it was previously sticky */ + if (IS_STICKY(e->counters)) + e->counters.usage = USAGE_INIT; + + e->counters.calls[kind] += 1; + e->counters.total_time[kind] += total_time; + + if (e->counters.calls[kind] == 1) + { + e->counters.min_time[kind] = total_time; + e->counters.max_time[kind] = total_time; + e->counters.mean_time[kind] = total_time; + } + else + { + /* + * Welford's method for accurately computing variance. See + * + */ + double old_mean = e->counters.mean_time[kind]; + + e->counters.mean_time[kind] += + (total_time - old_mean) / e->counters.calls[kind]; + e->counters.sum_var_time[kind] += + (total_time - old_mean) * (total_time - e->counters.mean_time[kind]); + + /* calculate min and max time */ + if (e->counters.min_time[kind] > total_time) + e->counters.min_time[kind] = total_time; + if (e->counters.max_time[kind] < total_time) + e->counters.max_time[kind] = total_time; + } + e->counters.rows += rows; + e->counters.shared_blks_hit += bufusage->shared_blks_hit; + e->counters.shared_blks_read += bufusage->shared_blks_read; + e->counters.shared_blks_dirtied += bufusage->shared_blks_dirtied; + e->counters.shared_blks_written += bufusage->shared_blks_written; + e->counters.local_blks_hit += bufusage->local_blks_hit; + e->counters.local_blks_read += bufusage->local_blks_read; + e->counters.local_blks_dirtied += bufusage->local_blks_dirtied; + e->counters.local_blks_written += bufusage->local_blks_written; + e->counters.temp_blks_read += bufusage->temp_blks_read; + e->counters.temp_blks_written += bufusage->temp_blks_written; + e->counters.blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage->blk_read_time); + e->counters.blk_write_time += INSTR_TIME_GET_MILLISEC(bufusage->blk_write_time); + e->counters.temp_blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage->temp_blk_read_time); + e->counters.temp_blk_write_time += INSTR_TIME_GET_MILLISEC(bufusage->temp_blk_write_time); + e->counters.usage += USAGE_EXEC(total_time); + e->counters.wal_records += walusage->wal_records; + e->counters.wal_fpi += walusage->wal_fpi; + e->counters.wal_bytes += walusage->wal_bytes; + if (jitusage) + { + e->counters.jit_functions += jitusage->created_functions; + e->counters.jit_generation_time += INSTR_TIME_GET_MILLISEC(jitusage->generation_counter); + + if (INSTR_TIME_GET_MILLISEC(jitusage->inlining_counter)) + e->counters.jit_inlining_count++; + e->counters.jit_inlining_time += INSTR_TIME_GET_MILLISEC(jitusage->inlining_counter); + + if (INSTR_TIME_GET_MILLISEC(jitusage->optimization_counter)) + e->counters.jit_optimization_count++; + e->counters.jit_optimization_time += INSTR_TIME_GET_MILLISEC(jitusage->optimization_counter); + + if (INSTR_TIME_GET_MILLISEC(jitusage->emission_counter)) + e->counters.jit_emission_count++; + e->counters.jit_emission_time += INSTR_TIME_GET_MILLISEC(jitusage->emission_counter); + } + + SpinLockRelease(&e->mutex); + } + +done: + LWLockRelease(pgss->lock); + + /* We postpone this clean-up until we're out of the lock */ + if (norm_query) + pfree(norm_query); +} + +/* + * Reset statement statistics corresponding to userid, dbid, and queryid. + */ +Datum +pg_stat_statements_reset_1_7(PG_FUNCTION_ARGS) +{ + Oid userid; + Oid dbid; + uint64 queryid; + + userid = PG_GETARG_OID(0); + dbid = PG_GETARG_OID(1); + queryid = (uint64) PG_GETARG_INT64(2); + + entry_reset(userid, dbid, queryid); + + PG_RETURN_VOID(); +} + +/* + * Reset statement statistics. + */ +Datum +pg_stat_statements_reset(PG_FUNCTION_ARGS) +{ + entry_reset(0, 0, 0); + + PG_RETURN_VOID(); +} + +/* Number of output arguments (columns) for various API versions */ +#define PG_STAT_STATEMENTS_COLS_V1_0 14 +#define PG_STAT_STATEMENTS_COLS_V1_1 18 +#define PG_STAT_STATEMENTS_COLS_V1_2 19 +#define PG_STAT_STATEMENTS_COLS_V1_3 23 +#define PG_STAT_STATEMENTS_COLS_V1_8 32 +#define PG_STAT_STATEMENTS_COLS_V1_9 33 +#define PG_STAT_STATEMENTS_COLS_V1_10 43 +#define PG_STAT_STATEMENTS_COLS 43 /* maximum of above */ + +/* + * Retrieve statement statistics. + * + * The SQL API of this function has changed multiple times, and will likely + * do so again in future. To support the case where a newer version of this + * loadable module is being used with an old SQL declaration of the function, + * we continue to support the older API versions. For 1.2 and later, the + * expected API version is identified by embedding it in the C name of the + * function. Unfortunately we weren't bright enough to do that for 1.1. + */ +Datum +pg_stat_statements_1_10(PG_FUNCTION_ARGS) +{ + bool showtext = PG_GETARG_BOOL(0); + + pg_stat_statements_internal(fcinfo, PGSS_V1_10, showtext); + + return (Datum) 0; +} + +Datum +pg_stat_statements_1_9(PG_FUNCTION_ARGS) +{ + bool showtext = PG_GETARG_BOOL(0); + + pg_stat_statements_internal(fcinfo, PGSS_V1_9, showtext); + + return (Datum) 0; +} + +Datum +pg_stat_statements_1_8(PG_FUNCTION_ARGS) +{ + bool showtext = PG_GETARG_BOOL(0); + + pg_stat_statements_internal(fcinfo, PGSS_V1_8, showtext); + + return (Datum) 0; +} + +Datum +pg_stat_statements_1_3(PG_FUNCTION_ARGS) +{ + bool showtext = PG_GETARG_BOOL(0); + + pg_stat_statements_internal(fcinfo, PGSS_V1_3, showtext); + + return (Datum) 0; +} + +Datum +pg_stat_statements_1_2(PG_FUNCTION_ARGS) +{ + bool showtext = PG_GETARG_BOOL(0); + + pg_stat_statements_internal(fcinfo, PGSS_V1_2, showtext); + + return (Datum) 0; +} + +/* + * Legacy entry point for pg_stat_statements() API versions 1.0 and 1.1. + * This can be removed someday, perhaps. + */ +Datum +pg_stat_statements(PG_FUNCTION_ARGS) +{ + /* If it's really API 1.1, we'll figure that out below */ + pg_stat_statements_internal(fcinfo, PGSS_V1_0, true); + + return (Datum) 0; +} + +/* Common code for all versions of pg_stat_statements() */ +static void +pg_stat_statements_internal(FunctionCallInfo fcinfo, + pgssVersion api_version, + bool showtext) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Oid userid = GetUserId(); + bool is_allowed_role = false; + char *qbuffer = NULL; + Size qbuffer_size = 0; + Size extent = 0; + int gc_count = 0; + HASH_SEQ_STATUS hash_seq; + pgssEntry *entry; + + /* + * Superusers or roles with the privileges of pg_read_all_stats members + * are allowed + */ + is_allowed_role = has_privs_of_role(userid, ROLE_PG_READ_ALL_STATS); + + /* hash table must exist already */ + if (!pgss || !pgss_hash) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("pg_stat_statements must be loaded via shared_preload_libraries"))); + + InitMaterializedSRF(fcinfo, 0); + + /* + * Check we have the expected number of output arguments. Aside from + * being a good safety check, we need a kluge here to detect API version + * 1.1, which was wedged into the code in an ill-considered way. + */ + switch (rsinfo->setDesc->natts) + { + case PG_STAT_STATEMENTS_COLS_V1_0: + if (api_version != PGSS_V1_0) + elog(ERROR, "incorrect number of output arguments"); + break; + case PG_STAT_STATEMENTS_COLS_V1_1: + /* pg_stat_statements() should have told us 1.0 */ + if (api_version != PGSS_V1_0) + elog(ERROR, "incorrect number of output arguments"); + api_version = PGSS_V1_1; + break; + case PG_STAT_STATEMENTS_COLS_V1_2: + if (api_version != PGSS_V1_2) + elog(ERROR, "incorrect number of output arguments"); + break; + case PG_STAT_STATEMENTS_COLS_V1_3: + if (api_version != PGSS_V1_3) + elog(ERROR, "incorrect number of output arguments"); + break; + case PG_STAT_STATEMENTS_COLS_V1_8: + if (api_version != PGSS_V1_8) + elog(ERROR, "incorrect number of output arguments"); + break; + case PG_STAT_STATEMENTS_COLS_V1_9: + if (api_version != PGSS_V1_9) + elog(ERROR, "incorrect number of output arguments"); + break; + case PG_STAT_STATEMENTS_COLS_V1_10: + if (api_version != PGSS_V1_10) + elog(ERROR, "incorrect number of output arguments"); + break; + default: + elog(ERROR, "incorrect number of output arguments"); + } + + /* + * We'd like to load the query text file (if needed) while not holding any + * lock on pgss->lock. In the worst case we'll have to do this again + * after we have the lock, but it's unlikely enough to make this a win + * despite occasional duplicated work. We need to reload if anybody + * writes to the file (either a retail qtext_store(), or a garbage + * collection) between this point and where we've gotten shared lock. If + * a qtext_store is actually in progress when we look, we might as well + * skip the speculative load entirely. + */ + if (showtext) + { + int n_writers; + + /* Take the mutex so we can examine variables */ + { + volatile pgssSharedState *s = (volatile pgssSharedState *) pgss; + + SpinLockAcquire(&s->mutex); + extent = s->extent; + n_writers = s->n_writers; + gc_count = s->gc_count; + SpinLockRelease(&s->mutex); + } + + /* No point in loading file now if there are active writers */ + if (n_writers == 0) + qbuffer = qtext_load_file(&qbuffer_size); + } + + /* + * Get shared lock, load or reload the query text file if we must, and + * iterate over the hashtable entries. + * + * With a large hash table, we might be holding the lock rather longer + * than one could wish. However, this only blocks creation of new hash + * table entries, and the larger the hash table the less likely that is to + * be needed. So we can hope this is okay. Perhaps someday we'll decide + * we need to partition the hash table to limit the time spent holding any + * one lock. + */ + LWLockAcquire(pgss->lock, LW_SHARED); + + if (showtext) + { + /* + * Here it is safe to examine extent and gc_count without taking the + * mutex. Note that although other processes might change + * pgss->extent just after we look at it, the strings they then write + * into the file cannot yet be referenced in the hashtable, so we + * don't care whether we see them or not. + * + * If qtext_load_file fails, we just press on; we'll return NULL for + * every query text. + */ + if (qbuffer == NULL || + pgss->extent != extent || + pgss->gc_count != gc_count) + { + free(qbuffer); + qbuffer = qtext_load_file(&qbuffer_size); + } + } + + hash_seq_init(&hash_seq, pgss_hash); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + Datum values[PG_STAT_STATEMENTS_COLS]; + bool nulls[PG_STAT_STATEMENTS_COLS]; + int i = 0; + Counters tmp; + double stddev; + int64 queryid = entry->key.queryid; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[i++] = ObjectIdGetDatum(entry->key.userid); + values[i++] = ObjectIdGetDatum(entry->key.dbid); + if (api_version >= PGSS_V1_9) + values[i++] = BoolGetDatum(entry->key.toplevel); + + if (is_allowed_role || entry->key.userid == userid) + { + if (api_version >= PGSS_V1_2) + values[i++] = Int64GetDatumFast(queryid); + + if (showtext) + { + char *qstr = qtext_fetch(entry->query_offset, + entry->query_len, + qbuffer, + qbuffer_size); + + if (qstr) + { + char *enc; + + enc = pg_any_to_server(qstr, + entry->query_len, + entry->encoding); + + values[i++] = CStringGetTextDatum(enc); + + if (enc != qstr) + pfree(enc); + } + else + { + /* Just return a null if we fail to find the text */ + nulls[i++] = true; + } + } + else + { + /* Query text not requested */ + nulls[i++] = true; + } + } + else + { + /* Don't show queryid */ + if (api_version >= PGSS_V1_2) + nulls[i++] = true; + + /* + * Don't show query text, but hint as to the reason for not doing + * so if it was requested + */ + if (showtext) + values[i++] = CStringGetTextDatum(""); + else + nulls[i++] = true; + } + + /* copy counters to a local variable to keep locking time short */ + { + volatile pgssEntry *e = (volatile pgssEntry *) entry; + + SpinLockAcquire(&e->mutex); + tmp = e->counters; + SpinLockRelease(&e->mutex); + } + + /* Skip entry if unexecuted (ie, it's a pending "sticky" entry) */ + if (IS_STICKY(tmp)) + continue; + + /* Note that we rely on PGSS_PLAN being 0 and PGSS_EXEC being 1. */ + for (int kind = 0; kind < PGSS_NUMKIND; kind++) + { + if (kind == PGSS_EXEC || api_version >= PGSS_V1_8) + { + values[i++] = Int64GetDatumFast(tmp.calls[kind]); + values[i++] = Float8GetDatumFast(tmp.total_time[kind]); + } + + if ((kind == PGSS_EXEC && api_version >= PGSS_V1_3) || + api_version >= PGSS_V1_8) + { + values[i++] = Float8GetDatumFast(tmp.min_time[kind]); + values[i++] = Float8GetDatumFast(tmp.max_time[kind]); + values[i++] = Float8GetDatumFast(tmp.mean_time[kind]); + + /* + * Note we are calculating the population variance here, not + * the sample variance, as we have data for the whole + * population, so Bessel's correction is not used, and we + * don't divide by tmp.calls - 1. + */ + if (tmp.calls[kind] > 1) + stddev = sqrt(tmp.sum_var_time[kind] / tmp.calls[kind]); + else + stddev = 0.0; + values[i++] = Float8GetDatumFast(stddev); + } + } + values[i++] = Int64GetDatumFast(tmp.rows); + values[i++] = Int64GetDatumFast(tmp.shared_blks_hit); + values[i++] = Int64GetDatumFast(tmp.shared_blks_read); + if (api_version >= PGSS_V1_1) + values[i++] = Int64GetDatumFast(tmp.shared_blks_dirtied); + values[i++] = Int64GetDatumFast(tmp.shared_blks_written); + values[i++] = Int64GetDatumFast(tmp.local_blks_hit); + values[i++] = Int64GetDatumFast(tmp.local_blks_read); + if (api_version >= PGSS_V1_1) + values[i++] = Int64GetDatumFast(tmp.local_blks_dirtied); + values[i++] = Int64GetDatumFast(tmp.local_blks_written); + values[i++] = Int64GetDatumFast(tmp.temp_blks_read); + values[i++] = Int64GetDatumFast(tmp.temp_blks_written); + if (api_version >= PGSS_V1_1) + { + values[i++] = Float8GetDatumFast(tmp.blk_read_time); + values[i++] = Float8GetDatumFast(tmp.blk_write_time); + } + if (api_version >= PGSS_V1_10) + { + values[i++] = Float8GetDatumFast(tmp.temp_blk_read_time); + values[i++] = Float8GetDatumFast(tmp.temp_blk_write_time); + } + if (api_version >= PGSS_V1_8) + { + char buf[256]; + Datum wal_bytes; + + values[i++] = Int64GetDatumFast(tmp.wal_records); + values[i++] = Int64GetDatumFast(tmp.wal_fpi); + + snprintf(buf, sizeof buf, UINT64_FORMAT, tmp.wal_bytes); + + /* Convert to numeric. */ + wal_bytes = DirectFunctionCall3(numeric_in, + CStringGetDatum(buf), + ObjectIdGetDatum(0), + Int32GetDatum(-1)); + values[i++] = wal_bytes; + } + if (api_version >= PGSS_V1_10) + { + values[i++] = Int64GetDatumFast(tmp.jit_functions); + values[i++] = Float8GetDatumFast(tmp.jit_generation_time); + values[i++] = Int64GetDatumFast(tmp.jit_inlining_count); + values[i++] = Float8GetDatumFast(tmp.jit_inlining_time); + values[i++] = Int64GetDatumFast(tmp.jit_optimization_count); + values[i++] = Float8GetDatumFast(tmp.jit_optimization_time); + values[i++] = Int64GetDatumFast(tmp.jit_emission_count); + values[i++] = Float8GetDatumFast(tmp.jit_emission_time); + } + + Assert(i == (api_version == PGSS_V1_0 ? PG_STAT_STATEMENTS_COLS_V1_0 : + api_version == PGSS_V1_1 ? PG_STAT_STATEMENTS_COLS_V1_1 : + api_version == PGSS_V1_2 ? PG_STAT_STATEMENTS_COLS_V1_2 : + api_version == PGSS_V1_3 ? PG_STAT_STATEMENTS_COLS_V1_3 : + api_version == PGSS_V1_8 ? PG_STAT_STATEMENTS_COLS_V1_8 : + api_version == PGSS_V1_9 ? PG_STAT_STATEMENTS_COLS_V1_9 : + api_version == PGSS_V1_10 ? PG_STAT_STATEMENTS_COLS_V1_10 : + -1 /* fail if you forget to update this assert */ )); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + LWLockRelease(pgss->lock); + + free(qbuffer); +} + +/* Number of output arguments (columns) for pg_stat_statements_info */ +#define PG_STAT_STATEMENTS_INFO_COLS 2 + +/* + * Return statistics of pg_stat_statements. + */ +Datum +pg_stat_statements_info(PG_FUNCTION_ARGS) +{ + pgssGlobalStats stats; + TupleDesc tupdesc; + Datum values[PG_STAT_STATEMENTS_INFO_COLS] = {0}; + bool nulls[PG_STAT_STATEMENTS_INFO_COLS] = {0}; + + if (!pgss || !pgss_hash) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("pg_stat_statements must be loaded via shared_preload_libraries"))); + + /* 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"); + + /* Read global statistics for pg_stat_statements */ + { + volatile pgssSharedState *s = (volatile pgssSharedState *) pgss; + + SpinLockAcquire(&s->mutex); + stats = s->stats; + SpinLockRelease(&s->mutex); + } + + values[0] = Int64GetDatum(stats.dealloc); + values[1] = TimestampTzGetDatum(stats.stats_reset); + + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Estimate shared memory space needed. + */ +static Size +pgss_memsize(void) +{ + Size size; + + size = MAXALIGN(sizeof(pgssSharedState)); + size = add_size(size, hash_estimate_size(pgss_max, sizeof(pgssEntry))); + + return size; +} + +/* + * Allocate a new hashtable entry. + * caller must hold an exclusive lock on pgss->lock + * + * "query" need not be null-terminated; we rely on query_len instead + * + * If "sticky" is true, make the new entry artificially sticky so that it will + * probably still be there when the query finishes execution. We do this by + * giving it a median usage value rather than the normal value. (Strictly + * speaking, query strings are normalized on a best effort basis, though it + * would be difficult to demonstrate this even under artificial conditions.) + * + * Note: despite needing exclusive lock, it's not an error for the target + * entry to already exist. This is because pgss_store releases and + * reacquires lock after failing to find a match; so someone else could + * have made the entry while we waited to get exclusive lock. + */ +static pgssEntry * +entry_alloc(pgssHashKey *key, Size query_offset, int query_len, int encoding, + bool sticky) +{ + pgssEntry *entry; + bool found; + + /* Make space if needed */ + while (hash_get_num_entries(pgss_hash) >= pgss_max) + entry_dealloc(); + + /* Find or create an entry with desired hash code */ + entry = (pgssEntry *) hash_search(pgss_hash, key, HASH_ENTER, &found); + + if (!found) + { + /* New entry, initialize it */ + + /* reset the statistics */ + memset(&entry->counters, 0, sizeof(Counters)); + /* set the appropriate initial usage count */ + entry->counters.usage = sticky ? pgss->cur_median_usage : USAGE_INIT; + /* re-initialize the mutex each time ... we assume no one using it */ + SpinLockInit(&entry->mutex); + /* ... and don't forget the query text metadata */ + Assert(query_len >= 0); + entry->query_offset = query_offset; + entry->query_len = query_len; + entry->encoding = encoding; + } + + return entry; +} + +/* + * qsort comparator for sorting into increasing usage order + */ +static int +entry_cmp(const void *lhs, const void *rhs) +{ + double l_usage = (*(pgssEntry *const *) lhs)->counters.usage; + double r_usage = (*(pgssEntry *const *) rhs)->counters.usage; + + if (l_usage < r_usage) + return -1; + else if (l_usage > r_usage) + return +1; + else + return 0; +} + +/* + * Deallocate least-used entries. + * + * Caller must hold an exclusive lock on pgss->lock. + */ +static void +entry_dealloc(void) +{ + HASH_SEQ_STATUS hash_seq; + pgssEntry **entries; + pgssEntry *entry; + int nvictims; + int i; + Size tottextlen; + int nvalidtexts; + + /* + * Sort entries by usage and deallocate USAGE_DEALLOC_PERCENT of them. + * While we're scanning the table, apply the decay factor to the usage + * values, and update the mean query length. + * + * Note that the mean query length is almost immediately obsolete, since + * we compute it before not after discarding the least-used entries. + * Hopefully, that doesn't affect the mean too much; it doesn't seem worth + * making two passes to get a more current result. Likewise, the new + * cur_median_usage includes the entries we're about to zap. + */ + + entries = palloc(hash_get_num_entries(pgss_hash) * sizeof(pgssEntry *)); + + i = 0; + tottextlen = 0; + nvalidtexts = 0; + + hash_seq_init(&hash_seq, pgss_hash); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + entries[i++] = entry; + /* "Sticky" entries get a different usage decay rate. */ + if (IS_STICKY(entry->counters)) + entry->counters.usage *= STICKY_DECREASE_FACTOR; + else + entry->counters.usage *= USAGE_DECREASE_FACTOR; + /* In the mean length computation, ignore dropped texts. */ + if (entry->query_len >= 0) + { + tottextlen += entry->query_len + 1; + nvalidtexts++; + } + } + + /* Sort into increasing order by usage */ + qsort(entries, i, sizeof(pgssEntry *), entry_cmp); + + /* Record the (approximate) median usage */ + if (i > 0) + pgss->cur_median_usage = entries[i / 2]->counters.usage; + /* Record the mean query length */ + if (nvalidtexts > 0) + pgss->mean_query_len = tottextlen / nvalidtexts; + else + pgss->mean_query_len = ASSUMED_LENGTH_INIT; + + /* Now zap an appropriate fraction of lowest-usage entries */ + nvictims = Max(10, i * USAGE_DEALLOC_PERCENT / 100); + nvictims = Min(nvictims, i); + + for (i = 0; i < nvictims; i++) + { + hash_search(pgss_hash, &entries[i]->key, HASH_REMOVE, NULL); + } + + pfree(entries); + + /* Increment the number of times entries are deallocated */ + { + volatile pgssSharedState *s = (volatile pgssSharedState *) pgss; + + SpinLockAcquire(&s->mutex); + s->stats.dealloc += 1; + SpinLockRelease(&s->mutex); + } +} + +/* + * Given a query string (not necessarily null-terminated), allocate a new + * entry in the external query text file and store the string there. + * + * If successful, returns true, and stores the new entry's offset in the file + * into *query_offset. Also, if gc_count isn't NULL, *gc_count is set to the + * number of garbage collections that have occurred so far. + * + * On failure, returns false. + * + * At least a shared lock on pgss->lock must be held by the caller, so as + * to prevent a concurrent garbage collection. Share-lock-holding callers + * should pass a gc_count pointer to obtain the number of garbage collections, + * so that they can recheck the count after obtaining exclusive lock to + * detect whether a garbage collection occurred (and removed this entry). + */ +static bool +qtext_store(const char *query, int query_len, + Size *query_offset, int *gc_count) +{ + Size off; + int fd; + + /* + * We use a spinlock to protect extent/n_writers/gc_count, so that + * multiple processes may execute this function concurrently. + */ + { + volatile pgssSharedState *s = (volatile pgssSharedState *) pgss; + + SpinLockAcquire(&s->mutex); + off = s->extent; + s->extent += query_len + 1; + s->n_writers++; + if (gc_count) + *gc_count = s->gc_count; + SpinLockRelease(&s->mutex); + } + + *query_offset = off; + + /* + * Don't allow the file to grow larger than what qtext_load_file can + * (theoretically) handle. This has been seen to be reachable on 32-bit + * platforms. + */ + if (unlikely(query_len >= MaxAllocHugeSize - off)) + { + errno = EFBIG; /* not quite right, but it'll do */ + fd = -1; + goto error; + } + + /* Now write the data into the successfully-reserved part of the file */ + fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDWR | O_CREAT | PG_BINARY); + if (fd < 0) + goto error; + + if (pg_pwrite(fd, query, query_len, off) != query_len) + goto error; + if (pg_pwrite(fd, "\0", 1, off + query_len) != 1) + goto error; + + CloseTransientFile(fd); + + /* Mark our write complete */ + { + volatile pgssSharedState *s = (volatile pgssSharedState *) pgss; + + SpinLockAcquire(&s->mutex); + s->n_writers--; + SpinLockRelease(&s->mutex); + } + + return true; + +error: + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write file \"%s\": %m", + PGSS_TEXT_FILE))); + + if (fd >= 0) + CloseTransientFile(fd); + + /* Mark our write complete */ + { + volatile pgssSharedState *s = (volatile pgssSharedState *) pgss; + + SpinLockAcquire(&s->mutex); + s->n_writers--; + SpinLockRelease(&s->mutex); + } + + return false; +} + +/* + * Read the external query text file into a malloc'd buffer. + * + * Returns NULL (without throwing an error) if unable to read, eg + * file not there or insufficient memory. + * + * On success, the buffer size is also returned into *buffer_size. + * + * This can be called without any lock on pgss->lock, but in that case + * the caller is responsible for verifying that the result is sane. + */ +static char * +qtext_load_file(Size *buffer_size) +{ + char *buf; + int fd; + struct stat stat; + Size nread; + + fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDONLY | PG_BINARY); + if (fd < 0) + { + if (errno != ENOENT) + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", + PGSS_TEXT_FILE))); + return NULL; + } + + /* Get file length */ + if (fstat(fd, &stat)) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", + PGSS_TEXT_FILE))); + CloseTransientFile(fd); + return NULL; + } + + /* Allocate buffer; beware that off_t might be wider than size_t */ + if (stat.st_size <= MaxAllocHugeSize) + buf = (char *) malloc(stat.st_size); + else + buf = NULL; + if (buf == NULL) + { + ereport(LOG, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Could not allocate enough memory to read file \"%s\".", + PGSS_TEXT_FILE))); + CloseTransientFile(fd); + return NULL; + } + + /* + * OK, slurp in the file. Windows fails if we try to read more than + * INT_MAX bytes at once, and other platforms might not like that either, + * so read a very large file in 1GB segments. + */ + nread = 0; + while (nread < stat.st_size) + { + int toread = Min(1024 * 1024 * 1024, stat.st_size - nread); + + /* + * If we get a short read and errno doesn't get set, the reason is + * probably that garbage collection truncated the file since we did + * the fstat(), so we don't log a complaint --- but we don't return + * the data, either, since it's most likely corrupt due to concurrent + * writes from garbage collection. + */ + errno = 0; + if (read(fd, buf + nread, toread) != toread) + { + if (errno) + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", + PGSS_TEXT_FILE))); + free(buf); + CloseTransientFile(fd); + return NULL; + } + nread += toread; + } + + if (CloseTransientFile(fd) != 0) + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not close file \"%s\": %m", PGSS_TEXT_FILE))); + + *buffer_size = nread; + return buf; +} + +/* + * Locate a query text in the file image previously read by qtext_load_file(). + * + * We validate the given offset/length, and return NULL if bogus. Otherwise, + * the result points to a null-terminated string within the buffer. + */ +static char * +qtext_fetch(Size query_offset, int query_len, + char *buffer, Size buffer_size) +{ + /* File read failed? */ + if (buffer == NULL) + return NULL; + /* Bogus offset/length? */ + if (query_len < 0 || + query_offset + query_len >= buffer_size) + return NULL; + /* As a further sanity check, make sure there's a trailing null */ + if (buffer[query_offset + query_len] != '\0') + return NULL; + /* Looks OK */ + return buffer + query_offset; +} + +/* + * Do we need to garbage-collect the external query text file? + * + * Caller should hold at least a shared lock on pgss->lock. + */ +static bool +need_gc_qtexts(void) +{ + Size extent; + + /* Read shared extent pointer */ + { + volatile pgssSharedState *s = (volatile pgssSharedState *) pgss; + + SpinLockAcquire(&s->mutex); + extent = s->extent; + SpinLockRelease(&s->mutex); + } + + /* + * Don't proceed if file does not exceed 512 bytes per possible entry. + * + * Here and in the next test, 32-bit machines have overflow hazards if + * pgss_max and/or mean_query_len are large. Force the multiplications + * and comparisons to be done in uint64 arithmetic to forestall trouble. + */ + if ((uint64) extent < (uint64) 512 * pgss_max) + return false; + + /* + * Don't proceed if file is less than about 50% bloat. Nothing can or + * should be done in the event of unusually large query texts accounting + * for file's large size. We go to the trouble of maintaining the mean + * query length in order to prevent garbage collection from thrashing + * uselessly. + */ + if ((uint64) extent < (uint64) pgss->mean_query_len * pgss_max * 2) + return false; + + return true; +} + +/* + * Garbage-collect orphaned query texts in external file. + * + * This won't be called often in the typical case, since it's likely that + * there won't be too much churn, and besides, a similar compaction process + * occurs when serializing to disk at shutdown or as part of resetting. + * Despite this, it seems prudent to plan for the edge case where the file + * becomes unreasonably large, with no other method of compaction likely to + * occur in the foreseeable future. + * + * The caller must hold an exclusive lock on pgss->lock. + * + * At the first sign of trouble we unlink the query text file to get a clean + * slate (although existing statistics are retained), rather than risk + * thrashing by allowing the same problem case to recur indefinitely. + */ +static void +gc_qtexts(void) +{ + char *qbuffer; + Size qbuffer_size; + FILE *qfile = NULL; + HASH_SEQ_STATUS hash_seq; + pgssEntry *entry; + Size extent; + int nentries; + + /* + * When called from pgss_store, some other session might have proceeded + * with garbage collection in the no-lock-held interim of lock strength + * escalation. Check once more that this is actually necessary. + */ + if (!need_gc_qtexts()) + return; + + /* + * Load the old texts file. If we fail (out of memory, for instance), + * invalidate query texts. Hopefully this is rare. It might seem better + * to leave things alone on an OOM failure, but the problem is that the + * file is only going to get bigger; hoping for a future non-OOM result is + * risky and can easily lead to complete denial of service. + */ + qbuffer = qtext_load_file(&qbuffer_size); + if (qbuffer == NULL) + goto gc_fail; + + /* + * We overwrite the query texts file in place, so as to reduce the risk of + * an out-of-disk-space failure. Since the file is guaranteed not to get + * larger, this should always work on traditional filesystems; though we + * could still lose on copy-on-write filesystems. + */ + qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W); + if (qfile == NULL) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write file \"%s\": %m", + PGSS_TEXT_FILE))); + goto gc_fail; + } + + extent = 0; + nentries = 0; + + hash_seq_init(&hash_seq, pgss_hash); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + int query_len = entry->query_len; + char *qry = qtext_fetch(entry->query_offset, + query_len, + qbuffer, + qbuffer_size); + + if (qry == NULL) + { + /* Trouble ... drop the text */ + entry->query_offset = 0; + entry->query_len = -1; + /* entry will not be counted in mean query length computation */ + continue; + } + + if (fwrite(qry, 1, query_len + 1, qfile) != query_len + 1) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write file \"%s\": %m", + PGSS_TEXT_FILE))); + hash_seq_term(&hash_seq); + goto gc_fail; + } + + entry->query_offset = extent; + extent += query_len + 1; + nentries++; + } + + /* + * Truncate away any now-unused space. If this fails for some odd reason, + * we log it, but there's no need to fail. + */ + if (ftruncate(fileno(qfile), extent) != 0) + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not truncate file \"%s\": %m", + PGSS_TEXT_FILE))); + + if (FreeFile(qfile)) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write file \"%s\": %m", + PGSS_TEXT_FILE))); + qfile = NULL; + goto gc_fail; + } + + elog(DEBUG1, "pgss gc of queries file shrunk size from %zu to %zu", + pgss->extent, extent); + + /* Reset the shared extent pointer */ + pgss->extent = extent; + + /* + * Also update the mean query length, to be sure that need_gc_qtexts() + * won't still think we have a problem. + */ + if (nentries > 0) + pgss->mean_query_len = extent / nentries; + else + pgss->mean_query_len = ASSUMED_LENGTH_INIT; + + free(qbuffer); + + /* + * OK, count a garbage collection cycle. (Note: even though we have + * exclusive lock on pgss->lock, we must take pgss->mutex for this, since + * other processes may examine gc_count while holding only the mutex. + * Also, we have to advance the count *after* we've rewritten the file, + * else other processes might not realize they read a stale file.) + */ + record_gc_qtexts(); + + return; + +gc_fail: + /* clean up resources */ + if (qfile) + FreeFile(qfile); + free(qbuffer); + + /* + * Since the contents of the external file are now uncertain, mark all + * hashtable entries as having invalid texts. + */ + hash_seq_init(&hash_seq, pgss_hash); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + entry->query_offset = 0; + entry->query_len = -1; + } + + /* + * Destroy the query text file and create a new, empty one + */ + (void) unlink(PGSS_TEXT_FILE); + qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W); + if (qfile == NULL) + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not recreate file \"%s\": %m", + PGSS_TEXT_FILE))); + else + FreeFile(qfile); + + /* Reset the shared extent pointer */ + pgss->extent = 0; + + /* Reset mean_query_len to match the new state */ + pgss->mean_query_len = ASSUMED_LENGTH_INIT; + + /* + * Bump the GC count even though we failed. + * + * This is needed to make concurrent readers of file without any lock on + * pgss->lock notice existence of new version of file. Once readers + * subsequently observe a change in GC count with pgss->lock held, that + * forces a safe reopen of file. Writers also require that we bump here, + * of course. (As required by locking protocol, readers and writers don't + * trust earlier file contents until gc_count is found unchanged after + * pgss->lock acquired in shared or exclusive mode respectively.) + */ + record_gc_qtexts(); +} + +/* + * Release entries corresponding to parameters passed. + */ +static void +entry_reset(Oid userid, Oid dbid, uint64 queryid) +{ + HASH_SEQ_STATUS hash_seq; + pgssEntry *entry; + FILE *qfile; + long num_entries; + long num_remove = 0; + pgssHashKey key; + + if (!pgss || !pgss_hash) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("pg_stat_statements must be loaded via shared_preload_libraries"))); + + LWLockAcquire(pgss->lock, LW_EXCLUSIVE); + num_entries = hash_get_num_entries(pgss_hash); + + if (userid != 0 && dbid != 0 && queryid != UINT64CONST(0)) + { + /* If all the parameters are available, use the fast path. */ + memset(&key, 0, sizeof(pgssHashKey)); + key.userid = userid; + key.dbid = dbid; + key.queryid = queryid; + + /* + * Remove the key if it exists, starting with the non-top-level entry. + */ + key.toplevel = false; + entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_REMOVE, NULL); + if (entry) /* found */ + num_remove++; + + /* Also remove the top-level entry if it exists. */ + key.toplevel = true; + entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_REMOVE, NULL); + if (entry) /* found */ + num_remove++; + } + else if (userid != 0 || dbid != 0 || queryid != UINT64CONST(0)) + { + /* Remove entries corresponding to valid parameters. */ + hash_seq_init(&hash_seq, pgss_hash); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if ((!userid || entry->key.userid == userid) && + (!dbid || entry->key.dbid == dbid) && + (!queryid || entry->key.queryid == queryid)) + { + hash_search(pgss_hash, &entry->key, HASH_REMOVE, NULL); + num_remove++; + } + } + } + else + { + /* Remove all entries. */ + hash_seq_init(&hash_seq, pgss_hash); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + hash_search(pgss_hash, &entry->key, HASH_REMOVE, NULL); + num_remove++; + } + } + + /* All entries are removed? */ + if (num_entries != num_remove) + goto release_lock; + + /* + * Reset global statistics for pg_stat_statements since all entries are + * removed. + */ + { + volatile pgssSharedState *s = (volatile pgssSharedState *) pgss; + TimestampTz stats_reset = GetCurrentTimestamp(); + + SpinLockAcquire(&s->mutex); + s->stats.dealloc = 0; + s->stats.stats_reset = stats_reset; + SpinLockRelease(&s->mutex); + } + + /* + * Write new empty query file, perhaps even creating a new one to recover + * if the file was missing. + */ + qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W); + if (qfile == NULL) + { + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not create file \"%s\": %m", + PGSS_TEXT_FILE))); + goto done; + } + + /* If ftruncate fails, log it, but it's not a fatal problem */ + if (ftruncate(fileno(qfile), 0) != 0) + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not truncate file \"%s\": %m", + PGSS_TEXT_FILE))); + + FreeFile(qfile); + +done: + pgss->extent = 0; + /* This counts as a query text garbage collection for our purposes */ + record_gc_qtexts(); + +release_lock: + LWLockRelease(pgss->lock); +} + +/* + * Generate a normalized version of the query string that will be used to + * represent all similar queries. + * + * Note that the normalized representation may well vary depending on + * just which "equivalent" query is used to create the hashtable entry. + * We assume this is OK. + * + * If query_loc > 0, then "query" has been advanced by that much compared to + * the original string start, so we need to translate the provided locations + * to compensate. (This lets us avoid re-scanning statements before the one + * of interest, so it's worth doing.) + * + * *query_len_p contains the input string length, and is updated with + * the result string length on exit. The resulting string might be longer + * or shorter depending on what happens with replacement of constants. + * + * Returns a palloc'd string. + */ +static char * +generate_normalized_query(JumbleState *jstate, const char *query, + int query_loc, int *query_len_p) +{ + char *norm_query; + int query_len = *query_len_p; + int i, + norm_query_buflen, /* Space allowed for norm_query */ + len_to_wrt, /* Length (in bytes) to write */ + quer_loc = 0, /* Source query byte location */ + n_quer_loc = 0, /* Normalized query byte location */ + last_off = 0, /* Offset from start for previous tok */ + last_tok_len = 0; /* Length (in bytes) of that tok */ + + /* + * Get constants' lengths (core system only gives us locations). Note + * this also ensures the items are sorted by location. + */ + fill_in_constant_lengths(jstate, query, query_loc); + + /* + * Allow for $n symbols to be longer than the constants they replace. + * Constants must take at least one byte in text form, while a $n symbol + * certainly isn't more than 11 bytes, even if n reaches INT_MAX. We + * could refine that limit based on the max value of n for the current + * query, but it hardly seems worth any extra effort to do so. + */ + norm_query_buflen = query_len + jstate->clocations_count * 10; + + /* Allocate result buffer */ + norm_query = palloc(norm_query_buflen + 1); + + for (i = 0; i < jstate->clocations_count; i++) + { + int off, /* Offset from start for cur tok */ + tok_len; /* Length (in bytes) of that tok */ + + off = jstate->clocations[i].location; + /* Adjust recorded location if we're dealing with partial string */ + off -= query_loc; + + tok_len = jstate->clocations[i].length; + + if (tok_len < 0) + continue; /* ignore any duplicates */ + + /* Copy next chunk (what precedes the next constant) */ + len_to_wrt = off - last_off; + len_to_wrt -= last_tok_len; + + Assert(len_to_wrt >= 0); + memcpy(norm_query + n_quer_loc, query + quer_loc, len_to_wrt); + n_quer_loc += len_to_wrt; + + /* And insert a param symbol in place of the constant token */ + n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d", + i + 1 + jstate->highest_extern_param_id); + + quer_loc = off + tok_len; + last_off = off; + last_tok_len = tok_len; + } + + /* + * We've copied up until the last ignorable constant. Copy over the + * remaining bytes of the original query string. + */ + len_to_wrt = query_len - quer_loc; + + Assert(len_to_wrt >= 0); + memcpy(norm_query + n_quer_loc, query + quer_loc, len_to_wrt); + n_quer_loc += len_to_wrt; + + Assert(n_quer_loc <= norm_query_buflen); + norm_query[n_quer_loc] = '\0'; + + *query_len_p = n_quer_loc; + return norm_query; +} + +/* + * Given a valid SQL string and an array of constant-location records, + * fill in the textual lengths of those constants. + * + * The constants may use any allowed constant syntax, such as float literals, + * bit-strings, single-quoted strings and dollar-quoted strings. This is + * accomplished by using the public API for the core scanner. + * + * It is the caller's job to ensure that the string is a valid SQL statement + * with constants at the indicated locations. Since in practice the string + * has already been parsed, and the locations that the caller provides will + * have originated from within the authoritative parser, this should not be + * a problem. + * + * Duplicate constant pointers are possible, and will have their lengths + * marked as '-1', so that they are later ignored. (Actually, we assume the + * lengths were initialized as -1 to start with, and don't change them here.) + * + * If query_loc > 0, then "query" has been advanced by that much compared to + * the original string start, so we need to translate the provided locations + * to compensate. (This lets us avoid re-scanning statements before the one + * of interest, so it's worth doing.) + * + * N.B. There is an assumption that a '-' character at a Const location begins + * a negative numeric constant. This precludes there ever being another + * reason for a constant to start with a '-'. + */ +static void +fill_in_constant_lengths(JumbleState *jstate, const char *query, + int query_loc) +{ + LocationLen *locs; + core_yyscan_t yyscanner; + core_yy_extra_type yyextra; + core_YYSTYPE yylval; + YYLTYPE yylloc; + int last_loc = -1; + int i; + + /* + * Sort the records by location so that we can process them in order while + * scanning the query text. + */ + if (jstate->clocations_count > 1) + qsort(jstate->clocations, jstate->clocations_count, + sizeof(LocationLen), comp_location); + locs = jstate->clocations; + + /* initialize the flex scanner --- should match raw_parser() */ + yyscanner = scanner_init(query, + &yyextra, + &ScanKeywords, + ScanKeywordTokens); + + /* we don't want to re-emit any escape string warnings */ + yyextra.escape_string_warning = false; + + /* Search for each constant, in sequence */ + for (i = 0; i < jstate->clocations_count; i++) + { + int loc = locs[i].location; + int tok; + + /* Adjust recorded location if we're dealing with partial string */ + loc -= query_loc; + + Assert(loc >= 0); + + if (loc <= last_loc) + continue; /* Duplicate constant, ignore */ + + /* Lex tokens until we find the desired constant */ + for (;;) + { + tok = core_yylex(&yylval, &yylloc, yyscanner); + + /* We should not hit end-of-string, but if we do, behave sanely */ + if (tok == 0) + break; /* out of inner for-loop */ + + /* + * We should find the token position exactly, but if we somehow + * run past it, work with that. + */ + if (yylloc >= loc) + { + if (query[loc] == '-') + { + /* + * It's a negative value - this is the one and only case + * where we replace more than a single token. + * + * Do not compensate for the core system's special-case + * adjustment of location to that of the leading '-' + * operator in the event of a negative constant. It is + * also useful for our purposes to start from the minus + * symbol. In this way, queries like "select * from foo + * where bar = 1" and "select * from foo where bar = -2" + * will have identical normalized query strings. + */ + tok = core_yylex(&yylval, &yylloc, yyscanner); + if (tok == 0) + break; /* out of inner for-loop */ + } + + /* + * We now rely on the assumption that flex has placed a zero + * byte after the text of the current token in scanbuf. + */ + locs[i].length = strlen(yyextra.scanbuf + loc); + break; /* out of inner for-loop */ + } + } + + /* If we hit end-of-string, give up, leaving remaining lengths -1 */ + if (tok == 0) + break; + + last_loc = loc; + } + + scanner_finish(yyscanner); +} + +/* + * comp_location: comparator for qsorting LocationLen structs by location + */ +static int +comp_location(const void *a, const void *b) +{ + int l = ((const LocationLen *) a)->location; + int r = ((const LocationLen *) b)->location; + + if (l < r) + return -1; + else if (l > r) + return +1; + else + return 0; +} diff --git a/contrib/pg_stat_statements/pg_stat_statements.conf b/contrib/pg_stat_statements/pg_stat_statements.conf new file mode 100644 index 0000000..13346e2 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements.conf @@ -0,0 +1 @@ +shared_preload_libraries = 'pg_stat_statements' diff --git a/contrib/pg_stat_statements/pg_stat_statements.control b/contrib/pg_stat_statements/pg_stat_statements.control new file mode 100644 index 0000000..0747e48 --- /dev/null +++ b/contrib/pg_stat_statements/pg_stat_statements.control @@ -0,0 +1,5 @@ +# pg_stat_statements extension +comment = 'track planning and execution statistics of all SQL statements executed' +default_version = '1.10' +module_pathname = '$libdir/pg_stat_statements' +relocatable = true diff --git a/contrib/pg_stat_statements/sql/cleanup.sql b/contrib/pg_stat_statements/sql/cleanup.sql new file mode 100644 index 0000000..36bec35 --- /dev/null +++ b/contrib/pg_stat_statements/sql/cleanup.sql @@ -0,0 +1 @@ +DROP EXTENSION pg_stat_statements; diff --git a/contrib/pg_stat_statements/sql/cursors.sql b/contrib/pg_stat_statements/sql/cursors.sql new file mode 100644 index 0000000..cef6dc9 --- /dev/null +++ b/contrib/pg_stat_statements/sql/cursors.sql @@ -0,0 +1,30 @@ +-- +-- Cursors +-- + +-- These tests require track_utility to be enabled. +SET pg_stat_statements.track_utility = TRUE; +SELECT pg_stat_statements_reset(); + +-- DECLARE +-- SELECT is normalized. +DECLARE cursor_stats_1 CURSOR WITH HOLD FOR SELECT 1; +CLOSE cursor_stats_1; +DECLARE cursor_stats_1 CURSOR WITH HOLD FOR SELECT 2; +CLOSE cursor_stats_1; + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- FETCH +BEGIN; +DECLARE cursor_stats_1 CURSOR WITH HOLD FOR SELECT 2; +DECLARE cursor_stats_2 CURSOR WITH HOLD FOR SELECT 3; +FETCH 1 IN cursor_stats_1; +FETCH 1 IN cursor_stats_2; +CLOSE cursor_stats_1; +CLOSE cursor_stats_2; +COMMIT; + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); diff --git a/contrib/pg_stat_statements/sql/dml.sql b/contrib/pg_stat_statements/sql/dml.sql new file mode 100644 index 0000000..3b5d2af --- /dev/null +++ b/contrib/pg_stat_statements/sql/dml.sql @@ -0,0 +1,95 @@ +-- +-- DMLs on test table +-- + +SET pg_stat_statements.track_utility = FALSE; + +CREATE TEMP TABLE pgss_dml_tab (a int, b char(20)); + +INSERT INTO pgss_dml_tab VALUES(generate_series(1, 10), 'aaa'); +UPDATE pgss_dml_tab SET b = 'bbb' WHERE a > 7; +DELETE FROM pgss_dml_tab WHERE a > 9; + +-- explicit transaction +BEGIN; +UPDATE pgss_dml_tab SET b = '111' WHERE a = 1 ; +COMMIT; + +BEGIN \; +UPDATE pgss_dml_tab SET b = '222' WHERE a = 2 \; +COMMIT ; + +UPDATE pgss_dml_tab SET b = '333' WHERE a = 3 \; +UPDATE pgss_dml_tab SET b = '444' WHERE a = 4 ; + +BEGIN \; +UPDATE pgss_dml_tab SET b = '555' WHERE a = 5 \; +UPDATE pgss_dml_tab SET b = '666' WHERE a = 6 \; +COMMIT ; + +-- many INSERT values +INSERT INTO pgss_dml_tab (a, b) VALUES (1, 'a'), (2, 'b'), (3, 'c'); + +-- SELECT with constants +SELECT * FROM pgss_dml_tab WHERE a > 5 ORDER BY a ; + +SELECT * + FROM pgss_dml_tab + WHERE a > 9 + ORDER BY a ; + +-- these two need to be done on a different table +-- SELECT without constants +SELECT * FROM pgss_dml_tab ORDER BY a; + +-- SELECT with IN clause +SELECT * FROM pgss_dml_tab WHERE a IN (1, 2, 3, 4, 5); + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- MERGE +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN MATCHED THEN UPDATE SET b = st.b || st.a::text; +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN MATCHED THEN UPDATE SET b = pgss_dml_tab.b || st.a::text; +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN MATCHED AND length(st.b) > 1 THEN UPDATE SET b = pgss_dml_tab.b || st.a::text; +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + WHEN NOT MATCHED THEN INSERT (a, b) VALUES (0, NULL); +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + WHEN NOT MATCHED THEN INSERT VALUES (0, NULL); -- same as above +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + WHEN NOT MATCHED THEN INSERT (b, a) VALUES (NULL, 0); +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a) + WHEN NOT MATCHED THEN INSERT (a) VALUES (0); +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN MATCHED THEN DELETE; +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN MATCHED THEN DO NOTHING; +MERGE INTO pgss_dml_tab USING pgss_dml_tab st ON (st.a = pgss_dml_tab.a AND st.a >= 4) + WHEN NOT MATCHED THEN DO NOTHING; + +DROP TABLE pgss_dml_tab; + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- check that [temp] table relation extensions are tracked as writes +CREATE TABLE pgss_extend_tab (a int, b text); +CREATE TEMP TABLE pgss_extend_temp_tab (a int, b text); +SELECT pg_stat_statements_reset(); +INSERT INTO pgss_extend_tab (a, b) SELECT generate_series(1, 1000), 'something'; +INSERT INTO pgss_extend_temp_tab (a, b) SELECT generate_series(1, 1000), 'something'; +WITH sizes AS ( + SELECT + pg_relation_size('pgss_extend_tab') / current_setting('block_size')::int8 AS rel_size, + pg_relation_size('pgss_extend_temp_tab') / current_setting('block_size')::int8 AS temp_rel_size +) +SELECT + SUM(local_blks_written) >= (SELECT temp_rel_size FROM sizes) AS temp_written_ok, + SUM(local_blks_dirtied) >= (SELECT temp_rel_size FROM sizes) AS temp_dirtied_ok, + SUM(shared_blks_written) >= (SELECT rel_size FROM sizes) AS written_ok, + SUM(shared_blks_dirtied) >= (SELECT rel_size FROM sizes) AS dirtied_ok +FROM pg_stat_statements; + +SELECT pg_stat_statements_reset(); diff --git a/contrib/pg_stat_statements/sql/level_tracking.sql b/contrib/pg_stat_statements/sql/level_tracking.sql new file mode 100644 index 0000000..0c20b8c --- /dev/null +++ b/contrib/pg_stat_statements/sql/level_tracking.sql @@ -0,0 +1,100 @@ +-- +-- Statement level tracking +-- + +SET pg_stat_statements.track_utility = TRUE; +SELECT pg_stat_statements_reset(); + +-- DO block - top-level tracking. +CREATE TABLE stats_track_tab (x int); +SET pg_stat_statements.track = 'top'; +DELETE FROM stats_track_tab; +DO $$ +BEGIN + DELETE FROM stats_track_tab; +END; +$$ LANGUAGE plpgsql; +SELECT toplevel, calls, query FROM pg_stat_statements + WHERE query LIKE '%DELETE%' ORDER BY query COLLATE "C", toplevel; +SELECT pg_stat_statements_reset(); + +-- DO block - all-level tracking. +SET pg_stat_statements.track = 'all'; +DELETE FROM stats_track_tab; +DO $$ +BEGIN + DELETE FROM stats_track_tab; +END; $$; +DO LANGUAGE plpgsql $$ +BEGIN + -- this is a SELECT + PERFORM 'hello world'::TEXT; +END; $$; +SELECT toplevel, calls, query FROM pg_stat_statements + ORDER BY query COLLATE "C", toplevel; + +-- PL/pgSQL function - top-level tracking. +SET pg_stat_statements.track = 'top'; +SET pg_stat_statements.track_utility = FALSE; +SELECT pg_stat_statements_reset(); +CREATE FUNCTION PLUS_TWO(i INTEGER) RETURNS INTEGER AS $$ +DECLARE + r INTEGER; +BEGIN + SELECT (i + 1 + 1.0)::INTEGER INTO r; + RETURN r; +END; $$ LANGUAGE plpgsql; + +SELECT PLUS_TWO(3); +SELECT PLUS_TWO(7); + +-- SQL function --- use LIMIT to keep it from being inlined +CREATE FUNCTION PLUS_ONE(i INTEGER) RETURNS INTEGER AS +$$ SELECT (i + 1.0)::INTEGER LIMIT 1 $$ LANGUAGE SQL; + +SELECT PLUS_ONE(8); +SELECT PLUS_ONE(10); + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- PL/pgSQL function - all-level tracking. +SET pg_stat_statements.track = 'all'; +SELECT pg_stat_statements_reset(); + +-- we drop and recreate the functions to avoid any caching funnies +DROP FUNCTION PLUS_ONE(INTEGER); +DROP FUNCTION PLUS_TWO(INTEGER); + +-- PL/pgSQL function +CREATE FUNCTION PLUS_TWO(i INTEGER) RETURNS INTEGER AS $$ +DECLARE + r INTEGER; +BEGIN + SELECT (i + 1 + 1.0)::INTEGER INTO r; + RETURN r; +END; $$ LANGUAGE plpgsql; + +SELECT PLUS_TWO(-1); +SELECT PLUS_TWO(2); + +-- SQL function --- use LIMIT to keep it from being inlined +CREATE FUNCTION PLUS_ONE(i INTEGER) RETURNS INTEGER AS +$$ SELECT (i + 1.0)::INTEGER LIMIT 1 $$ LANGUAGE SQL; + +SELECT PLUS_ONE(3); +SELECT PLUS_ONE(1); + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +DROP FUNCTION PLUS_ONE(INTEGER); + +-- +-- pg_stat_statements.track = none +-- +SET pg_stat_statements.track = 'none'; +SELECT pg_stat_statements_reset(); + +SELECT 1 AS "one"; +SELECT 1 + 1 AS "two"; + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); diff --git a/contrib/pg_stat_statements/sql/oldextversions.sql b/contrib/pg_stat_statements/sql/oldextversions.sql new file mode 100644 index 0000000..e2a8310 --- /dev/null +++ b/contrib/pg_stat_statements/sql/oldextversions.sql @@ -0,0 +1,51 @@ +-- test old extension version entry points + +CREATE EXTENSION pg_stat_statements WITH VERSION '1.4'; +-- Execution of pg_stat_statements_reset() is granted only to +-- superusers in 1.4, so this fails. +SET SESSION AUTHORIZATION pg_read_all_stats; +SELECT pg_stat_statements_reset(); +RESET SESSION AUTHORIZATION; + +AlTER EXTENSION pg_stat_statements UPDATE TO '1.5'; +-- Execution of pg_stat_statements_reset() should be granted to +-- pg_read_all_stats now, so this works. +SET SESSION AUTHORIZATION pg_read_all_stats; +SELECT pg_stat_statements_reset(); +RESET SESSION AUTHORIZATION; + +-- In 1.6, it got restricted back to superusers. +AlTER EXTENSION pg_stat_statements UPDATE TO '1.6'; +SET SESSION AUTHORIZATION pg_read_all_stats; +SELECT pg_stat_statements_reset(); +RESET SESSION AUTHORIZATION; +SELECT pg_get_functiondef('pg_stat_statements_reset'::regproc); + +-- New function for pg_stat_statements_reset introduced, still +-- restricted for non-superusers. +AlTER EXTENSION pg_stat_statements UPDATE TO '1.7'; +SET SESSION AUTHORIZATION pg_read_all_stats; +SELECT pg_stat_statements_reset(); +RESET SESSION AUTHORIZATION; +SELECT pg_get_functiondef('pg_stat_statements_reset'::regproc); +\d pg_stat_statements +SELECT count(*) > 0 AS has_data FROM pg_stat_statements; + +-- New functions and views for pg_stat_statements in 1.8 +AlTER EXTENSION pg_stat_statements UPDATE TO '1.8'; +\d pg_stat_statements +SELECT pg_get_functiondef('pg_stat_statements_reset'::regproc); + +-- New function pg_stat_statement_info, and new function +-- and view for pg_stat_statements introduced in 1.9 +AlTER EXTENSION pg_stat_statements UPDATE TO '1.9'; +SELECT pg_get_functiondef('pg_stat_statements_info'::regproc); +\d pg_stat_statements +SELECT count(*) > 0 AS has_data FROM pg_stat_statements; + +-- New functions and views for pg_stat_statements in 1.10 +AlTER EXTENSION pg_stat_statements UPDATE TO '1.10'; +\d pg_stat_statements +SELECT count(*) > 0 AS has_data FROM pg_stat_statements; + +DROP EXTENSION pg_stat_statements; diff --git a/contrib/pg_stat_statements/sql/planning.sql b/contrib/pg_stat_statements/sql/planning.sql new file mode 100644 index 0000000..a59b936 --- /dev/null +++ b/contrib/pg_stat_statements/sql/planning.sql @@ -0,0 +1,31 @@ +-- +-- Information related to planning +-- + +-- These tests require track_planning to be enabled. +SET pg_stat_statements.track_planning = TRUE; +SELECT pg_stat_statements_reset(); + +-- +-- [re]plan counting +-- +CREATE TABLE stats_plan_test (); +PREPARE prep1 AS SELECT COUNT(*) FROM stats_plan_test; +EXECUTE prep1; +EXECUTE prep1; +EXECUTE prep1; +ALTER TABLE stats_plan_test ADD COLUMN x int; +EXECUTE prep1; +SELECT 42; +SELECT 42; +SELECT 42; +SELECT plans, calls, rows, query FROM pg_stat_statements + WHERE query NOT LIKE 'PREPARE%' ORDER BY query COLLATE "C"; +-- for the prepared statement we expect at least one replan, but cache +-- invalidations could force more +SELECT plans >= 2 AND plans <= calls AS plans_ok, calls, rows, query FROM pg_stat_statements + WHERE query LIKE 'PREPARE%' ORDER BY query COLLATE "C"; + +-- Cleanup +DROP TABLE stats_plan_test; +SELECT pg_stat_statements_reset(); diff --git a/contrib/pg_stat_statements/sql/select.sql b/contrib/pg_stat_statements/sql/select.sql new file mode 100644 index 0000000..eef7b0b --- /dev/null +++ b/contrib/pg_stat_statements/sql/select.sql @@ -0,0 +1,149 @@ +-- +-- SELECT statements +-- + +CREATE EXTENSION pg_stat_statements; +SET pg_stat_statements.track_utility = FALSE; +SET pg_stat_statements.track_planning = TRUE; +SELECT pg_stat_statements_reset(); + +-- +-- simple and compound statements +-- +SELECT 1 AS "int"; + +SELECT 'hello' + -- multiline + AS "text"; + +SELECT 'world' AS "text"; + +-- transaction +BEGIN; +SELECT 1 AS "int"; +SELECT 'hello' AS "text"; +COMMIT; + +-- compound transaction +BEGIN \; +SELECT 2.0 AS "float" \; +SELECT 'world' AS "text" \; +COMMIT; + +-- compound with empty statements and spurious leading spacing +\;\; SELECT 3 + 3 \;\;\; SELECT ' ' || ' !' \;\; SELECT 1 + 4 \;; + +-- non ;-terminated statements +SELECT 1 + 1 + 1 AS "add" \gset +SELECT :add + 1 + 1 AS "add" \; +SELECT :add + 1 + 1 AS "add" \gset + +-- set operator +SELECT 1 AS i UNION SELECT 2 ORDER BY i; + +-- ? operator +select '{"a":1, "b":2}'::jsonb ? 'b'; + +-- cte +WITH t(f) AS ( + VALUES (1.0), (2.0) +) + SELECT f FROM t ORDER BY f; + +-- prepared statement with parameter +PREPARE pgss_test (int) AS SELECT $1, 'test' LIMIT 1; +EXECUTE pgss_test(1); +DEALLOCATE pgss_test; + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- +-- queries with locking clauses +-- +CREATE TABLE pgss_a (id integer PRIMARY KEY); +CREATE TABLE pgss_b (id integer PRIMARY KEY, a_id integer REFERENCES pgss_a); + +SELECT pg_stat_statements_reset(); + +-- control query +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id; + +-- test range tables +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_a; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_a, pgss_b; -- matches plain "FOR UPDATE" +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE OF pgss_b, pgss_a; + +-- test strengths +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR NO KEY UPDATE; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR SHARE; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR KEY SHARE; + +-- test wait policies +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE NOWAIT; +SELECT * FROM pgss_a JOIN pgss_b ON pgss_b.a_id = pgss_a.id FOR UPDATE SKIP LOCKED; + +SELECT calls, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + +DROP TABLE pgss_a, pgss_b CASCADE; + +-- +-- access to pg_stat_statements_info view +-- +SELECT pg_stat_statements_reset(); +SELECT dealloc FROM pg_stat_statements_info; + +-- FROM [ONLY] +CREATE TABLE tbl_inh(id integer); +CREATE TABLE tbl_inh_1() INHERITS (tbl_inh); +INSERT INTO tbl_inh_1 SELECT 1; + +SELECT * FROM tbl_inh; +SELECT * FROM ONLY tbl_inh; + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%FROM%tbl_inh%'; + +-- WITH TIES +CREATE TABLE limitoption AS SELECT 0 AS val FROM generate_series(1, 10); +SELECT * +FROM limitoption +WHERE val < 2 +ORDER BY val +FETCH FIRST 2 ROWS WITH TIES; + +SELECT * +FROM limitoption +WHERE val < 2 +ORDER BY val +FETCH FIRST 2 ROW ONLY; + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%FETCH FIRST%'; + +-- GROUP BY [DISTINCT] +SELECT a, b, c +FROM (VALUES (1, 2, 3), (4, NULL, 6), (7, 8, 9)) AS t (a, b, c) +GROUP BY ROLLUP(a, b), rollup(a, c) +ORDER BY a, b, c; +SELECT a, b, c +FROM (VALUES (1, 2, 3), (4, NULL, 6), (7, 8, 9)) AS t (a, b, c) +GROUP BY DISTINCT ROLLUP(a, b), rollup(a, c) +ORDER BY a, b, c; + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%GROUP BY%ROLLUP%'; + +-- GROUPING SET agglevelsup +SELECT ( + SELECT ( + SELECT GROUPING(a,b) FROM (VALUES (1)) v2(c) + ) FROM (VALUES (1,2)) v1(a,b) GROUP BY (a,b) +) FROM (VALUES(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); +SELECT ( + SELECT ( + SELECT GROUPING(e,f) FROM (VALUES (1)) v2(c) + ) FROM (VALUES (1,2)) v1(a,b) GROUP BY (a,b) +) FROM (VALUES(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); + +SELECT COUNT(*) FROM pg_stat_statements WHERE query LIKE '%SELECT GROUPING%'; +SELECT pg_stat_statements_reset(); diff --git a/contrib/pg_stat_statements/sql/user_activity.sql b/contrib/pg_stat_statements/sql/user_activity.sql new file mode 100644 index 0000000..4b95edd --- /dev/null +++ b/contrib/pg_stat_statements/sql/user_activity.sql @@ -0,0 +1,66 @@ +-- +-- Track user activity and reset them +-- + +SET pg_stat_statements.track_utility = TRUE; +SELECT pg_stat_statements_reset(); +CREATE ROLE regress_stats_user1; +CREATE ROLE regress_stats_user2; + +SET ROLE regress_stats_user1; + +SELECT 1 AS "ONE"; +SELECT 1+1 AS "TWO"; + +RESET ROLE; +SET ROLE regress_stats_user2; + +SELECT 1 AS "ONE"; +SELECT 1+1 AS "TWO"; + +RESET ROLE; +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- Don't reset anything if any of the parameter is NULL +-- +SELECT pg_stat_statements_reset(NULL); +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- remove query ('SELECT $1+$2 AS "TWO"') executed by regress_stats_user2 +-- in the current_database +-- +SELECT pg_stat_statements_reset( + (SELECT r.oid FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user2'), + (SELECT d.oid FROM pg_database As d where datname = current_database()), + (SELECT s.queryid FROM pg_stat_statements AS s + WHERE s.query = 'SELECT $1+$2 AS "TWO"' LIMIT 1)); +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- remove query ('SELECT $1 AS "ONE"') executed by two users +-- +SELECT pg_stat_statements_reset(0,0,s.queryid) + FROM pg_stat_statements AS s WHERE s.query = 'SELECT $1 AS "ONE"'; +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- remove query of a user (regress_stats_user1) +-- +SELECT pg_stat_statements_reset(r.oid) + FROM pg_roles AS r WHERE r.rolname = 'regress_stats_user1'; +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- reset all +-- +SELECT pg_stat_statements_reset(0,0,0); +SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- +-- cleanup +-- +DROP ROLE regress_stats_user1; +DROP ROLE regress_stats_user2; +SELECT pg_stat_statements_reset(); diff --git a/contrib/pg_stat_statements/sql/utility.sql b/contrib/pg_stat_statements/sql/utility.sql new file mode 100644 index 0000000..aec97d3 --- /dev/null +++ b/contrib/pg_stat_statements/sql/utility.sql @@ -0,0 +1,266 @@ +-- +-- Utility commands +-- + +-- These tests require track_utility to be enabled. +SET pg_stat_statements.track_utility = TRUE; +SELECT pg_stat_statements_reset(); + +-- Tables, indexes, triggers +CREATE TEMP TABLE tab_stats (a int, b char(20)); +CREATE INDEX index_stats ON tab_stats(b, (b || 'data1'), (b || 'data2')) WHERE a > 0; +ALTER TABLE tab_stats ALTER COLUMN b set default 'a'; +ALTER TABLE tab_stats ALTER COLUMN b TYPE text USING 'data' || b; +ALTER TABLE tab_stats ADD CONSTRAINT a_nonzero CHECK (a <> 0); +DROP TABLE tab_stats \; +DROP TABLE IF EXISTS tab_stats \; +-- This DROP query uses two different strings, still they count as one entry. +DROP TABLE IF EXISTS tab_stats \; +Drop Table If Exists tab_stats \; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- Partitions +CREATE TABLE pt_stats (a int, b int) PARTITION BY range (a); +CREATE TABLE pt_stats1 (a int, b int); +ALTER TABLE pt_stats ATTACH PARTITION pt_stats1 FOR VALUES FROM (0) TO (100); +CREATE TABLE pt_stats2 PARTITION OF pt_stats FOR VALUES FROM (100) TO (200); +CREATE INDEX pt_stats_index ON ONLY pt_stats (a); +CREATE INDEX pt_stats2_index ON ONLY pt_stats2 (a); +ALTER INDEX pt_stats_index ATTACH PARTITION pt_stats2_index; +DROP TABLE pt_stats; + +-- Views +CREATE VIEW view_stats AS SELECT 1::int AS a, 2::int AS b; +ALTER VIEW view_stats ALTER COLUMN a SET DEFAULT 2; +DROP VIEW view_stats; + +-- Foreign tables +CREATE FOREIGN DATA WRAPPER wrapper_stats; +CREATE SERVER server_stats FOREIGN DATA WRAPPER wrapper_stats; +CREATE FOREIGN TABLE foreign_stats (a int) SERVER server_stats; +ALTER FOREIGN TABLE foreign_stats ADD COLUMN b integer DEFAULT 1; +ALTER FOREIGN TABLE foreign_stats ADD CONSTRAINT b_nonzero CHECK (b <> 0); +DROP FOREIGN TABLE foreign_stats; +DROP SERVER server_stats; +DROP FOREIGN DATA WRAPPER wrapper_stats; + +-- Functions +CREATE FUNCTION func_stats(a text DEFAULT 'a_data', b text DEFAULT lower('b_data')) + RETURNS text AS $$ SELECT $1::text || '_' || $2::text; $$ LANGUAGE SQL; +DROP FUNCTION func_stats; + +-- Rules +CREATE TABLE tab_rule_stats (a int, b int); +CREATE TABLE tab_rule_stats_2 (a int, b int, c int, d int); +CREATE RULE rules_stats AS ON INSERT TO tab_rule_stats DO INSTEAD + INSERT INTO tab_rule_stats_2 VALUES(new.*, 1, 2); +DROP RULE rules_stats ON tab_rule_stats; +DROP TABLE tab_rule_stats, tab_rule_stats_2; + +-- Types +CREATE TYPE stats_type as (f1 numeric(35, 6), f2 numeric(35, 2)); +DROP TYPE stats_type; + +-- Triggers +CREATE TABLE trigger_tab_stats (a int, b int); +CREATE FUNCTION trigger_func_stats () RETURNS trigger LANGUAGE plpgsql + AS $$ BEGIN return OLD; end; $$; +CREATE TRIGGER trigger_tab_stats + AFTER UPDATE ON trigger_tab_stats + FOR EACH ROW WHEN (OLD.a < 0 AND OLD.b < 1 AND true) + EXECUTE FUNCTION trigger_func_stats(); +DROP TABLE trigger_tab_stats; + +-- Policies +CREATE TABLE tab_policy_stats (a int, b int); +CREATE POLICY policy_stats ON tab_policy_stats USING (a = 5) WITH CHECK (b < 5); +DROP TABLE tab_policy_stats; + +-- Statistics +CREATE TABLE tab_expr_stats (a int, b int); +CREATE STATISTICS tab_expr_stats_1 (mcv) ON a, (2*a), (3*b) FROM tab_expr_stats; +DROP TABLE tab_expr_stats; + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- Transaction statements +BEGIN; +ABORT; +BEGIN; +ROLLBACK; +-- WORK +BEGIN WORK; +COMMIT WORK; +BEGIN WORK; +ABORT WORK; +-- TRANSACTION +BEGIN TRANSACTION; +COMMIT TRANSACTION; +BEGIN TRANSACTION; +ABORT TRANSACTION; +-- More isolation levels +BEGIN TRANSACTION DEFERRABLE; +COMMIT TRANSACTION AND NO CHAIN; +BEGIN ISOLATION LEVEL SERIALIZABLE; +COMMIT; +BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; +COMMIT; +-- List of A_Const nodes, same lists. +BEGIN TRANSACTION READ ONLY, READ WRITE, DEFERRABLE, NOT DEFERRABLE; +COMMIT; +BEGIN TRANSACTION NOT DEFERRABLE, READ ONLY, READ WRITE, DEFERRABLE; +COMMIT; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- EXPLAIN statements +-- A Query is used, normalized by the query jumbling. +EXPLAIN (costs off) SELECT 1; +EXPLAIN (costs off) SELECT 2; +EXPLAIN (costs off) SELECT a FROM generate_series(1,10) AS tab(a) WHERE a = 3; +EXPLAIN (costs off) SELECT a FROM generate_series(1,10) AS tab(a) WHERE a = 7; + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- CALL +CREATE OR REPLACE PROCEDURE sum_one(i int) AS $$ +DECLARE + r int; +BEGIN + SELECT (i + i)::int INTO r; +END; $$ LANGUAGE plpgsql; +CREATE OR REPLACE PROCEDURE sum_two(i int, j int) AS $$ +DECLARE + r int; +BEGIN + SELECT (i + j)::int INTO r; +END; $$ LANGUAGE plpgsql; +SELECT pg_stat_statements_reset(); +CALL sum_one(3); +CALL sum_one(199); +CALL sum_two(1,1); +CALL sum_two(1,2); +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + +-- COPY +CREATE TABLE copy_stats (a int, b int); +SELECT pg_stat_statements_reset(); +-- Some queries with A_Const nodes. +COPY (SELECT 1) TO STDOUT; +COPY (SELECT 2) TO STDOUT; +COPY (INSERT INTO copy_stats VALUES (1, 1) RETURNING *) TO STDOUT; +COPY (INSERT INTO copy_stats VALUES (2, 2) RETURNING *) TO STDOUT; +COPY (UPDATE copy_stats SET b = b + 1 RETURNING *) TO STDOUT; +COPY (UPDATE copy_stats SET b = b + 2 RETURNING *) TO STDOUT; +COPY (DELETE FROM copy_stats WHERE a = 1 RETURNING *) TO STDOUT; + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +DROP TABLE copy_stats; +SELECT pg_stat_statements_reset(); + +-- CREATE TABLE AS +-- SELECT queries are normalized, creating matching query IDs. +CREATE TABLE ctas_stats_1 AS SELECT 1 AS a; +DROP TABLE ctas_stats_1; +CREATE TABLE ctas_stats_1 AS SELECT 2 AS a; +DROP TABLE ctas_stats_1; +CREATE TABLE ctas_stats_2 AS + SELECT a AS col1, 2::int AS col2 + FROM generate_series(1, 10) AS tab(a) WHERE a < 5 AND a > 2; +DROP TABLE ctas_stats_2; +CREATE TABLE ctas_stats_2 AS + SELECT a AS col1, 4::int AS col2 + FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 1; +DROP TABLE ctas_stats_2; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- CREATE MATERIALIZED VIEW +-- SELECT queries are normalized, creating matching query IDs. +CREATE MATERIALIZED VIEW matview_stats_1 AS + SELECT a AS col1, 2::int AS col2 + FROM generate_series(1, 10) AS tab(a) WHERE a < 5 AND a > 2; +DROP MATERIALIZED VIEW matview_stats_1; +CREATE MATERIALIZED VIEW matview_stats_1 AS + SELECT a AS col1, 4::int AS col2 + FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 3; +DROP MATERIALIZED VIEW matview_stats_1; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- CREATE VIEW +CREATE VIEW view_stats_1 AS + SELECT a AS col1, 2::int AS col2 + FROM generate_series(1, 10) AS tab(a) WHERE a < 5 AND a > 2; +DROP VIEW view_stats_1; +CREATE VIEW view_stats_1 AS + SELECT a AS col1, 4::int AS col2 + FROM generate_series(1, 5) AS tab(a) WHERE a < 4 AND a > 3; +DROP VIEW view_stats_1; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- Domains +CREATE DOMAIN domain_stats AS int CHECK (VALUE > 0); +ALTER DOMAIN domain_stats SET DEFAULT '3'; +ALTER DOMAIN domain_stats ADD CONSTRAINT higher_than_one CHECK (VALUE > 1); +DROP DOMAIN domain_stats; +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- SET statements. +-- These use two different strings, still they count as one entry. +SET work_mem = '1MB'; +Set work_mem = '1MB'; +SET work_mem = '2MB'; +RESET work_mem; +SET enable_seqscan = off; +SET enable_seqscan = on; +RESET enable_seqscan; +-- SET TRANSACTION ISOLATION +BEGIN; +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +COMMIT; +-- SET SESSION CHARACTERISTICS +SET SESSION SESSION AUTHORIZATION DEFAULT; +RESET SESSION AUTHORIZATION; +BEGIN; +SET LOCAL SESSION AUTHORIZATION DEFAULT; +RESET SESSION AUTHORIZATION; +COMMIT; + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); + +-- +-- Track the total number of rows retrieved or affected by the utility +-- commands of COPY, FETCH, CREATE TABLE AS, CREATE MATERIALIZED VIEW, +-- REFRESH MATERIALIZED VIEW and SELECT INTO +-- +CREATE TABLE pgss_ctas AS SELECT a, 'ctas' b FROM generate_series(1, 10) a; +SELECT generate_series(1, 10) c INTO pgss_select_into; +COPY pgss_ctas (a, b) FROM STDIN; +11 copy +12 copy +13 copy +\. +CREATE MATERIALIZED VIEW pgss_matv AS SELECT * FROM pgss_ctas; +REFRESH MATERIALIZED VIEW pgss_matv; +BEGIN; +DECLARE pgss_cursor CURSOR FOR SELECT * FROM pgss_matv; +FETCH NEXT pgss_cursor; +FETCH FORWARD 5 pgss_cursor; +FETCH FORWARD ALL pgss_cursor; +COMMIT; + +SELECT calls, rows, query FROM pg_stat_statements ORDER BY query COLLATE "C"; + +DROP MATERIALIZED VIEW pgss_matv; +DROP TABLE pgss_ctas; +DROP TABLE pgss_select_into; + +SELECT pg_stat_statements_reset(); diff --git a/contrib/pg_stat_statements/sql/wal.sql b/contrib/pg_stat_statements/sql/wal.sql new file mode 100644 index 0000000..34b21c0 --- /dev/null +++ b/contrib/pg_stat_statements/sql/wal.sql @@ -0,0 +1,20 @@ +-- +-- Validate WAL generation metrics +-- + +SET pg_stat_statements.track_utility = FALSE; + +CREATE TABLE pgss_wal_tab (a int, b char(20)); + +INSERT INTO pgss_wal_tab VALUES(generate_series(1, 10), 'aaa'); +UPDATE pgss_wal_tab SET b = 'bbb' WHERE a > 7; +DELETE FROM pgss_wal_tab WHERE a > 9; +DROP TABLE pgss_wal_tab; + +-- Check WAL is generated for the above statements +SELECT query, calls, rows, +wal_bytes > 0 as wal_bytes_generated, +wal_records > 0 as wal_records_generated, +wal_records >= rows as wal_records_ge_rows +FROM pg_stat_statements ORDER BY query COLLATE "C"; +SELECT pg_stat_statements_reset(); diff --git a/contrib/pg_surgery/.gitignore b/contrib/pg_surgery/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pg_surgery/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pg_surgery/Makefile b/contrib/pg_surgery/Makefile new file mode 100644 index 0000000..a66776c --- /dev/null +++ b/contrib/pg_surgery/Makefile @@ -0,0 +1,23 @@ +# contrib/pg_surgery/Makefile + +MODULE_big = pg_surgery +OBJS = \ + $(WIN32RES) \ + heap_surgery.o + +EXTENSION = pg_surgery +DATA = pg_surgery--1.0.sql +PGFILEDESC = "pg_surgery - perform surgery on a damaged relation" + +REGRESS = heap_surgery + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pg_surgery +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pg_surgery/expected/heap_surgery.out b/contrib/pg_surgery/expected/heap_surgery.out new file mode 100644 index 0000000..df7d13b --- /dev/null +++ b/contrib/pg_surgery/expected/heap_surgery.out @@ -0,0 +1,180 @@ +create extension pg_surgery; +-- create a normal heap table and insert some rows. +-- use a temp table so that vacuum behavior doesn't depend on global xmin +create temp table htab (a int); +insert into htab values (100), (200), (300), (400), (500); +-- test empty TID array +select heap_force_freeze('htab'::regclass, ARRAY[]::tid[]); + heap_force_freeze +------------------- + +(1 row) + +-- nothing should be frozen yet +select * from htab where xmin = 2; + a +--- +(0 rows) + +-- freeze forcibly +select heap_force_freeze('htab'::regclass, ARRAY['(0, 4)']::tid[]); + heap_force_freeze +------------------- + +(1 row) + +-- now we should have one frozen tuple +select ctid, xmax from htab where xmin = 2; + ctid | xmax +-------+------ + (0,4) | 0 +(1 row) + +-- kill forcibly +select heap_force_kill('htab'::regclass, ARRAY['(0, 4)']::tid[]); + heap_force_kill +----------------- + +(1 row) + +-- should be gone now +select * from htab where ctid = '(0, 4)'; + a +--- +(0 rows) + +-- should now be skipped because it's already dead +select heap_force_kill('htab'::regclass, ARRAY['(0, 4)']::tid[]); +NOTICE: skipping tid (0, 4) for relation "htab" because it is marked dead + heap_force_kill +----------------- + +(1 row) + +select heap_force_freeze('htab'::regclass, ARRAY['(0, 4)']::tid[]); +NOTICE: skipping tid (0, 4) for relation "htab" because it is marked dead + heap_force_freeze +------------------- + +(1 row) + +-- freeze two TIDs at once while skipping an out-of-range block number +select heap_force_freeze('htab'::regclass, + ARRAY['(0, 1)', '(0, 3)', '(1, 1)']::tid[]); +NOTICE: skipping block 1 for relation "htab" because the block number is out of range + heap_force_freeze +------------------- + +(1 row) + +-- we should now have two frozen tuples +select ctid, xmax from htab where xmin = 2; + ctid | xmax +-------+------ + (0,1) | 0 + (0,3) | 0 +(2 rows) + +-- out-of-range TIDs should be skipped +select heap_force_freeze('htab'::regclass, ARRAY['(0, 0)', '(0, 6)']::tid[]); +NOTICE: skipping tid (0, 0) for relation "htab" because the item number is out of range +NOTICE: skipping tid (0, 6) for relation "htab" because the item number is out of range + heap_force_freeze +------------------- + +(1 row) + +-- set up a new table with a redirected line pointer +-- use a temp table so that vacuum behavior doesn't depend on global xmin +create temp table htab2(a int); +insert into htab2 values (100); +update htab2 set a = 200; +vacuum htab2; +-- redirected TIDs should be skipped +select heap_force_kill('htab2'::regclass, ARRAY['(0, 1)']::tid[]); +NOTICE: skipping tid (0, 1) for relation "htab2" because it redirects to item 2 + heap_force_kill +----------------- + +(1 row) + +-- now create an unused line pointer +select ctid from htab2; + ctid +------- + (0,2) +(1 row) + +update htab2 set a = 300; +select ctid from htab2; + ctid +------- + (0,3) +(1 row) + +vacuum freeze htab2; +-- unused TIDs should be skipped +select heap_force_kill('htab2'::regclass, ARRAY['(0, 2)']::tid[]); +NOTICE: skipping tid (0, 2) for relation "htab2" because it is marked unused + heap_force_kill +----------------- + +(1 row) + +-- multidimensional TID array should be rejected +select heap_force_kill('htab2'::regclass, ARRAY[['(0, 2)']]::tid[]); +ERROR: argument must be empty or one-dimensional array +-- TID array with nulls should be rejected +select heap_force_kill('htab2'::regclass, ARRAY[NULL]::tid[]); +ERROR: array must not contain nulls +-- but we should be able to kill the one tuple we have +select heap_force_kill('htab2'::regclass, ARRAY['(0, 3)']::tid[]); + heap_force_kill +----------------- + +(1 row) + +-- materialized view. +-- note that we don't commit the transaction, so autovacuum can't interfere. +begin; +create materialized view mvw as select a from generate_series(1, 3) a; +select * from mvw where xmin = 2; + a +--- +(0 rows) + +select heap_force_freeze('mvw'::regclass, ARRAY['(0, 3)']::tid[]); + heap_force_freeze +------------------- + +(1 row) + +select * from mvw where xmin = 2; + a +--- + 3 +(1 row) + +select heap_force_kill('mvw'::regclass, ARRAY['(0, 3)']::tid[]); + heap_force_kill +----------------- + +(1 row) + +select * from mvw where ctid = '(0, 3)'; + a +--- +(0 rows) + +rollback; +-- check that it fails on an unsupported relkind +create view vw as select 1; +select heap_force_kill('vw'::regclass, ARRAY['(0, 1)']::tid[]); +ERROR: cannot operate on relation "vw" +DETAIL: This operation is not supported for views. +select heap_force_freeze('vw'::regclass, ARRAY['(0, 1)']::tid[]); +ERROR: cannot operate on relation "vw" +DETAIL: This operation is not supported for views. +-- cleanup. +drop view vw; +drop extension pg_surgery; diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c new file mode 100644 index 0000000..88a40ab --- /dev/null +++ b/contrib/pg_surgery/heap_surgery.c @@ -0,0 +1,418 @@ +/*------------------------------------------------------------------------- + * + * heap_surgery.c + * Functions to perform surgery on the damaged heap table. + * + * Copyright (c) 2020-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pg_surgery/heap_surgery.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heapam.h" +#include "access/visibilitymap.h" +#include "access/xloginsert.h" +#include "catalog/pg_am_d.h" +#include "catalog/pg_proc_d.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "utils/acl.h" +#include "utils/array.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +/* Options to forcefully change the state of a heap tuple. */ +typedef enum HeapTupleForceOption +{ + HEAP_FORCE_KILL, + HEAP_FORCE_FREEZE +} HeapTupleForceOption; + +PG_FUNCTION_INFO_V1(heap_force_kill); +PG_FUNCTION_INFO_V1(heap_force_freeze); + +static int32 tidcmp(const void *a, const void *b); +static Datum heap_force_common(FunctionCallInfo fcinfo, + HeapTupleForceOption heap_force_opt); +static void sanity_check_tid_array(ArrayType *ta, int *ntids); +static BlockNumber find_tids_one_page(ItemPointer tids, int ntids, + OffsetNumber *next_start_ptr); + +/*------------------------------------------------------------------------- + * heap_force_kill() + * + * Force kill the tuple(s) pointed to by the item pointer(s) stored in the + * given TID array. + * + * Usage: SELECT heap_force_kill(regclass, tid[]); + *------------------------------------------------------------------------- + */ +Datum +heap_force_kill(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(heap_force_common(fcinfo, HEAP_FORCE_KILL)); +} + +/*------------------------------------------------------------------------- + * heap_force_freeze() + * + * Force freeze the tuple(s) pointed to by the item pointer(s) stored in the + * given TID array. + * + * Usage: SELECT heap_force_freeze(regclass, tid[]); + *------------------------------------------------------------------------- + */ +Datum +heap_force_freeze(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATUM(heap_force_common(fcinfo, HEAP_FORCE_FREEZE)); +} + +/*------------------------------------------------------------------------- + * heap_force_common() + * + * Common code for heap_force_kill and heap_force_freeze + *------------------------------------------------------------------------- + */ +static Datum +heap_force_common(FunctionCallInfo fcinfo, HeapTupleForceOption heap_force_opt) +{ + Oid relid = PG_GETARG_OID(0); + ArrayType *ta = PG_GETARG_ARRAYTYPE_P_COPY(1); + ItemPointer tids; + int ntids, + nblocks; + Relation rel; + OffsetNumber curr_start_ptr, + next_start_ptr; + bool include_this_tid[MaxHeapTuplesPerPage]; + + if (RecoveryInProgress()) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("recovery is in progress"), + errhint("heap surgery functions cannot be executed during recovery."))); + + /* Check inputs. */ + sanity_check_tid_array(ta, &ntids); + + rel = relation_open(relid, RowExclusiveLock); + + /* + * Check target relation. + */ + if (!RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot operate on relation \"%s\"", + RelationGetRelationName(rel)), + errdetail_relkind_not_supported(rel->rd_rel->relkind))); + + if (rel->rd_rel->relam != HEAP_TABLE_AM_OID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only heap AM is supported"))); + + /* Must be owner of the table or superuser. */ + if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, + get_relkind_objtype(rel->rd_rel->relkind), + RelationGetRelationName(rel)); + + tids = ((ItemPointer) ARR_DATA_PTR(ta)); + + /* + * If there is more than one TID in the array, sort them so that we can + * easily fetch all the TIDs belonging to one particular page from the + * array. + */ + if (ntids > 1) + qsort(tids, ntids, sizeof(ItemPointerData), tidcmp); + + curr_start_ptr = next_start_ptr = 0; + nblocks = RelationGetNumberOfBlocks(rel); + + /* + * Loop, performing the necessary actions for each block. + */ + while (next_start_ptr != ntids) + { + Buffer buf; + Buffer vmbuf = InvalidBuffer; + Page page; + BlockNumber blkno; + OffsetNumber curoff; + OffsetNumber maxoffset; + int i; + bool did_modify_page = false; + bool did_modify_vm = false; + + CHECK_FOR_INTERRUPTS(); + + /* + * Find all the TIDs belonging to one particular page starting from + * next_start_ptr and process them one by one. + */ + blkno = find_tids_one_page(tids, ntids, &next_start_ptr); + + /* Check whether the block number is valid. */ + if (blkno >= nblocks) + { + /* Update the current_start_ptr before moving to the next page. */ + curr_start_ptr = next_start_ptr; + + ereport(NOTICE, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("skipping block %u for relation \"%s\" because the block number is out of range", + blkno, RelationGetRelationName(rel)))); + continue; + } + + buf = ReadBuffer(rel, blkno); + LockBufferForCleanup(buf); + + page = BufferGetPage(buf); + + maxoffset = PageGetMaxOffsetNumber(page); + + /* + * Figure out which TIDs we are going to process and which ones we are + * going to skip. + */ + memset(include_this_tid, 0, sizeof(include_this_tid)); + for (i = curr_start_ptr; i < next_start_ptr; i++) + { + OffsetNumber offno = ItemPointerGetOffsetNumberNoCheck(&tids[i]); + ItemId itemid; + + /* Check whether the offset number is valid. */ + if (offno == InvalidOffsetNumber || offno > maxoffset) + { + ereport(NOTICE, + errmsg("skipping tid (%u, %u) for relation \"%s\" because the item number is out of range", + blkno, offno, RelationGetRelationName(rel))); + continue; + } + + itemid = PageGetItemId(page, offno); + + /* Only accept an item ID that is used. */ + if (ItemIdIsRedirected(itemid)) + { + ereport(NOTICE, + errmsg("skipping tid (%u, %u) for relation \"%s\" because it redirects to item %u", + blkno, offno, RelationGetRelationName(rel), + ItemIdGetRedirect(itemid))); + continue; + } + else if (ItemIdIsDead(itemid)) + { + ereport(NOTICE, + (errmsg("skipping tid (%u, %u) for relation \"%s\" because it is marked dead", + blkno, offno, RelationGetRelationName(rel)))); + continue; + } + else if (!ItemIdIsUsed(itemid)) + { + ereport(NOTICE, + (errmsg("skipping tid (%u, %u) for relation \"%s\" because it is marked unused", + blkno, offno, RelationGetRelationName(rel)))); + continue; + } + + /* Mark it for processing. */ + Assert(offno < MaxHeapTuplesPerPage); + include_this_tid[offno] = true; + } + + /* + * Before entering the critical section, pin the visibility map page + * if it appears to be necessary. + */ + if (heap_force_opt == HEAP_FORCE_KILL && PageIsAllVisible(page)) + visibilitymap_pin(rel, blkno, &vmbuf); + + /* No ereport(ERROR) from here until all the changes are logged. */ + START_CRIT_SECTION(); + + for (curoff = FirstOffsetNumber; curoff <= maxoffset; + curoff = OffsetNumberNext(curoff)) + { + ItemId itemid; + + if (!include_this_tid[curoff]) + continue; + + itemid = PageGetItemId(page, curoff); + Assert(ItemIdIsNormal(itemid)); + + did_modify_page = true; + + if (heap_force_opt == HEAP_FORCE_KILL) + { + ItemIdSetDead(itemid); + + /* + * If the page is marked all-visible, we must clear + * PD_ALL_VISIBLE flag on the page header and an all-visible + * bit on the visibility map corresponding to the page. + */ + if (PageIsAllVisible(page)) + { + PageClearAllVisible(page); + visibilitymap_clear(rel, blkno, vmbuf, + VISIBILITYMAP_VALID_BITS); + did_modify_vm = true; + } + } + else + { + HeapTupleHeader htup; + + Assert(heap_force_opt == HEAP_FORCE_FREEZE); + + htup = (HeapTupleHeader) PageGetItem(page, itemid); + + /* + * Reset all visibility-related fields of the tuple. This + * logic should mimic heap_execute_freeze_tuple(), but we + * choose to reset xmin and ctid just to be sure that no + * potentially-garbled data is left behind. + */ + ItemPointerSet(&htup->t_ctid, blkno, curoff); + HeapTupleHeaderSetXmin(htup, FrozenTransactionId); + HeapTupleHeaderSetXmax(htup, InvalidTransactionId); + if (htup->t_infomask & HEAP_MOVED) + { + if (htup->t_infomask & HEAP_MOVED_OFF) + HeapTupleHeaderSetXvac(htup, InvalidTransactionId); + else + HeapTupleHeaderSetXvac(htup, FrozenTransactionId); + } + + /* + * Clear all the visibility-related bits of this tuple and + * mark it as frozen. Also, get rid of HOT_UPDATED and + * KEYS_UPDATES bits. + */ + htup->t_infomask &= ~HEAP_XACT_MASK; + htup->t_infomask |= (HEAP_XMIN_FROZEN | HEAP_XMAX_INVALID); + htup->t_infomask2 &= ~HEAP_HOT_UPDATED; + htup->t_infomask2 &= ~HEAP_KEYS_UPDATED; + } + } + + /* + * If the page was modified, only then, we mark the buffer dirty or do + * the WAL logging. + */ + if (did_modify_page) + { + /* Mark buffer dirty before we write WAL. */ + MarkBufferDirty(buf); + + /* XLOG stuff */ + if (RelationNeedsWAL(rel)) + log_newpage_buffer(buf, true); + } + + /* WAL log the VM page if it was modified. */ + if (did_modify_vm && RelationNeedsWAL(rel)) + log_newpage_buffer(vmbuf, false); + + END_CRIT_SECTION(); + + UnlockReleaseBuffer(buf); + + if (vmbuf != InvalidBuffer) + ReleaseBuffer(vmbuf); + + /* Update the current_start_ptr before moving to the next page. */ + curr_start_ptr = next_start_ptr; + } + + relation_close(rel, RowExclusiveLock); + + pfree(ta); + + PG_RETURN_VOID(); +} + +/*------------------------------------------------------------------------- + * tidcmp() + * + * Compare two item pointers, return -1, 0, or +1. + * + * See ItemPointerCompare for details. + * ------------------------------------------------------------------------ + */ +static int32 +tidcmp(const void *a, const void *b) +{ + ItemPointer iptr1 = ((const ItemPointer) a); + ItemPointer iptr2 = ((const ItemPointer) b); + + return ItemPointerCompare(iptr1, iptr2); +} + +/*------------------------------------------------------------------------- + * sanity_check_tid_array() + * + * Perform sanity checks on the given tid array, and set *ntids to the + * number of items in the array. + * ------------------------------------------------------------------------ + */ +static void +sanity_check_tid_array(ArrayType *ta, int *ntids) +{ + if (ARR_HASNULL(ta) && array_contains_nulls(ta)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array must not contain nulls"))); + + if (ARR_NDIM(ta) > 1) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("argument must be empty or one-dimensional array"))); + + *ntids = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta)); +} + +/*------------------------------------------------------------------------- + * find_tids_one_page() + * + * Find all the tids residing in the same page as tids[next_start_ptr], and + * update next_start_ptr so that it points to the first tid in the next page. + * + * NOTE: The input tids[] array must be sorted. + * ------------------------------------------------------------------------ + */ +static BlockNumber +find_tids_one_page(ItemPointer tids, int ntids, OffsetNumber *next_start_ptr) +{ + int i; + BlockNumber prev_blkno, + blkno; + + prev_blkno = blkno = InvalidBlockNumber; + + for (i = *next_start_ptr; i < ntids; i++) + { + ItemPointerData tid = tids[i]; + + blkno = ItemPointerGetBlockNumberNoCheck(&tid); + + if (i == *next_start_ptr) + prev_blkno = blkno; + + if (prev_blkno != blkno) + break; + } + + *next_start_ptr = i; + return prev_blkno; +} diff --git a/contrib/pg_surgery/meson.build b/contrib/pg_surgery/meson.build new file mode 100644 index 0000000..a08327e --- /dev/null +++ b/contrib/pg_surgery/meson.build @@ -0,0 +1,35 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pg_surgery_sources = files( + 'heap_surgery.c', +) + +if host_system == 'windows' + pg_surgery_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pg_surgery', + '--FILEDESC', 'pg_surgery - perform surgery on a damaged relation',]) +endif + +pg_surgery = shared_module('pg_surgery', + pg_surgery_sources, + kwargs: contrib_mod_args, +) +contrib_targets += pg_surgery + +install_data( + 'pg_surgery--1.0.sql', + 'pg_surgery.control', + kwargs: contrib_data_args, +) + + +tests += { + 'name': 'pg_surgery', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'heap_surgery', + ], + }, +} diff --git a/contrib/pg_surgery/pg_surgery--1.0.sql b/contrib/pg_surgery/pg_surgery--1.0.sql new file mode 100644 index 0000000..d1e53a0 --- /dev/null +++ b/contrib/pg_surgery/pg_surgery--1.0.sql @@ -0,0 +1,18 @@ +/* contrib/pg_surgery/pg_surgery--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_surgery" to load this file. \quit + +CREATE FUNCTION heap_force_kill(reloid regclass, tids tid[]) +RETURNS VOID +AS 'MODULE_PATHNAME', 'heap_force_kill' +LANGUAGE C STRICT; + +REVOKE EXECUTE ON FUNCTION heap_force_kill(regclass, tid[]) FROM PUBLIC; + +CREATE FUNCTION heap_force_freeze(reloid regclass, tids tid[]) +RETURNS VOID +AS 'MODULE_PATHNAME', 'heap_force_freeze' +LANGUAGE C STRICT; + +REVOKE EXECUTE ON FUNCTION heap_force_freeze(regclass, tid[]) FROM PUBLIC; diff --git a/contrib/pg_surgery/pg_surgery.control b/contrib/pg_surgery/pg_surgery.control new file mode 100644 index 0000000..2bcdad1 --- /dev/null +++ b/contrib/pg_surgery/pg_surgery.control @@ -0,0 +1,5 @@ +# pg_surgery extension +comment = 'extension to perform surgery on a damaged relation' +default_version = '1.0' +module_pathname = '$libdir/pg_surgery' +relocatable = true diff --git a/contrib/pg_surgery/sql/heap_surgery.sql b/contrib/pg_surgery/sql/heap_surgery.sql new file mode 100644 index 0000000..6526b27 --- /dev/null +++ b/contrib/pg_surgery/sql/heap_surgery.sql @@ -0,0 +1,88 @@ +create extension pg_surgery; + +-- create a normal heap table and insert some rows. +-- use a temp table so that vacuum behavior doesn't depend on global xmin +create temp table htab (a int); +insert into htab values (100), (200), (300), (400), (500); + +-- test empty TID array +select heap_force_freeze('htab'::regclass, ARRAY[]::tid[]); + +-- nothing should be frozen yet +select * from htab where xmin = 2; + +-- freeze forcibly +select heap_force_freeze('htab'::regclass, ARRAY['(0, 4)']::tid[]); + +-- now we should have one frozen tuple +select ctid, xmax from htab where xmin = 2; + +-- kill forcibly +select heap_force_kill('htab'::regclass, ARRAY['(0, 4)']::tid[]); + +-- should be gone now +select * from htab where ctid = '(0, 4)'; + +-- should now be skipped because it's already dead +select heap_force_kill('htab'::regclass, ARRAY['(0, 4)']::tid[]); +select heap_force_freeze('htab'::regclass, ARRAY['(0, 4)']::tid[]); + +-- freeze two TIDs at once while skipping an out-of-range block number +select heap_force_freeze('htab'::regclass, + ARRAY['(0, 1)', '(0, 3)', '(1, 1)']::tid[]); + +-- we should now have two frozen tuples +select ctid, xmax from htab where xmin = 2; + +-- out-of-range TIDs should be skipped +select heap_force_freeze('htab'::regclass, ARRAY['(0, 0)', '(0, 6)']::tid[]); + +-- set up a new table with a redirected line pointer +-- use a temp table so that vacuum behavior doesn't depend on global xmin +create temp table htab2(a int); +insert into htab2 values (100); +update htab2 set a = 200; +vacuum htab2; + +-- redirected TIDs should be skipped +select heap_force_kill('htab2'::regclass, ARRAY['(0, 1)']::tid[]); + +-- now create an unused line pointer +select ctid from htab2; +update htab2 set a = 300; +select ctid from htab2; +vacuum freeze htab2; + +-- unused TIDs should be skipped +select heap_force_kill('htab2'::regclass, ARRAY['(0, 2)']::tid[]); + +-- multidimensional TID array should be rejected +select heap_force_kill('htab2'::regclass, ARRAY[['(0, 2)']]::tid[]); + +-- TID array with nulls should be rejected +select heap_force_kill('htab2'::regclass, ARRAY[NULL]::tid[]); + +-- but we should be able to kill the one tuple we have +select heap_force_kill('htab2'::regclass, ARRAY['(0, 3)']::tid[]); + +-- materialized view. +-- note that we don't commit the transaction, so autovacuum can't interfere. +begin; +create materialized view mvw as select a from generate_series(1, 3) a; + +select * from mvw where xmin = 2; +select heap_force_freeze('mvw'::regclass, ARRAY['(0, 3)']::tid[]); +select * from mvw where xmin = 2; + +select heap_force_kill('mvw'::regclass, ARRAY['(0, 3)']::tid[]); +select * from mvw where ctid = '(0, 3)'; +rollback; + +-- check that it fails on an unsupported relkind +create view vw as select 1; +select heap_force_kill('vw'::regclass, ARRAY['(0, 1)']::tid[]); +select heap_force_freeze('vw'::regclass, ARRAY['(0, 1)']::tid[]); + +-- cleanup. +drop view vw; +drop extension pg_surgery; diff --git a/contrib/pg_trgm/.gitignore b/contrib/pg_trgm/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pg_trgm/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pg_trgm/Makefile b/contrib/pg_trgm/Makefile new file mode 100644 index 0000000..1fbdc9e --- /dev/null +++ b/contrib/pg_trgm/Makefile @@ -0,0 +1,28 @@ +# contrib/pg_trgm/Makefile + +MODULE_big = pg_trgm +OBJS = \ + $(WIN32RES) \ + trgm_gin.o \ + trgm_gist.o \ + trgm_op.o \ + trgm_regexp.o + +EXTENSION = pg_trgm +DATA = pg_trgm--1.5--1.6.sql pg_trgm--1.4--1.5.sql pg_trgm--1.3--1.4.sql \ + pg_trgm--1.3.sql pg_trgm--1.2--1.3.sql pg_trgm--1.1--1.2.sql \ + pg_trgm--1.0--1.1.sql +PGFILEDESC = "pg_trgm - trigram matching" + +REGRESS = pg_trgm pg_word_trgm pg_strict_word_trgm + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pg_trgm +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pg_trgm/data/trgm.data b/contrib/pg_trgm/data/trgm.data new file mode 100644 index 0000000..37319d6 --- /dev/null +++ b/contrib/pg_trgm/data/trgm.data @@ -0,0 +1,1000 @@ +qwertyu0001 +qwertyu0002 +qwertyu0003 +qwertyu0004 +qwertyu0005 +qwertyu0006 +qwertyu0007 +qwertyu0008 +qwertyu0009 +qwertyu0010 +qwertyu0011 +qwertyu0012 +qwertyu0013 +qwertyu0014 +qwertyu0015 +qwertyu0016 +qwertyu0017 +qwertyu0018 +qwertyu0019 +qwertyu0020 +qwertyu0021 +qwertyu0022 +qwertyu0023 +qwertyu0024 +qwertyu0025 +qwertyu0026 +qwertyu0027 +qwertyu0028 +qwertyu0029 +qwertyu0030 +qwertyu0031 +qwertyu0032 +qwertyu0033 +qwertyu0034 +qwertyu0035 +qwertyu0036 +qwertyu0037 +qwertyu0038 +qwertyu0039 +qwertyu0040 +qwertyu0041 +qwertyu0042 +qwertyu0043 +qwertyu0044 +qwertyu0045 +qwertyu0046 +qwertyu0047 +qwertyu0048 +qwertyu0049 +qwertyu0050 +qwertyu0051 +qwertyu0052 +qwertyu0053 +qwertyu0054 +qwertyu0055 +qwertyu0056 +qwertyu0057 +qwertyu0058 +qwertyu0059 +qwertyu0060 +qwertyu0061 +qwertyu0062 +qwertyu0063 +qwertyu0064 +qwertyu0065 +qwertyu0066 +qwertyu0067 +qwertyu0068 +qwertyu0069 +qwertyu0070 +qwertyu0071 +qwertyu0072 +qwertyu0073 +qwertyu0074 +qwertyu0075 +qwertyu0076 +qwertyu0077 +qwertyu0078 +qwertyu0079 +qwertyu0080 +qwertyu0081 +qwertyu0082 +qwertyu0083 +qwertyu0084 +qwertyu0085 +qwertyu0086 +qwertyu0087 +qwertyu0088 +qwertyu0089 +qwertyu0090 +qwertyu0091 +qwertyu0092 +qwertyu0093 +qwertyu0094 +qwertyu0095 +qwertyu0096 +qwertyu0097 +qwertyu0098 +qwertyu0099 +qwertyu0100 +qwertyu0101 +qwertyu0102 +qwertyu0103 +qwertyu0104 +qwertyu0105 +qwertyu0106 +qwertyu0107 +qwertyu0108 +qwertyu0109 +qwertyu0110 +qwertyu0111 +qwertyu0112 +qwertyu0113 +qwertyu0114 +qwertyu0115 +qwertyu0116 +qwertyu0117 +qwertyu0118 +qwertyu0119 +qwertyu0120 +qwertyu0121 +qwertyu0122 +qwertyu0123 +qwertyu0124 +qwertyu0125 +qwertyu0126 +qwertyu0127 +qwertyu0128 +qwertyu0129 +qwertyu0130 +qwertyu0131 +qwertyu0132 +qwertyu0133 +qwertyu0134 +qwertyu0135 +qwertyu0136 +qwertyu0137 +qwertyu0138 +qwertyu0139 +qwertyu0140 +qwertyu0141 +qwertyu0142 +qwertyu0143 +qwertyu0144 +qwertyu0145 +qwertyu0146 +qwertyu0147 +qwertyu0148 +qwertyu0149 +qwertyu0150 +qwertyu0151 +qwertyu0152 +qwertyu0153 +qwertyu0154 +qwertyu0155 +qwertyu0156 +qwertyu0157 +qwertyu0158 +qwertyu0159 +qwertyu0160 +qwertyu0161 +qwertyu0162 +qwertyu0163 +qwertyu0164 +qwertyu0165 +qwertyu0166 +qwertyu0167 +qwertyu0168 +qwertyu0169 +qwertyu0170 +qwertyu0171 +qwertyu0172 +qwertyu0173 +qwertyu0174 +qwertyu0175 +qwertyu0176 +qwertyu0177 +qwertyu0178 +qwertyu0179 +qwertyu0180 +qwertyu0181 +qwertyu0182 +qwertyu0183 +qwertyu0184 +qwertyu0185 +qwertyu0186 +qwertyu0187 +qwertyu0188 +qwertyu0189 +qwertyu0190 +qwertyu0191 +qwertyu0192 +qwertyu0193 +qwertyu0194 +qwertyu0195 +qwertyu0196 +qwertyu0197 +qwertyu0198 +qwertyu0199 +qwertyu0200 +qwertyu0201 +qwertyu0202 +qwertyu0203 +qwertyu0204 +qwertyu0205 +qwertyu0206 +qwertyu0207 +qwertyu0208 +qwertyu0209 +qwertyu0210 +qwertyu0211 +qwertyu0212 +qwertyu0213 +qwertyu0214 +qwertyu0215 +qwertyu0216 +qwertyu0217 +qwertyu0218 +qwertyu0219 +qwertyu0220 +qwertyu0221 +qwertyu0222 +qwertyu0223 +qwertyu0224 +qwertyu0225 +qwertyu0226 +qwertyu0227 +qwertyu0228 +qwertyu0229 +qwertyu0230 +qwertyu0231 +qwertyu0232 +qwertyu0233 +qwertyu0234 +qwertyu0235 +qwertyu0236 +qwertyu0237 +qwertyu0238 +qwertyu0239 +qwertyu0240 +qwertyu0241 +qwertyu0242 +qwertyu0243 +qwertyu0244 +qwertyu0245 +qwertyu0246 +qwertyu0247 +qwertyu0248 +qwertyu0249 +qwertyu0250 +qwertyu0251 +qwertyu0252 +qwertyu0253 +qwertyu0254 +qwertyu0255 +qwertyu0256 +qwertyu0257 +qwertyu0258 +qwertyu0259 +qwertyu0260 +qwertyu0261 +qwertyu0262 +qwertyu0263 +qwertyu0264 +qwertyu0265 +qwertyu0266 +qwertyu0267 +qwertyu0268 +qwertyu0269 +qwertyu0270 +qwertyu0271 +qwertyu0272 +qwertyu0273 +qwertyu0274 +qwertyu0275 +qwertyu0276 +qwertyu0277 +qwertyu0278 +qwertyu0279 +qwertyu0280 +qwertyu0281 +qwertyu0282 +qwertyu0283 +qwertyu0284 +qwertyu0285 +qwertyu0286 +qwertyu0287 +qwertyu0288 +qwertyu0289 +qwertyu0290 +qwertyu0291 +qwertyu0292 +qwertyu0293 +qwertyu0294 +qwertyu0295 +qwertyu0296 +qwertyu0297 +qwertyu0298 +qwertyu0299 +qwertyu0300 +qwertyu0301 +qwertyu0302 +qwertyu0303 +qwertyu0304 +qwertyu0305 +qwertyu0306 +qwertyu0307 +qwertyu0308 +qwertyu0309 +qwertyu0310 +qwertyu0311 +qwertyu0312 +qwertyu0313 +qwertyu0314 +qwertyu0315 +qwertyu0316 +qwertyu0317 +qwertyu0318 +qwertyu0319 +qwertyu0320 +qwertyu0321 +qwertyu0322 +qwertyu0323 +qwertyu0324 +qwertyu0325 +qwertyu0326 +qwertyu0327 +qwertyu0328 +qwertyu0329 +qwertyu0330 +qwertyu0331 +qwertyu0332 +qwertyu0333 +qwertyu0334 +qwertyu0335 +qwertyu0336 +qwertyu0337 +qwertyu0338 +qwertyu0339 +qwertyu0340 +qwertyu0341 +qwertyu0342 +qwertyu0343 +qwertyu0344 +qwertyu0345 +qwertyu0346 +qwertyu0347 +qwertyu0348 +qwertyu0349 +qwertyu0350 +qwertyu0351 +qwertyu0352 +qwertyu0353 +qwertyu0354 +qwertyu0355 +qwertyu0356 +qwertyu0357 +qwertyu0358 +qwertyu0359 +qwertyu0360 +qwertyu0361 +qwertyu0362 +qwertyu0363 +qwertyu0364 +qwertyu0365 +qwertyu0366 +qwertyu0367 +qwertyu0368 +qwertyu0369 +qwertyu0370 +qwertyu0371 +qwertyu0372 +qwertyu0373 +qwertyu0374 +qwertyu0375 +qwertyu0376 +qwertyu0377 +qwertyu0378 +qwertyu0379 +qwertyu0380 +qwertyu0381 +qwertyu0382 +qwertyu0383 +qwertyu0384 +qwertyu0385 +qwertyu0386 +qwertyu0387 +qwertyu0388 +qwertyu0389 +qwertyu0390 +qwertyu0391 +qwertyu0392 +qwertyu0393 +qwertyu0394 +qwertyu0395 +qwertyu0396 +qwertyu0397 +qwertyu0398 +qwertyu0399 +qwertyu0400 +qwertyu0401 +qwertyu0402 +qwertyu0403 +qwertyu0404 +qwertyu0405 +qwertyu0406 +qwertyu0407 +qwertyu0408 +qwertyu0409 +qwertyu0410 +qwertyu0411 +qwertyu0412 +qwertyu0413 +qwertyu0414 +qwertyu0415 +qwertyu0416 +qwertyu0417 +qwertyu0418 +qwertyu0419 +qwertyu0420 +qwertyu0421 +qwertyu0422 +qwertyu0423 +qwertyu0424 +qwertyu0425 +qwertyu0426 +qwertyu0427 +qwertyu0428 +qwertyu0429 +qwertyu0430 +qwertyu0431 +qwertyu0432 +qwertyu0433 +qwertyu0434 +qwertyu0435 +qwertyu0436 +qwertyu0437 +qwertyu0438 +qwertyu0439 +qwertyu0440 +qwertyu0441 +qwertyu0442 +qwertyu0443 +qwertyu0444 +qwertyu0445 +qwertyu0446 +qwertyu0447 +qwertyu0448 +qwertyu0449 +qwertyu0450 +qwertyu0451 +qwertyu0452 +qwertyu0453 +qwertyu0454 +qwertyu0455 +qwertyu0456 +qwertyu0457 +qwertyu0458 +qwertyu0459 +qwertyu0460 +qwertyu0461 +qwertyu0462 +qwertyu0463 +qwertyu0464 +qwertyu0465 +qwertyu0466 +qwertyu0467 +qwertyu0468 +qwertyu0469 +qwertyu0470 +qwertyu0471 +qwertyu0472 +qwertyu0473 +qwertyu0474 +qwertyu0475 +qwertyu0476 +qwertyu0477 +qwertyu0478 +qwertyu0479 +qwertyu0480 +qwertyu0481 +qwertyu0482 +qwertyu0483 +qwertyu0484 +qwertyu0485 +qwertyu0486 +qwertyu0487 +qwertyu0488 +qwertyu0489 +qwertyu0490 +qwertyu0491 +qwertyu0492 +qwertyu0493 +qwertyu0494 +qwertyu0495 +qwertyu0496 +qwertyu0497 +qwertyu0498 +qwertyu0499 +qwertyu0500 +qwertyu0501 +qwertyu0502 +qwertyu0503 +qwertyu0504 +qwertyu0505 +qwertyu0506 +qwertyu0507 +qwertyu0508 +qwertyu0509 +qwertyu0510 +qwertyu0511 +qwertyu0512 +qwertyu0513 +qwertyu0514 +qwertyu0515 +qwertyu0516 +qwertyu0517 +qwertyu0518 +qwertyu0519 +qwertyu0520 +qwertyu0521 +qwertyu0522 +qwertyu0523 +qwertyu0524 +qwertyu0525 +qwertyu0526 +qwertyu0527 +qwertyu0528 +qwertyu0529 +qwertyu0530 +qwertyu0531 +qwertyu0532 +qwertyu0533 +qwertyu0534 +qwertyu0535 +qwertyu0536 +qwertyu0537 +qwertyu0538 +qwertyu0539 +qwertyu0540 +qwertyu0541 +qwertyu0542 +qwertyu0543 +qwertyu0544 +qwertyu0545 +qwertyu0546 +qwertyu0547 +qwertyu0548 +qwertyu0549 +qwertyu0550 +qwertyu0551 +qwertyu0552 +qwertyu0553 +qwertyu0554 +qwertyu0555 +qwertyu0556 +qwertyu0557 +qwertyu0558 +qwertyu0559 +qwertyu0560 +qwertyu0561 +qwertyu0562 +qwertyu0563 +qwertyu0564 +qwertyu0565 +qwertyu0566 +qwertyu0567 +qwertyu0568 +qwertyu0569 +qwertyu0570 +qwertyu0571 +qwertyu0572 +qwertyu0573 +qwertyu0574 +qwertyu0575 +qwertyu0576 +qwertyu0577 +qwertyu0578 +qwertyu0579 +qwertyu0580 +qwertyu0581 +qwertyu0582 +qwertyu0583 +qwertyu0584 +qwertyu0585 +qwertyu0586 +qwertyu0587 +qwertyu0588 +qwertyu0589 +qwertyu0590 +qwertyu0591 +qwertyu0592 +qwertyu0593 +qwertyu0594 +qwertyu0595 +qwertyu0596 +qwertyu0597 +qwertyu0598 +qwertyu0599 +qwertyu0600 +qwertyu0601 +qwertyu0602 +qwertyu0603 +qwertyu0604 +qwertyu0605 +qwertyu0606 +qwertyu0607 +qwertyu0608 +qwertyu0609 +qwertyu0610 +qwertyu0611 +qwertyu0612 +qwertyu0613 +qwertyu0614 +qwertyu0615 +qwertyu0616 +qwertyu0617 +qwertyu0618 +qwertyu0619 +qwertyu0620 +qwertyu0621 +qwertyu0622 +qwertyu0623 +qwertyu0624 +qwertyu0625 +qwertyu0626 +qwertyu0627 +qwertyu0628 +qwertyu0629 +qwertyu0630 +qwertyu0631 +qwertyu0632 +qwertyu0633 +qwertyu0634 +qwertyu0635 +qwertyu0636 +qwertyu0637 +qwertyu0638 +qwertyu0639 +qwertyu0640 +qwertyu0641 +qwertyu0642 +qwertyu0643 +qwertyu0644 +qwertyu0645 +qwertyu0646 +qwertyu0647 +qwertyu0648 +qwertyu0649 +qwertyu0650 +qwertyu0651 +qwertyu0652 +qwertyu0653 +qwertyu0654 +qwertyu0655 +qwertyu0656 +qwertyu0657 +qwertyu0658 +qwertyu0659 +qwertyu0660 +qwertyu0661 +qwertyu0662 +qwertyu0663 +qwertyu0664 +qwertyu0665 +qwertyu0666 +qwertyu0667 +qwertyu0668 +qwertyu0669 +qwertyu0670 +qwertyu0671 +qwertyu0672 +qwertyu0673 +qwertyu0674 +qwertyu0675 +qwertyu0676 +qwertyu0677 +qwertyu0678 +qwertyu0679 +qwertyu0680 +qwertyu0681 +qwertyu0682 +qwertyu0683 +qwertyu0684 +qwertyu0685 +qwertyu0686 +qwertyu0687 +qwertyu0688 +qwertyu0689 +qwertyu0690 +qwertyu0691 +qwertyu0692 +qwertyu0693 +qwertyu0694 +qwertyu0695 +qwertyu0696 +qwertyu0697 +qwertyu0698 +qwertyu0699 +qwertyu0700 +qwertyu0701 +qwertyu0702 +qwertyu0703 +qwertyu0704 +qwertyu0705 +qwertyu0706 +qwertyu0707 +qwertyu0708 +qwertyu0709 +qwertyu0710 +qwertyu0711 +qwertyu0712 +qwertyu0713 +qwertyu0714 +qwertyu0715 +qwertyu0716 +qwertyu0717 +qwertyu0718 +qwertyu0719 +qwertyu0720 +qwertyu0721 +qwertyu0722 +qwertyu0723 +qwertyu0724 +qwertyu0725 +qwertyu0726 +qwertyu0727 +qwertyu0728 +qwertyu0729 +qwertyu0730 +qwertyu0731 +qwertyu0732 +qwertyu0733 +qwertyu0734 +qwertyu0735 +qwertyu0736 +qwertyu0737 +qwertyu0738 +qwertyu0739 +qwertyu0740 +qwertyu0741 +qwertyu0742 +qwertyu0743 +qwertyu0744 +qwertyu0745 +qwertyu0746 +qwertyu0747 +qwertyu0748 +qwertyu0749 +qwertyu0750 +qwertyu0751 +qwertyu0752 +qwertyu0753 +qwertyu0754 +qwertyu0755 +qwertyu0756 +qwertyu0757 +qwertyu0758 +qwertyu0759 +qwertyu0760 +qwertyu0761 +qwertyu0762 +qwertyu0763 +qwertyu0764 +qwertyu0765 +qwertyu0766 +qwertyu0767 +qwertyu0768 +qwertyu0769 +qwertyu0770 +qwertyu0771 +qwertyu0772 +qwertyu0773 +qwertyu0774 +qwertyu0775 +qwertyu0776 +qwertyu0777 +qwertyu0778 +qwertyu0779 +qwertyu0780 +qwertyu0781 +qwertyu0782 +qwertyu0783 +qwertyu0784 +qwertyu0785 +qwertyu0786 +qwertyu0787 +qwertyu0788 +qwertyu0789 +qwertyu0790 +qwertyu0791 +qwertyu0792 +qwertyu0793 +qwertyu0794 +qwertyu0795 +qwertyu0796 +qwertyu0797 +qwertyu0798 +qwertyu0799 +qwertyu0800 +qwertyu0801 +qwertyu0802 +qwertyu0803 +qwertyu0804 +qwertyu0805 +qwertyu0806 +qwertyu0807 +qwertyu0808 +qwertyu0809 +qwertyu0810 +qwertyu0811 +qwertyu0812 +qwertyu0813 +qwertyu0814 +qwertyu0815 +qwertyu0816 +qwertyu0817 +qwertyu0818 +qwertyu0819 +qwertyu0820 +qwertyu0821 +qwertyu0822 +qwertyu0823 +qwertyu0824 +qwertyu0825 +qwertyu0826 +qwertyu0827 +qwertyu0828 +qwertyu0829 +qwertyu0830 +qwertyu0831 +qwertyu0832 +qwertyu0833 +qwertyu0834 +qwertyu0835 +qwertyu0836 +qwertyu0837 +qwertyu0838 +qwertyu0839 +qwertyu0840 +qwertyu0841 +qwertyu0842 +qwertyu0843 +qwertyu0844 +qwertyu0845 +qwertyu0846 +qwertyu0847 +qwertyu0848 +qwertyu0849 +qwertyu0850 +qwertyu0851 +qwertyu0852 +qwertyu0853 +qwertyu0854 +qwertyu0855 +qwertyu0856 +qwertyu0857 +qwertyu0858 +qwertyu0859 +qwertyu0860 +qwertyu0861 +qwertyu0862 +qwertyu0863 +qwertyu0864 +qwertyu0865 +qwertyu0866 +qwertyu0867 +qwertyu0868 +qwertyu0869 +qwertyu0870 +qwertyu0871 +qwertyu0872 +qwertyu0873 +qwertyu0874 +qwertyu0875 +qwertyu0876 +qwertyu0877 +qwertyu0878 +qwertyu0879 +qwertyu0880 +qwertyu0881 +qwertyu0882 +qwertyu0883 +qwertyu0884 +qwertyu0885 +qwertyu0886 +qwertyu0887 +qwertyu0888 +qwertyu0889 +qwertyu0890 +qwertyu0891 +qwertyu0892 +qwertyu0893 +qwertyu0894 +qwertyu0895 +qwertyu0896 +qwertyu0897 +qwertyu0898 +qwertyu0899 +qwertyu0900 +qwertyu0901 +qwertyu0902 +qwertyu0903 +qwertyu0904 +qwertyu0905 +qwertyu0906 +qwertyu0907 +qwertyu0908 +qwertyu0909 +qwertyu0910 +qwertyu0911 +qwertyu0912 +qwertyu0913 +qwertyu0914 +qwertyu0915 +qwertyu0916 +qwertyu0917 +qwertyu0918 +qwertyu0919 +qwertyu0920 +qwertyu0921 +qwertyu0922 +qwertyu0923 +qwertyu0924 +qwertyu0925 +qwertyu0926 +qwertyu0927 +qwertyu0928 +qwertyu0929 +qwertyu0930 +qwertyu0931 +qwertyu0932 +qwertyu0933 +qwertyu0934 +qwertyu0935 +qwertyu0936 +qwertyu0937 +qwertyu0938 +qwertyu0939 +qwertyu0940 +qwertyu0941 +qwertyu0942 +qwertyu0943 +qwertyu0944 +qwertyu0945 +qwertyu0946 +qwertyu0947 +qwertyu0948 +qwertyu0949 +qwertyu0950 +qwertyu0951 +qwertyu0952 +qwertyu0953 +qwertyu0954 +qwertyu0955 +qwertyu0956 +qwertyu0957 +qwertyu0958 +qwertyu0959 +qwertyu0960 +qwertyu0961 +qwertyu0962 +qwertyu0963 +qwertyu0964 +qwertyu0965 +qwertyu0966 +qwertyu0967 +qwertyu0968 +qwertyu0969 +qwertyu0970 +qwertyu0971 +qwertyu0972 +qwertyu0973 +qwertyu0974 +qwertyu0975 +qwertyu0976 +qwertyu0977 +qwertyu0978 +qwertyu0979 +qwertyu0980 +qwertyu0981 +qwertyu0982 +qwertyu0983 +qwertyu0984 +qwertyu0985 +qwertyu0986 +qwertyu0987 +qwertyu0988 +qwertyu0989 +qwertyu0990 +qwertyu0991 +qwertyu0992 +qwertyu0993 +qwertyu0994 +qwertyu0995 +qwertyu0996 +qwertyu0997 +qwertyu0998 +qwertyu0999 +qwertyu1000 diff --git a/contrib/pg_trgm/data/trgm2.data b/contrib/pg_trgm/data/trgm2.data new file mode 100644 index 0000000..664e079 --- /dev/null +++ b/contrib/pg_trgm/data/trgm2.data @@ -0,0 +1,696 @@ +Baikal +Baikaluobbal +Lake Baikal +Baikalakko +Baikal Business Centre +Baikal Listvyanka Hotel +Baikal Airfield +Baikalovo +Transbaikalia +Baikal Mountains +Baikal Hotel Moscow +Zabaikalie +Pribaikalskaya +Baikal Plaza +Rubaikale +Tandobai Algad +Daikalay +Bakall +Stubaital +Neustift im Stubaital +Anonyme Appartments Stubaital +Barkaladja Pool +Awabakal Nature Reserve +Awabakal Field Studies Centre +Barkala +Bailallie +Barkala Park +Purba Kalaujan +Nabakalas +Barkal +Baikanthapur +Baikarjhuti +Baika +Baikari +Bakalia Char +Dakshin Bakalia +Purba Kalmegha +Efreytor-Bakalovo +Baykalsko +Baykal +Baskaltsi +Bakalite +Bajkal +Efrejtor Bakalovo +Kampong Bakaladong +Riacho do Sambaibal +Sambaibal +Barkalabava +Zabaykal +Bakalar Lake +Kaikalahun Indian Reserve 25 +Tumba-Kalamba +Kamba-Kalele +Boyagbakala +Bombakalo +Batikalengbe +Bakalukudu +Bakalawa +Bakala +Matamba-Kalenge +Kusu-Bakali +Kambakala +Bakali +Abakalu +Bonagbakala +Bakalua +Bikala Madila +Bikala +Bumba-Kaloki +Tumba-Kalunga +Kabankala +Mambakala +Tumba-Kalumba +Kabakala +Bikalabwa +Bomba-Kalende +Mwalaba-Kalamba +Matamba-Kalenga +Bumba-Kalumba +Bikalange +Kabikala +Mubikale +Kanampumba-Kalawa +Tshiabakale +Bakaly +Bakalongo +Bakale +Bakala Koupi +Bambakala +Bakalou +Tsibakala +Kimbakala +Dabakalakoro +Dabakala +Bakalafoulou +Ngao Bakala +Mobaika +Baimalou +Xibaitaling +Baikai +Baikang +Baitaling +Baikan +Baimaling Linchang +Baimalong +Baikanzui +Baiyali +Baimaling +Baimalang Donggang +Baikangshuoma +Baitaliao +Taikale +Babainale +Bailale +Baibale +Baiwale +Baikangnei +Baitali +Xiabaikan +Bailalong +Baimaluo +Baikacun +Baisala +Bailalin +Baimala +Baidalong +Dabaika +Caikalong +Cuobaikacun +Baikadangcun +Baimalin +Subaika +Gabakkale +Barkallou +Embatkala +Bodega Tabaibal +Golba Kalo +Haikala +Kaikale +Waikaloulevu +Waikalou Creek +Waikalou +Ndelaikalou +Ndelaikalokalo +Bay of Backaland +Bankali +Ker Samba Kalla +Demba Kali +Bakalarr +Baipal +Kalibakalako +Dalabakala +Bikal +Sembaikan +Praikalogu +Tanjung Ompaikalio +Bonebabakal +Tanjung Batikala +Pulau Bakalanpauno +Teluk Bakalan +Bakaltua Bank +Bakalrejo +Bakalan +Sungai Bakaladiyan +Bakal +Buku Baikole +Pulau Baika +Tanjung Bakalinga +Pulau Bakalan +Desa Bakalan +Kebakkalang +Ngambakalang +Mota Sabakal +Bakalan Lor +Babakalo +Buyu Rapanbakalai +Kalimundubakalan +Bakalpokok +Bakaldukuh +Tanabakal +Tanjung Aikaluin +Desa Bakalrejo +Bakalan Kidul +Desa Kebakalan +Kebakalan +Bakalan Kulon +Gunung Bakalan +Kalibakal +Bakaljaya +Trobakal +Bakalan Wetan +Desa Bakal +Alue Bakkala +Uruk Bakal +Bakalbuah +Kwala Bakala +Bakal Lama +Bakal Julu +Bakal Batu +Moncong Baika +Sampangbakalan +Bakalam +Desa Bakalankrapyak +Lebakkalapa Tonggoh +Trembakal +Bakalan Tengah +Kali Bakalan +Desa Cemengbakalan +Desa Bakalanpule +Gunung Bakal +Desa Tambakkalisogo +Tambakkalisogo +Desa Bakalanrayung +Salu Bakalaeng +Bakalaeng +Danau Bakalan +Selat Bakalan +Selat Bakalanpauno +Laikalanda +Bakalinga +Tanjung Mbakalang +Desa Bakalankrajan +Bakalan Dua +Kali Purbakala +Desa Bakalanwringinpitu +Tukad Kubakal +Praikalangga +Banjar Kubakal +Eat Bakal +Sungai Bakala +Kombakalada +Sori Rabakalo +Kahambikalela +Baikarara +Baikapaka +Tukad Bakalan +Teluk Haludubakal +Yabakalewa +Praikalumbang +Waikalowo +Praikalubu +Loko Praikalubu +Ramuk Ombakalada +Praikalebung +Praikaleka +Andabakal +Praikalau +Praikalokat +Praikalimbung +Bambakalo +Leubakkalian +Pematang Baitalimbangan +Lebakalil +Gereba Kaler +Krajan Bakalan +Bakalan Barat +Muarabakal +Umbulan Maharobakal +Bakaldalam +Talang Bakal +Pematang Bakalpanang +Baidaloen +Jatibakal +Tubu Bakalekuk +Dola Peimambakal +Bakalang +Teluk Bakalang +Salu Baidale +Bakalerek +Ile Bakalibu +Parbakalan +Praikalembu +Palindi Laikali +Praikalu +Sori Labakalate +Air Bakal-kecil +Sungaikalung +Sungaikalong +Pematang Bakalpanjang +Payabakal +Waikala +Sungaikali +Sungai Pebakalan +Parit Membakal +Bakalpakebo +Baikat Abu Jaraban +Maikalganj +Maikala Range +Bakalha +Baitalpur +Baikanthpur +Baihal +Barkala Reserved Forest +Babaipalli +Kaikalapettai +Kambainallur +Bakkalale +Kaikalui +Baijalpur +Nehalla Bankalah Reserved Forest +Barkala Rao +Barkali +Baidal +Barkaleh +Darreh Pumba Kal +Bahkalleh +Wibakale +Gaikali +Gagaba Kalo +Barkalare +Bakkalmal +Gora Bakalyadyr +Rodnik Bakalybulak +Urochishche Bakaly +Sopka Bakaly +Gory Bakaly +Bugor Arba-Kalgan +Ozero Baykal +Kolodets Tabakkalgan +Walangivattu Vaikal +Vattevaikal Anicut +Vaikali Tevar Kulam +Vaikalitevan Kulam +Vaikaladichchenai +Uchchodaikallu +Sellapattu Vaikal +Savata Vaikal +Puttadivali Vaikal +Palukadu Vaikal +Mulaikallu Kulam +Koraikallimadu +Koraikalapu Kulam +Karaiyamullivaikal +Karaivaikal Kulam +Kanawali Vaikal +Habakkala +Chalam Vaikal Aru +Ambakala Wewa +Alaikallupoddakulam +Alaikallupodda Alankulam +Akamadi Vaikal +Alaikalluppodda Kulam +Vaikaliththevakulam +Baikole +Sidi Mohammed el Bakali +Sidi Mohammed Bakkal +Sidi Bakal +Oulad el Bakkal +Zaouia Oulad Bakal +Azib el Bakkali +Tombakala +Malaikaly +Ambadikala +Bakalica +Bakalnica +Abankala +Kombakala +Bawkalut +Bakaleko +Bawkalut Chaung +Baukala +Cerro Bainaltzin +Sungai Bakal +Bukit Ubaibalih +Kampong Sombakal +Kampung Lebai Ali +Batikal +Bakalalan Airport +Maikali +Bakalum +Bakalambani +Abakaliki +Tsaunin Maikalaji +Baikaha +Llano Limbaika +Barkald +Barkald stasjon +Barkaleitet +Barkaldfossen +Barkaldvola +Bakkalegskardet +Baikajavri +Barkalden +Bakkalia +Siljabaika +Aikaluokta +Blombakkali +Bavkalasis +Baikajohka +Bakkalykkja +Bakalauri +Bakalauri1 +Bakalauri2 +Bakalauri3 +Bakalauri4 +Bakalauri5 +Bakalauri6 +Bakalauri7 +Bakalauri8 +Bakalauri9 +Bakalsen +Baiyaldi +Naikala +Baikanda +Barkalne +Bakalipur +Bakaldum +Raikal +Baikatte +Maikal +Bakalbhar +Waikalabubu Bay +Baikai Island +Abikal +Boikalakalawa Bay +Maikal River +Bakalao Asibi Point +Bankal +Bakalod Island +Bakalao Point +Bakalan River +Bakal Dos +Bakal Uno +Daang Bakal +Bankal School +Bakal Tres +Kabankalan City Public Plaza +Ranra Tabai Algad +Bairkal Jabal +Bairkal Dhora +Bairkal +Zaibai Algad +Gulba Kalle +Ragha Bakalzai +Dabbarkal Sar +Tabai Algad +Haikalzai +Wuchobai Algad +Jabba Kalai +Goth Soba Kaloi +Baikar Tsarai +Dudgaikal +Baixale Kamar +Zebai Algad +Bakal Khel +Goth Haikal +Haikal +Jaba Kalle +Bakalovina +Salabaikasy +Guba Kalita +Guba Kalgalaksha +Guba Kaldo +Bakalovo +Baykalovo +Baskalino +Sopka Barkaleptskaya +Bakalovskaya Ferma +Bakalinskiy Rayon +Sovkhoz Bakalinskiy +Bakalinskiy +Bakaldy +Bakaldinskoye +Urochishche Bakaldikha +Zabaykalovskiy +Barkalova +Barkalovka +Gora Barkalova +Gora Barkalyu +Bikalamakhi +Stantsiya Bakal +Baykalovskiy Rayon +Baykalovskiy +Baykalovsk +Bakalda +Boloto Malyy Baykal +Boloto Baykal +Zabaykalka +Stantsiya Baykal +Baykalo-Amurskaya Zheleznaya Doroga +Kolkhoz Krasnyy Baykal +Zaliv Baykal +Bakalino +Ovrag Bakalda +Bakaldovshchina +Prud Novyy Baykal +Bakaleyka +Bakalka +Bakaly TV Mast +Urochishche Bakalovo +Kambaika +Maloye Baykalovo +Bakalinskiy Leskhoz +Bikalikha +Kordon Barkalo +Sanatoriy Baykal +Port Baykal +Baykalikha +Polevoy Stan Baykal +Bakalovka +Ramada Makkah Shubaika +Mount Tohebakala +Tambakale Island +Mbanitambaika Island +Mbakalaka Island +Kumbakale +Kaikaloka +Kelesaikal +Nasb Gabakallah +Jabal Barkal +Jabal Abakallah +Al Barkali +Shabakal Abbass +Mabaikuli +Bambakalema +Bambakalia +Baiwala +Babakalia +Baikama +Bankalol +Kundebakali +Yumbaikamadu +Tabakali +Daba Kalharereh +Barkale +Bakalshile +Bakaloolay +Buur Bakaley +Bakaley +Buur Bakale +Bakalaale +Jabal Mobakali +Khor Bakallii +Korombaital +Ambakali +Ba Kaliin +Mbay Bakala +Tagobikala +Fayzabadkala +Aghbai Allazy +Aghbai Alikagar +Gora Fayzabadkala +Daraikalot +Aghbai Alakisirak +Beikala +Foho Berbakalau +Mota Caicabaisala +Sungai Utabailale +Urochishche Bakalarnyn-Ayasy +Urochishche Batkali +Khrebet Batkali +Ras Barkallah +Babakale +Fabrikalar +Bakalukalu Shan +Bakalukalu +Laikala +Waikalakaka +Columbus Bakalar Municipal Airport +Bakalar Library +Bakkala Cemetery +Clifton T Barkalow Elementary School +Barkalow Hollow +Kailuapuhi Waikalua Homesteads +Kawaikalia Gulch +Waikalae +Waikaloa Stream +Waikalua-Loko Fish Pond +Halekou Waikaluakai Homesteads +East Waikalua +Omar Haikal Islamic Academy +Bakalar Air Force Base (historical) +Koshbakaly +Bagkalen +Gora Baikara +Mfumbaika +Mbakalungu +Chumbaika +Ntombankala School +Bakalabwa Pans +Khobai al Janhra +Holiday Inn Dubai Al Barsha +Novotel Dubai Al Barsha +Doubletree Res.Dubai-Al Barsha +Doubletree By Hilton Hotel and Apartments Dubai Al Barsha +Doubletree By Hilton Dubai Al Barsha Hotel and Res +Park Inn By Radisson Dubai Al Barsha +Ramee Rose Hotel Dubai Al Barsha +Aparthotel Adagio Premium Dubai Al Barsha +Ataikala +Selman Marrakech +Riad Ain Marrakech +Taj Palace Marrakech +Delano Marrakech +Pullman Marrakech Palmeraie Resort And Spa +Lalla Calipau Marrakech +Hotel Fashion Marrakech +Four Seasons Resort Marrakech +Adama Resort Marrakech +Pullman Marrakech Palmeraie Re +Ramada Resort Marrakech Douar Al Hana +Hotel Zahia Marrakech +Hotel Marrakech Le Tichka +Le Chems Marrakech +Beachcomber Royal Palm Marrakech +Residence Marrakech +Riad Hermes Marrakech +Riad La Lune De Marrakech +Hotel Marrakech Le Sangho Privilege +Tempoo Hotel Marrakech +Ag Hotel & Spa Marrakech +Palm Appart Club Marrakech +Hotel Ibis Moussafir Marrakech Palmeraie +Ibis Marrakech Gare Voyageurs +Marrakech Ryads Parc And Spa +Terra Mia Marrakech Riad +Residence Dar Lamia Marrakech +Pullman Marrakech Palmeraie Rs +Moussaf Marrakech Centre Gare +Tempoo Hotel Marrakech Adults Only +Sahara Palace Marrakech +Moroccan House Marrakech +El Andalouss And Spa Marrakech +Suite Novotel Marrakech Rs +Dar Catalina Marrakech Hotel Non Refundable Room +Marrakech Hotel +Oued Tammarrakech +Tammarrakech +Cercle de Marrakech-Banlieue +Marrakech-Tensift-Al Haouz +Koudia Marrakech +Hotel Tichka Salam Marrakech +L'Atlas Marrakech +Royal Mirage Deluxe Marrakech +Golden Tulip Farah Marrakech +Ryad Mogador Marrakech +Coralia Club Marrakech Palmariva +La Sultana Marrakech +Marrakech-Medina +Marrakech +Museum of Marrakech +Douar Marrakechiyinc +Ibis Marrakech Centre Gare +Golden Tulip Rawabi Marrakech +Murano Resort Marrakech +Marrakech Garden Hotel +Pullman Marrakech Palmerai Resort & Spa +The Pearl Marrakech +Palais Calipau Marrakech +Hostal Equity Point Marrakech +Sofitel Marrakech Lounge And Spa +Pullman Marrakech Hotel And Spa +Sofitel Marrakech Palais Imperial +Hotel Ibis Moussafir Marrakech Centre Gare +Red Hotel Marrakech +Riad Zenith Marrakech +Ksar Catalina Marrakech Hotel +Blue Sea Hotel Marrakech Ryads Parc & Spa +Bluebay Marrakech +Pullman Marrakech Palmeraie Resort & Spa Hotel +Riad Litzy Marrakech +Sultana Hotel & Spa Marrakech +Albatros Club Marrakech +Hotel Sangho Club Marrakech +Suite Novotel Marrakech Hotel +Riad Utopia Suites & Spa Marrakech +Riad Fatinat Marrakech +Riad Dar El Aila Marrakech +Es Saadi And Casino De Marrakech +Dar Catalina Marrakech Hotel +Grace Marrakech +Marrakesh Apartments +Marrakesh Country Club +Koudiat Lmerrakechiyine +Sidi Mohammed el Marrakchi +Marrakesh +Marrakchien +Marrakchia +Marrakesh Menara Airport +Marrakesh Hua Hin Resort & Spa +Marrakesh Hua Hin Resort And Spa +Marrakesh Resort And Spa (Pool Suite) +Marrakesh Huahin Resort & Spa +Ibis Moussafir Marrakesh Centre Gare Hotel +Maerak-chi +Dar Hammou Ben Merrakchi +Lalla el Marakchia +Khrebet Marrakh +Sungai Maru Kechil +Marrache +Goth Marracha +Maramech Hill +Maramech Woods Nature Preserve +Oued Karakech +Samarra School +Jangal-e Marakeh Sar diff --git a/contrib/pg_trgm/expected/pg_strict_word_trgm.out b/contrib/pg_trgm/expected/pg_strict_word_trgm.out new file mode 100644 index 0000000..1e1ee16 --- /dev/null +++ b/contrib/pg_trgm/expected/pg_strict_word_trgm.out @@ -0,0 +1,1027 @@ +DROP INDEX trgm_idx2; +\copy test_trgm3 from 'data/trgm2.data' +ERROR: relation "test_trgm3" does not exist +-- reduce noise +set extra_float_digits = 0; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 +(17 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; + t | sml +------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 +(4 rows) + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 +(17 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + t | sml +------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 +(4 rows) + +select t <->>> 'Alaikallupoddakulam', t from test_trgm2 order by t <->>> 'Alaikallupoddakulam' limit 7; + ?column? | t +----------+-------------------------- + 0 | Alaikallupoddakulam + 0.25 | Alaikallupodda Alankulam + 0.32 | Alaikalluppodda Kulam + 0.615385 | Mulaikallu Kulam + 0.724138 | Koraikalapu Kulam + 0.75 | Vaikaliththevakulam + 0.766667 | Karaivaikal Kulam +(7 rows) + +create index trgm_idx2 on test_trgm2 using gist (t gist_trgm_ops); +set enable_seqscan=off; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 +(17 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; + t | sml +------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 +(4 rows) + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 +(17 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + t | sml +------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 +(4 rows) + +explain (costs off) +select t <->>> 'Alaikallupoddakulam', t from test_trgm2 order by t <->>> 'Alaikallupoddakulam' limit 7; + QUERY PLAN +--------------------------------------------------------- + Limit + -> Index Scan using trgm_idx2 on test_trgm2 + Order By: (t <->>> 'Alaikallupoddakulam'::text) +(3 rows) + +select t <->>> 'Alaikallupoddakulam', t from test_trgm2 order by t <->>> 'Alaikallupoddakulam' limit 7; + ?column? | t +----------+-------------------------- + 0 | Alaikallupoddakulam + 0.25 | Alaikallupodda Alankulam + 0.32 | Alaikalluppodda Kulam + 0.615385 | Mulaikallu Kulam + 0.724138 | Koraikalapu Kulam + 0.75 | Vaikaliththevakulam + 0.766667 | Karaivaikal Kulam +(7 rows) + +drop index trgm_idx2; +create index trgm_idx2 on test_trgm2 using gin (t gin_trgm_ops); +set enable_seqscan=off; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 +(17 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; + t | sml +------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 +(4 rows) + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 +(17 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + t | sml +------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 +(4 rows) + +set "pg_trgm.strict_word_similarity_threshold" to 0.4; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 + Zabaykal | 0.454545 + Air Bakal-kecil | 0.444444 + Bakal | 0.444444 + Bakal Batu | 0.444444 + Bakal Dos | 0.444444 + Bakal Julu | 0.444444 + Bakal Khel | 0.444444 + Bakal Lama | 0.444444 + Bakal Tres | 0.444444 + Bakal Uno | 0.444444 + Daang Bakal | 0.444444 + Desa Bakal | 0.444444 + Eat Bakal | 0.444444 + Gunung Bakal | 0.444444 + Sidi Bakal | 0.444444 + Stantsiya Bakal | 0.444444 + Sungai Bakal | 0.444444 + Talang Bakal | 0.444444 + Uruk Bakal | 0.444444 + Zaouia Oulad Bakal | 0.444444 + Baykalovskiy | 0.428571 + Baykalovskiy Rayon | 0.428571 + Baikal | 0.4 + Baikal Airfield | 0.4 + Baikal Business Centre | 0.4 + Baikal Hotel Moscow | 0.4 + Baikal Listvyanka Hotel | 0.4 + Baikal Mountains | 0.4 + Baikal Plaza | 0.4 + Bajkal | 0.4 + Bankal | 0.4 + Bankal School | 0.4 + Barkal | 0.4 + Jabal Barkal | 0.4 + Lake Baikal | 0.4 + Oulad el Bakkal | 0.4 + Sidi Mohammed Bakkal | 0.4 +(54 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; + t | sml +------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 + Kabikala | 0.461538 +(5 rows) + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 + Zabaykal | 0.454545 + Air Bakal-kecil | 0.444444 + Bakal | 0.444444 + Bakal Batu | 0.444444 + Bakal Dos | 0.444444 + Bakal Julu | 0.444444 + Bakal Khel | 0.444444 + Bakal Lama | 0.444444 + Bakal Tres | 0.444444 + Bakal Uno | 0.444444 + Daang Bakal | 0.444444 + Desa Bakal | 0.444444 + Eat Bakal | 0.444444 + Gunung Bakal | 0.444444 + Sidi Bakal | 0.444444 + Stantsiya Bakal | 0.444444 + Sungai Bakal | 0.444444 + Talang Bakal | 0.444444 + Uruk Bakal | 0.444444 + Zaouia Oulad Bakal | 0.444444 + Baykalovskiy | 0.428571 + Baykalovskiy Rayon | 0.428571 + Baikal | 0.4 + Baikal Airfield | 0.4 + Baikal Business Centre | 0.4 + Baikal Hotel Moscow | 0.4 + Baikal Listvyanka Hotel | 0.4 + Baikal Mountains | 0.4 + Baikal Plaza | 0.4 + Bajkal | 0.4 + Bankal | 0.4 + Bankal School | 0.4 + Barkal | 0.4 + Jabal Barkal | 0.4 + Lake Baikal | 0.4 + Oulad el Bakkal | 0.4 + Sidi Mohammed Bakkal | 0.4 +(54 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + t | sml +------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 + Kabikala | 0.461538 +(5 rows) + +set "pg_trgm.strict_word_similarity_threshold" to 0.2; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; + t | sml +-----------------------------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 + Zabaykal | 0.454545 + Air Bakal-kecil | 0.444444 + Bakal | 0.444444 + Bakal Batu | 0.444444 + Bakal Dos | 0.444444 + Bakal Julu | 0.444444 + Bakal Khel | 0.444444 + Bakal Lama | 0.444444 + Bakal Tres | 0.444444 + Bakal Uno | 0.444444 + Daang Bakal | 0.444444 + Desa Bakal | 0.444444 + Eat Bakal | 0.444444 + Gunung Bakal | 0.444444 + Sidi Bakal | 0.444444 + Stantsiya Bakal | 0.444444 + Sungai Bakal | 0.444444 + Talang Bakal | 0.444444 + Uruk Bakal | 0.444444 + Zaouia Oulad Bakal | 0.444444 + Baykalovskiy | 0.428571 + Baykalovskiy Rayon | 0.428571 + Baikal | 0.4 + Baikal Airfield | 0.4 + Baikal Business Centre | 0.4 + Baikal Hotel Moscow | 0.4 + Baikal Listvyanka Hotel | 0.4 + Baikal Mountains | 0.4 + Baikal Plaza | 0.4 + Bajkal | 0.4 + Bankal | 0.4 + Bankal School | 0.4 + Barkal | 0.4 + Jabal Barkal | 0.4 + Lake Baikal | 0.4 + Oulad el Bakkal | 0.4 + Sidi Mohammed Bakkal | 0.4 + Bay of Backaland | 0.375 + Boikalakalawa Bay | 0.375 + Waikalabubu Bay | 0.375 + Bairkal | 0.363636 + Bairkal Dhora | 0.363636 + Bairkal Jabal | 0.363636 + Batikal | 0.363636 + Bakaleyka | 0.307692 + Bakkalmal | 0.307692 + Bikal | 0.3 + Al Barkali | 0.285714 + Zabaykalka | 0.285714 + Baidal | 0.272727 + Baihal | 0.272727 + Baipal | 0.272727 + Bakala | 0.272727 + Bakala Koupi | 0.272727 + Bakale | 0.272727 + Bakali | 0.272727 + Bakall | 0.272727 + Bakaly | 0.272727 + Bakaly TV Mast | 0.272727 + Buur Bakale | 0.272727 + Gory Bakaly | 0.272727 + Kusu-Bakali | 0.272727 + Kwala Bakala | 0.272727 + Mbay Bakala | 0.272727 + Ngao Bakala | 0.272727 + Sidi Mohammed el Bakali | 0.272727 + Sopka Bakaly | 0.272727 + Sungai Bakala | 0.272727 + Urochishche Bakaly | 0.272727 + Alue Bakkala | 0.25 + Azib el Bakkali | 0.25 + Ba Kaliin | 0.25 + Baikaluobbal | 0.25 + Bakalam | 0.25 + Bakalan | 0.25 + Bakalan Barat | 0.25 + Bakalan Dua | 0.25 + Bakalan Kidul | 0.25 + Bakalan Kulon | 0.25 + Bakalan Lor | 0.25 + Bakalan River | 0.25 + Bakalan Tengah | 0.25 + Bakalan Wetan | 0.25 + Bakalao Asibi Point | 0.25 + Bakalao Point | 0.25 + Bakalar Air Force Base (historical) | 0.25 + Bakalar Lake | 0.25 + Bakalar Library | 0.25 + Bakalda | 0.25 + Bakaldy | 0.25 + Bakaley | 0.25 + Bakalha | 0.25 + Bakalia Char | 0.25 + Bakalka | 0.25 + Bakalod Island | 0.25 + Bakalou | 0.25 + Bakalua | 0.25 + Bakalum | 0.25 + Bakkala Cemetery | 0.25 + Bankali | 0.25 + Barkala | 0.25 + Barkala Park | 0.25 + Barkala Rao | 0.25 + Barkala Reserved Forest | 0.25 + Barkald | 0.25 + Barkald stasjon | 0.25 + Barkale | 0.25 + Barkali | 0.25 + Baukala | 0.25 + Buur Bakaley | 0.25 + Columbus Bakalar Municipal Airport | 0.25 + Dakshin Bakalia | 0.25 + Danau Bakalan | 0.25 + Desa Bakalan | 0.25 + Gunung Bakalan | 0.25 + Kali Bakalan | 0.25 + Khrebet Batkali | 0.25 + Kordon Barkalo | 0.25 + Krajan Bakalan | 0.25 + Ovrag Bakalda | 0.25 + Pulau Bakalan | 0.25 + Selat Bakalan | 0.25 + Teluk Bakalan | 0.25 + Tukad Bakalan | 0.25 + Urochishche Batkali | 0.25 + Babakale | 0.230769 + Babakalo | 0.230769 + Bagkalen | 0.230769 + Bakalalan Airport | 0.230769 + Bakalang | 0.230769 + Bakalarr | 0.230769 + Bakalawa | 0.230769 + Bakaldum | 0.230769 + Bakaleko | 0.230769 + Bakalica | 0.230769 + Bakalino | 0.230769 + Bakalite | 0.230769 + Bakalovo | 0.230769 + Bakalsen | 0.230769 + Bakaltua Bank | 0.230769 + Bakalukalu | 0.230769 + Bakalukalu Shan | 0.230769 + Bakkalia | 0.230769 + Bankalol | 0.230769 + Barkaleh | 0.230769 + Barkalne | 0.230769 + Barkalow Hollow | 0.230769 + Bawkalut | 0.230769 + Bawkalut Chaung | 0.230769 + Clifton T Barkalow Elementary School | 0.230769 + Efrejtor Bakalovo | 0.230769 + Efreytor-Bakalovo | 0.230769 + Gora Barkalyu | 0.230769 + Ile Bakalibu | 0.230769 + Khor Bakallii | 0.230769 + Nehalla Bankalah Reserved Forest | 0.230769 + Ragha Bakalzai | 0.230769 + Tanjung Batikala | 0.230769 + Teluk Bakalang | 0.230769 + Urochishche Bakalovo | 0.230769 + Banjar Kubakal | 0.222222 + Darreh Pumba Kal | 0.222222 + Zabaykalovskiy | 0.222222 + Aparthotel Adagio Premium Dubai Al Barsha | 0.214286 + Babakalia | 0.214286 + Bahkalleh | 0.214286 + Baikalovo | 0.214286 + Bakalaale | 0.214286 + Bakalabwa Pans | 0.214286 + Bakalaeng | 0.214286 + Bakalauri | 0.214286 + Bakalbhar | 0.214286 + Bakalbuah | 0.214286 + Bakalerek | 0.214286 + Bakalinga | 0.214286 + Bakalipur | 0.214286 + Bakaljaya | 0.214286 + Bakalnica | 0.214286 + Bakalongo | 0.214286 + Bakalovka | 0.214286 + Bakalrejo | 0.214286 + Bakkalale | 0.214286 + Bambakala | 0.214286 + Bambakalo | 0.214286 + Barkalare | 0.214286 + Barkalden | 0.214286 + Barkallou | 0.214286 + Barkalova | 0.214286 + Baskalino | 0.214286 + Baskaltsi | 0.214286 + Desa Bakalrejo | 0.214286 + Doubletree By Hilton Dubai Al Barsha Hotel and Res | 0.214286 + Doubletree By Hilton Hotel and Apartments Dubai Al Barsha | 0.214286 + Doubletree Res.Dubai-Al Barsha | 0.214286 + Gora Barkalova | 0.214286 + Holiday Inn Dubai Al Barsha | 0.214286 + Novotel Dubai Al Barsha | 0.214286 + Park Inn By Radisson Dubai Al Barsha | 0.214286 + Ramee Rose Hotel Dubai Al Barsha | 0.214286 + Ras Barkallah | 0.214286 + Salu Bakalaeng | 0.214286 + Tanjung Bakalinga | 0.214286 + Tubu Bakalekuk | 0.214286 + Baikalakko | 0.2 + Bakalauri1 | 0.2 + Bakalauri2 | 0.2 + Bakalauri3 | 0.2 + Bakalauri4 | 0.2 + Bakalauri5 | 0.2 + Bakalauri6 | 0.2 + Bakalauri7 | 0.2 + Bakalauri8 | 0.2 + Bakalauri9 | 0.2 + Bakaldalam | 0.2 + Bakaldukuh | 0.2 + Bakaloolay | 0.2 + Bakalovina | 0.2 + Bakalpokok | 0.2 + Bakalshile | 0.2 + Bakalukudu | 0.2 + Bambakalia | 0.2 + Barkaladja Pool | 0.2 + Barkalovka | 0.2 + Bavkalasis | 0.2 + Gora Bakalyadyr | 0.2 + Kampong Bakaladong | 0.2 + Urochishche Bakalarnyn-Ayasy | 0.2 + Urochishche Bakaldikha | 0.2 +(245 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; + t | sml +----------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 + Kabikala | 0.461538 + Ntombankala School | 0.375 + Nehalla Bankalah Reserved Forest | 0.357143 + Jabba Kalai | 0.333333 + Kambakala | 0.333333 + Ker Samba Kalla | 0.333333 + Bankal | 0.307692 + Bankal School | 0.307692 + Kanampumba-Kalawa | 0.307692 + Bankali | 0.285714 + Mwalaba-Kalamba | 0.285714 + Tumba-Kalamba | 0.285714 + Darreh Pumba Kal | 0.272727 + Bankalol | 0.266667 + Dabakala | 0.266667 + Purba Kalaujan | 0.266667 + Kali Purbakala | 0.263158 + Dalabakala | 0.25 + Demba Kali | 0.25 + Gagaba Kalo | 0.25 + Golba Kalo | 0.25 + Habakkala | 0.25 + Kali Bakalan | 0.25 + Kimbakala | 0.25 + Kombakala | 0.25 + Jaba Kalle | 0.235294 + Kaikalahun Indian Reserve 25 | 0.235294 + Kwala Bakala | 0.235294 + Gereba Kaler | 0.230769 + Goth Soba Kaloi | 0.230769 + Guba Kaldo | 0.230769 + Gulba Kalle | 0.230769 + Guba Kalgalaksha | 0.222222 + Kalibakalako | 0.222222 + Ba Kaliin | 0.214286 + Bakala | 0.214286 + Bakala Koupi | 0.214286 + Bikala | 0.214286 + Bikala Madila | 0.214286 + Bugor Arba-Kalgan | 0.214286 + Bumba-Kaloki | 0.214286 + Guba Kalita | 0.214286 + Kamba-Kalele | 0.214286 + Mbay Bakala | 0.214286 + Ngao Bakala | 0.214286 + Sungai Bakala | 0.214286 + Fayzabadkala | 0.210526 + Gora Fayzabadkala | 0.210526 + Alue Bakkala | 0.2 + Bakkala Cemetery | 0.2 + Barkala | 0.2 + Barkala Park | 0.2 + Barkala Rao | 0.2 + Barkala Reserved Forest | 0.2 + Baukala | 0.2 + Beikala | 0.2 + Bomba-Kalende | 0.2 + Bumba-Kalumba | 0.2 + Haikala | 0.2 + Kahambikalela | 0.2 + Kaikalapettai | 0.2 + Kaikale | 0.2 + Laikala | 0.2 + Maikala Range | 0.2 + Matamba-Kalenga | 0.2 + Matamba-Kalenge | 0.2 + Naikala | 0.2 + Tumba-Kalumba | 0.2 + Tumba-Kalunga | 0.2 + Waikala | 0.2 +(74 rows) + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; + t | sml +-----------------------------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalo-Amurskaya Zheleznaya Doroga | 0.666667 + Baykalovo | 0.545455 + Baykalsko | 0.545455 + Maloye Baykalovo | 0.545455 + Baykalikha | 0.5 + Baykalovsk | 0.5 + Zabaykal | 0.454545 + Air Bakal-kecil | 0.444444 + Bakal | 0.444444 + Bakal Batu | 0.444444 + Bakal Dos | 0.444444 + Bakal Julu | 0.444444 + Bakal Khel | 0.444444 + Bakal Lama | 0.444444 + Bakal Tres | 0.444444 + Bakal Uno | 0.444444 + Daang Bakal | 0.444444 + Desa Bakal | 0.444444 + Eat Bakal | 0.444444 + Gunung Bakal | 0.444444 + Sidi Bakal | 0.444444 + Stantsiya Bakal | 0.444444 + Sungai Bakal | 0.444444 + Talang Bakal | 0.444444 + Uruk Bakal | 0.444444 + Zaouia Oulad Bakal | 0.444444 + Baykalovskiy | 0.428571 + Baykalovskiy Rayon | 0.428571 + Baikal | 0.4 + Baikal Airfield | 0.4 + Baikal Business Centre | 0.4 + Baikal Hotel Moscow | 0.4 + Baikal Listvyanka Hotel | 0.4 + Baikal Mountains | 0.4 + Baikal Plaza | 0.4 + Bajkal | 0.4 + Bankal | 0.4 + Bankal School | 0.4 + Barkal | 0.4 + Jabal Barkal | 0.4 + Lake Baikal | 0.4 + Oulad el Bakkal | 0.4 + Sidi Mohammed Bakkal | 0.4 + Bay of Backaland | 0.375 + Boikalakalawa Bay | 0.375 + Waikalabubu Bay | 0.375 + Bairkal | 0.363636 + Bairkal Dhora | 0.363636 + Bairkal Jabal | 0.363636 + Batikal | 0.363636 + Bakaleyka | 0.307692 + Bakkalmal | 0.307692 + Bikal | 0.3 + Al Barkali | 0.285714 + Zabaykalka | 0.285714 + Baidal | 0.272727 + Baihal | 0.272727 + Baipal | 0.272727 + Bakala | 0.272727 + Bakala Koupi | 0.272727 + Bakale | 0.272727 + Bakali | 0.272727 + Bakall | 0.272727 + Bakaly | 0.272727 + Bakaly TV Mast | 0.272727 + Buur Bakale | 0.272727 + Gory Bakaly | 0.272727 + Kusu-Bakali | 0.272727 + Kwala Bakala | 0.272727 + Mbay Bakala | 0.272727 + Ngao Bakala | 0.272727 + Sidi Mohammed el Bakali | 0.272727 + Sopka Bakaly | 0.272727 + Sungai Bakala | 0.272727 + Urochishche Bakaly | 0.272727 + Alue Bakkala | 0.25 + Azib el Bakkali | 0.25 + Ba Kaliin | 0.25 + Baikaluobbal | 0.25 + Bakalam | 0.25 + Bakalan | 0.25 + Bakalan Barat | 0.25 + Bakalan Dua | 0.25 + Bakalan Kidul | 0.25 + Bakalan Kulon | 0.25 + Bakalan Lor | 0.25 + Bakalan River | 0.25 + Bakalan Tengah | 0.25 + Bakalan Wetan | 0.25 + Bakalao Asibi Point | 0.25 + Bakalao Point | 0.25 + Bakalar Air Force Base (historical) | 0.25 + Bakalar Lake | 0.25 + Bakalar Library | 0.25 + Bakalda | 0.25 + Bakaldy | 0.25 + Bakaley | 0.25 + Bakalha | 0.25 + Bakalia Char | 0.25 + Bakalka | 0.25 + Bakalod Island | 0.25 + Bakalou | 0.25 + Bakalua | 0.25 + Bakalum | 0.25 + Bakkala Cemetery | 0.25 + Bankali | 0.25 + Barkala | 0.25 + Barkala Park | 0.25 + Barkala Rao | 0.25 + Barkala Reserved Forest | 0.25 + Barkald | 0.25 + Barkald stasjon | 0.25 + Barkale | 0.25 + Barkali | 0.25 + Baukala | 0.25 + Buur Bakaley | 0.25 + Columbus Bakalar Municipal Airport | 0.25 + Dakshin Bakalia | 0.25 + Danau Bakalan | 0.25 + Desa Bakalan | 0.25 + Gunung Bakalan | 0.25 + Kali Bakalan | 0.25 + Khrebet Batkali | 0.25 + Kordon Barkalo | 0.25 + Krajan Bakalan | 0.25 + Ovrag Bakalda | 0.25 + Pulau Bakalan | 0.25 + Selat Bakalan | 0.25 + Teluk Bakalan | 0.25 + Tukad Bakalan | 0.25 + Urochishche Batkali | 0.25 + Babakale | 0.230769 + Babakalo | 0.230769 + Bagkalen | 0.230769 + Bakalalan Airport | 0.230769 + Bakalang | 0.230769 + Bakalarr | 0.230769 + Bakalawa | 0.230769 + Bakaldum | 0.230769 + Bakaleko | 0.230769 + Bakalica | 0.230769 + Bakalino | 0.230769 + Bakalite | 0.230769 + Bakalovo | 0.230769 + Bakalsen | 0.230769 + Bakaltua Bank | 0.230769 + Bakalukalu | 0.230769 + Bakalukalu Shan | 0.230769 + Bakkalia | 0.230769 + Bankalol | 0.230769 + Barkaleh | 0.230769 + Barkalne | 0.230769 + Barkalow Hollow | 0.230769 + Bawkalut | 0.230769 + Bawkalut Chaung | 0.230769 + Clifton T Barkalow Elementary School | 0.230769 + Efrejtor Bakalovo | 0.230769 + Efreytor-Bakalovo | 0.230769 + Gora Barkalyu | 0.230769 + Ile Bakalibu | 0.230769 + Khor Bakallii | 0.230769 + Nehalla Bankalah Reserved Forest | 0.230769 + Ragha Bakalzai | 0.230769 + Tanjung Batikala | 0.230769 + Teluk Bakalang | 0.230769 + Urochishche Bakalovo | 0.230769 + Banjar Kubakal | 0.222222 + Darreh Pumba Kal | 0.222222 + Zabaykalovskiy | 0.222222 + Aparthotel Adagio Premium Dubai Al Barsha | 0.214286 + Babakalia | 0.214286 + Bahkalleh | 0.214286 + Baikalovo | 0.214286 + Bakalaale | 0.214286 + Bakalabwa Pans | 0.214286 + Bakalaeng | 0.214286 + Bakalauri | 0.214286 + Bakalbhar | 0.214286 + Bakalbuah | 0.214286 + Bakalerek | 0.214286 + Bakalinga | 0.214286 + Bakalipur | 0.214286 + Bakaljaya | 0.214286 + Bakalnica | 0.214286 + Bakalongo | 0.214286 + Bakalovka | 0.214286 + Bakalrejo | 0.214286 + Bakkalale | 0.214286 + Bambakala | 0.214286 + Bambakalo | 0.214286 + Barkalare | 0.214286 + Barkalden | 0.214286 + Barkallou | 0.214286 + Barkalova | 0.214286 + Baskalino | 0.214286 + Baskaltsi | 0.214286 + Desa Bakalrejo | 0.214286 + Doubletree By Hilton Dubai Al Barsha Hotel and Res | 0.214286 + Doubletree By Hilton Hotel and Apartments Dubai Al Barsha | 0.214286 + Doubletree Res.Dubai-Al Barsha | 0.214286 + Gora Barkalova | 0.214286 + Holiday Inn Dubai Al Barsha | 0.214286 + Novotel Dubai Al Barsha | 0.214286 + Park Inn By Radisson Dubai Al Barsha | 0.214286 + Ramee Rose Hotel Dubai Al Barsha | 0.214286 + Ras Barkallah | 0.214286 + Salu Bakalaeng | 0.214286 + Tanjung Bakalinga | 0.214286 + Tubu Bakalekuk | 0.214286 + Baikalakko | 0.2 + Bakalauri1 | 0.2 + Bakalauri2 | 0.2 + Bakalauri3 | 0.2 + Bakalauri4 | 0.2 + Bakalauri5 | 0.2 + Bakalauri6 | 0.2 + Bakalauri7 | 0.2 + Bakalauri8 | 0.2 + Bakalauri9 | 0.2 + Bakaldalam | 0.2 + Bakaldukuh | 0.2 + Bakaloolay | 0.2 + Bakalovina | 0.2 + Bakalpokok | 0.2 + Bakalshile | 0.2 + Bakalukudu | 0.2 + Bambakalia | 0.2 + Barkaladja Pool | 0.2 + Barkalovka | 0.2 + Bavkalasis | 0.2 + Gora Bakalyadyr | 0.2 + Kampong Bakaladong | 0.2 + Urochishche Bakalarnyn-Ayasy | 0.2 + Urochishche Bakaldikha | 0.2 +(245 rows) + +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + t | sml +----------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.75 + Abankala | 0.583333 + Kabakala | 0.583333 + Kabikala | 0.461538 + Ntombankala School | 0.375 + Nehalla Bankalah Reserved Forest | 0.357143 + Jabba Kalai | 0.333333 + Kambakala | 0.333333 + Ker Samba Kalla | 0.333333 + Bankal | 0.307692 + Bankal School | 0.307692 + Kanampumba-Kalawa | 0.307692 + Bankali | 0.285714 + Mwalaba-Kalamba | 0.285714 + Tumba-Kalamba | 0.285714 + Darreh Pumba Kal | 0.272727 + Bankalol | 0.266667 + Dabakala | 0.266667 + Purba Kalaujan | 0.266667 + Kali Purbakala | 0.263158 + Dalabakala | 0.25 + Demba Kali | 0.25 + Gagaba Kalo | 0.25 + Golba Kalo | 0.25 + Habakkala | 0.25 + Kali Bakalan | 0.25 + Kimbakala | 0.25 + Kombakala | 0.25 + Jaba Kalle | 0.235294 + Kaikalahun Indian Reserve 25 | 0.235294 + Kwala Bakala | 0.235294 + Gereba Kaler | 0.230769 + Goth Soba Kaloi | 0.230769 + Guba Kaldo | 0.230769 + Gulba Kalle | 0.230769 + Guba Kalgalaksha | 0.222222 + Kalibakalako | 0.222222 + Ba Kaliin | 0.214286 + Bakala | 0.214286 + Bakala Koupi | 0.214286 + Bikala | 0.214286 + Bikala Madila | 0.214286 + Bugor Arba-Kalgan | 0.214286 + Bumba-Kaloki | 0.214286 + Guba Kalita | 0.214286 + Kamba-Kalele | 0.214286 + Mbay Bakala | 0.214286 + Ngao Bakala | 0.214286 + Sungai Bakala | 0.214286 + Fayzabadkala | 0.210526 + Gora Fayzabadkala | 0.210526 + Alue Bakkala | 0.2 + Bakkala Cemetery | 0.2 + Barkala | 0.2 + Barkala Park | 0.2 + Barkala Rao | 0.2 + Barkala Reserved Forest | 0.2 + Baukala | 0.2 + Beikala | 0.2 + Bomba-Kalende | 0.2 + Bumba-Kalumba | 0.2 + Haikala | 0.2 + Kahambikalela | 0.2 + Kaikalapettai | 0.2 + Kaikale | 0.2 + Laikala | 0.2 + Maikala Range | 0.2 + Matamba-Kalenga | 0.2 + Matamba-Kalenge | 0.2 + Naikala | 0.2 + Tumba-Kalumba | 0.2 + Tumba-Kalunga | 0.2 + Waikala | 0.2 +(74 rows) + diff --git a/contrib/pg_trgm/expected/pg_trgm.out b/contrib/pg_trgm/expected/pg_trgm.out new file mode 100644 index 0000000..ce4bf1d --- /dev/null +++ b/contrib/pg_trgm/expected/pg_trgm.out @@ -0,0 +1,5404 @@ +CREATE EXTENSION pg_trgm; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + amname | opcname +--------+--------- +(0 rows) + +--backslash is used in tests below, installcheck will fail if +--standard_conforming_string is off +set standard_conforming_strings=on; +-- reduce noise +set extra_float_digits = 0; +select show_trgm(''); + show_trgm +----------- + {} +(1 row) + +select show_trgm('(*&^$@%@'); + show_trgm +----------- + {} +(1 row) + +select show_trgm('a b c'); + show_trgm +--------------------------------------- + {" a"," b"," c"," a "," b "," c "} +(1 row) + +select show_trgm(' a b c '); + show_trgm +--------------------------------------- + {" a"," b"," c"," a "," b "," c "} +(1 row) + +select show_trgm('aA bB cC'); + show_trgm +--------------------------------------------------------- + {" a"," b"," c"," aa"," bb"," cc","aa ","bb ","cc "} +(1 row) + +select show_trgm(' aA bB cC '); + show_trgm +--------------------------------------------------------- + {" a"," b"," c"," aa"," bb"," cc","aa ","bb ","cc "} +(1 row) + +select show_trgm('a b C0*%^'); + show_trgm +--------------------------------------------- + {" a"," b"," c"," a "," b "," c0","c0 "} +(1 row) + +select similarity('wow','WOWa '); + similarity +------------ + 0.5 +(1 row) + +select similarity('wow',' WOW '); + similarity +------------ + 1 +(1 row) + +select similarity('---', '####---'); + similarity +------------ + 0 +(1 row) + +CREATE TABLE test_trgm(t text COLLATE "C"); +\copy test_trgm from 'data/trgm.data' +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 1 + qwertyu0980 | 0.714286 + qwertyu0981 | 0.714286 + qwertyu0982 | 0.714286 + qwertyu0983 | 0.714286 + qwertyu0984 | 0.714286 + qwertyu0985 | 0.714286 + qwertyu0986 | 0.714286 + qwertyu0987 | 0.714286 + qwertyu0989 | 0.714286 + qwertyu0088 | 0.6 + qwertyu0098 | 0.6 + qwertyu0188 | 0.6 + qwertyu0288 | 0.6 + qwertyu0388 | 0.6 + qwertyu0488 | 0.6 + qwertyu0588 | 0.6 + qwertyu0688 | 0.6 + qwertyu0788 | 0.6 + qwertyu0888 | 0.6 + qwertyu0900 | 0.6 + qwertyu0901 | 0.6 + qwertyu0902 | 0.6 + qwertyu0903 | 0.6 + qwertyu0904 | 0.6 + qwertyu0905 | 0.6 + qwertyu0906 | 0.6 + qwertyu0907 | 0.6 + qwertyu0908 | 0.6 + qwertyu0909 | 0.6 + qwertyu0910 | 0.6 + qwertyu0911 | 0.6 + qwertyu0912 | 0.6 + qwertyu0913 | 0.6 + qwertyu0914 | 0.6 + qwertyu0915 | 0.6 + qwertyu0916 | 0.6 + qwertyu0917 | 0.6 + qwertyu0918 | 0.6 + qwertyu0919 | 0.6 + qwertyu0920 | 0.6 + qwertyu0921 | 0.6 + qwertyu0922 | 0.6 + qwertyu0923 | 0.6 + qwertyu0924 | 0.6 + qwertyu0925 | 0.6 + qwertyu0926 | 0.6 + qwertyu0927 | 0.6 + qwertyu0928 | 0.6 + qwertyu0929 | 0.6 + qwertyu0930 | 0.6 + qwertyu0931 | 0.6 + qwertyu0932 | 0.6 + qwertyu0933 | 0.6 + qwertyu0934 | 0.6 + qwertyu0935 | 0.6 + qwertyu0936 | 0.6 + qwertyu0937 | 0.6 + qwertyu0938 | 0.6 + qwertyu0939 | 0.6 + qwertyu0940 | 0.6 + qwertyu0941 | 0.6 + qwertyu0942 | 0.6 + qwertyu0943 | 0.6 + qwertyu0944 | 0.6 + qwertyu0945 | 0.6 + qwertyu0946 | 0.6 + qwertyu0947 | 0.6 + qwertyu0948 | 0.6 + qwertyu0949 | 0.6 + qwertyu0950 | 0.6 + qwertyu0951 | 0.6 + qwertyu0952 | 0.6 + qwertyu0953 | 0.6 + qwertyu0954 | 0.6 + qwertyu0955 | 0.6 + qwertyu0956 | 0.6 + qwertyu0957 | 0.6 + qwertyu0958 | 0.6 + qwertyu0959 | 0.6 + qwertyu0960 | 0.6 + qwertyu0961 | 0.6 + qwertyu0962 | 0.6 + qwertyu0963 | 0.6 + qwertyu0964 | 0.6 + qwertyu0965 | 0.6 + qwertyu0966 | 0.6 + qwertyu0967 | 0.6 + qwertyu0968 | 0.6 + qwertyu0969 | 0.6 + qwertyu0970 | 0.6 + qwertyu0971 | 0.6 + qwertyu0972 | 0.6 + qwertyu0973 | 0.6 + qwertyu0974 | 0.6 + qwertyu0975 | 0.6 + qwertyu0976 | 0.6 + qwertyu0977 | 0.6 + qwertyu0978 | 0.6 + qwertyu0979 | 0.6 + qwertyu0990 | 0.6 + qwertyu0991 | 0.6 + qwertyu0992 | 0.6 + qwertyu0993 | 0.6 + qwertyu0994 | 0.6 + qwertyu0995 | 0.6 + qwertyu0996 | 0.6 + qwertyu0997 | 0.6 + qwertyu0998 | 0.6 + qwertyu0999 | 0.6 + qwertyu0001 | 0.5 + qwertyu0002 | 0.5 + qwertyu0003 | 0.5 + qwertyu0004 | 0.5 + qwertyu0005 | 0.5 + qwertyu0006 | 0.5 + qwertyu0007 | 0.5 + qwertyu0008 | 0.5 + qwertyu0009 | 0.5 + qwertyu0010 | 0.5 + qwertyu0011 | 0.5 + qwertyu0012 | 0.5 + qwertyu0013 | 0.5 + qwertyu0014 | 0.5 + qwertyu0015 | 0.5 + qwertyu0016 | 0.5 + qwertyu0017 | 0.5 + qwertyu0018 | 0.5 + qwertyu0019 | 0.5 + qwertyu0020 | 0.5 + qwertyu0021 | 0.5 + qwertyu0022 | 0.5 + qwertyu0023 | 0.5 + qwertyu0024 | 0.5 + qwertyu0025 | 0.5 + qwertyu0026 | 0.5 + qwertyu0027 | 0.5 + qwertyu0028 | 0.5 + qwertyu0029 | 0.5 + qwertyu0030 | 0.5 + qwertyu0031 | 0.5 + qwertyu0032 | 0.5 + qwertyu0033 | 0.5 + qwertyu0034 | 0.5 + qwertyu0035 | 0.5 + qwertyu0036 | 0.5 + qwertyu0037 | 0.5 + qwertyu0038 | 0.5 + qwertyu0039 | 0.5 + qwertyu0040 | 0.5 + qwertyu0041 | 0.5 + qwertyu0042 | 0.5 + qwertyu0043 | 0.5 + qwertyu0044 | 0.5 + qwertyu0045 | 0.5 + qwertyu0046 | 0.5 + qwertyu0047 | 0.5 + qwertyu0048 | 0.5 + qwertyu0049 | 0.5 + qwertyu0050 | 0.5 + qwertyu0051 | 0.5 + qwertyu0052 | 0.5 + qwertyu0053 | 0.5 + qwertyu0054 | 0.5 + qwertyu0055 | 0.5 + qwertyu0056 | 0.5 + qwertyu0057 | 0.5 + qwertyu0058 | 0.5 + qwertyu0059 | 0.5 + qwertyu0060 | 0.5 + qwertyu0061 | 0.5 + qwertyu0062 | 0.5 + qwertyu0063 | 0.5 + qwertyu0064 | 0.5 + qwertyu0065 | 0.5 + qwertyu0066 | 0.5 + qwertyu0067 | 0.5 + qwertyu0068 | 0.5 + qwertyu0069 | 0.5 + qwertyu0070 | 0.5 + qwertyu0071 | 0.5 + qwertyu0072 | 0.5 + qwertyu0073 | 0.5 + qwertyu0074 | 0.5 + qwertyu0075 | 0.5 + qwertyu0076 | 0.5 + qwertyu0077 | 0.5 + qwertyu0078 | 0.5 + qwertyu0079 | 0.5 + qwertyu0080 | 0.5 + qwertyu0081 | 0.5 + qwertyu0082 | 0.5 + qwertyu0083 | 0.5 + qwertyu0084 | 0.5 + qwertyu0085 | 0.5 + qwertyu0086 | 0.5 + qwertyu0087 | 0.5 + qwertyu0089 | 0.5 + qwertyu0090 | 0.5 + qwertyu0091 | 0.5 + qwertyu0092 | 0.5 + qwertyu0093 | 0.5 + qwertyu0094 | 0.5 + qwertyu0095 | 0.5 + qwertyu0096 | 0.5 + qwertyu0097 | 0.5 + qwertyu0099 | 0.5 + qwertyu0100 | 0.5 + qwertyu0101 | 0.5 + qwertyu0102 | 0.5 + qwertyu0103 | 0.5 + qwertyu0104 | 0.5 + qwertyu0105 | 0.5 + qwertyu0106 | 0.5 + qwertyu0107 | 0.5 + qwertyu0108 | 0.5 + qwertyu0109 | 0.5 + qwertyu0110 | 0.5 + qwertyu0111 | 0.5 + qwertyu0112 | 0.5 + qwertyu0113 | 0.5 + qwertyu0114 | 0.5 + qwertyu0115 | 0.5 + qwertyu0116 | 0.5 + qwertyu0117 | 0.5 + qwertyu0118 | 0.5 + qwertyu0119 | 0.5 + qwertyu0120 | 0.5 + qwertyu0121 | 0.5 + qwertyu0122 | 0.5 + qwertyu0123 | 0.5 + qwertyu0124 | 0.5 + qwertyu0125 | 0.5 + qwertyu0126 | 0.5 + qwertyu0127 | 0.5 + qwertyu0128 | 0.5 + qwertyu0129 | 0.5 + qwertyu0130 | 0.5 + qwertyu0131 | 0.5 + qwertyu0132 | 0.5 + qwertyu0133 | 0.5 + qwertyu0134 | 0.5 + qwertyu0135 | 0.5 + qwertyu0136 | 0.5 + qwertyu0137 | 0.5 + qwertyu0138 | 0.5 + qwertyu0139 | 0.5 + qwertyu0140 | 0.5 + qwertyu0141 | 0.5 + qwertyu0142 | 0.5 + qwertyu0143 | 0.5 + qwertyu0144 | 0.5 + qwertyu0145 | 0.5 + qwertyu0146 | 0.5 + qwertyu0147 | 0.5 + qwertyu0148 | 0.5 + qwertyu0149 | 0.5 + qwertyu0150 | 0.5 + qwertyu0151 | 0.5 + qwertyu0152 | 0.5 + qwertyu0153 | 0.5 + qwertyu0154 | 0.5 + qwertyu0155 | 0.5 + qwertyu0156 | 0.5 + qwertyu0157 | 0.5 + qwertyu0158 | 0.5 + qwertyu0159 | 0.5 + qwertyu0160 | 0.5 + qwertyu0161 | 0.5 + qwertyu0162 | 0.5 + qwertyu0163 | 0.5 + qwertyu0164 | 0.5 + qwertyu0165 | 0.5 + qwertyu0166 | 0.5 + qwertyu0167 | 0.5 + qwertyu0168 | 0.5 + qwertyu0169 | 0.5 + qwertyu0170 | 0.5 + qwertyu0171 | 0.5 + qwertyu0172 | 0.5 + qwertyu0173 | 0.5 + qwertyu0174 | 0.5 + qwertyu0175 | 0.5 + qwertyu0176 | 0.5 + qwertyu0177 | 0.5 + qwertyu0178 | 0.5 + qwertyu0179 | 0.5 + qwertyu0180 | 0.5 + qwertyu0181 | 0.5 + qwertyu0182 | 0.5 + qwertyu0183 | 0.5 + qwertyu0184 | 0.5 + qwertyu0185 | 0.5 + qwertyu0186 | 0.5 + qwertyu0187 | 0.5 + qwertyu0189 | 0.5 + qwertyu0190 | 0.5 + qwertyu0191 | 0.5 + qwertyu0192 | 0.5 + qwertyu0193 | 0.5 + qwertyu0194 | 0.5 + qwertyu0195 | 0.5 + qwertyu0196 | 0.5 + qwertyu0197 | 0.5 + qwertyu0198 | 0.5 + qwertyu0199 | 0.5 + qwertyu0200 | 0.5 + qwertyu0201 | 0.5 + qwertyu0202 | 0.5 + qwertyu0203 | 0.5 + qwertyu0204 | 0.5 + qwertyu0205 | 0.5 + qwertyu0206 | 0.5 + qwertyu0207 | 0.5 + qwertyu0208 | 0.5 + qwertyu0209 | 0.5 + qwertyu0210 | 0.5 + qwertyu0211 | 0.5 + qwertyu0212 | 0.5 + qwertyu0213 | 0.5 + qwertyu0214 | 0.5 + qwertyu0215 | 0.5 + qwertyu0216 | 0.5 + qwertyu0217 | 0.5 + qwertyu0218 | 0.5 + qwertyu0219 | 0.5 + qwertyu0220 | 0.5 + qwertyu0221 | 0.5 + qwertyu0222 | 0.5 + qwertyu0223 | 0.5 + qwertyu0224 | 0.5 + qwertyu0225 | 0.5 + qwertyu0226 | 0.5 + qwertyu0227 | 0.5 + qwertyu0228 | 0.5 + qwertyu0229 | 0.5 + qwertyu0230 | 0.5 + qwertyu0231 | 0.5 + qwertyu0232 | 0.5 + qwertyu0233 | 0.5 + qwertyu0234 | 0.5 + qwertyu0235 | 0.5 + qwertyu0236 | 0.5 + qwertyu0237 | 0.5 + qwertyu0238 | 0.5 + qwertyu0239 | 0.5 + qwertyu0240 | 0.5 + qwertyu0241 | 0.5 + qwertyu0242 | 0.5 + qwertyu0243 | 0.5 + qwertyu0244 | 0.5 + qwertyu0245 | 0.5 + qwertyu0246 | 0.5 + qwertyu0247 | 0.5 + qwertyu0248 | 0.5 + qwertyu0249 | 0.5 + qwertyu0250 | 0.5 + qwertyu0251 | 0.5 + qwertyu0252 | 0.5 + qwertyu0253 | 0.5 + qwertyu0254 | 0.5 + qwertyu0255 | 0.5 + qwertyu0256 | 0.5 + qwertyu0257 | 0.5 + qwertyu0258 | 0.5 + qwertyu0259 | 0.5 + qwertyu0260 | 0.5 + qwertyu0261 | 0.5 + qwertyu0262 | 0.5 + qwertyu0263 | 0.5 + qwertyu0264 | 0.5 + qwertyu0265 | 0.5 + qwertyu0266 | 0.5 + qwertyu0267 | 0.5 + qwertyu0268 | 0.5 + qwertyu0269 | 0.5 + qwertyu0270 | 0.5 + qwertyu0271 | 0.5 + qwertyu0272 | 0.5 + qwertyu0273 | 0.5 + qwertyu0274 | 0.5 + qwertyu0275 | 0.5 + qwertyu0276 | 0.5 + qwertyu0277 | 0.5 + qwertyu0278 | 0.5 + qwertyu0279 | 0.5 + qwertyu0280 | 0.5 + qwertyu0281 | 0.5 + qwertyu0282 | 0.5 + qwertyu0283 | 0.5 + qwertyu0284 | 0.5 + qwertyu0285 | 0.5 + qwertyu0286 | 0.5 + qwertyu0287 | 0.5 + qwertyu0289 | 0.5 + qwertyu0290 | 0.5 + qwertyu0291 | 0.5 + qwertyu0292 | 0.5 + qwertyu0293 | 0.5 + qwertyu0294 | 0.5 + qwertyu0295 | 0.5 + qwertyu0296 | 0.5 + qwertyu0297 | 0.5 + qwertyu0298 | 0.5 + qwertyu0299 | 0.5 + qwertyu0300 | 0.5 + qwertyu0301 | 0.5 + qwertyu0302 | 0.5 + qwertyu0303 | 0.5 + qwertyu0304 | 0.5 + qwertyu0305 | 0.5 + qwertyu0306 | 0.5 + qwertyu0307 | 0.5 + qwertyu0308 | 0.5 + qwertyu0309 | 0.5 + qwertyu0310 | 0.5 + qwertyu0311 | 0.5 + qwertyu0312 | 0.5 + qwertyu0313 | 0.5 + qwertyu0314 | 0.5 + qwertyu0315 | 0.5 + qwertyu0316 | 0.5 + qwertyu0317 | 0.5 + qwertyu0318 | 0.5 + qwertyu0319 | 0.5 + qwertyu0320 | 0.5 + qwertyu0321 | 0.5 + qwertyu0322 | 0.5 + qwertyu0323 | 0.5 + qwertyu0324 | 0.5 + qwertyu0325 | 0.5 + qwertyu0326 | 0.5 + qwertyu0327 | 0.5 + qwertyu0328 | 0.5 + qwertyu0329 | 0.5 + qwertyu0330 | 0.5 + qwertyu0331 | 0.5 + qwertyu0332 | 0.5 + qwertyu0333 | 0.5 + qwertyu0334 | 0.5 + qwertyu0335 | 0.5 + qwertyu0336 | 0.5 + qwertyu0337 | 0.5 + qwertyu0338 | 0.5 + qwertyu0339 | 0.5 + qwertyu0340 | 0.5 + qwertyu0341 | 0.5 + qwertyu0342 | 0.5 + qwertyu0343 | 0.5 + qwertyu0344 | 0.5 + qwertyu0345 | 0.5 + qwertyu0346 | 0.5 + qwertyu0347 | 0.5 + qwertyu0348 | 0.5 + qwertyu0349 | 0.5 + qwertyu0350 | 0.5 + qwertyu0351 | 0.5 + qwertyu0352 | 0.5 + qwertyu0353 | 0.5 + qwertyu0354 | 0.5 + qwertyu0355 | 0.5 + qwertyu0356 | 0.5 + qwertyu0357 | 0.5 + qwertyu0358 | 0.5 + qwertyu0359 | 0.5 + qwertyu0360 | 0.5 + qwertyu0361 | 0.5 + qwertyu0362 | 0.5 + qwertyu0363 | 0.5 + qwertyu0364 | 0.5 + qwertyu0365 | 0.5 + qwertyu0366 | 0.5 + qwertyu0367 | 0.5 + qwertyu0368 | 0.5 + qwertyu0369 | 0.5 + qwertyu0370 | 0.5 + qwertyu0371 | 0.5 + qwertyu0372 | 0.5 + qwertyu0373 | 0.5 + qwertyu0374 | 0.5 + qwertyu0375 | 0.5 + qwertyu0376 | 0.5 + qwertyu0377 | 0.5 + qwertyu0378 | 0.5 + qwertyu0379 | 0.5 + qwertyu0380 | 0.5 + qwertyu0381 | 0.5 + qwertyu0382 | 0.5 + qwertyu0383 | 0.5 + qwertyu0384 | 0.5 + qwertyu0385 | 0.5 + qwertyu0386 | 0.5 + qwertyu0387 | 0.5 + qwertyu0389 | 0.5 + qwertyu0390 | 0.5 + qwertyu0391 | 0.5 + qwertyu0392 | 0.5 + qwertyu0393 | 0.5 + qwertyu0394 | 0.5 + qwertyu0395 | 0.5 + qwertyu0396 | 0.5 + qwertyu0397 | 0.5 + qwertyu0398 | 0.5 + qwertyu0399 | 0.5 + qwertyu0400 | 0.5 + qwertyu0401 | 0.5 + qwertyu0402 | 0.5 + qwertyu0403 | 0.5 + qwertyu0404 | 0.5 + qwertyu0405 | 0.5 + qwertyu0406 | 0.5 + qwertyu0407 | 0.5 + qwertyu0408 | 0.5 + qwertyu0409 | 0.5 + qwertyu0410 | 0.5 + qwertyu0411 | 0.5 + qwertyu0412 | 0.5 + qwertyu0413 | 0.5 + qwertyu0414 | 0.5 + qwertyu0415 | 0.5 + qwertyu0416 | 0.5 + qwertyu0417 | 0.5 + qwertyu0418 | 0.5 + qwertyu0419 | 0.5 + qwertyu0420 | 0.5 + qwertyu0421 | 0.5 + qwertyu0422 | 0.5 + qwertyu0423 | 0.5 + qwertyu0424 | 0.5 + qwertyu0425 | 0.5 + qwertyu0426 | 0.5 + qwertyu0427 | 0.5 + qwertyu0428 | 0.5 + qwertyu0429 | 0.5 + qwertyu0430 | 0.5 + qwertyu0431 | 0.5 + qwertyu0432 | 0.5 + qwertyu0433 | 0.5 + qwertyu0434 | 0.5 + qwertyu0435 | 0.5 + qwertyu0436 | 0.5 + qwertyu0437 | 0.5 + qwertyu0438 | 0.5 + qwertyu0439 | 0.5 + qwertyu0440 | 0.5 + qwertyu0441 | 0.5 + qwertyu0442 | 0.5 + qwertyu0443 | 0.5 + qwertyu0444 | 0.5 + qwertyu0445 | 0.5 + qwertyu0446 | 0.5 + qwertyu0447 | 0.5 + qwertyu0448 | 0.5 + qwertyu0449 | 0.5 + qwertyu0450 | 0.5 + qwertyu0451 | 0.5 + qwertyu0452 | 0.5 + qwertyu0453 | 0.5 + qwertyu0454 | 0.5 + qwertyu0455 | 0.5 + qwertyu0456 | 0.5 + qwertyu0457 | 0.5 + qwertyu0458 | 0.5 + qwertyu0459 | 0.5 + qwertyu0460 | 0.5 + qwertyu0461 | 0.5 + qwertyu0462 | 0.5 + qwertyu0463 | 0.5 + qwertyu0464 | 0.5 + qwertyu0465 | 0.5 + qwertyu0466 | 0.5 + qwertyu0467 | 0.5 + qwertyu0468 | 0.5 + qwertyu0469 | 0.5 + qwertyu0470 | 0.5 + qwertyu0471 | 0.5 + qwertyu0472 | 0.5 + qwertyu0473 | 0.5 + qwertyu0474 | 0.5 + qwertyu0475 | 0.5 + qwertyu0476 | 0.5 + qwertyu0477 | 0.5 + qwertyu0478 | 0.5 + qwertyu0479 | 0.5 + qwertyu0480 | 0.5 + qwertyu0481 | 0.5 + qwertyu0482 | 0.5 + qwertyu0483 | 0.5 + qwertyu0484 | 0.5 + qwertyu0485 | 0.5 + qwertyu0486 | 0.5 + qwertyu0487 | 0.5 + qwertyu0489 | 0.5 + qwertyu0490 | 0.5 + qwertyu0491 | 0.5 + qwertyu0492 | 0.5 + qwertyu0493 | 0.5 + qwertyu0494 | 0.5 + qwertyu0495 | 0.5 + qwertyu0496 | 0.5 + qwertyu0497 | 0.5 + qwertyu0498 | 0.5 + qwertyu0499 | 0.5 + qwertyu0500 | 0.5 + qwertyu0501 | 0.5 + qwertyu0502 | 0.5 + qwertyu0503 | 0.5 + qwertyu0504 | 0.5 + qwertyu0505 | 0.5 + qwertyu0506 | 0.5 + qwertyu0507 | 0.5 + qwertyu0508 | 0.5 + qwertyu0509 | 0.5 + qwertyu0510 | 0.5 + qwertyu0511 | 0.5 + qwertyu0512 | 0.5 + qwertyu0513 | 0.5 + qwertyu0514 | 0.5 + qwertyu0515 | 0.5 + qwertyu0516 | 0.5 + qwertyu0517 | 0.5 + qwertyu0518 | 0.5 + qwertyu0519 | 0.5 + qwertyu0520 | 0.5 + qwertyu0521 | 0.5 + qwertyu0522 | 0.5 + qwertyu0523 | 0.5 + qwertyu0524 | 0.5 + qwertyu0525 | 0.5 + qwertyu0526 | 0.5 + qwertyu0527 | 0.5 + qwertyu0528 | 0.5 + qwertyu0529 | 0.5 + qwertyu0530 | 0.5 + qwertyu0531 | 0.5 + qwertyu0532 | 0.5 + qwertyu0533 | 0.5 + qwertyu0534 | 0.5 + qwertyu0535 | 0.5 + qwertyu0536 | 0.5 + qwertyu0537 | 0.5 + qwertyu0538 | 0.5 + qwertyu0539 | 0.5 + qwertyu0540 | 0.5 + qwertyu0541 | 0.5 + qwertyu0542 | 0.5 + qwertyu0543 | 0.5 + qwertyu0544 | 0.5 + qwertyu0545 | 0.5 + qwertyu0546 | 0.5 + qwertyu0547 | 0.5 + qwertyu0548 | 0.5 + qwertyu0549 | 0.5 + qwertyu0550 | 0.5 + qwertyu0551 | 0.5 + qwertyu0552 | 0.5 + qwertyu0553 | 0.5 + qwertyu0554 | 0.5 + qwertyu0555 | 0.5 + qwertyu0556 | 0.5 + qwertyu0557 | 0.5 + qwertyu0558 | 0.5 + qwertyu0559 | 0.5 + qwertyu0560 | 0.5 + qwertyu0561 | 0.5 + qwertyu0562 | 0.5 + qwertyu0563 | 0.5 + qwertyu0564 | 0.5 + qwertyu0565 | 0.5 + qwertyu0566 | 0.5 + qwertyu0567 | 0.5 + qwertyu0568 | 0.5 + qwertyu0569 | 0.5 + qwertyu0570 | 0.5 + qwertyu0571 | 0.5 + qwertyu0572 | 0.5 + qwertyu0573 | 0.5 + qwertyu0574 | 0.5 + qwertyu0575 | 0.5 + qwertyu0576 | 0.5 + qwertyu0577 | 0.5 + qwertyu0578 | 0.5 + qwertyu0579 | 0.5 + qwertyu0580 | 0.5 + qwertyu0581 | 0.5 + qwertyu0582 | 0.5 + qwertyu0583 | 0.5 + qwertyu0584 | 0.5 + qwertyu0585 | 0.5 + qwertyu0586 | 0.5 + qwertyu0587 | 0.5 + qwertyu0589 | 0.5 + qwertyu0590 | 0.5 + qwertyu0591 | 0.5 + qwertyu0592 | 0.5 + qwertyu0593 | 0.5 + qwertyu0594 | 0.5 + qwertyu0595 | 0.5 + qwertyu0596 | 0.5 + qwertyu0597 | 0.5 + qwertyu0598 | 0.5 + qwertyu0599 | 0.5 + qwertyu0600 | 0.5 + qwertyu0601 | 0.5 + qwertyu0602 | 0.5 + qwertyu0603 | 0.5 + qwertyu0604 | 0.5 + qwertyu0605 | 0.5 + qwertyu0606 | 0.5 + qwertyu0607 | 0.5 + qwertyu0608 | 0.5 + qwertyu0609 | 0.5 + qwertyu0610 | 0.5 + qwertyu0611 | 0.5 + qwertyu0612 | 0.5 + qwertyu0613 | 0.5 + qwertyu0614 | 0.5 + qwertyu0615 | 0.5 + qwertyu0616 | 0.5 + qwertyu0617 | 0.5 + qwertyu0618 | 0.5 + qwertyu0619 | 0.5 + qwertyu0620 | 0.5 + qwertyu0621 | 0.5 + qwertyu0622 | 0.5 + qwertyu0623 | 0.5 + qwertyu0624 | 0.5 + qwertyu0625 | 0.5 + qwertyu0626 | 0.5 + qwertyu0627 | 0.5 + qwertyu0628 | 0.5 + qwertyu0629 | 0.5 + qwertyu0630 | 0.5 + qwertyu0631 | 0.5 + qwertyu0632 | 0.5 + qwertyu0633 | 0.5 + qwertyu0634 | 0.5 + qwertyu0635 | 0.5 + qwertyu0636 | 0.5 + qwertyu0637 | 0.5 + qwertyu0638 | 0.5 + qwertyu0639 | 0.5 + qwertyu0640 | 0.5 + qwertyu0641 | 0.5 + qwertyu0642 | 0.5 + qwertyu0643 | 0.5 + qwertyu0644 | 0.5 + qwertyu0645 | 0.5 + qwertyu0646 | 0.5 + qwertyu0647 | 0.5 + qwertyu0648 | 0.5 + qwertyu0649 | 0.5 + qwertyu0650 | 0.5 + qwertyu0651 | 0.5 + qwertyu0652 | 0.5 + qwertyu0653 | 0.5 + qwertyu0654 | 0.5 + qwertyu0655 | 0.5 + qwertyu0656 | 0.5 + qwertyu0657 | 0.5 + qwertyu0658 | 0.5 + qwertyu0659 | 0.5 + qwertyu0660 | 0.5 + qwertyu0661 | 0.5 + qwertyu0662 | 0.5 + qwertyu0663 | 0.5 + qwertyu0664 | 0.5 + qwertyu0665 | 0.5 + qwertyu0666 | 0.5 + qwertyu0667 | 0.5 + qwertyu0668 | 0.5 + qwertyu0669 | 0.5 + qwertyu0670 | 0.5 + qwertyu0671 | 0.5 + qwertyu0672 | 0.5 + qwertyu0673 | 0.5 + qwertyu0674 | 0.5 + qwertyu0675 | 0.5 + qwertyu0676 | 0.5 + qwertyu0677 | 0.5 + qwertyu0678 | 0.5 + qwertyu0679 | 0.5 + qwertyu0680 | 0.5 + qwertyu0681 | 0.5 + qwertyu0682 | 0.5 + qwertyu0683 | 0.5 + qwertyu0684 | 0.5 + qwertyu0685 | 0.5 + qwertyu0686 | 0.5 + qwertyu0687 | 0.5 + qwertyu0689 | 0.5 + qwertyu0690 | 0.5 + qwertyu0691 | 0.5 + qwertyu0692 | 0.5 + qwertyu0693 | 0.5 + qwertyu0694 | 0.5 + qwertyu0695 | 0.5 + qwertyu0696 | 0.5 + qwertyu0697 | 0.5 + qwertyu0698 | 0.5 + qwertyu0699 | 0.5 + qwertyu0700 | 0.5 + qwertyu0701 | 0.5 + qwertyu0702 | 0.5 + qwertyu0703 | 0.5 + qwertyu0704 | 0.5 + qwertyu0705 | 0.5 + qwertyu0706 | 0.5 + qwertyu0707 | 0.5 + qwertyu0708 | 0.5 + qwertyu0709 | 0.5 + qwertyu0710 | 0.5 + qwertyu0711 | 0.5 + qwertyu0712 | 0.5 + qwertyu0713 | 0.5 + qwertyu0714 | 0.5 + qwertyu0715 | 0.5 + qwertyu0716 | 0.5 + qwertyu0717 | 0.5 + qwertyu0718 | 0.5 + qwertyu0719 | 0.5 + qwertyu0720 | 0.5 + qwertyu0721 | 0.5 + qwertyu0722 | 0.5 + qwertyu0723 | 0.5 + qwertyu0724 | 0.5 + qwertyu0725 | 0.5 + qwertyu0726 | 0.5 + qwertyu0727 | 0.5 + qwertyu0728 | 0.5 + qwertyu0729 | 0.5 + qwertyu0730 | 0.5 + qwertyu0731 | 0.5 + qwertyu0732 | 0.5 + qwertyu0733 | 0.5 + qwertyu0734 | 0.5 + qwertyu0735 | 0.5 + qwertyu0736 | 0.5 + qwertyu0737 | 0.5 + qwertyu0738 | 0.5 + qwertyu0739 | 0.5 + qwertyu0740 | 0.5 + qwertyu0741 | 0.5 + qwertyu0742 | 0.5 + qwertyu0743 | 0.5 + qwertyu0744 | 0.5 + qwertyu0745 | 0.5 + qwertyu0746 | 0.5 + qwertyu0747 | 0.5 + qwertyu0748 | 0.5 + qwertyu0749 | 0.5 + qwertyu0750 | 0.5 + qwertyu0751 | 0.5 + qwertyu0752 | 0.5 + qwertyu0753 | 0.5 + qwertyu0754 | 0.5 + qwertyu0755 | 0.5 + qwertyu0756 | 0.5 + qwertyu0757 | 0.5 + qwertyu0758 | 0.5 + qwertyu0759 | 0.5 + qwertyu0760 | 0.5 + qwertyu0761 | 0.5 + qwertyu0762 | 0.5 + qwertyu0763 | 0.5 + qwertyu0764 | 0.5 + qwertyu0765 | 0.5 + qwertyu0766 | 0.5 + qwertyu0767 | 0.5 + qwertyu0768 | 0.5 + qwertyu0769 | 0.5 + qwertyu0770 | 0.5 + qwertyu0771 | 0.5 + qwertyu0772 | 0.5 + qwertyu0773 | 0.5 + qwertyu0774 | 0.5 + qwertyu0775 | 0.5 + qwertyu0776 | 0.5 + qwertyu0777 | 0.5 + qwertyu0778 | 0.5 + qwertyu0779 | 0.5 + qwertyu0780 | 0.5 + qwertyu0781 | 0.5 + qwertyu0782 | 0.5 + qwertyu0783 | 0.5 + qwertyu0784 | 0.5 + qwertyu0785 | 0.5 + qwertyu0786 | 0.5 + qwertyu0787 | 0.5 + qwertyu0789 | 0.5 + qwertyu0790 | 0.5 + qwertyu0791 | 0.5 + qwertyu0792 | 0.5 + qwertyu0793 | 0.5 + qwertyu0794 | 0.5 + qwertyu0795 | 0.5 + qwertyu0796 | 0.5 + qwertyu0797 | 0.5 + qwertyu0798 | 0.5 + qwertyu0799 | 0.5 + qwertyu0800 | 0.5 + qwertyu0801 | 0.5 + qwertyu0802 | 0.5 + qwertyu0803 | 0.5 + qwertyu0804 | 0.5 + qwertyu0805 | 0.5 + qwertyu0806 | 0.5 + qwertyu0807 | 0.5 + qwertyu0808 | 0.5 + qwertyu0809 | 0.5 + qwertyu0810 | 0.5 + qwertyu0811 | 0.5 + qwertyu0812 | 0.5 + qwertyu0813 | 0.5 + qwertyu0814 | 0.5 + qwertyu0815 | 0.5 + qwertyu0816 | 0.5 + qwertyu0817 | 0.5 + qwertyu0818 | 0.5 + qwertyu0819 | 0.5 + qwertyu0820 | 0.5 + qwertyu0821 | 0.5 + qwertyu0822 | 0.5 + qwertyu0823 | 0.5 + qwertyu0824 | 0.5 + qwertyu0825 | 0.5 + qwertyu0826 | 0.5 + qwertyu0827 | 0.5 + qwertyu0828 | 0.5 + qwertyu0829 | 0.5 + qwertyu0830 | 0.5 + qwertyu0831 | 0.5 + qwertyu0832 | 0.5 + qwertyu0833 | 0.5 + qwertyu0834 | 0.5 + qwertyu0835 | 0.5 + qwertyu0836 | 0.5 + qwertyu0837 | 0.5 + qwertyu0838 | 0.5 + qwertyu0839 | 0.5 + qwertyu0840 | 0.5 + qwertyu0841 | 0.5 + qwertyu0842 | 0.5 + qwertyu0843 | 0.5 + qwertyu0844 | 0.5 + qwertyu0845 | 0.5 + qwertyu0846 | 0.5 + qwertyu0847 | 0.5 + qwertyu0848 | 0.5 + qwertyu0849 | 0.5 + qwertyu0850 | 0.5 + qwertyu0851 | 0.5 + qwertyu0852 | 0.5 + qwertyu0853 | 0.5 + qwertyu0854 | 0.5 + qwertyu0855 | 0.5 + qwertyu0856 | 0.5 + qwertyu0857 | 0.5 + qwertyu0858 | 0.5 + qwertyu0859 | 0.5 + qwertyu0860 | 0.5 + qwertyu0861 | 0.5 + qwertyu0862 | 0.5 + qwertyu0863 | 0.5 + qwertyu0864 | 0.5 + qwertyu0865 | 0.5 + qwertyu0866 | 0.5 + qwertyu0867 | 0.5 + qwertyu0868 | 0.5 + qwertyu0869 | 0.5 + qwertyu0870 | 0.5 + qwertyu0871 | 0.5 + qwertyu0872 | 0.5 + qwertyu0873 | 0.5 + qwertyu0874 | 0.5 + qwertyu0875 | 0.5 + qwertyu0876 | 0.5 + qwertyu0877 | 0.5 + qwertyu0878 | 0.5 + qwertyu0879 | 0.5 + qwertyu0880 | 0.5 + qwertyu0881 | 0.5 + qwertyu0882 | 0.5 + qwertyu0883 | 0.5 + qwertyu0884 | 0.5 + qwertyu0885 | 0.5 + qwertyu0886 | 0.5 + qwertyu0887 | 0.5 + qwertyu0889 | 0.5 + qwertyu0890 | 0.5 + qwertyu0891 | 0.5 + qwertyu0892 | 0.5 + qwertyu0893 | 0.5 + qwertyu0894 | 0.5 + qwertyu0895 | 0.5 + qwertyu0896 | 0.5 + qwertyu0897 | 0.5 + qwertyu0898 | 0.5 + qwertyu0899 | 0.5 + qwertyu1000 | 0.411765 +(1000 rows) + +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 0.6 + qwertyu0980 | 0.411765 + qwertyu0981 | 0.411765 + qwertyu0982 | 0.411765 + qwertyu0983 | 0.411765 + qwertyu0984 | 0.411765 + qwertyu0985 | 0.411765 + qwertyu0986 | 0.411765 + qwertyu0987 | 0.411765 + qwertyu0989 | 0.411765 + qwertyu0088 | 0.333333 + qwertyu0098 | 0.333333 + qwertyu0188 | 0.333333 + qwertyu0288 | 0.333333 + qwertyu0388 | 0.333333 + qwertyu0488 | 0.333333 + qwertyu0588 | 0.333333 + qwertyu0688 | 0.333333 + qwertyu0788 | 0.333333 + qwertyu0888 | 0.333333 + qwertyu0900 | 0.333333 + qwertyu0901 | 0.333333 + qwertyu0902 | 0.333333 + qwertyu0903 | 0.333333 + qwertyu0904 | 0.333333 + qwertyu0905 | 0.333333 + qwertyu0906 | 0.333333 + qwertyu0907 | 0.333333 + qwertyu0908 | 0.333333 + qwertyu0909 | 0.333333 + qwertyu0910 | 0.333333 + qwertyu0911 | 0.333333 + qwertyu0912 | 0.333333 + qwertyu0913 | 0.333333 + qwertyu0914 | 0.333333 + qwertyu0915 | 0.333333 + qwertyu0916 | 0.333333 + qwertyu0917 | 0.333333 + qwertyu0918 | 0.333333 + qwertyu0919 | 0.333333 + qwertyu0920 | 0.333333 + qwertyu0921 | 0.333333 + qwertyu0922 | 0.333333 + qwertyu0923 | 0.333333 + qwertyu0924 | 0.333333 + qwertyu0925 | 0.333333 + qwertyu0926 | 0.333333 + qwertyu0927 | 0.333333 + qwertyu0928 | 0.333333 + qwertyu0929 | 0.333333 + qwertyu0930 | 0.333333 + qwertyu0931 | 0.333333 + qwertyu0932 | 0.333333 + qwertyu0933 | 0.333333 + qwertyu0934 | 0.333333 + qwertyu0935 | 0.333333 + qwertyu0936 | 0.333333 + qwertyu0937 | 0.333333 + qwertyu0938 | 0.333333 + qwertyu0939 | 0.333333 + qwertyu0940 | 0.333333 + qwertyu0941 | 0.333333 + qwertyu0942 | 0.333333 + qwertyu0943 | 0.333333 + qwertyu0944 | 0.333333 + qwertyu0945 | 0.333333 + qwertyu0946 | 0.333333 + qwertyu0947 | 0.333333 + qwertyu0948 | 0.333333 + qwertyu0949 | 0.333333 + qwertyu0950 | 0.333333 + qwertyu0951 | 0.333333 + qwertyu0952 | 0.333333 + qwertyu0953 | 0.333333 + qwertyu0954 | 0.333333 + qwertyu0955 | 0.333333 + qwertyu0956 | 0.333333 + qwertyu0957 | 0.333333 + qwertyu0958 | 0.333333 + qwertyu0959 | 0.333333 + qwertyu0960 | 0.333333 + qwertyu0961 | 0.333333 + qwertyu0962 | 0.333333 + qwertyu0963 | 0.333333 + qwertyu0964 | 0.333333 + qwertyu0965 | 0.333333 + qwertyu0966 | 0.333333 + qwertyu0967 | 0.333333 + qwertyu0968 | 0.333333 + qwertyu0969 | 0.333333 + qwertyu0970 | 0.333333 + qwertyu0971 | 0.333333 + qwertyu0972 | 0.333333 + qwertyu0973 | 0.333333 + qwertyu0974 | 0.333333 + qwertyu0975 | 0.333333 + qwertyu0976 | 0.333333 + qwertyu0977 | 0.333333 + qwertyu0978 | 0.333333 + qwertyu0979 | 0.333333 + qwertyu0990 | 0.333333 + qwertyu0991 | 0.333333 + qwertyu0992 | 0.333333 + qwertyu0993 | 0.333333 + qwertyu0994 | 0.333333 + qwertyu0995 | 0.333333 + qwertyu0996 | 0.333333 + qwertyu0997 | 0.333333 + qwertyu0998 | 0.333333 + qwertyu0999 | 0.333333 +(110 rows) + +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 0.333333 +(1 row) + +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; + ?column? | t +----------+------------- + 0.411765 | qwertyu0988 + 0.5 | qwertyu0987 +(2 rows) + +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; + count +------- + 1000 +(1 row) + +create index trgm_idx on test_trgm using gist (t gist_trgm_ops); +set enable_seqscan=off; +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 1 + qwertyu0980 | 0.714286 + qwertyu0981 | 0.714286 + qwertyu0982 | 0.714286 + qwertyu0983 | 0.714286 + qwertyu0984 | 0.714286 + qwertyu0985 | 0.714286 + qwertyu0986 | 0.714286 + qwertyu0987 | 0.714286 + qwertyu0989 | 0.714286 + qwertyu0088 | 0.6 + qwertyu0098 | 0.6 + qwertyu0188 | 0.6 + qwertyu0288 | 0.6 + qwertyu0388 | 0.6 + qwertyu0488 | 0.6 + qwertyu0588 | 0.6 + qwertyu0688 | 0.6 + qwertyu0788 | 0.6 + qwertyu0888 | 0.6 + qwertyu0900 | 0.6 + qwertyu0901 | 0.6 + qwertyu0902 | 0.6 + qwertyu0903 | 0.6 + qwertyu0904 | 0.6 + qwertyu0905 | 0.6 + qwertyu0906 | 0.6 + qwertyu0907 | 0.6 + qwertyu0908 | 0.6 + qwertyu0909 | 0.6 + qwertyu0910 | 0.6 + qwertyu0911 | 0.6 + qwertyu0912 | 0.6 + qwertyu0913 | 0.6 + qwertyu0914 | 0.6 + qwertyu0915 | 0.6 + qwertyu0916 | 0.6 + qwertyu0917 | 0.6 + qwertyu0918 | 0.6 + qwertyu0919 | 0.6 + qwertyu0920 | 0.6 + qwertyu0921 | 0.6 + qwertyu0922 | 0.6 + qwertyu0923 | 0.6 + qwertyu0924 | 0.6 + qwertyu0925 | 0.6 + qwertyu0926 | 0.6 + qwertyu0927 | 0.6 + qwertyu0928 | 0.6 + qwertyu0929 | 0.6 + qwertyu0930 | 0.6 + qwertyu0931 | 0.6 + qwertyu0932 | 0.6 + qwertyu0933 | 0.6 + qwertyu0934 | 0.6 + qwertyu0935 | 0.6 + qwertyu0936 | 0.6 + qwertyu0937 | 0.6 + qwertyu0938 | 0.6 + qwertyu0939 | 0.6 + qwertyu0940 | 0.6 + qwertyu0941 | 0.6 + qwertyu0942 | 0.6 + qwertyu0943 | 0.6 + qwertyu0944 | 0.6 + qwertyu0945 | 0.6 + qwertyu0946 | 0.6 + qwertyu0947 | 0.6 + qwertyu0948 | 0.6 + qwertyu0949 | 0.6 + qwertyu0950 | 0.6 + qwertyu0951 | 0.6 + qwertyu0952 | 0.6 + qwertyu0953 | 0.6 + qwertyu0954 | 0.6 + qwertyu0955 | 0.6 + qwertyu0956 | 0.6 + qwertyu0957 | 0.6 + qwertyu0958 | 0.6 + qwertyu0959 | 0.6 + qwertyu0960 | 0.6 + qwertyu0961 | 0.6 + qwertyu0962 | 0.6 + qwertyu0963 | 0.6 + qwertyu0964 | 0.6 + qwertyu0965 | 0.6 + qwertyu0966 | 0.6 + qwertyu0967 | 0.6 + qwertyu0968 | 0.6 + qwertyu0969 | 0.6 + qwertyu0970 | 0.6 + qwertyu0971 | 0.6 + qwertyu0972 | 0.6 + qwertyu0973 | 0.6 + qwertyu0974 | 0.6 + qwertyu0975 | 0.6 + qwertyu0976 | 0.6 + qwertyu0977 | 0.6 + qwertyu0978 | 0.6 + qwertyu0979 | 0.6 + qwertyu0990 | 0.6 + qwertyu0991 | 0.6 + qwertyu0992 | 0.6 + qwertyu0993 | 0.6 + qwertyu0994 | 0.6 + qwertyu0995 | 0.6 + qwertyu0996 | 0.6 + qwertyu0997 | 0.6 + qwertyu0998 | 0.6 + qwertyu0999 | 0.6 + qwertyu0001 | 0.5 + qwertyu0002 | 0.5 + qwertyu0003 | 0.5 + qwertyu0004 | 0.5 + qwertyu0005 | 0.5 + qwertyu0006 | 0.5 + qwertyu0007 | 0.5 + qwertyu0008 | 0.5 + qwertyu0009 | 0.5 + qwertyu0010 | 0.5 + qwertyu0011 | 0.5 + qwertyu0012 | 0.5 + qwertyu0013 | 0.5 + qwertyu0014 | 0.5 + qwertyu0015 | 0.5 + qwertyu0016 | 0.5 + qwertyu0017 | 0.5 + qwertyu0018 | 0.5 + qwertyu0019 | 0.5 + qwertyu0020 | 0.5 + qwertyu0021 | 0.5 + qwertyu0022 | 0.5 + qwertyu0023 | 0.5 + qwertyu0024 | 0.5 + qwertyu0025 | 0.5 + qwertyu0026 | 0.5 + qwertyu0027 | 0.5 + qwertyu0028 | 0.5 + qwertyu0029 | 0.5 + qwertyu0030 | 0.5 + qwertyu0031 | 0.5 + qwertyu0032 | 0.5 + qwertyu0033 | 0.5 + qwertyu0034 | 0.5 + qwertyu0035 | 0.5 + qwertyu0036 | 0.5 + qwertyu0037 | 0.5 + qwertyu0038 | 0.5 + qwertyu0039 | 0.5 + qwertyu0040 | 0.5 + qwertyu0041 | 0.5 + qwertyu0042 | 0.5 + qwertyu0043 | 0.5 + qwertyu0044 | 0.5 + qwertyu0045 | 0.5 + qwertyu0046 | 0.5 + qwertyu0047 | 0.5 + qwertyu0048 | 0.5 + qwertyu0049 | 0.5 + qwertyu0050 | 0.5 + qwertyu0051 | 0.5 + qwertyu0052 | 0.5 + qwertyu0053 | 0.5 + qwertyu0054 | 0.5 + qwertyu0055 | 0.5 + qwertyu0056 | 0.5 + qwertyu0057 | 0.5 + qwertyu0058 | 0.5 + qwertyu0059 | 0.5 + qwertyu0060 | 0.5 + qwertyu0061 | 0.5 + qwertyu0062 | 0.5 + qwertyu0063 | 0.5 + qwertyu0064 | 0.5 + qwertyu0065 | 0.5 + qwertyu0066 | 0.5 + qwertyu0067 | 0.5 + qwertyu0068 | 0.5 + qwertyu0069 | 0.5 + qwertyu0070 | 0.5 + qwertyu0071 | 0.5 + qwertyu0072 | 0.5 + qwertyu0073 | 0.5 + qwertyu0074 | 0.5 + qwertyu0075 | 0.5 + qwertyu0076 | 0.5 + qwertyu0077 | 0.5 + qwertyu0078 | 0.5 + qwertyu0079 | 0.5 + qwertyu0080 | 0.5 + qwertyu0081 | 0.5 + qwertyu0082 | 0.5 + qwertyu0083 | 0.5 + qwertyu0084 | 0.5 + qwertyu0085 | 0.5 + qwertyu0086 | 0.5 + qwertyu0087 | 0.5 + qwertyu0089 | 0.5 + qwertyu0090 | 0.5 + qwertyu0091 | 0.5 + qwertyu0092 | 0.5 + qwertyu0093 | 0.5 + qwertyu0094 | 0.5 + qwertyu0095 | 0.5 + qwertyu0096 | 0.5 + qwertyu0097 | 0.5 + qwertyu0099 | 0.5 + qwertyu0100 | 0.5 + qwertyu0101 | 0.5 + qwertyu0102 | 0.5 + qwertyu0103 | 0.5 + qwertyu0104 | 0.5 + qwertyu0105 | 0.5 + qwertyu0106 | 0.5 + qwertyu0107 | 0.5 + qwertyu0108 | 0.5 + qwertyu0109 | 0.5 + qwertyu0110 | 0.5 + qwertyu0111 | 0.5 + qwertyu0112 | 0.5 + qwertyu0113 | 0.5 + qwertyu0114 | 0.5 + qwertyu0115 | 0.5 + qwertyu0116 | 0.5 + qwertyu0117 | 0.5 + qwertyu0118 | 0.5 + qwertyu0119 | 0.5 + qwertyu0120 | 0.5 + qwertyu0121 | 0.5 + qwertyu0122 | 0.5 + qwertyu0123 | 0.5 + qwertyu0124 | 0.5 + qwertyu0125 | 0.5 + qwertyu0126 | 0.5 + qwertyu0127 | 0.5 + qwertyu0128 | 0.5 + qwertyu0129 | 0.5 + qwertyu0130 | 0.5 + qwertyu0131 | 0.5 + qwertyu0132 | 0.5 + qwertyu0133 | 0.5 + qwertyu0134 | 0.5 + qwertyu0135 | 0.5 + qwertyu0136 | 0.5 + qwertyu0137 | 0.5 + qwertyu0138 | 0.5 + qwertyu0139 | 0.5 + qwertyu0140 | 0.5 + qwertyu0141 | 0.5 + qwertyu0142 | 0.5 + qwertyu0143 | 0.5 + qwertyu0144 | 0.5 + qwertyu0145 | 0.5 + qwertyu0146 | 0.5 + qwertyu0147 | 0.5 + qwertyu0148 | 0.5 + qwertyu0149 | 0.5 + qwertyu0150 | 0.5 + qwertyu0151 | 0.5 + qwertyu0152 | 0.5 + qwertyu0153 | 0.5 + qwertyu0154 | 0.5 + qwertyu0155 | 0.5 + qwertyu0156 | 0.5 + qwertyu0157 | 0.5 + qwertyu0158 | 0.5 + qwertyu0159 | 0.5 + qwertyu0160 | 0.5 + qwertyu0161 | 0.5 + qwertyu0162 | 0.5 + qwertyu0163 | 0.5 + qwertyu0164 | 0.5 + qwertyu0165 | 0.5 + qwertyu0166 | 0.5 + qwertyu0167 | 0.5 + qwertyu0168 | 0.5 + qwertyu0169 | 0.5 + qwertyu0170 | 0.5 + qwertyu0171 | 0.5 + qwertyu0172 | 0.5 + qwertyu0173 | 0.5 + qwertyu0174 | 0.5 + qwertyu0175 | 0.5 + qwertyu0176 | 0.5 + qwertyu0177 | 0.5 + qwertyu0178 | 0.5 + qwertyu0179 | 0.5 + qwertyu0180 | 0.5 + qwertyu0181 | 0.5 + qwertyu0182 | 0.5 + qwertyu0183 | 0.5 + qwertyu0184 | 0.5 + qwertyu0185 | 0.5 + qwertyu0186 | 0.5 + qwertyu0187 | 0.5 + qwertyu0189 | 0.5 + qwertyu0190 | 0.5 + qwertyu0191 | 0.5 + qwertyu0192 | 0.5 + qwertyu0193 | 0.5 + qwertyu0194 | 0.5 + qwertyu0195 | 0.5 + qwertyu0196 | 0.5 + qwertyu0197 | 0.5 + qwertyu0198 | 0.5 + qwertyu0199 | 0.5 + qwertyu0200 | 0.5 + qwertyu0201 | 0.5 + qwertyu0202 | 0.5 + qwertyu0203 | 0.5 + qwertyu0204 | 0.5 + qwertyu0205 | 0.5 + qwertyu0206 | 0.5 + qwertyu0207 | 0.5 + qwertyu0208 | 0.5 + qwertyu0209 | 0.5 + qwertyu0210 | 0.5 + qwertyu0211 | 0.5 + qwertyu0212 | 0.5 + qwertyu0213 | 0.5 + qwertyu0214 | 0.5 + qwertyu0215 | 0.5 + qwertyu0216 | 0.5 + qwertyu0217 | 0.5 + qwertyu0218 | 0.5 + qwertyu0219 | 0.5 + qwertyu0220 | 0.5 + qwertyu0221 | 0.5 + qwertyu0222 | 0.5 + qwertyu0223 | 0.5 + qwertyu0224 | 0.5 + qwertyu0225 | 0.5 + qwertyu0226 | 0.5 + qwertyu0227 | 0.5 + qwertyu0228 | 0.5 + qwertyu0229 | 0.5 + qwertyu0230 | 0.5 + qwertyu0231 | 0.5 + qwertyu0232 | 0.5 + qwertyu0233 | 0.5 + qwertyu0234 | 0.5 + qwertyu0235 | 0.5 + qwertyu0236 | 0.5 + qwertyu0237 | 0.5 + qwertyu0238 | 0.5 + qwertyu0239 | 0.5 + qwertyu0240 | 0.5 + qwertyu0241 | 0.5 + qwertyu0242 | 0.5 + qwertyu0243 | 0.5 + qwertyu0244 | 0.5 + qwertyu0245 | 0.5 + qwertyu0246 | 0.5 + qwertyu0247 | 0.5 + qwertyu0248 | 0.5 + qwertyu0249 | 0.5 + qwertyu0250 | 0.5 + qwertyu0251 | 0.5 + qwertyu0252 | 0.5 + qwertyu0253 | 0.5 + qwertyu0254 | 0.5 + qwertyu0255 | 0.5 + qwertyu0256 | 0.5 + qwertyu0257 | 0.5 + qwertyu0258 | 0.5 + qwertyu0259 | 0.5 + qwertyu0260 | 0.5 + qwertyu0261 | 0.5 + qwertyu0262 | 0.5 + qwertyu0263 | 0.5 + qwertyu0264 | 0.5 + qwertyu0265 | 0.5 + qwertyu0266 | 0.5 + qwertyu0267 | 0.5 + qwertyu0268 | 0.5 + qwertyu0269 | 0.5 + qwertyu0270 | 0.5 + qwertyu0271 | 0.5 + qwertyu0272 | 0.5 + qwertyu0273 | 0.5 + qwertyu0274 | 0.5 + qwertyu0275 | 0.5 + qwertyu0276 | 0.5 + qwertyu0277 | 0.5 + qwertyu0278 | 0.5 + qwertyu0279 | 0.5 + qwertyu0280 | 0.5 + qwertyu0281 | 0.5 + qwertyu0282 | 0.5 + qwertyu0283 | 0.5 + qwertyu0284 | 0.5 + qwertyu0285 | 0.5 + qwertyu0286 | 0.5 + qwertyu0287 | 0.5 + qwertyu0289 | 0.5 + qwertyu0290 | 0.5 + qwertyu0291 | 0.5 + qwertyu0292 | 0.5 + qwertyu0293 | 0.5 + qwertyu0294 | 0.5 + qwertyu0295 | 0.5 + qwertyu0296 | 0.5 + qwertyu0297 | 0.5 + qwertyu0298 | 0.5 + qwertyu0299 | 0.5 + qwertyu0300 | 0.5 + qwertyu0301 | 0.5 + qwertyu0302 | 0.5 + qwertyu0303 | 0.5 + qwertyu0304 | 0.5 + qwertyu0305 | 0.5 + qwertyu0306 | 0.5 + qwertyu0307 | 0.5 + qwertyu0308 | 0.5 + qwertyu0309 | 0.5 + qwertyu0310 | 0.5 + qwertyu0311 | 0.5 + qwertyu0312 | 0.5 + qwertyu0313 | 0.5 + qwertyu0314 | 0.5 + qwertyu0315 | 0.5 + qwertyu0316 | 0.5 + qwertyu0317 | 0.5 + qwertyu0318 | 0.5 + qwertyu0319 | 0.5 + qwertyu0320 | 0.5 + qwertyu0321 | 0.5 + qwertyu0322 | 0.5 + qwertyu0323 | 0.5 + qwertyu0324 | 0.5 + qwertyu0325 | 0.5 + qwertyu0326 | 0.5 + qwertyu0327 | 0.5 + qwertyu0328 | 0.5 + qwertyu0329 | 0.5 + qwertyu0330 | 0.5 + qwertyu0331 | 0.5 + qwertyu0332 | 0.5 + qwertyu0333 | 0.5 + qwertyu0334 | 0.5 + qwertyu0335 | 0.5 + qwertyu0336 | 0.5 + qwertyu0337 | 0.5 + qwertyu0338 | 0.5 + qwertyu0339 | 0.5 + qwertyu0340 | 0.5 + qwertyu0341 | 0.5 + qwertyu0342 | 0.5 + qwertyu0343 | 0.5 + qwertyu0344 | 0.5 + qwertyu0345 | 0.5 + qwertyu0346 | 0.5 + qwertyu0347 | 0.5 + qwertyu0348 | 0.5 + qwertyu0349 | 0.5 + qwertyu0350 | 0.5 + qwertyu0351 | 0.5 + qwertyu0352 | 0.5 + qwertyu0353 | 0.5 + qwertyu0354 | 0.5 + qwertyu0355 | 0.5 + qwertyu0356 | 0.5 + qwertyu0357 | 0.5 + qwertyu0358 | 0.5 + qwertyu0359 | 0.5 + qwertyu0360 | 0.5 + qwertyu0361 | 0.5 + qwertyu0362 | 0.5 + qwertyu0363 | 0.5 + qwertyu0364 | 0.5 + qwertyu0365 | 0.5 + qwertyu0366 | 0.5 + qwertyu0367 | 0.5 + qwertyu0368 | 0.5 + qwertyu0369 | 0.5 + qwertyu0370 | 0.5 + qwertyu0371 | 0.5 + qwertyu0372 | 0.5 + qwertyu0373 | 0.5 + qwertyu0374 | 0.5 + qwertyu0375 | 0.5 + qwertyu0376 | 0.5 + qwertyu0377 | 0.5 + qwertyu0378 | 0.5 + qwertyu0379 | 0.5 + qwertyu0380 | 0.5 + qwertyu0381 | 0.5 + qwertyu0382 | 0.5 + qwertyu0383 | 0.5 + qwertyu0384 | 0.5 + qwertyu0385 | 0.5 + qwertyu0386 | 0.5 + qwertyu0387 | 0.5 + qwertyu0389 | 0.5 + qwertyu0390 | 0.5 + qwertyu0391 | 0.5 + qwertyu0392 | 0.5 + qwertyu0393 | 0.5 + qwertyu0394 | 0.5 + qwertyu0395 | 0.5 + qwertyu0396 | 0.5 + qwertyu0397 | 0.5 + qwertyu0398 | 0.5 + qwertyu0399 | 0.5 + qwertyu0400 | 0.5 + qwertyu0401 | 0.5 + qwertyu0402 | 0.5 + qwertyu0403 | 0.5 + qwertyu0404 | 0.5 + qwertyu0405 | 0.5 + qwertyu0406 | 0.5 + qwertyu0407 | 0.5 + qwertyu0408 | 0.5 + qwertyu0409 | 0.5 + qwertyu0410 | 0.5 + qwertyu0411 | 0.5 + qwertyu0412 | 0.5 + qwertyu0413 | 0.5 + qwertyu0414 | 0.5 + qwertyu0415 | 0.5 + qwertyu0416 | 0.5 + qwertyu0417 | 0.5 + qwertyu0418 | 0.5 + qwertyu0419 | 0.5 + qwertyu0420 | 0.5 + qwertyu0421 | 0.5 + qwertyu0422 | 0.5 + qwertyu0423 | 0.5 + qwertyu0424 | 0.5 + qwertyu0425 | 0.5 + qwertyu0426 | 0.5 + qwertyu0427 | 0.5 + qwertyu0428 | 0.5 + qwertyu0429 | 0.5 + qwertyu0430 | 0.5 + qwertyu0431 | 0.5 + qwertyu0432 | 0.5 + qwertyu0433 | 0.5 + qwertyu0434 | 0.5 + qwertyu0435 | 0.5 + qwertyu0436 | 0.5 + qwertyu0437 | 0.5 + qwertyu0438 | 0.5 + qwertyu0439 | 0.5 + qwertyu0440 | 0.5 + qwertyu0441 | 0.5 + qwertyu0442 | 0.5 + qwertyu0443 | 0.5 + qwertyu0444 | 0.5 + qwertyu0445 | 0.5 + qwertyu0446 | 0.5 + qwertyu0447 | 0.5 + qwertyu0448 | 0.5 + qwertyu0449 | 0.5 + qwertyu0450 | 0.5 + qwertyu0451 | 0.5 + qwertyu0452 | 0.5 + qwertyu0453 | 0.5 + qwertyu0454 | 0.5 + qwertyu0455 | 0.5 + qwertyu0456 | 0.5 + qwertyu0457 | 0.5 + qwertyu0458 | 0.5 + qwertyu0459 | 0.5 + qwertyu0460 | 0.5 + qwertyu0461 | 0.5 + qwertyu0462 | 0.5 + qwertyu0463 | 0.5 + qwertyu0464 | 0.5 + qwertyu0465 | 0.5 + qwertyu0466 | 0.5 + qwertyu0467 | 0.5 + qwertyu0468 | 0.5 + qwertyu0469 | 0.5 + qwertyu0470 | 0.5 + qwertyu0471 | 0.5 + qwertyu0472 | 0.5 + qwertyu0473 | 0.5 + qwertyu0474 | 0.5 + qwertyu0475 | 0.5 + qwertyu0476 | 0.5 + qwertyu0477 | 0.5 + qwertyu0478 | 0.5 + qwertyu0479 | 0.5 + qwertyu0480 | 0.5 + qwertyu0481 | 0.5 + qwertyu0482 | 0.5 + qwertyu0483 | 0.5 + qwertyu0484 | 0.5 + qwertyu0485 | 0.5 + qwertyu0486 | 0.5 + qwertyu0487 | 0.5 + qwertyu0489 | 0.5 + qwertyu0490 | 0.5 + qwertyu0491 | 0.5 + qwertyu0492 | 0.5 + qwertyu0493 | 0.5 + qwertyu0494 | 0.5 + qwertyu0495 | 0.5 + qwertyu0496 | 0.5 + qwertyu0497 | 0.5 + qwertyu0498 | 0.5 + qwertyu0499 | 0.5 + qwertyu0500 | 0.5 + qwertyu0501 | 0.5 + qwertyu0502 | 0.5 + qwertyu0503 | 0.5 + qwertyu0504 | 0.5 + qwertyu0505 | 0.5 + qwertyu0506 | 0.5 + qwertyu0507 | 0.5 + qwertyu0508 | 0.5 + qwertyu0509 | 0.5 + qwertyu0510 | 0.5 + qwertyu0511 | 0.5 + qwertyu0512 | 0.5 + qwertyu0513 | 0.5 + qwertyu0514 | 0.5 + qwertyu0515 | 0.5 + qwertyu0516 | 0.5 + qwertyu0517 | 0.5 + qwertyu0518 | 0.5 + qwertyu0519 | 0.5 + qwertyu0520 | 0.5 + qwertyu0521 | 0.5 + qwertyu0522 | 0.5 + qwertyu0523 | 0.5 + qwertyu0524 | 0.5 + qwertyu0525 | 0.5 + qwertyu0526 | 0.5 + qwertyu0527 | 0.5 + qwertyu0528 | 0.5 + qwertyu0529 | 0.5 + qwertyu0530 | 0.5 + qwertyu0531 | 0.5 + qwertyu0532 | 0.5 + qwertyu0533 | 0.5 + qwertyu0534 | 0.5 + qwertyu0535 | 0.5 + qwertyu0536 | 0.5 + qwertyu0537 | 0.5 + qwertyu0538 | 0.5 + qwertyu0539 | 0.5 + qwertyu0540 | 0.5 + qwertyu0541 | 0.5 + qwertyu0542 | 0.5 + qwertyu0543 | 0.5 + qwertyu0544 | 0.5 + qwertyu0545 | 0.5 + qwertyu0546 | 0.5 + qwertyu0547 | 0.5 + qwertyu0548 | 0.5 + qwertyu0549 | 0.5 + qwertyu0550 | 0.5 + qwertyu0551 | 0.5 + qwertyu0552 | 0.5 + qwertyu0553 | 0.5 + qwertyu0554 | 0.5 + qwertyu0555 | 0.5 + qwertyu0556 | 0.5 + qwertyu0557 | 0.5 + qwertyu0558 | 0.5 + qwertyu0559 | 0.5 + qwertyu0560 | 0.5 + qwertyu0561 | 0.5 + qwertyu0562 | 0.5 + qwertyu0563 | 0.5 + qwertyu0564 | 0.5 + qwertyu0565 | 0.5 + qwertyu0566 | 0.5 + qwertyu0567 | 0.5 + qwertyu0568 | 0.5 + qwertyu0569 | 0.5 + qwertyu0570 | 0.5 + qwertyu0571 | 0.5 + qwertyu0572 | 0.5 + qwertyu0573 | 0.5 + qwertyu0574 | 0.5 + qwertyu0575 | 0.5 + qwertyu0576 | 0.5 + qwertyu0577 | 0.5 + qwertyu0578 | 0.5 + qwertyu0579 | 0.5 + qwertyu0580 | 0.5 + qwertyu0581 | 0.5 + qwertyu0582 | 0.5 + qwertyu0583 | 0.5 + qwertyu0584 | 0.5 + qwertyu0585 | 0.5 + qwertyu0586 | 0.5 + qwertyu0587 | 0.5 + qwertyu0589 | 0.5 + qwertyu0590 | 0.5 + qwertyu0591 | 0.5 + qwertyu0592 | 0.5 + qwertyu0593 | 0.5 + qwertyu0594 | 0.5 + qwertyu0595 | 0.5 + qwertyu0596 | 0.5 + qwertyu0597 | 0.5 + qwertyu0598 | 0.5 + qwertyu0599 | 0.5 + qwertyu0600 | 0.5 + qwertyu0601 | 0.5 + qwertyu0602 | 0.5 + qwertyu0603 | 0.5 + qwertyu0604 | 0.5 + qwertyu0605 | 0.5 + qwertyu0606 | 0.5 + qwertyu0607 | 0.5 + qwertyu0608 | 0.5 + qwertyu0609 | 0.5 + qwertyu0610 | 0.5 + qwertyu0611 | 0.5 + qwertyu0612 | 0.5 + qwertyu0613 | 0.5 + qwertyu0614 | 0.5 + qwertyu0615 | 0.5 + qwertyu0616 | 0.5 + qwertyu0617 | 0.5 + qwertyu0618 | 0.5 + qwertyu0619 | 0.5 + qwertyu0620 | 0.5 + qwertyu0621 | 0.5 + qwertyu0622 | 0.5 + qwertyu0623 | 0.5 + qwertyu0624 | 0.5 + qwertyu0625 | 0.5 + qwertyu0626 | 0.5 + qwertyu0627 | 0.5 + qwertyu0628 | 0.5 + qwertyu0629 | 0.5 + qwertyu0630 | 0.5 + qwertyu0631 | 0.5 + qwertyu0632 | 0.5 + qwertyu0633 | 0.5 + qwertyu0634 | 0.5 + qwertyu0635 | 0.5 + qwertyu0636 | 0.5 + qwertyu0637 | 0.5 + qwertyu0638 | 0.5 + qwertyu0639 | 0.5 + qwertyu0640 | 0.5 + qwertyu0641 | 0.5 + qwertyu0642 | 0.5 + qwertyu0643 | 0.5 + qwertyu0644 | 0.5 + qwertyu0645 | 0.5 + qwertyu0646 | 0.5 + qwertyu0647 | 0.5 + qwertyu0648 | 0.5 + qwertyu0649 | 0.5 + qwertyu0650 | 0.5 + qwertyu0651 | 0.5 + qwertyu0652 | 0.5 + qwertyu0653 | 0.5 + qwertyu0654 | 0.5 + qwertyu0655 | 0.5 + qwertyu0656 | 0.5 + qwertyu0657 | 0.5 + qwertyu0658 | 0.5 + qwertyu0659 | 0.5 + qwertyu0660 | 0.5 + qwertyu0661 | 0.5 + qwertyu0662 | 0.5 + qwertyu0663 | 0.5 + qwertyu0664 | 0.5 + qwertyu0665 | 0.5 + qwertyu0666 | 0.5 + qwertyu0667 | 0.5 + qwertyu0668 | 0.5 + qwertyu0669 | 0.5 + qwertyu0670 | 0.5 + qwertyu0671 | 0.5 + qwertyu0672 | 0.5 + qwertyu0673 | 0.5 + qwertyu0674 | 0.5 + qwertyu0675 | 0.5 + qwertyu0676 | 0.5 + qwertyu0677 | 0.5 + qwertyu0678 | 0.5 + qwertyu0679 | 0.5 + qwertyu0680 | 0.5 + qwertyu0681 | 0.5 + qwertyu0682 | 0.5 + qwertyu0683 | 0.5 + qwertyu0684 | 0.5 + qwertyu0685 | 0.5 + qwertyu0686 | 0.5 + qwertyu0687 | 0.5 + qwertyu0689 | 0.5 + qwertyu0690 | 0.5 + qwertyu0691 | 0.5 + qwertyu0692 | 0.5 + qwertyu0693 | 0.5 + qwertyu0694 | 0.5 + qwertyu0695 | 0.5 + qwertyu0696 | 0.5 + qwertyu0697 | 0.5 + qwertyu0698 | 0.5 + qwertyu0699 | 0.5 + qwertyu0700 | 0.5 + qwertyu0701 | 0.5 + qwertyu0702 | 0.5 + qwertyu0703 | 0.5 + qwertyu0704 | 0.5 + qwertyu0705 | 0.5 + qwertyu0706 | 0.5 + qwertyu0707 | 0.5 + qwertyu0708 | 0.5 + qwertyu0709 | 0.5 + qwertyu0710 | 0.5 + qwertyu0711 | 0.5 + qwertyu0712 | 0.5 + qwertyu0713 | 0.5 + qwertyu0714 | 0.5 + qwertyu0715 | 0.5 + qwertyu0716 | 0.5 + qwertyu0717 | 0.5 + qwertyu0718 | 0.5 + qwertyu0719 | 0.5 + qwertyu0720 | 0.5 + qwertyu0721 | 0.5 + qwertyu0722 | 0.5 + qwertyu0723 | 0.5 + qwertyu0724 | 0.5 + qwertyu0725 | 0.5 + qwertyu0726 | 0.5 + qwertyu0727 | 0.5 + qwertyu0728 | 0.5 + qwertyu0729 | 0.5 + qwertyu0730 | 0.5 + qwertyu0731 | 0.5 + qwertyu0732 | 0.5 + qwertyu0733 | 0.5 + qwertyu0734 | 0.5 + qwertyu0735 | 0.5 + qwertyu0736 | 0.5 + qwertyu0737 | 0.5 + qwertyu0738 | 0.5 + qwertyu0739 | 0.5 + qwertyu0740 | 0.5 + qwertyu0741 | 0.5 + qwertyu0742 | 0.5 + qwertyu0743 | 0.5 + qwertyu0744 | 0.5 + qwertyu0745 | 0.5 + qwertyu0746 | 0.5 + qwertyu0747 | 0.5 + qwertyu0748 | 0.5 + qwertyu0749 | 0.5 + qwertyu0750 | 0.5 + qwertyu0751 | 0.5 + qwertyu0752 | 0.5 + qwertyu0753 | 0.5 + qwertyu0754 | 0.5 + qwertyu0755 | 0.5 + qwertyu0756 | 0.5 + qwertyu0757 | 0.5 + qwertyu0758 | 0.5 + qwertyu0759 | 0.5 + qwertyu0760 | 0.5 + qwertyu0761 | 0.5 + qwertyu0762 | 0.5 + qwertyu0763 | 0.5 + qwertyu0764 | 0.5 + qwertyu0765 | 0.5 + qwertyu0766 | 0.5 + qwertyu0767 | 0.5 + qwertyu0768 | 0.5 + qwertyu0769 | 0.5 + qwertyu0770 | 0.5 + qwertyu0771 | 0.5 + qwertyu0772 | 0.5 + qwertyu0773 | 0.5 + qwertyu0774 | 0.5 + qwertyu0775 | 0.5 + qwertyu0776 | 0.5 + qwertyu0777 | 0.5 + qwertyu0778 | 0.5 + qwertyu0779 | 0.5 + qwertyu0780 | 0.5 + qwertyu0781 | 0.5 + qwertyu0782 | 0.5 + qwertyu0783 | 0.5 + qwertyu0784 | 0.5 + qwertyu0785 | 0.5 + qwertyu0786 | 0.5 + qwertyu0787 | 0.5 + qwertyu0789 | 0.5 + qwertyu0790 | 0.5 + qwertyu0791 | 0.5 + qwertyu0792 | 0.5 + qwertyu0793 | 0.5 + qwertyu0794 | 0.5 + qwertyu0795 | 0.5 + qwertyu0796 | 0.5 + qwertyu0797 | 0.5 + qwertyu0798 | 0.5 + qwertyu0799 | 0.5 + qwertyu0800 | 0.5 + qwertyu0801 | 0.5 + qwertyu0802 | 0.5 + qwertyu0803 | 0.5 + qwertyu0804 | 0.5 + qwertyu0805 | 0.5 + qwertyu0806 | 0.5 + qwertyu0807 | 0.5 + qwertyu0808 | 0.5 + qwertyu0809 | 0.5 + qwertyu0810 | 0.5 + qwertyu0811 | 0.5 + qwertyu0812 | 0.5 + qwertyu0813 | 0.5 + qwertyu0814 | 0.5 + qwertyu0815 | 0.5 + qwertyu0816 | 0.5 + qwertyu0817 | 0.5 + qwertyu0818 | 0.5 + qwertyu0819 | 0.5 + qwertyu0820 | 0.5 + qwertyu0821 | 0.5 + qwertyu0822 | 0.5 + qwertyu0823 | 0.5 + qwertyu0824 | 0.5 + qwertyu0825 | 0.5 + qwertyu0826 | 0.5 + qwertyu0827 | 0.5 + qwertyu0828 | 0.5 + qwertyu0829 | 0.5 + qwertyu0830 | 0.5 + qwertyu0831 | 0.5 + qwertyu0832 | 0.5 + qwertyu0833 | 0.5 + qwertyu0834 | 0.5 + qwertyu0835 | 0.5 + qwertyu0836 | 0.5 + qwertyu0837 | 0.5 + qwertyu0838 | 0.5 + qwertyu0839 | 0.5 + qwertyu0840 | 0.5 + qwertyu0841 | 0.5 + qwertyu0842 | 0.5 + qwertyu0843 | 0.5 + qwertyu0844 | 0.5 + qwertyu0845 | 0.5 + qwertyu0846 | 0.5 + qwertyu0847 | 0.5 + qwertyu0848 | 0.5 + qwertyu0849 | 0.5 + qwertyu0850 | 0.5 + qwertyu0851 | 0.5 + qwertyu0852 | 0.5 + qwertyu0853 | 0.5 + qwertyu0854 | 0.5 + qwertyu0855 | 0.5 + qwertyu0856 | 0.5 + qwertyu0857 | 0.5 + qwertyu0858 | 0.5 + qwertyu0859 | 0.5 + qwertyu0860 | 0.5 + qwertyu0861 | 0.5 + qwertyu0862 | 0.5 + qwertyu0863 | 0.5 + qwertyu0864 | 0.5 + qwertyu0865 | 0.5 + qwertyu0866 | 0.5 + qwertyu0867 | 0.5 + qwertyu0868 | 0.5 + qwertyu0869 | 0.5 + qwertyu0870 | 0.5 + qwertyu0871 | 0.5 + qwertyu0872 | 0.5 + qwertyu0873 | 0.5 + qwertyu0874 | 0.5 + qwertyu0875 | 0.5 + qwertyu0876 | 0.5 + qwertyu0877 | 0.5 + qwertyu0878 | 0.5 + qwertyu0879 | 0.5 + qwertyu0880 | 0.5 + qwertyu0881 | 0.5 + qwertyu0882 | 0.5 + qwertyu0883 | 0.5 + qwertyu0884 | 0.5 + qwertyu0885 | 0.5 + qwertyu0886 | 0.5 + qwertyu0887 | 0.5 + qwertyu0889 | 0.5 + qwertyu0890 | 0.5 + qwertyu0891 | 0.5 + qwertyu0892 | 0.5 + qwertyu0893 | 0.5 + qwertyu0894 | 0.5 + qwertyu0895 | 0.5 + qwertyu0896 | 0.5 + qwertyu0897 | 0.5 + qwertyu0898 | 0.5 + qwertyu0899 | 0.5 + qwertyu1000 | 0.411765 +(1000 rows) + +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 0.6 + qwertyu0980 | 0.411765 + qwertyu0981 | 0.411765 + qwertyu0982 | 0.411765 + qwertyu0983 | 0.411765 + qwertyu0984 | 0.411765 + qwertyu0985 | 0.411765 + qwertyu0986 | 0.411765 + qwertyu0987 | 0.411765 + qwertyu0989 | 0.411765 + qwertyu0088 | 0.333333 + qwertyu0098 | 0.333333 + qwertyu0188 | 0.333333 + qwertyu0288 | 0.333333 + qwertyu0388 | 0.333333 + qwertyu0488 | 0.333333 + qwertyu0588 | 0.333333 + qwertyu0688 | 0.333333 + qwertyu0788 | 0.333333 + qwertyu0888 | 0.333333 + qwertyu0900 | 0.333333 + qwertyu0901 | 0.333333 + qwertyu0902 | 0.333333 + qwertyu0903 | 0.333333 + qwertyu0904 | 0.333333 + qwertyu0905 | 0.333333 + qwertyu0906 | 0.333333 + qwertyu0907 | 0.333333 + qwertyu0908 | 0.333333 + qwertyu0909 | 0.333333 + qwertyu0910 | 0.333333 + qwertyu0911 | 0.333333 + qwertyu0912 | 0.333333 + qwertyu0913 | 0.333333 + qwertyu0914 | 0.333333 + qwertyu0915 | 0.333333 + qwertyu0916 | 0.333333 + qwertyu0917 | 0.333333 + qwertyu0918 | 0.333333 + qwertyu0919 | 0.333333 + qwertyu0920 | 0.333333 + qwertyu0921 | 0.333333 + qwertyu0922 | 0.333333 + qwertyu0923 | 0.333333 + qwertyu0924 | 0.333333 + qwertyu0925 | 0.333333 + qwertyu0926 | 0.333333 + qwertyu0927 | 0.333333 + qwertyu0928 | 0.333333 + qwertyu0929 | 0.333333 + qwertyu0930 | 0.333333 + qwertyu0931 | 0.333333 + qwertyu0932 | 0.333333 + qwertyu0933 | 0.333333 + qwertyu0934 | 0.333333 + qwertyu0935 | 0.333333 + qwertyu0936 | 0.333333 + qwertyu0937 | 0.333333 + qwertyu0938 | 0.333333 + qwertyu0939 | 0.333333 + qwertyu0940 | 0.333333 + qwertyu0941 | 0.333333 + qwertyu0942 | 0.333333 + qwertyu0943 | 0.333333 + qwertyu0944 | 0.333333 + qwertyu0945 | 0.333333 + qwertyu0946 | 0.333333 + qwertyu0947 | 0.333333 + qwertyu0948 | 0.333333 + qwertyu0949 | 0.333333 + qwertyu0950 | 0.333333 + qwertyu0951 | 0.333333 + qwertyu0952 | 0.333333 + qwertyu0953 | 0.333333 + qwertyu0954 | 0.333333 + qwertyu0955 | 0.333333 + qwertyu0956 | 0.333333 + qwertyu0957 | 0.333333 + qwertyu0958 | 0.333333 + qwertyu0959 | 0.333333 + qwertyu0960 | 0.333333 + qwertyu0961 | 0.333333 + qwertyu0962 | 0.333333 + qwertyu0963 | 0.333333 + qwertyu0964 | 0.333333 + qwertyu0965 | 0.333333 + qwertyu0966 | 0.333333 + qwertyu0967 | 0.333333 + qwertyu0968 | 0.333333 + qwertyu0969 | 0.333333 + qwertyu0970 | 0.333333 + qwertyu0971 | 0.333333 + qwertyu0972 | 0.333333 + qwertyu0973 | 0.333333 + qwertyu0974 | 0.333333 + qwertyu0975 | 0.333333 + qwertyu0976 | 0.333333 + qwertyu0977 | 0.333333 + qwertyu0978 | 0.333333 + qwertyu0979 | 0.333333 + qwertyu0990 | 0.333333 + qwertyu0991 | 0.333333 + qwertyu0992 | 0.333333 + qwertyu0993 | 0.333333 + qwertyu0994 | 0.333333 + qwertyu0995 | 0.333333 + qwertyu0996 | 0.333333 + qwertyu0997 | 0.333333 + qwertyu0998 | 0.333333 + qwertyu0999 | 0.333333 +(110 rows) + +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 0.333333 +(1 row) + +explain (costs off) +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; + QUERY PLAN +--------------------------------------------------- + Limit + -> Index Scan using trgm_idx on test_trgm + Order By: (t <-> 'q0987wertyu0988'::text) +(3 rows) + +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; + ?column? | t +----------+------------- + 0.411765 | qwertyu0988 + 0.5 | qwertyu0987 +(2 rows) + +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; + count +------- + 1000 +(1 row) + +drop index trgm_idx; +create index trgm_idx on test_trgm using gist (t gist_trgm_ops(siglen=0)); +ERROR: value 0 out of bounds for option "siglen" +DETAIL: Valid values are between "1" and "2024". +create index trgm_idx on test_trgm using gist (t gist_trgm_ops(siglen=2025)); +ERROR: value 2025 out of bounds for option "siglen" +DETAIL: Valid values are between "1" and "2024". +create index trgm_idx on test_trgm using gist (t gist_trgm_ops(siglen=2024)); +set enable_seqscan=off; +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 1 + qwertyu0980 | 0.714286 + qwertyu0981 | 0.714286 + qwertyu0982 | 0.714286 + qwertyu0983 | 0.714286 + qwertyu0984 | 0.714286 + qwertyu0985 | 0.714286 + qwertyu0986 | 0.714286 + qwertyu0987 | 0.714286 + qwertyu0989 | 0.714286 + qwertyu0088 | 0.6 + qwertyu0098 | 0.6 + qwertyu0188 | 0.6 + qwertyu0288 | 0.6 + qwertyu0388 | 0.6 + qwertyu0488 | 0.6 + qwertyu0588 | 0.6 + qwertyu0688 | 0.6 + qwertyu0788 | 0.6 + qwertyu0888 | 0.6 + qwertyu0900 | 0.6 + qwertyu0901 | 0.6 + qwertyu0902 | 0.6 + qwertyu0903 | 0.6 + qwertyu0904 | 0.6 + qwertyu0905 | 0.6 + qwertyu0906 | 0.6 + qwertyu0907 | 0.6 + qwertyu0908 | 0.6 + qwertyu0909 | 0.6 + qwertyu0910 | 0.6 + qwertyu0911 | 0.6 + qwertyu0912 | 0.6 + qwertyu0913 | 0.6 + qwertyu0914 | 0.6 + qwertyu0915 | 0.6 + qwertyu0916 | 0.6 + qwertyu0917 | 0.6 + qwertyu0918 | 0.6 + qwertyu0919 | 0.6 + qwertyu0920 | 0.6 + qwertyu0921 | 0.6 + qwertyu0922 | 0.6 + qwertyu0923 | 0.6 + qwertyu0924 | 0.6 + qwertyu0925 | 0.6 + qwertyu0926 | 0.6 + qwertyu0927 | 0.6 + qwertyu0928 | 0.6 + qwertyu0929 | 0.6 + qwertyu0930 | 0.6 + qwertyu0931 | 0.6 + qwertyu0932 | 0.6 + qwertyu0933 | 0.6 + qwertyu0934 | 0.6 + qwertyu0935 | 0.6 + qwertyu0936 | 0.6 + qwertyu0937 | 0.6 + qwertyu0938 | 0.6 + qwertyu0939 | 0.6 + qwertyu0940 | 0.6 + qwertyu0941 | 0.6 + qwertyu0942 | 0.6 + qwertyu0943 | 0.6 + qwertyu0944 | 0.6 + qwertyu0945 | 0.6 + qwertyu0946 | 0.6 + qwertyu0947 | 0.6 + qwertyu0948 | 0.6 + qwertyu0949 | 0.6 + qwertyu0950 | 0.6 + qwertyu0951 | 0.6 + qwertyu0952 | 0.6 + qwertyu0953 | 0.6 + qwertyu0954 | 0.6 + qwertyu0955 | 0.6 + qwertyu0956 | 0.6 + qwertyu0957 | 0.6 + qwertyu0958 | 0.6 + qwertyu0959 | 0.6 + qwertyu0960 | 0.6 + qwertyu0961 | 0.6 + qwertyu0962 | 0.6 + qwertyu0963 | 0.6 + qwertyu0964 | 0.6 + qwertyu0965 | 0.6 + qwertyu0966 | 0.6 + qwertyu0967 | 0.6 + qwertyu0968 | 0.6 + qwertyu0969 | 0.6 + qwertyu0970 | 0.6 + qwertyu0971 | 0.6 + qwertyu0972 | 0.6 + qwertyu0973 | 0.6 + qwertyu0974 | 0.6 + qwertyu0975 | 0.6 + qwertyu0976 | 0.6 + qwertyu0977 | 0.6 + qwertyu0978 | 0.6 + qwertyu0979 | 0.6 + qwertyu0990 | 0.6 + qwertyu0991 | 0.6 + qwertyu0992 | 0.6 + qwertyu0993 | 0.6 + qwertyu0994 | 0.6 + qwertyu0995 | 0.6 + qwertyu0996 | 0.6 + qwertyu0997 | 0.6 + qwertyu0998 | 0.6 + qwertyu0999 | 0.6 + qwertyu0001 | 0.5 + qwertyu0002 | 0.5 + qwertyu0003 | 0.5 + qwertyu0004 | 0.5 + qwertyu0005 | 0.5 + qwertyu0006 | 0.5 + qwertyu0007 | 0.5 + qwertyu0008 | 0.5 + qwertyu0009 | 0.5 + qwertyu0010 | 0.5 + qwertyu0011 | 0.5 + qwertyu0012 | 0.5 + qwertyu0013 | 0.5 + qwertyu0014 | 0.5 + qwertyu0015 | 0.5 + qwertyu0016 | 0.5 + qwertyu0017 | 0.5 + qwertyu0018 | 0.5 + qwertyu0019 | 0.5 + qwertyu0020 | 0.5 + qwertyu0021 | 0.5 + qwertyu0022 | 0.5 + qwertyu0023 | 0.5 + qwertyu0024 | 0.5 + qwertyu0025 | 0.5 + qwertyu0026 | 0.5 + qwertyu0027 | 0.5 + qwertyu0028 | 0.5 + qwertyu0029 | 0.5 + qwertyu0030 | 0.5 + qwertyu0031 | 0.5 + qwertyu0032 | 0.5 + qwertyu0033 | 0.5 + qwertyu0034 | 0.5 + qwertyu0035 | 0.5 + qwertyu0036 | 0.5 + qwertyu0037 | 0.5 + qwertyu0038 | 0.5 + qwertyu0039 | 0.5 + qwertyu0040 | 0.5 + qwertyu0041 | 0.5 + qwertyu0042 | 0.5 + qwertyu0043 | 0.5 + qwertyu0044 | 0.5 + qwertyu0045 | 0.5 + qwertyu0046 | 0.5 + qwertyu0047 | 0.5 + qwertyu0048 | 0.5 + qwertyu0049 | 0.5 + qwertyu0050 | 0.5 + qwertyu0051 | 0.5 + qwertyu0052 | 0.5 + qwertyu0053 | 0.5 + qwertyu0054 | 0.5 + qwertyu0055 | 0.5 + qwertyu0056 | 0.5 + qwertyu0057 | 0.5 + qwertyu0058 | 0.5 + qwertyu0059 | 0.5 + qwertyu0060 | 0.5 + qwertyu0061 | 0.5 + qwertyu0062 | 0.5 + qwertyu0063 | 0.5 + qwertyu0064 | 0.5 + qwertyu0065 | 0.5 + qwertyu0066 | 0.5 + qwertyu0067 | 0.5 + qwertyu0068 | 0.5 + qwertyu0069 | 0.5 + qwertyu0070 | 0.5 + qwertyu0071 | 0.5 + qwertyu0072 | 0.5 + qwertyu0073 | 0.5 + qwertyu0074 | 0.5 + qwertyu0075 | 0.5 + qwertyu0076 | 0.5 + qwertyu0077 | 0.5 + qwertyu0078 | 0.5 + qwertyu0079 | 0.5 + qwertyu0080 | 0.5 + qwertyu0081 | 0.5 + qwertyu0082 | 0.5 + qwertyu0083 | 0.5 + qwertyu0084 | 0.5 + qwertyu0085 | 0.5 + qwertyu0086 | 0.5 + qwertyu0087 | 0.5 + qwertyu0089 | 0.5 + qwertyu0090 | 0.5 + qwertyu0091 | 0.5 + qwertyu0092 | 0.5 + qwertyu0093 | 0.5 + qwertyu0094 | 0.5 + qwertyu0095 | 0.5 + qwertyu0096 | 0.5 + qwertyu0097 | 0.5 + qwertyu0099 | 0.5 + qwertyu0100 | 0.5 + qwertyu0101 | 0.5 + qwertyu0102 | 0.5 + qwertyu0103 | 0.5 + qwertyu0104 | 0.5 + qwertyu0105 | 0.5 + qwertyu0106 | 0.5 + qwertyu0107 | 0.5 + qwertyu0108 | 0.5 + qwertyu0109 | 0.5 + qwertyu0110 | 0.5 + qwertyu0111 | 0.5 + qwertyu0112 | 0.5 + qwertyu0113 | 0.5 + qwertyu0114 | 0.5 + qwertyu0115 | 0.5 + qwertyu0116 | 0.5 + qwertyu0117 | 0.5 + qwertyu0118 | 0.5 + qwertyu0119 | 0.5 + qwertyu0120 | 0.5 + qwertyu0121 | 0.5 + qwertyu0122 | 0.5 + qwertyu0123 | 0.5 + qwertyu0124 | 0.5 + qwertyu0125 | 0.5 + qwertyu0126 | 0.5 + qwertyu0127 | 0.5 + qwertyu0128 | 0.5 + qwertyu0129 | 0.5 + qwertyu0130 | 0.5 + qwertyu0131 | 0.5 + qwertyu0132 | 0.5 + qwertyu0133 | 0.5 + qwertyu0134 | 0.5 + qwertyu0135 | 0.5 + qwertyu0136 | 0.5 + qwertyu0137 | 0.5 + qwertyu0138 | 0.5 + qwertyu0139 | 0.5 + qwertyu0140 | 0.5 + qwertyu0141 | 0.5 + qwertyu0142 | 0.5 + qwertyu0143 | 0.5 + qwertyu0144 | 0.5 + qwertyu0145 | 0.5 + qwertyu0146 | 0.5 + qwertyu0147 | 0.5 + qwertyu0148 | 0.5 + qwertyu0149 | 0.5 + qwertyu0150 | 0.5 + qwertyu0151 | 0.5 + qwertyu0152 | 0.5 + qwertyu0153 | 0.5 + qwertyu0154 | 0.5 + qwertyu0155 | 0.5 + qwertyu0156 | 0.5 + qwertyu0157 | 0.5 + qwertyu0158 | 0.5 + qwertyu0159 | 0.5 + qwertyu0160 | 0.5 + qwertyu0161 | 0.5 + qwertyu0162 | 0.5 + qwertyu0163 | 0.5 + qwertyu0164 | 0.5 + qwertyu0165 | 0.5 + qwertyu0166 | 0.5 + qwertyu0167 | 0.5 + qwertyu0168 | 0.5 + qwertyu0169 | 0.5 + qwertyu0170 | 0.5 + qwertyu0171 | 0.5 + qwertyu0172 | 0.5 + qwertyu0173 | 0.5 + qwertyu0174 | 0.5 + qwertyu0175 | 0.5 + qwertyu0176 | 0.5 + qwertyu0177 | 0.5 + qwertyu0178 | 0.5 + qwertyu0179 | 0.5 + qwertyu0180 | 0.5 + qwertyu0181 | 0.5 + qwertyu0182 | 0.5 + qwertyu0183 | 0.5 + qwertyu0184 | 0.5 + qwertyu0185 | 0.5 + qwertyu0186 | 0.5 + qwertyu0187 | 0.5 + qwertyu0189 | 0.5 + qwertyu0190 | 0.5 + qwertyu0191 | 0.5 + qwertyu0192 | 0.5 + qwertyu0193 | 0.5 + qwertyu0194 | 0.5 + qwertyu0195 | 0.5 + qwertyu0196 | 0.5 + qwertyu0197 | 0.5 + qwertyu0198 | 0.5 + qwertyu0199 | 0.5 + qwertyu0200 | 0.5 + qwertyu0201 | 0.5 + qwertyu0202 | 0.5 + qwertyu0203 | 0.5 + qwertyu0204 | 0.5 + qwertyu0205 | 0.5 + qwertyu0206 | 0.5 + qwertyu0207 | 0.5 + qwertyu0208 | 0.5 + qwertyu0209 | 0.5 + qwertyu0210 | 0.5 + qwertyu0211 | 0.5 + qwertyu0212 | 0.5 + qwertyu0213 | 0.5 + qwertyu0214 | 0.5 + qwertyu0215 | 0.5 + qwertyu0216 | 0.5 + qwertyu0217 | 0.5 + qwertyu0218 | 0.5 + qwertyu0219 | 0.5 + qwertyu0220 | 0.5 + qwertyu0221 | 0.5 + qwertyu0222 | 0.5 + qwertyu0223 | 0.5 + qwertyu0224 | 0.5 + qwertyu0225 | 0.5 + qwertyu0226 | 0.5 + qwertyu0227 | 0.5 + qwertyu0228 | 0.5 + qwertyu0229 | 0.5 + qwertyu0230 | 0.5 + qwertyu0231 | 0.5 + qwertyu0232 | 0.5 + qwertyu0233 | 0.5 + qwertyu0234 | 0.5 + qwertyu0235 | 0.5 + qwertyu0236 | 0.5 + qwertyu0237 | 0.5 + qwertyu0238 | 0.5 + qwertyu0239 | 0.5 + qwertyu0240 | 0.5 + qwertyu0241 | 0.5 + qwertyu0242 | 0.5 + qwertyu0243 | 0.5 + qwertyu0244 | 0.5 + qwertyu0245 | 0.5 + qwertyu0246 | 0.5 + qwertyu0247 | 0.5 + qwertyu0248 | 0.5 + qwertyu0249 | 0.5 + qwertyu0250 | 0.5 + qwertyu0251 | 0.5 + qwertyu0252 | 0.5 + qwertyu0253 | 0.5 + qwertyu0254 | 0.5 + qwertyu0255 | 0.5 + qwertyu0256 | 0.5 + qwertyu0257 | 0.5 + qwertyu0258 | 0.5 + qwertyu0259 | 0.5 + qwertyu0260 | 0.5 + qwertyu0261 | 0.5 + qwertyu0262 | 0.5 + qwertyu0263 | 0.5 + qwertyu0264 | 0.5 + qwertyu0265 | 0.5 + qwertyu0266 | 0.5 + qwertyu0267 | 0.5 + qwertyu0268 | 0.5 + qwertyu0269 | 0.5 + qwertyu0270 | 0.5 + qwertyu0271 | 0.5 + qwertyu0272 | 0.5 + qwertyu0273 | 0.5 + qwertyu0274 | 0.5 + qwertyu0275 | 0.5 + qwertyu0276 | 0.5 + qwertyu0277 | 0.5 + qwertyu0278 | 0.5 + qwertyu0279 | 0.5 + qwertyu0280 | 0.5 + qwertyu0281 | 0.5 + qwertyu0282 | 0.5 + qwertyu0283 | 0.5 + qwertyu0284 | 0.5 + qwertyu0285 | 0.5 + qwertyu0286 | 0.5 + qwertyu0287 | 0.5 + qwertyu0289 | 0.5 + qwertyu0290 | 0.5 + qwertyu0291 | 0.5 + qwertyu0292 | 0.5 + qwertyu0293 | 0.5 + qwertyu0294 | 0.5 + qwertyu0295 | 0.5 + qwertyu0296 | 0.5 + qwertyu0297 | 0.5 + qwertyu0298 | 0.5 + qwertyu0299 | 0.5 + qwertyu0300 | 0.5 + qwertyu0301 | 0.5 + qwertyu0302 | 0.5 + qwertyu0303 | 0.5 + qwertyu0304 | 0.5 + qwertyu0305 | 0.5 + qwertyu0306 | 0.5 + qwertyu0307 | 0.5 + qwertyu0308 | 0.5 + qwertyu0309 | 0.5 + qwertyu0310 | 0.5 + qwertyu0311 | 0.5 + qwertyu0312 | 0.5 + qwertyu0313 | 0.5 + qwertyu0314 | 0.5 + qwertyu0315 | 0.5 + qwertyu0316 | 0.5 + qwertyu0317 | 0.5 + qwertyu0318 | 0.5 + qwertyu0319 | 0.5 + qwertyu0320 | 0.5 + qwertyu0321 | 0.5 + qwertyu0322 | 0.5 + qwertyu0323 | 0.5 + qwertyu0324 | 0.5 + qwertyu0325 | 0.5 + qwertyu0326 | 0.5 + qwertyu0327 | 0.5 + qwertyu0328 | 0.5 + qwertyu0329 | 0.5 + qwertyu0330 | 0.5 + qwertyu0331 | 0.5 + qwertyu0332 | 0.5 + qwertyu0333 | 0.5 + qwertyu0334 | 0.5 + qwertyu0335 | 0.5 + qwertyu0336 | 0.5 + qwertyu0337 | 0.5 + qwertyu0338 | 0.5 + qwertyu0339 | 0.5 + qwertyu0340 | 0.5 + qwertyu0341 | 0.5 + qwertyu0342 | 0.5 + qwertyu0343 | 0.5 + qwertyu0344 | 0.5 + qwertyu0345 | 0.5 + qwertyu0346 | 0.5 + qwertyu0347 | 0.5 + qwertyu0348 | 0.5 + qwertyu0349 | 0.5 + qwertyu0350 | 0.5 + qwertyu0351 | 0.5 + qwertyu0352 | 0.5 + qwertyu0353 | 0.5 + qwertyu0354 | 0.5 + qwertyu0355 | 0.5 + qwertyu0356 | 0.5 + qwertyu0357 | 0.5 + qwertyu0358 | 0.5 + qwertyu0359 | 0.5 + qwertyu0360 | 0.5 + qwertyu0361 | 0.5 + qwertyu0362 | 0.5 + qwertyu0363 | 0.5 + qwertyu0364 | 0.5 + qwertyu0365 | 0.5 + qwertyu0366 | 0.5 + qwertyu0367 | 0.5 + qwertyu0368 | 0.5 + qwertyu0369 | 0.5 + qwertyu0370 | 0.5 + qwertyu0371 | 0.5 + qwertyu0372 | 0.5 + qwertyu0373 | 0.5 + qwertyu0374 | 0.5 + qwertyu0375 | 0.5 + qwertyu0376 | 0.5 + qwertyu0377 | 0.5 + qwertyu0378 | 0.5 + qwertyu0379 | 0.5 + qwertyu0380 | 0.5 + qwertyu0381 | 0.5 + qwertyu0382 | 0.5 + qwertyu0383 | 0.5 + qwertyu0384 | 0.5 + qwertyu0385 | 0.5 + qwertyu0386 | 0.5 + qwertyu0387 | 0.5 + qwertyu0389 | 0.5 + qwertyu0390 | 0.5 + qwertyu0391 | 0.5 + qwertyu0392 | 0.5 + qwertyu0393 | 0.5 + qwertyu0394 | 0.5 + qwertyu0395 | 0.5 + qwertyu0396 | 0.5 + qwertyu0397 | 0.5 + qwertyu0398 | 0.5 + qwertyu0399 | 0.5 + qwertyu0400 | 0.5 + qwertyu0401 | 0.5 + qwertyu0402 | 0.5 + qwertyu0403 | 0.5 + qwertyu0404 | 0.5 + qwertyu0405 | 0.5 + qwertyu0406 | 0.5 + qwertyu0407 | 0.5 + qwertyu0408 | 0.5 + qwertyu0409 | 0.5 + qwertyu0410 | 0.5 + qwertyu0411 | 0.5 + qwertyu0412 | 0.5 + qwertyu0413 | 0.5 + qwertyu0414 | 0.5 + qwertyu0415 | 0.5 + qwertyu0416 | 0.5 + qwertyu0417 | 0.5 + qwertyu0418 | 0.5 + qwertyu0419 | 0.5 + qwertyu0420 | 0.5 + qwertyu0421 | 0.5 + qwertyu0422 | 0.5 + qwertyu0423 | 0.5 + qwertyu0424 | 0.5 + qwertyu0425 | 0.5 + qwertyu0426 | 0.5 + qwertyu0427 | 0.5 + qwertyu0428 | 0.5 + qwertyu0429 | 0.5 + qwertyu0430 | 0.5 + qwertyu0431 | 0.5 + qwertyu0432 | 0.5 + qwertyu0433 | 0.5 + qwertyu0434 | 0.5 + qwertyu0435 | 0.5 + qwertyu0436 | 0.5 + qwertyu0437 | 0.5 + qwertyu0438 | 0.5 + qwertyu0439 | 0.5 + qwertyu0440 | 0.5 + qwertyu0441 | 0.5 + qwertyu0442 | 0.5 + qwertyu0443 | 0.5 + qwertyu0444 | 0.5 + qwertyu0445 | 0.5 + qwertyu0446 | 0.5 + qwertyu0447 | 0.5 + qwertyu0448 | 0.5 + qwertyu0449 | 0.5 + qwertyu0450 | 0.5 + qwertyu0451 | 0.5 + qwertyu0452 | 0.5 + qwertyu0453 | 0.5 + qwertyu0454 | 0.5 + qwertyu0455 | 0.5 + qwertyu0456 | 0.5 + qwertyu0457 | 0.5 + qwertyu0458 | 0.5 + qwertyu0459 | 0.5 + qwertyu0460 | 0.5 + qwertyu0461 | 0.5 + qwertyu0462 | 0.5 + qwertyu0463 | 0.5 + qwertyu0464 | 0.5 + qwertyu0465 | 0.5 + qwertyu0466 | 0.5 + qwertyu0467 | 0.5 + qwertyu0468 | 0.5 + qwertyu0469 | 0.5 + qwertyu0470 | 0.5 + qwertyu0471 | 0.5 + qwertyu0472 | 0.5 + qwertyu0473 | 0.5 + qwertyu0474 | 0.5 + qwertyu0475 | 0.5 + qwertyu0476 | 0.5 + qwertyu0477 | 0.5 + qwertyu0478 | 0.5 + qwertyu0479 | 0.5 + qwertyu0480 | 0.5 + qwertyu0481 | 0.5 + qwertyu0482 | 0.5 + qwertyu0483 | 0.5 + qwertyu0484 | 0.5 + qwertyu0485 | 0.5 + qwertyu0486 | 0.5 + qwertyu0487 | 0.5 + qwertyu0489 | 0.5 + qwertyu0490 | 0.5 + qwertyu0491 | 0.5 + qwertyu0492 | 0.5 + qwertyu0493 | 0.5 + qwertyu0494 | 0.5 + qwertyu0495 | 0.5 + qwertyu0496 | 0.5 + qwertyu0497 | 0.5 + qwertyu0498 | 0.5 + qwertyu0499 | 0.5 + qwertyu0500 | 0.5 + qwertyu0501 | 0.5 + qwertyu0502 | 0.5 + qwertyu0503 | 0.5 + qwertyu0504 | 0.5 + qwertyu0505 | 0.5 + qwertyu0506 | 0.5 + qwertyu0507 | 0.5 + qwertyu0508 | 0.5 + qwertyu0509 | 0.5 + qwertyu0510 | 0.5 + qwertyu0511 | 0.5 + qwertyu0512 | 0.5 + qwertyu0513 | 0.5 + qwertyu0514 | 0.5 + qwertyu0515 | 0.5 + qwertyu0516 | 0.5 + qwertyu0517 | 0.5 + qwertyu0518 | 0.5 + qwertyu0519 | 0.5 + qwertyu0520 | 0.5 + qwertyu0521 | 0.5 + qwertyu0522 | 0.5 + qwertyu0523 | 0.5 + qwertyu0524 | 0.5 + qwertyu0525 | 0.5 + qwertyu0526 | 0.5 + qwertyu0527 | 0.5 + qwertyu0528 | 0.5 + qwertyu0529 | 0.5 + qwertyu0530 | 0.5 + qwertyu0531 | 0.5 + qwertyu0532 | 0.5 + qwertyu0533 | 0.5 + qwertyu0534 | 0.5 + qwertyu0535 | 0.5 + qwertyu0536 | 0.5 + qwertyu0537 | 0.5 + qwertyu0538 | 0.5 + qwertyu0539 | 0.5 + qwertyu0540 | 0.5 + qwertyu0541 | 0.5 + qwertyu0542 | 0.5 + qwertyu0543 | 0.5 + qwertyu0544 | 0.5 + qwertyu0545 | 0.5 + qwertyu0546 | 0.5 + qwertyu0547 | 0.5 + qwertyu0548 | 0.5 + qwertyu0549 | 0.5 + qwertyu0550 | 0.5 + qwertyu0551 | 0.5 + qwertyu0552 | 0.5 + qwertyu0553 | 0.5 + qwertyu0554 | 0.5 + qwertyu0555 | 0.5 + qwertyu0556 | 0.5 + qwertyu0557 | 0.5 + qwertyu0558 | 0.5 + qwertyu0559 | 0.5 + qwertyu0560 | 0.5 + qwertyu0561 | 0.5 + qwertyu0562 | 0.5 + qwertyu0563 | 0.5 + qwertyu0564 | 0.5 + qwertyu0565 | 0.5 + qwertyu0566 | 0.5 + qwertyu0567 | 0.5 + qwertyu0568 | 0.5 + qwertyu0569 | 0.5 + qwertyu0570 | 0.5 + qwertyu0571 | 0.5 + qwertyu0572 | 0.5 + qwertyu0573 | 0.5 + qwertyu0574 | 0.5 + qwertyu0575 | 0.5 + qwertyu0576 | 0.5 + qwertyu0577 | 0.5 + qwertyu0578 | 0.5 + qwertyu0579 | 0.5 + qwertyu0580 | 0.5 + qwertyu0581 | 0.5 + qwertyu0582 | 0.5 + qwertyu0583 | 0.5 + qwertyu0584 | 0.5 + qwertyu0585 | 0.5 + qwertyu0586 | 0.5 + qwertyu0587 | 0.5 + qwertyu0589 | 0.5 + qwertyu0590 | 0.5 + qwertyu0591 | 0.5 + qwertyu0592 | 0.5 + qwertyu0593 | 0.5 + qwertyu0594 | 0.5 + qwertyu0595 | 0.5 + qwertyu0596 | 0.5 + qwertyu0597 | 0.5 + qwertyu0598 | 0.5 + qwertyu0599 | 0.5 + qwertyu0600 | 0.5 + qwertyu0601 | 0.5 + qwertyu0602 | 0.5 + qwertyu0603 | 0.5 + qwertyu0604 | 0.5 + qwertyu0605 | 0.5 + qwertyu0606 | 0.5 + qwertyu0607 | 0.5 + qwertyu0608 | 0.5 + qwertyu0609 | 0.5 + qwertyu0610 | 0.5 + qwertyu0611 | 0.5 + qwertyu0612 | 0.5 + qwertyu0613 | 0.5 + qwertyu0614 | 0.5 + qwertyu0615 | 0.5 + qwertyu0616 | 0.5 + qwertyu0617 | 0.5 + qwertyu0618 | 0.5 + qwertyu0619 | 0.5 + qwertyu0620 | 0.5 + qwertyu0621 | 0.5 + qwertyu0622 | 0.5 + qwertyu0623 | 0.5 + qwertyu0624 | 0.5 + qwertyu0625 | 0.5 + qwertyu0626 | 0.5 + qwertyu0627 | 0.5 + qwertyu0628 | 0.5 + qwertyu0629 | 0.5 + qwertyu0630 | 0.5 + qwertyu0631 | 0.5 + qwertyu0632 | 0.5 + qwertyu0633 | 0.5 + qwertyu0634 | 0.5 + qwertyu0635 | 0.5 + qwertyu0636 | 0.5 + qwertyu0637 | 0.5 + qwertyu0638 | 0.5 + qwertyu0639 | 0.5 + qwertyu0640 | 0.5 + qwertyu0641 | 0.5 + qwertyu0642 | 0.5 + qwertyu0643 | 0.5 + qwertyu0644 | 0.5 + qwertyu0645 | 0.5 + qwertyu0646 | 0.5 + qwertyu0647 | 0.5 + qwertyu0648 | 0.5 + qwertyu0649 | 0.5 + qwertyu0650 | 0.5 + qwertyu0651 | 0.5 + qwertyu0652 | 0.5 + qwertyu0653 | 0.5 + qwertyu0654 | 0.5 + qwertyu0655 | 0.5 + qwertyu0656 | 0.5 + qwertyu0657 | 0.5 + qwertyu0658 | 0.5 + qwertyu0659 | 0.5 + qwertyu0660 | 0.5 + qwertyu0661 | 0.5 + qwertyu0662 | 0.5 + qwertyu0663 | 0.5 + qwertyu0664 | 0.5 + qwertyu0665 | 0.5 + qwertyu0666 | 0.5 + qwertyu0667 | 0.5 + qwertyu0668 | 0.5 + qwertyu0669 | 0.5 + qwertyu0670 | 0.5 + qwertyu0671 | 0.5 + qwertyu0672 | 0.5 + qwertyu0673 | 0.5 + qwertyu0674 | 0.5 + qwertyu0675 | 0.5 + qwertyu0676 | 0.5 + qwertyu0677 | 0.5 + qwertyu0678 | 0.5 + qwertyu0679 | 0.5 + qwertyu0680 | 0.5 + qwertyu0681 | 0.5 + qwertyu0682 | 0.5 + qwertyu0683 | 0.5 + qwertyu0684 | 0.5 + qwertyu0685 | 0.5 + qwertyu0686 | 0.5 + qwertyu0687 | 0.5 + qwertyu0689 | 0.5 + qwertyu0690 | 0.5 + qwertyu0691 | 0.5 + qwertyu0692 | 0.5 + qwertyu0693 | 0.5 + qwertyu0694 | 0.5 + qwertyu0695 | 0.5 + qwertyu0696 | 0.5 + qwertyu0697 | 0.5 + qwertyu0698 | 0.5 + qwertyu0699 | 0.5 + qwertyu0700 | 0.5 + qwertyu0701 | 0.5 + qwertyu0702 | 0.5 + qwertyu0703 | 0.5 + qwertyu0704 | 0.5 + qwertyu0705 | 0.5 + qwertyu0706 | 0.5 + qwertyu0707 | 0.5 + qwertyu0708 | 0.5 + qwertyu0709 | 0.5 + qwertyu0710 | 0.5 + qwertyu0711 | 0.5 + qwertyu0712 | 0.5 + qwertyu0713 | 0.5 + qwertyu0714 | 0.5 + qwertyu0715 | 0.5 + qwertyu0716 | 0.5 + qwertyu0717 | 0.5 + qwertyu0718 | 0.5 + qwertyu0719 | 0.5 + qwertyu0720 | 0.5 + qwertyu0721 | 0.5 + qwertyu0722 | 0.5 + qwertyu0723 | 0.5 + qwertyu0724 | 0.5 + qwertyu0725 | 0.5 + qwertyu0726 | 0.5 + qwertyu0727 | 0.5 + qwertyu0728 | 0.5 + qwertyu0729 | 0.5 + qwertyu0730 | 0.5 + qwertyu0731 | 0.5 + qwertyu0732 | 0.5 + qwertyu0733 | 0.5 + qwertyu0734 | 0.5 + qwertyu0735 | 0.5 + qwertyu0736 | 0.5 + qwertyu0737 | 0.5 + qwertyu0738 | 0.5 + qwertyu0739 | 0.5 + qwertyu0740 | 0.5 + qwertyu0741 | 0.5 + qwertyu0742 | 0.5 + qwertyu0743 | 0.5 + qwertyu0744 | 0.5 + qwertyu0745 | 0.5 + qwertyu0746 | 0.5 + qwertyu0747 | 0.5 + qwertyu0748 | 0.5 + qwertyu0749 | 0.5 + qwertyu0750 | 0.5 + qwertyu0751 | 0.5 + qwertyu0752 | 0.5 + qwertyu0753 | 0.5 + qwertyu0754 | 0.5 + qwertyu0755 | 0.5 + qwertyu0756 | 0.5 + qwertyu0757 | 0.5 + qwertyu0758 | 0.5 + qwertyu0759 | 0.5 + qwertyu0760 | 0.5 + qwertyu0761 | 0.5 + qwertyu0762 | 0.5 + qwertyu0763 | 0.5 + qwertyu0764 | 0.5 + qwertyu0765 | 0.5 + qwertyu0766 | 0.5 + qwertyu0767 | 0.5 + qwertyu0768 | 0.5 + qwertyu0769 | 0.5 + qwertyu0770 | 0.5 + qwertyu0771 | 0.5 + qwertyu0772 | 0.5 + qwertyu0773 | 0.5 + qwertyu0774 | 0.5 + qwertyu0775 | 0.5 + qwertyu0776 | 0.5 + qwertyu0777 | 0.5 + qwertyu0778 | 0.5 + qwertyu0779 | 0.5 + qwertyu0780 | 0.5 + qwertyu0781 | 0.5 + qwertyu0782 | 0.5 + qwertyu0783 | 0.5 + qwertyu0784 | 0.5 + qwertyu0785 | 0.5 + qwertyu0786 | 0.5 + qwertyu0787 | 0.5 + qwertyu0789 | 0.5 + qwertyu0790 | 0.5 + qwertyu0791 | 0.5 + qwertyu0792 | 0.5 + qwertyu0793 | 0.5 + qwertyu0794 | 0.5 + qwertyu0795 | 0.5 + qwertyu0796 | 0.5 + qwertyu0797 | 0.5 + qwertyu0798 | 0.5 + qwertyu0799 | 0.5 + qwertyu0800 | 0.5 + qwertyu0801 | 0.5 + qwertyu0802 | 0.5 + qwertyu0803 | 0.5 + qwertyu0804 | 0.5 + qwertyu0805 | 0.5 + qwertyu0806 | 0.5 + qwertyu0807 | 0.5 + qwertyu0808 | 0.5 + qwertyu0809 | 0.5 + qwertyu0810 | 0.5 + qwertyu0811 | 0.5 + qwertyu0812 | 0.5 + qwertyu0813 | 0.5 + qwertyu0814 | 0.5 + qwertyu0815 | 0.5 + qwertyu0816 | 0.5 + qwertyu0817 | 0.5 + qwertyu0818 | 0.5 + qwertyu0819 | 0.5 + qwertyu0820 | 0.5 + qwertyu0821 | 0.5 + qwertyu0822 | 0.5 + qwertyu0823 | 0.5 + qwertyu0824 | 0.5 + qwertyu0825 | 0.5 + qwertyu0826 | 0.5 + qwertyu0827 | 0.5 + qwertyu0828 | 0.5 + qwertyu0829 | 0.5 + qwertyu0830 | 0.5 + qwertyu0831 | 0.5 + qwertyu0832 | 0.5 + qwertyu0833 | 0.5 + qwertyu0834 | 0.5 + qwertyu0835 | 0.5 + qwertyu0836 | 0.5 + qwertyu0837 | 0.5 + qwertyu0838 | 0.5 + qwertyu0839 | 0.5 + qwertyu0840 | 0.5 + qwertyu0841 | 0.5 + qwertyu0842 | 0.5 + qwertyu0843 | 0.5 + qwertyu0844 | 0.5 + qwertyu0845 | 0.5 + qwertyu0846 | 0.5 + qwertyu0847 | 0.5 + qwertyu0848 | 0.5 + qwertyu0849 | 0.5 + qwertyu0850 | 0.5 + qwertyu0851 | 0.5 + qwertyu0852 | 0.5 + qwertyu0853 | 0.5 + qwertyu0854 | 0.5 + qwertyu0855 | 0.5 + qwertyu0856 | 0.5 + qwertyu0857 | 0.5 + qwertyu0858 | 0.5 + qwertyu0859 | 0.5 + qwertyu0860 | 0.5 + qwertyu0861 | 0.5 + qwertyu0862 | 0.5 + qwertyu0863 | 0.5 + qwertyu0864 | 0.5 + qwertyu0865 | 0.5 + qwertyu0866 | 0.5 + qwertyu0867 | 0.5 + qwertyu0868 | 0.5 + qwertyu0869 | 0.5 + qwertyu0870 | 0.5 + qwertyu0871 | 0.5 + qwertyu0872 | 0.5 + qwertyu0873 | 0.5 + qwertyu0874 | 0.5 + qwertyu0875 | 0.5 + qwertyu0876 | 0.5 + qwertyu0877 | 0.5 + qwertyu0878 | 0.5 + qwertyu0879 | 0.5 + qwertyu0880 | 0.5 + qwertyu0881 | 0.5 + qwertyu0882 | 0.5 + qwertyu0883 | 0.5 + qwertyu0884 | 0.5 + qwertyu0885 | 0.5 + qwertyu0886 | 0.5 + qwertyu0887 | 0.5 + qwertyu0889 | 0.5 + qwertyu0890 | 0.5 + qwertyu0891 | 0.5 + qwertyu0892 | 0.5 + qwertyu0893 | 0.5 + qwertyu0894 | 0.5 + qwertyu0895 | 0.5 + qwertyu0896 | 0.5 + qwertyu0897 | 0.5 + qwertyu0898 | 0.5 + qwertyu0899 | 0.5 + qwertyu1000 | 0.411765 +(1000 rows) + +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 0.6 + qwertyu0980 | 0.411765 + qwertyu0981 | 0.411765 + qwertyu0982 | 0.411765 + qwertyu0983 | 0.411765 + qwertyu0984 | 0.411765 + qwertyu0985 | 0.411765 + qwertyu0986 | 0.411765 + qwertyu0987 | 0.411765 + qwertyu0989 | 0.411765 + qwertyu0088 | 0.333333 + qwertyu0098 | 0.333333 + qwertyu0188 | 0.333333 + qwertyu0288 | 0.333333 + qwertyu0388 | 0.333333 + qwertyu0488 | 0.333333 + qwertyu0588 | 0.333333 + qwertyu0688 | 0.333333 + qwertyu0788 | 0.333333 + qwertyu0888 | 0.333333 + qwertyu0900 | 0.333333 + qwertyu0901 | 0.333333 + qwertyu0902 | 0.333333 + qwertyu0903 | 0.333333 + qwertyu0904 | 0.333333 + qwertyu0905 | 0.333333 + qwertyu0906 | 0.333333 + qwertyu0907 | 0.333333 + qwertyu0908 | 0.333333 + qwertyu0909 | 0.333333 + qwertyu0910 | 0.333333 + qwertyu0911 | 0.333333 + qwertyu0912 | 0.333333 + qwertyu0913 | 0.333333 + qwertyu0914 | 0.333333 + qwertyu0915 | 0.333333 + qwertyu0916 | 0.333333 + qwertyu0917 | 0.333333 + qwertyu0918 | 0.333333 + qwertyu0919 | 0.333333 + qwertyu0920 | 0.333333 + qwertyu0921 | 0.333333 + qwertyu0922 | 0.333333 + qwertyu0923 | 0.333333 + qwertyu0924 | 0.333333 + qwertyu0925 | 0.333333 + qwertyu0926 | 0.333333 + qwertyu0927 | 0.333333 + qwertyu0928 | 0.333333 + qwertyu0929 | 0.333333 + qwertyu0930 | 0.333333 + qwertyu0931 | 0.333333 + qwertyu0932 | 0.333333 + qwertyu0933 | 0.333333 + qwertyu0934 | 0.333333 + qwertyu0935 | 0.333333 + qwertyu0936 | 0.333333 + qwertyu0937 | 0.333333 + qwertyu0938 | 0.333333 + qwertyu0939 | 0.333333 + qwertyu0940 | 0.333333 + qwertyu0941 | 0.333333 + qwertyu0942 | 0.333333 + qwertyu0943 | 0.333333 + qwertyu0944 | 0.333333 + qwertyu0945 | 0.333333 + qwertyu0946 | 0.333333 + qwertyu0947 | 0.333333 + qwertyu0948 | 0.333333 + qwertyu0949 | 0.333333 + qwertyu0950 | 0.333333 + qwertyu0951 | 0.333333 + qwertyu0952 | 0.333333 + qwertyu0953 | 0.333333 + qwertyu0954 | 0.333333 + qwertyu0955 | 0.333333 + qwertyu0956 | 0.333333 + qwertyu0957 | 0.333333 + qwertyu0958 | 0.333333 + qwertyu0959 | 0.333333 + qwertyu0960 | 0.333333 + qwertyu0961 | 0.333333 + qwertyu0962 | 0.333333 + qwertyu0963 | 0.333333 + qwertyu0964 | 0.333333 + qwertyu0965 | 0.333333 + qwertyu0966 | 0.333333 + qwertyu0967 | 0.333333 + qwertyu0968 | 0.333333 + qwertyu0969 | 0.333333 + qwertyu0970 | 0.333333 + qwertyu0971 | 0.333333 + qwertyu0972 | 0.333333 + qwertyu0973 | 0.333333 + qwertyu0974 | 0.333333 + qwertyu0975 | 0.333333 + qwertyu0976 | 0.333333 + qwertyu0977 | 0.333333 + qwertyu0978 | 0.333333 + qwertyu0979 | 0.333333 + qwertyu0990 | 0.333333 + qwertyu0991 | 0.333333 + qwertyu0992 | 0.333333 + qwertyu0993 | 0.333333 + qwertyu0994 | 0.333333 + qwertyu0995 | 0.333333 + qwertyu0996 | 0.333333 + qwertyu0997 | 0.333333 + qwertyu0998 | 0.333333 + qwertyu0999 | 0.333333 +(110 rows) + +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 0.333333 +(1 row) + +explain (costs off) +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; + QUERY PLAN +--------------------------------------------------- + Limit + -> Index Scan using trgm_idx on test_trgm + Order By: (t <-> 'q0987wertyu0988'::text) +(3 rows) + +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; + ?column? | t +----------+------------- + 0.411765 | qwertyu0988 + 0.5 | qwertyu0987 +(2 rows) + +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; + count +------- + 1000 +(1 row) + +drop index trgm_idx; +create index trgm_idx on test_trgm using gin (t gin_trgm_ops); +set enable_seqscan=off; +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 1 + qwertyu0980 | 0.714286 + qwertyu0981 | 0.714286 + qwertyu0982 | 0.714286 + qwertyu0983 | 0.714286 + qwertyu0984 | 0.714286 + qwertyu0985 | 0.714286 + qwertyu0986 | 0.714286 + qwertyu0987 | 0.714286 + qwertyu0989 | 0.714286 + qwertyu0088 | 0.6 + qwertyu0098 | 0.6 + qwertyu0188 | 0.6 + qwertyu0288 | 0.6 + qwertyu0388 | 0.6 + qwertyu0488 | 0.6 + qwertyu0588 | 0.6 + qwertyu0688 | 0.6 + qwertyu0788 | 0.6 + qwertyu0888 | 0.6 + qwertyu0900 | 0.6 + qwertyu0901 | 0.6 + qwertyu0902 | 0.6 + qwertyu0903 | 0.6 + qwertyu0904 | 0.6 + qwertyu0905 | 0.6 + qwertyu0906 | 0.6 + qwertyu0907 | 0.6 + qwertyu0908 | 0.6 + qwertyu0909 | 0.6 + qwertyu0910 | 0.6 + qwertyu0911 | 0.6 + qwertyu0912 | 0.6 + qwertyu0913 | 0.6 + qwertyu0914 | 0.6 + qwertyu0915 | 0.6 + qwertyu0916 | 0.6 + qwertyu0917 | 0.6 + qwertyu0918 | 0.6 + qwertyu0919 | 0.6 + qwertyu0920 | 0.6 + qwertyu0921 | 0.6 + qwertyu0922 | 0.6 + qwertyu0923 | 0.6 + qwertyu0924 | 0.6 + qwertyu0925 | 0.6 + qwertyu0926 | 0.6 + qwertyu0927 | 0.6 + qwertyu0928 | 0.6 + qwertyu0929 | 0.6 + qwertyu0930 | 0.6 + qwertyu0931 | 0.6 + qwertyu0932 | 0.6 + qwertyu0933 | 0.6 + qwertyu0934 | 0.6 + qwertyu0935 | 0.6 + qwertyu0936 | 0.6 + qwertyu0937 | 0.6 + qwertyu0938 | 0.6 + qwertyu0939 | 0.6 + qwertyu0940 | 0.6 + qwertyu0941 | 0.6 + qwertyu0942 | 0.6 + qwertyu0943 | 0.6 + qwertyu0944 | 0.6 + qwertyu0945 | 0.6 + qwertyu0946 | 0.6 + qwertyu0947 | 0.6 + qwertyu0948 | 0.6 + qwertyu0949 | 0.6 + qwertyu0950 | 0.6 + qwertyu0951 | 0.6 + qwertyu0952 | 0.6 + qwertyu0953 | 0.6 + qwertyu0954 | 0.6 + qwertyu0955 | 0.6 + qwertyu0956 | 0.6 + qwertyu0957 | 0.6 + qwertyu0958 | 0.6 + qwertyu0959 | 0.6 + qwertyu0960 | 0.6 + qwertyu0961 | 0.6 + qwertyu0962 | 0.6 + qwertyu0963 | 0.6 + qwertyu0964 | 0.6 + qwertyu0965 | 0.6 + qwertyu0966 | 0.6 + qwertyu0967 | 0.6 + qwertyu0968 | 0.6 + qwertyu0969 | 0.6 + qwertyu0970 | 0.6 + qwertyu0971 | 0.6 + qwertyu0972 | 0.6 + qwertyu0973 | 0.6 + qwertyu0974 | 0.6 + qwertyu0975 | 0.6 + qwertyu0976 | 0.6 + qwertyu0977 | 0.6 + qwertyu0978 | 0.6 + qwertyu0979 | 0.6 + qwertyu0990 | 0.6 + qwertyu0991 | 0.6 + qwertyu0992 | 0.6 + qwertyu0993 | 0.6 + qwertyu0994 | 0.6 + qwertyu0995 | 0.6 + qwertyu0996 | 0.6 + qwertyu0997 | 0.6 + qwertyu0998 | 0.6 + qwertyu0999 | 0.6 + qwertyu0001 | 0.5 + qwertyu0002 | 0.5 + qwertyu0003 | 0.5 + qwertyu0004 | 0.5 + qwertyu0005 | 0.5 + qwertyu0006 | 0.5 + qwertyu0007 | 0.5 + qwertyu0008 | 0.5 + qwertyu0009 | 0.5 + qwertyu0010 | 0.5 + qwertyu0011 | 0.5 + qwertyu0012 | 0.5 + qwertyu0013 | 0.5 + qwertyu0014 | 0.5 + qwertyu0015 | 0.5 + qwertyu0016 | 0.5 + qwertyu0017 | 0.5 + qwertyu0018 | 0.5 + qwertyu0019 | 0.5 + qwertyu0020 | 0.5 + qwertyu0021 | 0.5 + qwertyu0022 | 0.5 + qwertyu0023 | 0.5 + qwertyu0024 | 0.5 + qwertyu0025 | 0.5 + qwertyu0026 | 0.5 + qwertyu0027 | 0.5 + qwertyu0028 | 0.5 + qwertyu0029 | 0.5 + qwertyu0030 | 0.5 + qwertyu0031 | 0.5 + qwertyu0032 | 0.5 + qwertyu0033 | 0.5 + qwertyu0034 | 0.5 + qwertyu0035 | 0.5 + qwertyu0036 | 0.5 + qwertyu0037 | 0.5 + qwertyu0038 | 0.5 + qwertyu0039 | 0.5 + qwertyu0040 | 0.5 + qwertyu0041 | 0.5 + qwertyu0042 | 0.5 + qwertyu0043 | 0.5 + qwertyu0044 | 0.5 + qwertyu0045 | 0.5 + qwertyu0046 | 0.5 + qwertyu0047 | 0.5 + qwertyu0048 | 0.5 + qwertyu0049 | 0.5 + qwertyu0050 | 0.5 + qwertyu0051 | 0.5 + qwertyu0052 | 0.5 + qwertyu0053 | 0.5 + qwertyu0054 | 0.5 + qwertyu0055 | 0.5 + qwertyu0056 | 0.5 + qwertyu0057 | 0.5 + qwertyu0058 | 0.5 + qwertyu0059 | 0.5 + qwertyu0060 | 0.5 + qwertyu0061 | 0.5 + qwertyu0062 | 0.5 + qwertyu0063 | 0.5 + qwertyu0064 | 0.5 + qwertyu0065 | 0.5 + qwertyu0066 | 0.5 + qwertyu0067 | 0.5 + qwertyu0068 | 0.5 + qwertyu0069 | 0.5 + qwertyu0070 | 0.5 + qwertyu0071 | 0.5 + qwertyu0072 | 0.5 + qwertyu0073 | 0.5 + qwertyu0074 | 0.5 + qwertyu0075 | 0.5 + qwertyu0076 | 0.5 + qwertyu0077 | 0.5 + qwertyu0078 | 0.5 + qwertyu0079 | 0.5 + qwertyu0080 | 0.5 + qwertyu0081 | 0.5 + qwertyu0082 | 0.5 + qwertyu0083 | 0.5 + qwertyu0084 | 0.5 + qwertyu0085 | 0.5 + qwertyu0086 | 0.5 + qwertyu0087 | 0.5 + qwertyu0089 | 0.5 + qwertyu0090 | 0.5 + qwertyu0091 | 0.5 + qwertyu0092 | 0.5 + qwertyu0093 | 0.5 + qwertyu0094 | 0.5 + qwertyu0095 | 0.5 + qwertyu0096 | 0.5 + qwertyu0097 | 0.5 + qwertyu0099 | 0.5 + qwertyu0100 | 0.5 + qwertyu0101 | 0.5 + qwertyu0102 | 0.5 + qwertyu0103 | 0.5 + qwertyu0104 | 0.5 + qwertyu0105 | 0.5 + qwertyu0106 | 0.5 + qwertyu0107 | 0.5 + qwertyu0108 | 0.5 + qwertyu0109 | 0.5 + qwertyu0110 | 0.5 + qwertyu0111 | 0.5 + qwertyu0112 | 0.5 + qwertyu0113 | 0.5 + qwertyu0114 | 0.5 + qwertyu0115 | 0.5 + qwertyu0116 | 0.5 + qwertyu0117 | 0.5 + qwertyu0118 | 0.5 + qwertyu0119 | 0.5 + qwertyu0120 | 0.5 + qwertyu0121 | 0.5 + qwertyu0122 | 0.5 + qwertyu0123 | 0.5 + qwertyu0124 | 0.5 + qwertyu0125 | 0.5 + qwertyu0126 | 0.5 + qwertyu0127 | 0.5 + qwertyu0128 | 0.5 + qwertyu0129 | 0.5 + qwertyu0130 | 0.5 + qwertyu0131 | 0.5 + qwertyu0132 | 0.5 + qwertyu0133 | 0.5 + qwertyu0134 | 0.5 + qwertyu0135 | 0.5 + qwertyu0136 | 0.5 + qwertyu0137 | 0.5 + qwertyu0138 | 0.5 + qwertyu0139 | 0.5 + qwertyu0140 | 0.5 + qwertyu0141 | 0.5 + qwertyu0142 | 0.5 + qwertyu0143 | 0.5 + qwertyu0144 | 0.5 + qwertyu0145 | 0.5 + qwertyu0146 | 0.5 + qwertyu0147 | 0.5 + qwertyu0148 | 0.5 + qwertyu0149 | 0.5 + qwertyu0150 | 0.5 + qwertyu0151 | 0.5 + qwertyu0152 | 0.5 + qwertyu0153 | 0.5 + qwertyu0154 | 0.5 + qwertyu0155 | 0.5 + qwertyu0156 | 0.5 + qwertyu0157 | 0.5 + qwertyu0158 | 0.5 + qwertyu0159 | 0.5 + qwertyu0160 | 0.5 + qwertyu0161 | 0.5 + qwertyu0162 | 0.5 + qwertyu0163 | 0.5 + qwertyu0164 | 0.5 + qwertyu0165 | 0.5 + qwertyu0166 | 0.5 + qwertyu0167 | 0.5 + qwertyu0168 | 0.5 + qwertyu0169 | 0.5 + qwertyu0170 | 0.5 + qwertyu0171 | 0.5 + qwertyu0172 | 0.5 + qwertyu0173 | 0.5 + qwertyu0174 | 0.5 + qwertyu0175 | 0.5 + qwertyu0176 | 0.5 + qwertyu0177 | 0.5 + qwertyu0178 | 0.5 + qwertyu0179 | 0.5 + qwertyu0180 | 0.5 + qwertyu0181 | 0.5 + qwertyu0182 | 0.5 + qwertyu0183 | 0.5 + qwertyu0184 | 0.5 + qwertyu0185 | 0.5 + qwertyu0186 | 0.5 + qwertyu0187 | 0.5 + qwertyu0189 | 0.5 + qwertyu0190 | 0.5 + qwertyu0191 | 0.5 + qwertyu0192 | 0.5 + qwertyu0193 | 0.5 + qwertyu0194 | 0.5 + qwertyu0195 | 0.5 + qwertyu0196 | 0.5 + qwertyu0197 | 0.5 + qwertyu0198 | 0.5 + qwertyu0199 | 0.5 + qwertyu0200 | 0.5 + qwertyu0201 | 0.5 + qwertyu0202 | 0.5 + qwertyu0203 | 0.5 + qwertyu0204 | 0.5 + qwertyu0205 | 0.5 + qwertyu0206 | 0.5 + qwertyu0207 | 0.5 + qwertyu0208 | 0.5 + qwertyu0209 | 0.5 + qwertyu0210 | 0.5 + qwertyu0211 | 0.5 + qwertyu0212 | 0.5 + qwertyu0213 | 0.5 + qwertyu0214 | 0.5 + qwertyu0215 | 0.5 + qwertyu0216 | 0.5 + qwertyu0217 | 0.5 + qwertyu0218 | 0.5 + qwertyu0219 | 0.5 + qwertyu0220 | 0.5 + qwertyu0221 | 0.5 + qwertyu0222 | 0.5 + qwertyu0223 | 0.5 + qwertyu0224 | 0.5 + qwertyu0225 | 0.5 + qwertyu0226 | 0.5 + qwertyu0227 | 0.5 + qwertyu0228 | 0.5 + qwertyu0229 | 0.5 + qwertyu0230 | 0.5 + qwertyu0231 | 0.5 + qwertyu0232 | 0.5 + qwertyu0233 | 0.5 + qwertyu0234 | 0.5 + qwertyu0235 | 0.5 + qwertyu0236 | 0.5 + qwertyu0237 | 0.5 + qwertyu0238 | 0.5 + qwertyu0239 | 0.5 + qwertyu0240 | 0.5 + qwertyu0241 | 0.5 + qwertyu0242 | 0.5 + qwertyu0243 | 0.5 + qwertyu0244 | 0.5 + qwertyu0245 | 0.5 + qwertyu0246 | 0.5 + qwertyu0247 | 0.5 + qwertyu0248 | 0.5 + qwertyu0249 | 0.5 + qwertyu0250 | 0.5 + qwertyu0251 | 0.5 + qwertyu0252 | 0.5 + qwertyu0253 | 0.5 + qwertyu0254 | 0.5 + qwertyu0255 | 0.5 + qwertyu0256 | 0.5 + qwertyu0257 | 0.5 + qwertyu0258 | 0.5 + qwertyu0259 | 0.5 + qwertyu0260 | 0.5 + qwertyu0261 | 0.5 + qwertyu0262 | 0.5 + qwertyu0263 | 0.5 + qwertyu0264 | 0.5 + qwertyu0265 | 0.5 + qwertyu0266 | 0.5 + qwertyu0267 | 0.5 + qwertyu0268 | 0.5 + qwertyu0269 | 0.5 + qwertyu0270 | 0.5 + qwertyu0271 | 0.5 + qwertyu0272 | 0.5 + qwertyu0273 | 0.5 + qwertyu0274 | 0.5 + qwertyu0275 | 0.5 + qwertyu0276 | 0.5 + qwertyu0277 | 0.5 + qwertyu0278 | 0.5 + qwertyu0279 | 0.5 + qwertyu0280 | 0.5 + qwertyu0281 | 0.5 + qwertyu0282 | 0.5 + qwertyu0283 | 0.5 + qwertyu0284 | 0.5 + qwertyu0285 | 0.5 + qwertyu0286 | 0.5 + qwertyu0287 | 0.5 + qwertyu0289 | 0.5 + qwertyu0290 | 0.5 + qwertyu0291 | 0.5 + qwertyu0292 | 0.5 + qwertyu0293 | 0.5 + qwertyu0294 | 0.5 + qwertyu0295 | 0.5 + qwertyu0296 | 0.5 + qwertyu0297 | 0.5 + qwertyu0298 | 0.5 + qwertyu0299 | 0.5 + qwertyu0300 | 0.5 + qwertyu0301 | 0.5 + qwertyu0302 | 0.5 + qwertyu0303 | 0.5 + qwertyu0304 | 0.5 + qwertyu0305 | 0.5 + qwertyu0306 | 0.5 + qwertyu0307 | 0.5 + qwertyu0308 | 0.5 + qwertyu0309 | 0.5 + qwertyu0310 | 0.5 + qwertyu0311 | 0.5 + qwertyu0312 | 0.5 + qwertyu0313 | 0.5 + qwertyu0314 | 0.5 + qwertyu0315 | 0.5 + qwertyu0316 | 0.5 + qwertyu0317 | 0.5 + qwertyu0318 | 0.5 + qwertyu0319 | 0.5 + qwertyu0320 | 0.5 + qwertyu0321 | 0.5 + qwertyu0322 | 0.5 + qwertyu0323 | 0.5 + qwertyu0324 | 0.5 + qwertyu0325 | 0.5 + qwertyu0326 | 0.5 + qwertyu0327 | 0.5 + qwertyu0328 | 0.5 + qwertyu0329 | 0.5 + qwertyu0330 | 0.5 + qwertyu0331 | 0.5 + qwertyu0332 | 0.5 + qwertyu0333 | 0.5 + qwertyu0334 | 0.5 + qwertyu0335 | 0.5 + qwertyu0336 | 0.5 + qwertyu0337 | 0.5 + qwertyu0338 | 0.5 + qwertyu0339 | 0.5 + qwertyu0340 | 0.5 + qwertyu0341 | 0.5 + qwertyu0342 | 0.5 + qwertyu0343 | 0.5 + qwertyu0344 | 0.5 + qwertyu0345 | 0.5 + qwertyu0346 | 0.5 + qwertyu0347 | 0.5 + qwertyu0348 | 0.5 + qwertyu0349 | 0.5 + qwertyu0350 | 0.5 + qwertyu0351 | 0.5 + qwertyu0352 | 0.5 + qwertyu0353 | 0.5 + qwertyu0354 | 0.5 + qwertyu0355 | 0.5 + qwertyu0356 | 0.5 + qwertyu0357 | 0.5 + qwertyu0358 | 0.5 + qwertyu0359 | 0.5 + qwertyu0360 | 0.5 + qwertyu0361 | 0.5 + qwertyu0362 | 0.5 + qwertyu0363 | 0.5 + qwertyu0364 | 0.5 + qwertyu0365 | 0.5 + qwertyu0366 | 0.5 + qwertyu0367 | 0.5 + qwertyu0368 | 0.5 + qwertyu0369 | 0.5 + qwertyu0370 | 0.5 + qwertyu0371 | 0.5 + qwertyu0372 | 0.5 + qwertyu0373 | 0.5 + qwertyu0374 | 0.5 + qwertyu0375 | 0.5 + qwertyu0376 | 0.5 + qwertyu0377 | 0.5 + qwertyu0378 | 0.5 + qwertyu0379 | 0.5 + qwertyu0380 | 0.5 + qwertyu0381 | 0.5 + qwertyu0382 | 0.5 + qwertyu0383 | 0.5 + qwertyu0384 | 0.5 + qwertyu0385 | 0.5 + qwertyu0386 | 0.5 + qwertyu0387 | 0.5 + qwertyu0389 | 0.5 + qwertyu0390 | 0.5 + qwertyu0391 | 0.5 + qwertyu0392 | 0.5 + qwertyu0393 | 0.5 + qwertyu0394 | 0.5 + qwertyu0395 | 0.5 + qwertyu0396 | 0.5 + qwertyu0397 | 0.5 + qwertyu0398 | 0.5 + qwertyu0399 | 0.5 + qwertyu0400 | 0.5 + qwertyu0401 | 0.5 + qwertyu0402 | 0.5 + qwertyu0403 | 0.5 + qwertyu0404 | 0.5 + qwertyu0405 | 0.5 + qwertyu0406 | 0.5 + qwertyu0407 | 0.5 + qwertyu0408 | 0.5 + qwertyu0409 | 0.5 + qwertyu0410 | 0.5 + qwertyu0411 | 0.5 + qwertyu0412 | 0.5 + qwertyu0413 | 0.5 + qwertyu0414 | 0.5 + qwertyu0415 | 0.5 + qwertyu0416 | 0.5 + qwertyu0417 | 0.5 + qwertyu0418 | 0.5 + qwertyu0419 | 0.5 + qwertyu0420 | 0.5 + qwertyu0421 | 0.5 + qwertyu0422 | 0.5 + qwertyu0423 | 0.5 + qwertyu0424 | 0.5 + qwertyu0425 | 0.5 + qwertyu0426 | 0.5 + qwertyu0427 | 0.5 + qwertyu0428 | 0.5 + qwertyu0429 | 0.5 + qwertyu0430 | 0.5 + qwertyu0431 | 0.5 + qwertyu0432 | 0.5 + qwertyu0433 | 0.5 + qwertyu0434 | 0.5 + qwertyu0435 | 0.5 + qwertyu0436 | 0.5 + qwertyu0437 | 0.5 + qwertyu0438 | 0.5 + qwertyu0439 | 0.5 + qwertyu0440 | 0.5 + qwertyu0441 | 0.5 + qwertyu0442 | 0.5 + qwertyu0443 | 0.5 + qwertyu0444 | 0.5 + qwertyu0445 | 0.5 + qwertyu0446 | 0.5 + qwertyu0447 | 0.5 + qwertyu0448 | 0.5 + qwertyu0449 | 0.5 + qwertyu0450 | 0.5 + qwertyu0451 | 0.5 + qwertyu0452 | 0.5 + qwertyu0453 | 0.5 + qwertyu0454 | 0.5 + qwertyu0455 | 0.5 + qwertyu0456 | 0.5 + qwertyu0457 | 0.5 + qwertyu0458 | 0.5 + qwertyu0459 | 0.5 + qwertyu0460 | 0.5 + qwertyu0461 | 0.5 + qwertyu0462 | 0.5 + qwertyu0463 | 0.5 + qwertyu0464 | 0.5 + qwertyu0465 | 0.5 + qwertyu0466 | 0.5 + qwertyu0467 | 0.5 + qwertyu0468 | 0.5 + qwertyu0469 | 0.5 + qwertyu0470 | 0.5 + qwertyu0471 | 0.5 + qwertyu0472 | 0.5 + qwertyu0473 | 0.5 + qwertyu0474 | 0.5 + qwertyu0475 | 0.5 + qwertyu0476 | 0.5 + qwertyu0477 | 0.5 + qwertyu0478 | 0.5 + qwertyu0479 | 0.5 + qwertyu0480 | 0.5 + qwertyu0481 | 0.5 + qwertyu0482 | 0.5 + qwertyu0483 | 0.5 + qwertyu0484 | 0.5 + qwertyu0485 | 0.5 + qwertyu0486 | 0.5 + qwertyu0487 | 0.5 + qwertyu0489 | 0.5 + qwertyu0490 | 0.5 + qwertyu0491 | 0.5 + qwertyu0492 | 0.5 + qwertyu0493 | 0.5 + qwertyu0494 | 0.5 + qwertyu0495 | 0.5 + qwertyu0496 | 0.5 + qwertyu0497 | 0.5 + qwertyu0498 | 0.5 + qwertyu0499 | 0.5 + qwertyu0500 | 0.5 + qwertyu0501 | 0.5 + qwertyu0502 | 0.5 + qwertyu0503 | 0.5 + qwertyu0504 | 0.5 + qwertyu0505 | 0.5 + qwertyu0506 | 0.5 + qwertyu0507 | 0.5 + qwertyu0508 | 0.5 + qwertyu0509 | 0.5 + qwertyu0510 | 0.5 + qwertyu0511 | 0.5 + qwertyu0512 | 0.5 + qwertyu0513 | 0.5 + qwertyu0514 | 0.5 + qwertyu0515 | 0.5 + qwertyu0516 | 0.5 + qwertyu0517 | 0.5 + qwertyu0518 | 0.5 + qwertyu0519 | 0.5 + qwertyu0520 | 0.5 + qwertyu0521 | 0.5 + qwertyu0522 | 0.5 + qwertyu0523 | 0.5 + qwertyu0524 | 0.5 + qwertyu0525 | 0.5 + qwertyu0526 | 0.5 + qwertyu0527 | 0.5 + qwertyu0528 | 0.5 + qwertyu0529 | 0.5 + qwertyu0530 | 0.5 + qwertyu0531 | 0.5 + qwertyu0532 | 0.5 + qwertyu0533 | 0.5 + qwertyu0534 | 0.5 + qwertyu0535 | 0.5 + qwertyu0536 | 0.5 + qwertyu0537 | 0.5 + qwertyu0538 | 0.5 + qwertyu0539 | 0.5 + qwertyu0540 | 0.5 + qwertyu0541 | 0.5 + qwertyu0542 | 0.5 + qwertyu0543 | 0.5 + qwertyu0544 | 0.5 + qwertyu0545 | 0.5 + qwertyu0546 | 0.5 + qwertyu0547 | 0.5 + qwertyu0548 | 0.5 + qwertyu0549 | 0.5 + qwertyu0550 | 0.5 + qwertyu0551 | 0.5 + qwertyu0552 | 0.5 + qwertyu0553 | 0.5 + qwertyu0554 | 0.5 + qwertyu0555 | 0.5 + qwertyu0556 | 0.5 + qwertyu0557 | 0.5 + qwertyu0558 | 0.5 + qwertyu0559 | 0.5 + qwertyu0560 | 0.5 + qwertyu0561 | 0.5 + qwertyu0562 | 0.5 + qwertyu0563 | 0.5 + qwertyu0564 | 0.5 + qwertyu0565 | 0.5 + qwertyu0566 | 0.5 + qwertyu0567 | 0.5 + qwertyu0568 | 0.5 + qwertyu0569 | 0.5 + qwertyu0570 | 0.5 + qwertyu0571 | 0.5 + qwertyu0572 | 0.5 + qwertyu0573 | 0.5 + qwertyu0574 | 0.5 + qwertyu0575 | 0.5 + qwertyu0576 | 0.5 + qwertyu0577 | 0.5 + qwertyu0578 | 0.5 + qwertyu0579 | 0.5 + qwertyu0580 | 0.5 + qwertyu0581 | 0.5 + qwertyu0582 | 0.5 + qwertyu0583 | 0.5 + qwertyu0584 | 0.5 + qwertyu0585 | 0.5 + qwertyu0586 | 0.5 + qwertyu0587 | 0.5 + qwertyu0589 | 0.5 + qwertyu0590 | 0.5 + qwertyu0591 | 0.5 + qwertyu0592 | 0.5 + qwertyu0593 | 0.5 + qwertyu0594 | 0.5 + qwertyu0595 | 0.5 + qwertyu0596 | 0.5 + qwertyu0597 | 0.5 + qwertyu0598 | 0.5 + qwertyu0599 | 0.5 + qwertyu0600 | 0.5 + qwertyu0601 | 0.5 + qwertyu0602 | 0.5 + qwertyu0603 | 0.5 + qwertyu0604 | 0.5 + qwertyu0605 | 0.5 + qwertyu0606 | 0.5 + qwertyu0607 | 0.5 + qwertyu0608 | 0.5 + qwertyu0609 | 0.5 + qwertyu0610 | 0.5 + qwertyu0611 | 0.5 + qwertyu0612 | 0.5 + qwertyu0613 | 0.5 + qwertyu0614 | 0.5 + qwertyu0615 | 0.5 + qwertyu0616 | 0.5 + qwertyu0617 | 0.5 + qwertyu0618 | 0.5 + qwertyu0619 | 0.5 + qwertyu0620 | 0.5 + qwertyu0621 | 0.5 + qwertyu0622 | 0.5 + qwertyu0623 | 0.5 + qwertyu0624 | 0.5 + qwertyu0625 | 0.5 + qwertyu0626 | 0.5 + qwertyu0627 | 0.5 + qwertyu0628 | 0.5 + qwertyu0629 | 0.5 + qwertyu0630 | 0.5 + qwertyu0631 | 0.5 + qwertyu0632 | 0.5 + qwertyu0633 | 0.5 + qwertyu0634 | 0.5 + qwertyu0635 | 0.5 + qwertyu0636 | 0.5 + qwertyu0637 | 0.5 + qwertyu0638 | 0.5 + qwertyu0639 | 0.5 + qwertyu0640 | 0.5 + qwertyu0641 | 0.5 + qwertyu0642 | 0.5 + qwertyu0643 | 0.5 + qwertyu0644 | 0.5 + qwertyu0645 | 0.5 + qwertyu0646 | 0.5 + qwertyu0647 | 0.5 + qwertyu0648 | 0.5 + qwertyu0649 | 0.5 + qwertyu0650 | 0.5 + qwertyu0651 | 0.5 + qwertyu0652 | 0.5 + qwertyu0653 | 0.5 + qwertyu0654 | 0.5 + qwertyu0655 | 0.5 + qwertyu0656 | 0.5 + qwertyu0657 | 0.5 + qwertyu0658 | 0.5 + qwertyu0659 | 0.5 + qwertyu0660 | 0.5 + qwertyu0661 | 0.5 + qwertyu0662 | 0.5 + qwertyu0663 | 0.5 + qwertyu0664 | 0.5 + qwertyu0665 | 0.5 + qwertyu0666 | 0.5 + qwertyu0667 | 0.5 + qwertyu0668 | 0.5 + qwertyu0669 | 0.5 + qwertyu0670 | 0.5 + qwertyu0671 | 0.5 + qwertyu0672 | 0.5 + qwertyu0673 | 0.5 + qwertyu0674 | 0.5 + qwertyu0675 | 0.5 + qwertyu0676 | 0.5 + qwertyu0677 | 0.5 + qwertyu0678 | 0.5 + qwertyu0679 | 0.5 + qwertyu0680 | 0.5 + qwertyu0681 | 0.5 + qwertyu0682 | 0.5 + qwertyu0683 | 0.5 + qwertyu0684 | 0.5 + qwertyu0685 | 0.5 + qwertyu0686 | 0.5 + qwertyu0687 | 0.5 + qwertyu0689 | 0.5 + qwertyu0690 | 0.5 + qwertyu0691 | 0.5 + qwertyu0692 | 0.5 + qwertyu0693 | 0.5 + qwertyu0694 | 0.5 + qwertyu0695 | 0.5 + qwertyu0696 | 0.5 + qwertyu0697 | 0.5 + qwertyu0698 | 0.5 + qwertyu0699 | 0.5 + qwertyu0700 | 0.5 + qwertyu0701 | 0.5 + qwertyu0702 | 0.5 + qwertyu0703 | 0.5 + qwertyu0704 | 0.5 + qwertyu0705 | 0.5 + qwertyu0706 | 0.5 + qwertyu0707 | 0.5 + qwertyu0708 | 0.5 + qwertyu0709 | 0.5 + qwertyu0710 | 0.5 + qwertyu0711 | 0.5 + qwertyu0712 | 0.5 + qwertyu0713 | 0.5 + qwertyu0714 | 0.5 + qwertyu0715 | 0.5 + qwertyu0716 | 0.5 + qwertyu0717 | 0.5 + qwertyu0718 | 0.5 + qwertyu0719 | 0.5 + qwertyu0720 | 0.5 + qwertyu0721 | 0.5 + qwertyu0722 | 0.5 + qwertyu0723 | 0.5 + qwertyu0724 | 0.5 + qwertyu0725 | 0.5 + qwertyu0726 | 0.5 + qwertyu0727 | 0.5 + qwertyu0728 | 0.5 + qwertyu0729 | 0.5 + qwertyu0730 | 0.5 + qwertyu0731 | 0.5 + qwertyu0732 | 0.5 + qwertyu0733 | 0.5 + qwertyu0734 | 0.5 + qwertyu0735 | 0.5 + qwertyu0736 | 0.5 + qwertyu0737 | 0.5 + qwertyu0738 | 0.5 + qwertyu0739 | 0.5 + qwertyu0740 | 0.5 + qwertyu0741 | 0.5 + qwertyu0742 | 0.5 + qwertyu0743 | 0.5 + qwertyu0744 | 0.5 + qwertyu0745 | 0.5 + qwertyu0746 | 0.5 + qwertyu0747 | 0.5 + qwertyu0748 | 0.5 + qwertyu0749 | 0.5 + qwertyu0750 | 0.5 + qwertyu0751 | 0.5 + qwertyu0752 | 0.5 + qwertyu0753 | 0.5 + qwertyu0754 | 0.5 + qwertyu0755 | 0.5 + qwertyu0756 | 0.5 + qwertyu0757 | 0.5 + qwertyu0758 | 0.5 + qwertyu0759 | 0.5 + qwertyu0760 | 0.5 + qwertyu0761 | 0.5 + qwertyu0762 | 0.5 + qwertyu0763 | 0.5 + qwertyu0764 | 0.5 + qwertyu0765 | 0.5 + qwertyu0766 | 0.5 + qwertyu0767 | 0.5 + qwertyu0768 | 0.5 + qwertyu0769 | 0.5 + qwertyu0770 | 0.5 + qwertyu0771 | 0.5 + qwertyu0772 | 0.5 + qwertyu0773 | 0.5 + qwertyu0774 | 0.5 + qwertyu0775 | 0.5 + qwertyu0776 | 0.5 + qwertyu0777 | 0.5 + qwertyu0778 | 0.5 + qwertyu0779 | 0.5 + qwertyu0780 | 0.5 + qwertyu0781 | 0.5 + qwertyu0782 | 0.5 + qwertyu0783 | 0.5 + qwertyu0784 | 0.5 + qwertyu0785 | 0.5 + qwertyu0786 | 0.5 + qwertyu0787 | 0.5 + qwertyu0789 | 0.5 + qwertyu0790 | 0.5 + qwertyu0791 | 0.5 + qwertyu0792 | 0.5 + qwertyu0793 | 0.5 + qwertyu0794 | 0.5 + qwertyu0795 | 0.5 + qwertyu0796 | 0.5 + qwertyu0797 | 0.5 + qwertyu0798 | 0.5 + qwertyu0799 | 0.5 + qwertyu0800 | 0.5 + qwertyu0801 | 0.5 + qwertyu0802 | 0.5 + qwertyu0803 | 0.5 + qwertyu0804 | 0.5 + qwertyu0805 | 0.5 + qwertyu0806 | 0.5 + qwertyu0807 | 0.5 + qwertyu0808 | 0.5 + qwertyu0809 | 0.5 + qwertyu0810 | 0.5 + qwertyu0811 | 0.5 + qwertyu0812 | 0.5 + qwertyu0813 | 0.5 + qwertyu0814 | 0.5 + qwertyu0815 | 0.5 + qwertyu0816 | 0.5 + qwertyu0817 | 0.5 + qwertyu0818 | 0.5 + qwertyu0819 | 0.5 + qwertyu0820 | 0.5 + qwertyu0821 | 0.5 + qwertyu0822 | 0.5 + qwertyu0823 | 0.5 + qwertyu0824 | 0.5 + qwertyu0825 | 0.5 + qwertyu0826 | 0.5 + qwertyu0827 | 0.5 + qwertyu0828 | 0.5 + qwertyu0829 | 0.5 + qwertyu0830 | 0.5 + qwertyu0831 | 0.5 + qwertyu0832 | 0.5 + qwertyu0833 | 0.5 + qwertyu0834 | 0.5 + qwertyu0835 | 0.5 + qwertyu0836 | 0.5 + qwertyu0837 | 0.5 + qwertyu0838 | 0.5 + qwertyu0839 | 0.5 + qwertyu0840 | 0.5 + qwertyu0841 | 0.5 + qwertyu0842 | 0.5 + qwertyu0843 | 0.5 + qwertyu0844 | 0.5 + qwertyu0845 | 0.5 + qwertyu0846 | 0.5 + qwertyu0847 | 0.5 + qwertyu0848 | 0.5 + qwertyu0849 | 0.5 + qwertyu0850 | 0.5 + qwertyu0851 | 0.5 + qwertyu0852 | 0.5 + qwertyu0853 | 0.5 + qwertyu0854 | 0.5 + qwertyu0855 | 0.5 + qwertyu0856 | 0.5 + qwertyu0857 | 0.5 + qwertyu0858 | 0.5 + qwertyu0859 | 0.5 + qwertyu0860 | 0.5 + qwertyu0861 | 0.5 + qwertyu0862 | 0.5 + qwertyu0863 | 0.5 + qwertyu0864 | 0.5 + qwertyu0865 | 0.5 + qwertyu0866 | 0.5 + qwertyu0867 | 0.5 + qwertyu0868 | 0.5 + qwertyu0869 | 0.5 + qwertyu0870 | 0.5 + qwertyu0871 | 0.5 + qwertyu0872 | 0.5 + qwertyu0873 | 0.5 + qwertyu0874 | 0.5 + qwertyu0875 | 0.5 + qwertyu0876 | 0.5 + qwertyu0877 | 0.5 + qwertyu0878 | 0.5 + qwertyu0879 | 0.5 + qwertyu0880 | 0.5 + qwertyu0881 | 0.5 + qwertyu0882 | 0.5 + qwertyu0883 | 0.5 + qwertyu0884 | 0.5 + qwertyu0885 | 0.5 + qwertyu0886 | 0.5 + qwertyu0887 | 0.5 + qwertyu0889 | 0.5 + qwertyu0890 | 0.5 + qwertyu0891 | 0.5 + qwertyu0892 | 0.5 + qwertyu0893 | 0.5 + qwertyu0894 | 0.5 + qwertyu0895 | 0.5 + qwertyu0896 | 0.5 + qwertyu0897 | 0.5 + qwertyu0898 | 0.5 + qwertyu0899 | 0.5 + qwertyu1000 | 0.411765 +(1000 rows) + +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 0.6 + qwertyu0980 | 0.411765 + qwertyu0981 | 0.411765 + qwertyu0982 | 0.411765 + qwertyu0983 | 0.411765 + qwertyu0984 | 0.411765 + qwertyu0985 | 0.411765 + qwertyu0986 | 0.411765 + qwertyu0987 | 0.411765 + qwertyu0989 | 0.411765 + qwertyu0088 | 0.333333 + qwertyu0098 | 0.333333 + qwertyu0188 | 0.333333 + qwertyu0288 | 0.333333 + qwertyu0388 | 0.333333 + qwertyu0488 | 0.333333 + qwertyu0588 | 0.333333 + qwertyu0688 | 0.333333 + qwertyu0788 | 0.333333 + qwertyu0888 | 0.333333 + qwertyu0900 | 0.333333 + qwertyu0901 | 0.333333 + qwertyu0902 | 0.333333 + qwertyu0903 | 0.333333 + qwertyu0904 | 0.333333 + qwertyu0905 | 0.333333 + qwertyu0906 | 0.333333 + qwertyu0907 | 0.333333 + qwertyu0908 | 0.333333 + qwertyu0909 | 0.333333 + qwertyu0910 | 0.333333 + qwertyu0911 | 0.333333 + qwertyu0912 | 0.333333 + qwertyu0913 | 0.333333 + qwertyu0914 | 0.333333 + qwertyu0915 | 0.333333 + qwertyu0916 | 0.333333 + qwertyu0917 | 0.333333 + qwertyu0918 | 0.333333 + qwertyu0919 | 0.333333 + qwertyu0920 | 0.333333 + qwertyu0921 | 0.333333 + qwertyu0922 | 0.333333 + qwertyu0923 | 0.333333 + qwertyu0924 | 0.333333 + qwertyu0925 | 0.333333 + qwertyu0926 | 0.333333 + qwertyu0927 | 0.333333 + qwertyu0928 | 0.333333 + qwertyu0929 | 0.333333 + qwertyu0930 | 0.333333 + qwertyu0931 | 0.333333 + qwertyu0932 | 0.333333 + qwertyu0933 | 0.333333 + qwertyu0934 | 0.333333 + qwertyu0935 | 0.333333 + qwertyu0936 | 0.333333 + qwertyu0937 | 0.333333 + qwertyu0938 | 0.333333 + qwertyu0939 | 0.333333 + qwertyu0940 | 0.333333 + qwertyu0941 | 0.333333 + qwertyu0942 | 0.333333 + qwertyu0943 | 0.333333 + qwertyu0944 | 0.333333 + qwertyu0945 | 0.333333 + qwertyu0946 | 0.333333 + qwertyu0947 | 0.333333 + qwertyu0948 | 0.333333 + qwertyu0949 | 0.333333 + qwertyu0950 | 0.333333 + qwertyu0951 | 0.333333 + qwertyu0952 | 0.333333 + qwertyu0953 | 0.333333 + qwertyu0954 | 0.333333 + qwertyu0955 | 0.333333 + qwertyu0956 | 0.333333 + qwertyu0957 | 0.333333 + qwertyu0958 | 0.333333 + qwertyu0959 | 0.333333 + qwertyu0960 | 0.333333 + qwertyu0961 | 0.333333 + qwertyu0962 | 0.333333 + qwertyu0963 | 0.333333 + qwertyu0964 | 0.333333 + qwertyu0965 | 0.333333 + qwertyu0966 | 0.333333 + qwertyu0967 | 0.333333 + qwertyu0968 | 0.333333 + qwertyu0969 | 0.333333 + qwertyu0970 | 0.333333 + qwertyu0971 | 0.333333 + qwertyu0972 | 0.333333 + qwertyu0973 | 0.333333 + qwertyu0974 | 0.333333 + qwertyu0975 | 0.333333 + qwertyu0976 | 0.333333 + qwertyu0977 | 0.333333 + qwertyu0978 | 0.333333 + qwertyu0979 | 0.333333 + qwertyu0990 | 0.333333 + qwertyu0991 | 0.333333 + qwertyu0992 | 0.333333 + qwertyu0993 | 0.333333 + qwertyu0994 | 0.333333 + qwertyu0995 | 0.333333 + qwertyu0996 | 0.333333 + qwertyu0997 | 0.333333 + qwertyu0998 | 0.333333 + qwertyu0999 | 0.333333 +(110 rows) + +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; + t | sml +-------------+---------- + qwertyu0988 | 0.333333 +(1 row) + +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; + count +------- + 1000 +(1 row) + +-- check handling of indexquals that generate no searchable conditions +explain (costs off) +select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; + QUERY PLAN +----------------------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on test_trgm + Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) + -> Bitmap Index Scan on trgm_idx + Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) +(5 rows) + +select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; + count +------- + 19 +(1 row) + +explain (costs off) +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; + QUERY PLAN +------------------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on test_trgm + Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) + -> Bitmap Index Scan on trgm_idx + Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) +(5 rows) + +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; + count +------- + 19 +(1 row) + +-- ensure that pending-list items are handled correctly, too +create temp table t_test_trgm(t text COLLATE "C"); +create index t_trgm_idx on t_test_trgm using gin (t gin_trgm_ops); +insert into t_test_trgm values ('qwerty99'), ('qwerty01'); +explain (costs off) +select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; + QUERY PLAN +----------------------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on t_test_trgm + Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) + -> Bitmap Index Scan on t_trgm_idx + Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qwerty%'::text)) +(5 rows) + +select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; + count +------- + 1 +(1 row) + +explain (costs off) +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; + QUERY PLAN +------------------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on t_test_trgm + Recheck Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) + -> Bitmap Index Scan on t_trgm_idx + Index Cond: ((t ~~ '%99%'::text) AND (t ~~ '%qw%'::text)) +(5 rows) + +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; + count +------- + 1 +(1 row) + +-- run the same queries with sequential scan to check the results +set enable_bitmapscan=off; +set enable_seqscan=on; +select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; + count +------- + 19 +(1 row) + +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; + count +------- + 19 +(1 row) + +select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; + count +------- + 1 +(1 row) + +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; + count +------- + 1 +(1 row) + +reset enable_bitmapscan; +create table test2(t text COLLATE "C"); +insert into test2 values ('abcdef'); +insert into test2 values ('quark'); +insert into test2 values (' z foo bar'); +insert into test2 values ('/123/-45/'); +insert into test2 values ('line 1'); +insert into test2 values ('%line 2'); +insert into test2 values ('line 3%'); +insert into test2 values ('%line 4%'); +insert into test2 values ('%li%ne 5%'); +insert into test2 values ('li_e 6'); +create index test2_idx_gin on test2 using gin (t gin_trgm_ops); +set enable_seqscan=off; +explain (costs off) + select * from test2 where t like '%BCD%'; + QUERY PLAN +------------------------------------------ + Bitmap Heap Scan on test2 + Recheck Cond: (t ~~ '%BCD%'::text) + -> Bitmap Index Scan on test2_idx_gin + Index Cond: (t ~~ '%BCD%'::text) +(4 rows) + +explain (costs off) + select * from test2 where t ilike '%BCD%'; + QUERY PLAN +------------------------------------------- + Bitmap Heap Scan on test2 + Recheck Cond: (t ~~* '%BCD%'::text) + -> Bitmap Index Scan on test2_idx_gin + Index Cond: (t ~~* '%BCD%'::text) +(4 rows) + +select * from test2 where t like '%BCD%'; + t +--- +(0 rows) + +select * from test2 where t like '%bcd%'; + t +-------- + abcdef +(1 row) + +select * from test2 where t like E'%\\bcd%'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ilike '%BCD%'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ilike 'qua%'; + t +------- + quark +(1 row) + +select * from test2 where t like '%z foo bar%'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t like ' z foo%'; + t +------------- + z foo bar +(1 row) + +explain (costs off) + select * from test2 where t ~ '[abc]{3}'; + QUERY PLAN +-------------------------------------------- + Bitmap Heap Scan on test2 + Recheck Cond: (t ~ '[abc]{3}'::text) + -> Bitmap Index Scan on test2_idx_gin + Index Cond: (t ~ '[abc]{3}'::text) +(4 rows) + +explain (costs off) + select * from test2 where t ~* 'DEF'; + QUERY PLAN +------------------------------------------ + Bitmap Heap Scan on test2 + Recheck Cond: (t ~* 'DEF'::text) + -> Bitmap Index Scan on test2_idx_gin + Index Cond: (t ~* 'DEF'::text) +(4 rows) + +select * from test2 where t ~ '[abc]{3}'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~ 'a[bc]+d'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~ '(abc)*$'; + t +------------- + abcdef + quark + z foo bar + /123/-45/ + line 1 + %line 2 + line 3% + %line 4% + %li%ne 5% + li_e 6 +(10 rows) + +select * from test2 where t ~* 'DEF'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~ 'dEf'; + t +--- +(0 rows) + +select * from test2 where t ~* '^q'; + t +------- + quark +(1 row) + +select * from test2 where t ~* '[abc]{3}[def]{3}'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~* 'ab[a-z]{3}'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~* '(^| )qua'; + t +------- + quark +(1 row) + +select * from test2 where t ~ 'q.*rk$'; + t +------- + quark +(1 row) + +select * from test2 where t ~ 'q'; + t +------- + quark +(1 row) + +select * from test2 where t ~ '[a-z]{3}'; + t +------------- + abcdef + quark + z foo bar + line 1 + %line 2 + line 3% + %line 4% +(7 rows) + +select * from test2 where t ~* '(a{10}|b{10}|c{10}){10}'; + t +--- +(0 rows) + +select * from test2 where t ~ 'z foo bar'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ ' z foo bar'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ ' z foo bar'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ ' z foo'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ 'qua(?!foo)'; + t +------- + quark +(1 row) + +select * from test2 where t ~ '/\d+/-\d'; + t +----------- + /123/-45/ +(1 row) + +-- test = operator +explain (costs off) + select * from test2 where t = 'abcdef'; + QUERY PLAN +------------------------------------------ + Bitmap Heap Scan on test2 + Recheck Cond: (t = 'abcdef'::text) + -> Bitmap Index Scan on test2_idx_gin + Index Cond: (t = 'abcdef'::text) +(4 rows) + +select * from test2 where t = 'abcdef'; + t +-------- + abcdef +(1 row) + +explain (costs off) + select * from test2 where t = '%line%'; + QUERY PLAN +------------------------------------------ + Bitmap Heap Scan on test2 + Recheck Cond: (t = '%line%'::text) + -> Bitmap Index Scan on test2_idx_gin + Index Cond: (t = '%line%'::text) +(4 rows) + +select * from test2 where t = '%line%'; + t +--- +(0 rows) + +select * from test2 where t = 'li_e 1'; + t +--- +(0 rows) + +select * from test2 where t = '%line 2'; + t +--------- + %line 2 +(1 row) + +select * from test2 where t = 'line 3%'; + t +--------- + line 3% +(1 row) + +select * from test2 where t = '%line 3%'; + t +--- +(0 rows) + +select * from test2 where t = '%line 4%'; + t +---------- + %line 4% +(1 row) + +select * from test2 where t = '%line 5%'; + t +--- +(0 rows) + +select * from test2 where t = '%li_ne 5%'; + t +--- +(0 rows) + +select * from test2 where t = '%li%ne 5%'; + t +----------- + %li%ne 5% +(1 row) + +select * from test2 where t = 'line 6'; + t +--- +(0 rows) + +select * from test2 where t = 'li_e 6'; + t +-------- + li_e 6 +(1 row) + +drop index test2_idx_gin; +create index test2_idx_gist on test2 using gist (t gist_trgm_ops); +set enable_seqscan=off; +explain (costs off) + select * from test2 where t like '%BCD%'; + QUERY PLAN +------------------------------------------ + Index Scan using test2_idx_gist on test2 + Index Cond: (t ~~ '%BCD%'::text) +(2 rows) + +explain (costs off) + select * from test2 where t ilike '%BCD%'; + QUERY PLAN +------------------------------------------ + Index Scan using test2_idx_gist on test2 + Index Cond: (t ~~* '%BCD%'::text) +(2 rows) + +select * from test2 where t like '%BCD%'; + t +--- +(0 rows) + +select * from test2 where t like '%bcd%'; + t +-------- + abcdef +(1 row) + +select * from test2 where t like E'%\\bcd%'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ilike '%BCD%'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ilike 'qua%'; + t +------- + quark +(1 row) + +select * from test2 where t like '%z foo bar%'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t like ' z foo%'; + t +------------- + z foo bar +(1 row) + +explain (costs off) + select * from test2 where t ~ '[abc]{3}'; + QUERY PLAN +------------------------------------------ + Index Scan using test2_idx_gist on test2 + Index Cond: (t ~ '[abc]{3}'::text) +(2 rows) + +explain (costs off) + select * from test2 where t ~* 'DEF'; + QUERY PLAN +------------------------------------------ + Index Scan using test2_idx_gist on test2 + Index Cond: (t ~* 'DEF'::text) +(2 rows) + +select * from test2 where t ~ '[abc]{3}'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~ 'a[bc]+d'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~ '(abc)*$'; + t +------------- + abcdef + quark + z foo bar + /123/-45/ + line 1 + %line 2 + line 3% + %line 4% + %li%ne 5% + li_e 6 +(10 rows) + +select * from test2 where t ~* 'DEF'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~ 'dEf'; + t +--- +(0 rows) + +select * from test2 where t ~* '^q'; + t +------- + quark +(1 row) + +select * from test2 where t ~* '[abc]{3}[def]{3}'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~* 'ab[a-z]{3}'; + t +-------- + abcdef +(1 row) + +select * from test2 where t ~* '(^| )qua'; + t +------- + quark +(1 row) + +select * from test2 where t ~ 'q.*rk$'; + t +------- + quark +(1 row) + +select * from test2 where t ~ 'q'; + t +------- + quark +(1 row) + +select * from test2 where t ~ '[a-z]{3}'; + t +------------- + abcdef + quark + z foo bar + line 1 + %line 2 + line 3% + %line 4% +(7 rows) + +select * from test2 where t ~* '(a{10}|b{10}|c{10}){10}'; + t +--- +(0 rows) + +select * from test2 where t ~ 'z foo bar'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ ' z foo bar'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ ' z foo bar'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ ' z foo'; + t +------------- + z foo bar +(1 row) + +select * from test2 where t ~ 'qua(?!foo)'; + t +------- + quark +(1 row) + +select * from test2 where t ~ '/\d+/-\d'; + t +----------- + /123/-45/ +(1 row) + +-- test = operator +explain (costs off) + select * from test2 where t = 'abcdef'; + QUERY PLAN +------------------------------------------ + Index Scan using test2_idx_gist on test2 + Index Cond: (t = 'abcdef'::text) +(2 rows) + +select * from test2 where t = 'abcdef'; + t +-------- + abcdef +(1 row) + +explain (costs off) + select * from test2 where t = '%line%'; + QUERY PLAN +------------------------------------------ + Index Scan using test2_idx_gist on test2 + Index Cond: (t = '%line%'::text) +(2 rows) + +select * from test2 where t = '%line%'; + t +--- +(0 rows) + +select * from test2 where t = 'li_e 1'; + t +--- +(0 rows) + +select * from test2 where t = '%line 2'; + t +--------- + %line 2 +(1 row) + +select * from test2 where t = 'line 3%'; + t +--------- + line 3% +(1 row) + +select * from test2 where t = '%line 3%'; + t +--- +(0 rows) + +select * from test2 where t = '%line 4%'; + t +---------- + %line 4% +(1 row) + +select * from test2 where t = '%line 5%'; + t +--- +(0 rows) + +select * from test2 where t = '%li_ne 5%'; + t +--- +(0 rows) + +select * from test2 where t = '%li%ne 5%'; + t +----------- + %li%ne 5% +(1 row) + +select * from test2 where t = 'line 6'; + t +--- +(0 rows) + +select * from test2 where t = 'li_e 6'; + t +-------- + li_e 6 +(1 row) + +-- Check similarity threshold (bug #14202) +CREATE TEMP TABLE restaurants (city text); +INSERT INTO restaurants SELECT 'Warsaw' FROM generate_series(1, 10000); +INSERT INTO restaurants SELECT 'Szczecin' FROM generate_series(1, 10000); +CREATE INDEX ON restaurants USING gist(city gist_trgm_ops); +-- Similarity of the two names (for reference). +SELECT similarity('Szczecin', 'Warsaw'); + similarity +------------ + 0 +(1 row) + +-- Should get only 'Warsaw' for either setting of set_limit. +EXPLAIN (COSTS OFF) +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; + QUERY PLAN +------------------------------------------------------- + HashAggregate + Group Key: city, similarity(city, 'Warsaw'::text) + -> Bitmap Heap Scan on restaurants + Recheck Cond: (city % 'Warsaw'::text) + -> Bitmap Index Scan on restaurants_city_idx + Index Cond: (city % 'Warsaw'::text) +(6 rows) + +SELECT set_limit(0.3); + set_limit +----------- + 0.3 +(1 row) + +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; + city | similarity | show_limit +--------+------------+------------ + Warsaw | 1 | 0.3 +(1 row) + +SELECT set_limit(0.5); + set_limit +----------- + 0.5 +(1 row) + +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; + city | similarity | show_limit +--------+------------+------------ + Warsaw | 1 | 0.5 +(1 row) + diff --git a/contrib/pg_trgm/expected/pg_word_trgm.out b/contrib/pg_trgm/expected/pg_word_trgm.out new file mode 100644 index 0000000..c66a67f --- /dev/null +++ b/contrib/pg_trgm/expected/pg_word_trgm.out @@ -0,0 +1,1052 @@ +CREATE TABLE test_trgm2(t text COLLATE "C"); +\copy test_trgm2 from 'data/trgm2.data' +-- reduce noise +set extra_float_digits = 0; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 +(20 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; + t | sml +------------------------------+----- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 +(4 rows) + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 +(20 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + t | sml +------------------------------+----- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 +(4 rows) + +select t <->> 'Kabankala', t from test_trgm2 order by t <->> 'Kabankala' limit 7; + ?column? | t +----------+---------------------------------- + 0 | Kabankala + 0.1 | Kabankalan City Public Plaza + 0.3 | Abankala + 0.4 | Ntombankala School + 0.416667 | Kabakala + 0.5 | Nehalla Bankalah Reserved Forest + 0.538462 | Kabikala +(7 rows) + +create index trgm_idx2 on test_trgm2 using gist (t gist_trgm_ops); +set enable_seqscan=off; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 +(20 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; + t | sml +------------------------------+----- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 +(4 rows) + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 +(20 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + t | sml +------------------------------+----- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 +(4 rows) + +explain (costs off) +select t <->> 'Kabankala', t from test_trgm2 order by t <->> 'Kabankala' limit 7; + QUERY PLAN +------------------------------------------------ + Limit + -> Index Scan using trgm_idx2 on test_trgm2 + Order By: (t <->> 'Kabankala'::text) +(3 rows) + +select t <->> 'Kabankala', t from test_trgm2 order by t <->> 'Kabankala' limit 7; + ?column? | t +----------+---------------------------------- + 0 | Kabankala + 0.1 | Kabankalan City Public Plaza + 0.3 | Abankala + 0.4 | Ntombankala School + 0.416667 | Kabakala + 0.5 | Nehalla Bankalah Reserved Forest + 0.538462 | Kabikala +(7 rows) + +drop index trgm_idx2; +create index trgm_idx2 on test_trgm2 using gin (t gin_trgm_ops); +set enable_seqscan=off; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 +(20 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; + t | sml +------------------------------+----- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 +(4 rows) + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 +(20 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + t | sml +------------------------------+----- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 +(4 rows) + +set "pg_trgm.word_similarity_threshold" to 0.5; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 + Bakal Batu | 0.571429 + Zabaykalka | 0.571429 + Zabaykalovskiy | 0.571429 +(23 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; + t | sml +----------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 + Kabakala | 0.583333 + Nehalla Bankalah Reserved Forest | 0.5 +(6 rows) + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; + t | sml +-------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 + Bakal Batu | 0.571429 + Zabaykalka | 0.571429 + Zabaykalovskiy | 0.571429 +(23 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + t | sml +----------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 + Kabakala | 0.583333 + Nehalla Bankalah Reserved Forest | 0.5 +(6 rows) + +set "pg_trgm.word_similarity_threshold" to 0.3; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; + t | sml +-----------------------------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 + Bakal Batu | 0.571429 + Zabaykalka | 0.571429 + Zabaykalovskiy | 0.571429 + Air Bakal-kecil | 0.444444 + Bakal | 0.444444 + Bakal Dos | 0.444444 + Bakal Julu | 0.444444 + Bakal Khel | 0.444444 + Bakal Lama | 0.444444 + Bakal Tres | 0.444444 + Bakal Uno | 0.444444 + Daang Bakal | 0.444444 + Desa Bakal | 0.444444 + Eat Bakal | 0.444444 + Gunung Bakal | 0.444444 + Sidi Bakal | 0.444444 + Stantsiya Bakal | 0.444444 + Sungai Bakal | 0.444444 + Talang Bakal | 0.444444 + Uruk Bakal | 0.444444 + Zaouia Oulad Bakal | 0.444444 + Al Barkali | 0.428571 + Aparthotel Adagio Premium Dubai Al Barsha | 0.428571 + Baikal Business Centre | 0.428571 + Bay of Backaland | 0.428571 + Boikalakalawa Bay | 0.428571 + Doubletree By Hilton Dubai Al Barsha Hotel and Res | 0.428571 + Doubletree By Hilton Hotel and Apartments Dubai Al Barsha | 0.428571 + Doubletree Res.Dubai-Al Barsha | 0.428571 + Holiday Inn Dubai Al Barsha | 0.428571 + Jabal Barkal | 0.428571 + Novotel Dubai Al Barsha | 0.428571 + Park Inn By Radisson Dubai Al Barsha | 0.428571 + Ramee Rose Hotel Dubai Al Barsha | 0.428571 + Waikalabubu Bay | 0.428571 + Baikal | 0.4 + Baikal Airfield | 0.4 + Baikal Hotel Moscow | 0.4 + Baikal Listvyanka Hotel | 0.4 + Baikal Mountains | 0.4 + Baikal Plaza | 0.4 + Bajkal | 0.4 + Bankal | 0.4 + Bankal School | 0.4 + Barkal | 0.4 + Lake Baikal | 0.4 + Mbay Bakala | 0.4 + Oulad el Bakkal | 0.4 + Sidi Mohammed Bakkal | 0.4 + Bairkal | 0.363636 + Bairkal Dhora | 0.363636 + Bairkal Jabal | 0.363636 + Batikal | 0.363636 + Bakala | 0.333333 + Bakala Koupi | 0.333333 + Bakalaale | 0.333333 + Bakalabwa Pans | 0.333333 + Bakalaeng | 0.333333 + Bakalafoulou | 0.333333 + Bakalalan Airport | 0.333333 + Bakalam | 0.333333 + Bakalambani | 0.333333 + Bakalan | 0.333333 + Bakalan Barat | 0.333333 + Bakalan Dua | 0.333333 + Bakalan Kidul | 0.333333 + Bakalan Kulon | 0.333333 + Bakalan Lor | 0.333333 + Bakalan River | 0.333333 + Bakalan Tengah | 0.333333 + Bakalan Wetan | 0.333333 + Bakalang | 0.333333 + Bakalao Asibi Point | 0.333333 + Bakalao Point | 0.333333 + Bakalar Air Force Base (historical) | 0.333333 + Bakalar Lake | 0.333333 + Bakalar Library | 0.333333 + Bakalarr | 0.333333 + Bakalauri | 0.333333 + Bakalauri1 | 0.333333 + Bakalauri2 | 0.333333 + Bakalauri3 | 0.333333 + Bakalauri4 | 0.333333 + Bakalauri5 | 0.333333 + Bakalauri6 | 0.333333 + Bakalauri7 | 0.333333 + Bakalauri8 | 0.333333 + Bakalauri9 | 0.333333 + Bakalawa | 0.333333 + Bakalbhar | 0.333333 + Bakalbuah | 0.333333 + Bakalda | 0.333333 + Bakaldalam | 0.333333 + Bakaldinskoye | 0.333333 + Bakaldovshchina | 0.333333 + Bakaldukuh | 0.333333 + Bakaldum | 0.333333 + Bakaldy | 0.333333 + Bakale | 0.333333 + Bakaleko | 0.333333 + Bakalerek | 0.333333 + Bakaley | 0.333333 + Bakaleyka | 0.333333 + Bakalha | 0.333333 + Bakali | 0.333333 + Bakalia Char | 0.333333 + Bakalica | 0.333333 + Bakalinga | 0.333333 + Bakalino | 0.333333 + Bakalinskiy | 0.333333 + Bakalinskiy Leskhoz | 0.333333 + Bakalinskiy Rayon | 0.333333 + Bakalipur | 0.333333 + Bakalite | 0.333333 + Bakaljaya | 0.333333 + Bakalka | 0.333333 + Bakall | 0.333333 + Bakalnica | 0.333333 + Bakalod Island | 0.333333 + Bakalongo | 0.333333 + Bakaloolay | 0.333333 + Bakalou | 0.333333 + Bakalovina | 0.333333 + Bakalovka | 0.333333 + Bakalovo | 0.333333 + Bakalovskaya Ferma | 0.333333 + Bakalpakebo | 0.333333 + Bakalpokok | 0.333333 + Bakalrejo | 0.333333 + Bakalsen | 0.333333 + Bakalshile | 0.333333 + Bakaltua Bank | 0.333333 + Bakalua | 0.333333 + Bakalukalu | 0.333333 + Bakalukalu Shan | 0.333333 + Bakalukudu | 0.333333 + Bakalum | 0.333333 + Bakaly | 0.333333 + Bakaly TV Mast | 0.333333 + Buur Bakale | 0.333333 + Buur Bakaley | 0.333333 + Columbus Bakalar Municipal Airport | 0.333333 + Dakshin Bakalia | 0.333333 + Danau Bakalan | 0.333333 + Desa Bakalan | 0.333333 + Desa Bakalankrajan | 0.333333 + Desa Bakalankrapyak | 0.333333 + Desa Bakalanpule | 0.333333 + Desa Bakalanrayung | 0.333333 + Desa Bakalanwringinpitu | 0.333333 + Desa Bakalrejo | 0.333333 + Efrejtor Bakalovo | 0.333333 + Efreytor-Bakalovo | 0.333333 + Gora Bakalyadyr | 0.333333 + Gory Bakaly | 0.333333 + Gunung Bakalan | 0.333333 + Ile Bakalibu | 0.333333 + Kali Bakalan | 0.333333 + Kampong Bakaladong | 0.333333 + Khor Bakallii | 0.333333 + Krajan Bakalan | 0.333333 + Kusu-Bakali | 0.333333 + Kwala Bakala | 0.333333 + Ngao Bakala | 0.333333 + Ovrag Bakalda | 0.333333 + Pematang Bakalpanang | 0.333333 + Pematang Bakalpanjang | 0.333333 + Pulau Bakalan | 0.333333 + Pulau Bakalanpauno | 0.333333 + Ragha Bakalzai | 0.333333 + Rodnik Bakalybulak | 0.333333 + Salu Bakalaeng | 0.333333 + Selat Bakalan | 0.333333 + Selat Bakalanpauno | 0.333333 + Sidi Mohammed el Bakali | 0.333333 + Sopka Bakaly | 0.333333 + Sovkhoz Bakalinskiy | 0.333333 + Sungai Bakala | 0.333333 + Sungai Bakaladiyan | 0.333333 + Tanjung Bakalinga | 0.333333 + Teluk Bakalan | 0.333333 + Teluk Bakalang | 0.333333 + Tubu Bakalekuk | 0.333333 + Tukad Bakalan | 0.333333 + Urochishche Bakalarnyn-Ayasy | 0.333333 + Urochishche Bakaldikha | 0.333333 + Urochishche Bakalovo | 0.333333 + Urochishche Bakaly | 0.333333 + Bakkalmal | 0.307692 + Alue Bakkala | 0.3 + Azib el Bakkali | 0.3 + Ba Kaliin | 0.3 + Bagkalen | 0.3 + Bahkalleh | 0.3 + Baikalakko | 0.3 + Baikalovo | 0.3 + Baikaluobbal | 0.3 + Bakkala Cemetery | 0.3 + Bakkalale | 0.3 + Bakkalegskardet | 0.3 + Bakkalia | 0.3 + Bakkalykkja | 0.3 + Bankali | 0.3 + Bankalol | 0.3 + Barkala | 0.3 + Barkala Park | 0.3 + Barkala Rao | 0.3 + Barkala Reserved Forest | 0.3 + Barkalabava | 0.3 + Barkaladja Pool | 0.3 + Barkalare | 0.3 + Barkald | 0.3 + Barkald stasjon | 0.3 + Barkalden | 0.3 + Barkaldfossen | 0.3 + Barkaldvola | 0.3 + Barkale | 0.3 + Barkaleh | 0.3 + Barkaleitet | 0.3 + Barkali | 0.3 + Barkallou | 0.3 + Barkalne | 0.3 + Barkalova | 0.3 + Barkalovka | 0.3 + Barkalow Hollow | 0.3 + Baskalino | 0.3 + Baskaltsi | 0.3 + Baukala | 0.3 + Bavkalasis | 0.3 + Bawkalut | 0.3 + Bawkalut Chaung | 0.3 + Bikal | 0.3 + Clifton T Barkalow Elementary School | 0.3 + Gora Barkalova | 0.3 + Gora Barkalyu | 0.3 + Khrebet Batkali | 0.3 + Kordon Barkalo | 0.3 + Nehalla Bankalah Reserved Forest | 0.3 + Ras Barkallah | 0.3 + Sopka Barkaleptskaya | 0.3 + Urochishche Batkali | 0.3 +(261 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; + t | sml +----------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 + Kabakala | 0.583333 + Nehalla Bankalah Reserved Forest | 0.5 + Kabikala | 0.461538 + Mwalaba-Kalamba | 0.454545 + Bakala Koupi | 0.4 + Bankal | 0.4 + Bankal School | 0.4 + Bankali | 0.4 + Bankalol | 0.4 + Jabba Kalai | 0.4 + Kanampumba-Kalawa | 0.4 + Purba Kalaujan | 0.4 + Tumba-Kalamba | 0.4 + Daba Kalharereh | 0.363636 + Gagaba Kalo | 0.363636 + Jaba Kalle | 0.363636 + Dabakala | 0.333333 + Dalabakala | 0.333333 + Kambakala | 0.333333 + Ker Samba Kalla | 0.333333 + Fayzabadkala | 0.307692 + Gora Fayzabadkala | 0.307692 + Guba Kalgalaksha | 0.307692 + Habakkala | 0.307692 + Kaikalahun Indian Reserve 25 | 0.307692 + Kaikalapettai | 0.307692 + Alue Bakkala | 0.3 + Ambadikala | 0.3 + Ambakala Wewa | 0.3 + Ataikala | 0.3 + Ba Kaliin | 0.3 + Bakala | 0.3 + Bakkala Cemetery | 0.3 + Bambakala | 0.3 + Barkala | 0.3 + Barkala Park | 0.3 + Barkala Rao | 0.3 + Barkala Reserved Forest | 0.3 + Baukala | 0.3 + Beikala | 0.3 + Bikala | 0.3 + Bikala Madila | 0.3 + Bomba-Kalende | 0.3 + Bonagbakala | 0.3 + Boyagbakala | 0.3 + Bugor Arba-Kalgan | 0.3 + Bumba-Kaloki | 0.3 + Bumba-Kalumba | 0.3 + Darreh Pumba Kal | 0.3 + Demba Kali | 0.3 + Embatkala | 0.3 + Gereba Kaler | 0.3 + Golba Kalo | 0.3 + Goth Soba Kaloi | 0.3 + Guba Kaldo | 0.3 + Guba Kalita | 0.3 + Gulba Kalle | 0.3 + Haikala | 0.3 + Kali Bakalan | 0.3 + Kali Purbakala | 0.3 + Kalibakal | 0.3 + Kalibakalako | 0.3 + Kalimundubakalan | 0.3 + Kamba-Kalele | 0.3 + Kimbakala | 0.3 + Kombakala | 0.3 + Kwala Bakala | 0.3 + Laikala | 0.3 + Maikala Range | 0.3 + Mambakala | 0.3 + Matamba-Kalenga | 0.3 + Matamba-Kalenge | 0.3 + Mbay Bakala | 0.3 + Mount Tohebakala | 0.3 + Naikala | 0.3 + Ngao Bakala | 0.3 + Purba Kalmegha | 0.3 + Sungai Bakala | 0.3 + Tagobikala | 0.3 + Tanjung Batikala | 0.3 + Tombakala | 0.3 + Tsibakala | 0.3 + Tumba-Kalumba | 0.3 + Tumba-Kalunga | 0.3 + Waikala | 0.3 +(89 rows) + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; + t | sml +-----------------------------------------------------------+---------- + Baykal | 1 + Boloto Baykal | 1 + Boloto Malyy Baykal | 1 + Kolkhoz Krasnyy Baykal | 1 + Ozero Baykal | 1 + Polevoy Stan Baykal | 1 + Port Baykal | 1 + Prud Novyy Baykal | 1 + Sanatoriy Baykal | 1 + Stantsiya Baykal | 1 + Zaliv Baykal | 1 + Baykalikha | 0.857143 + Baykalo-Amurskaya Zheleznaya Doroga | 0.857143 + Baykalovo | 0.857143 + Baykalovsk | 0.857143 + Baykalovskiy | 0.857143 + Baykalovskiy Rayon | 0.857143 + Baykalsko | 0.857143 + Maloye Baykalovo | 0.857143 + Zabaykal | 0.714286 + Bakal Batu | 0.571429 + Zabaykalka | 0.571429 + Zabaykalovskiy | 0.571429 + Air Bakal-kecil | 0.444444 + Bakal | 0.444444 + Bakal Dos | 0.444444 + Bakal Julu | 0.444444 + Bakal Khel | 0.444444 + Bakal Lama | 0.444444 + Bakal Tres | 0.444444 + Bakal Uno | 0.444444 + Daang Bakal | 0.444444 + Desa Bakal | 0.444444 + Eat Bakal | 0.444444 + Gunung Bakal | 0.444444 + Sidi Bakal | 0.444444 + Stantsiya Bakal | 0.444444 + Sungai Bakal | 0.444444 + Talang Bakal | 0.444444 + Uruk Bakal | 0.444444 + Zaouia Oulad Bakal | 0.444444 + Al Barkali | 0.428571 + Aparthotel Adagio Premium Dubai Al Barsha | 0.428571 + Baikal Business Centre | 0.428571 + Bay of Backaland | 0.428571 + Boikalakalawa Bay | 0.428571 + Doubletree By Hilton Dubai Al Barsha Hotel and Res | 0.428571 + Doubletree By Hilton Hotel and Apartments Dubai Al Barsha | 0.428571 + Doubletree Res.Dubai-Al Barsha | 0.428571 + Holiday Inn Dubai Al Barsha | 0.428571 + Jabal Barkal | 0.428571 + Novotel Dubai Al Barsha | 0.428571 + Park Inn By Radisson Dubai Al Barsha | 0.428571 + Ramee Rose Hotel Dubai Al Barsha | 0.428571 + Waikalabubu Bay | 0.428571 + Baikal | 0.4 + Baikal Airfield | 0.4 + Baikal Hotel Moscow | 0.4 + Baikal Listvyanka Hotel | 0.4 + Baikal Mountains | 0.4 + Baikal Plaza | 0.4 + Bajkal | 0.4 + Bankal | 0.4 + Bankal School | 0.4 + Barkal | 0.4 + Lake Baikal | 0.4 + Mbay Bakala | 0.4 + Oulad el Bakkal | 0.4 + Sidi Mohammed Bakkal | 0.4 + Bairkal | 0.363636 + Bairkal Dhora | 0.363636 + Bairkal Jabal | 0.363636 + Batikal | 0.363636 + Bakala | 0.333333 + Bakala Koupi | 0.333333 + Bakalaale | 0.333333 + Bakalabwa Pans | 0.333333 + Bakalaeng | 0.333333 + Bakalafoulou | 0.333333 + Bakalalan Airport | 0.333333 + Bakalam | 0.333333 + Bakalambani | 0.333333 + Bakalan | 0.333333 + Bakalan Barat | 0.333333 + Bakalan Dua | 0.333333 + Bakalan Kidul | 0.333333 + Bakalan Kulon | 0.333333 + Bakalan Lor | 0.333333 + Bakalan River | 0.333333 + Bakalan Tengah | 0.333333 + Bakalan Wetan | 0.333333 + Bakalang | 0.333333 + Bakalao Asibi Point | 0.333333 + Bakalao Point | 0.333333 + Bakalar Air Force Base (historical) | 0.333333 + Bakalar Lake | 0.333333 + Bakalar Library | 0.333333 + Bakalarr | 0.333333 + Bakalauri | 0.333333 + Bakalauri1 | 0.333333 + Bakalauri2 | 0.333333 + Bakalauri3 | 0.333333 + Bakalauri4 | 0.333333 + Bakalauri5 | 0.333333 + Bakalauri6 | 0.333333 + Bakalauri7 | 0.333333 + Bakalauri8 | 0.333333 + Bakalauri9 | 0.333333 + Bakalawa | 0.333333 + Bakalbhar | 0.333333 + Bakalbuah | 0.333333 + Bakalda | 0.333333 + Bakaldalam | 0.333333 + Bakaldinskoye | 0.333333 + Bakaldovshchina | 0.333333 + Bakaldukuh | 0.333333 + Bakaldum | 0.333333 + Bakaldy | 0.333333 + Bakale | 0.333333 + Bakaleko | 0.333333 + Bakalerek | 0.333333 + Bakaley | 0.333333 + Bakaleyka | 0.333333 + Bakalha | 0.333333 + Bakali | 0.333333 + Bakalia Char | 0.333333 + Bakalica | 0.333333 + Bakalinga | 0.333333 + Bakalino | 0.333333 + Bakalinskiy | 0.333333 + Bakalinskiy Leskhoz | 0.333333 + Bakalinskiy Rayon | 0.333333 + Bakalipur | 0.333333 + Bakalite | 0.333333 + Bakaljaya | 0.333333 + Bakalka | 0.333333 + Bakall | 0.333333 + Bakalnica | 0.333333 + Bakalod Island | 0.333333 + Bakalongo | 0.333333 + Bakaloolay | 0.333333 + Bakalou | 0.333333 + Bakalovina | 0.333333 + Bakalovka | 0.333333 + Bakalovo | 0.333333 + Bakalovskaya Ferma | 0.333333 + Bakalpakebo | 0.333333 + Bakalpokok | 0.333333 + Bakalrejo | 0.333333 + Bakalsen | 0.333333 + Bakalshile | 0.333333 + Bakaltua Bank | 0.333333 + Bakalua | 0.333333 + Bakalukalu | 0.333333 + Bakalukalu Shan | 0.333333 + Bakalukudu | 0.333333 + Bakalum | 0.333333 + Bakaly | 0.333333 + Bakaly TV Mast | 0.333333 + Buur Bakale | 0.333333 + Buur Bakaley | 0.333333 + Columbus Bakalar Municipal Airport | 0.333333 + Dakshin Bakalia | 0.333333 + Danau Bakalan | 0.333333 + Desa Bakalan | 0.333333 + Desa Bakalankrajan | 0.333333 + Desa Bakalankrapyak | 0.333333 + Desa Bakalanpule | 0.333333 + Desa Bakalanrayung | 0.333333 + Desa Bakalanwringinpitu | 0.333333 + Desa Bakalrejo | 0.333333 + Efrejtor Bakalovo | 0.333333 + Efreytor-Bakalovo | 0.333333 + Gora Bakalyadyr | 0.333333 + Gory Bakaly | 0.333333 + Gunung Bakalan | 0.333333 + Ile Bakalibu | 0.333333 + Kali Bakalan | 0.333333 + Kampong Bakaladong | 0.333333 + Khor Bakallii | 0.333333 + Krajan Bakalan | 0.333333 + Kusu-Bakali | 0.333333 + Kwala Bakala | 0.333333 + Ngao Bakala | 0.333333 + Ovrag Bakalda | 0.333333 + Pematang Bakalpanang | 0.333333 + Pematang Bakalpanjang | 0.333333 + Pulau Bakalan | 0.333333 + Pulau Bakalanpauno | 0.333333 + Ragha Bakalzai | 0.333333 + Rodnik Bakalybulak | 0.333333 + Salu Bakalaeng | 0.333333 + Selat Bakalan | 0.333333 + Selat Bakalanpauno | 0.333333 + Sidi Mohammed el Bakali | 0.333333 + Sopka Bakaly | 0.333333 + Sovkhoz Bakalinskiy | 0.333333 + Sungai Bakala | 0.333333 + Sungai Bakaladiyan | 0.333333 + Tanjung Bakalinga | 0.333333 + Teluk Bakalan | 0.333333 + Teluk Bakalang | 0.333333 + Tubu Bakalekuk | 0.333333 + Tukad Bakalan | 0.333333 + Urochishche Bakalarnyn-Ayasy | 0.333333 + Urochishche Bakaldikha | 0.333333 + Urochishche Bakalovo | 0.333333 + Urochishche Bakaly | 0.333333 + Bakkalmal | 0.307692 + Alue Bakkala | 0.3 + Azib el Bakkali | 0.3 + Ba Kaliin | 0.3 + Bagkalen | 0.3 + Bahkalleh | 0.3 + Baikalakko | 0.3 + Baikalovo | 0.3 + Baikaluobbal | 0.3 + Bakkala Cemetery | 0.3 + Bakkalale | 0.3 + Bakkalegskardet | 0.3 + Bakkalia | 0.3 + Bakkalykkja | 0.3 + Bankali | 0.3 + Bankalol | 0.3 + Barkala | 0.3 + Barkala Park | 0.3 + Barkala Rao | 0.3 + Barkala Reserved Forest | 0.3 + Barkalabava | 0.3 + Barkaladja Pool | 0.3 + Barkalare | 0.3 + Barkald | 0.3 + Barkald stasjon | 0.3 + Barkalden | 0.3 + Barkaldfossen | 0.3 + Barkaldvola | 0.3 + Barkale | 0.3 + Barkaleh | 0.3 + Barkaleitet | 0.3 + Barkali | 0.3 + Barkallou | 0.3 + Barkalne | 0.3 + Barkalova | 0.3 + Barkalovka | 0.3 + Barkalow Hollow | 0.3 + Baskalino | 0.3 + Baskaltsi | 0.3 + Baukala | 0.3 + Bavkalasis | 0.3 + Bawkalut | 0.3 + Bawkalut Chaung | 0.3 + Bikal | 0.3 + Clifton T Barkalow Elementary School | 0.3 + Gora Barkalova | 0.3 + Gora Barkalyu | 0.3 + Khrebet Batkali | 0.3 + Kordon Barkalo | 0.3 + Nehalla Bankalah Reserved Forest | 0.3 + Ras Barkallah | 0.3 + Sopka Barkaleptskaya | 0.3 + Urochishche Batkali | 0.3 +(261 rows) + +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + t | sml +----------------------------------+---------- + Kabankala | 1 + Kabankalan City Public Plaza | 0.9 + Abankala | 0.7 + Ntombankala School | 0.6 + Kabakala | 0.583333 + Nehalla Bankalah Reserved Forest | 0.5 + Kabikala | 0.461538 + Mwalaba-Kalamba | 0.454545 + Bakala Koupi | 0.4 + Bankal | 0.4 + Bankal School | 0.4 + Bankali | 0.4 + Bankalol | 0.4 + Jabba Kalai | 0.4 + Kanampumba-Kalawa | 0.4 + Purba Kalaujan | 0.4 + Tumba-Kalamba | 0.4 + Daba Kalharereh | 0.363636 + Gagaba Kalo | 0.363636 + Jaba Kalle | 0.363636 + Dabakala | 0.333333 + Dalabakala | 0.333333 + Kambakala | 0.333333 + Ker Samba Kalla | 0.333333 + Fayzabadkala | 0.307692 + Gora Fayzabadkala | 0.307692 + Guba Kalgalaksha | 0.307692 + Habakkala | 0.307692 + Kaikalahun Indian Reserve 25 | 0.307692 + Kaikalapettai | 0.307692 + Alue Bakkala | 0.3 + Ambadikala | 0.3 + Ambakala Wewa | 0.3 + Ataikala | 0.3 + Ba Kaliin | 0.3 + Bakala | 0.3 + Bakkala Cemetery | 0.3 + Bambakala | 0.3 + Barkala | 0.3 + Barkala Park | 0.3 + Barkala Rao | 0.3 + Barkala Reserved Forest | 0.3 + Baukala | 0.3 + Beikala | 0.3 + Bikala | 0.3 + Bikala Madila | 0.3 + Bomba-Kalende | 0.3 + Bonagbakala | 0.3 + Boyagbakala | 0.3 + Bugor Arba-Kalgan | 0.3 + Bumba-Kaloki | 0.3 + Bumba-Kalumba | 0.3 + Darreh Pumba Kal | 0.3 + Demba Kali | 0.3 + Embatkala | 0.3 + Gereba Kaler | 0.3 + Golba Kalo | 0.3 + Goth Soba Kaloi | 0.3 + Guba Kaldo | 0.3 + Guba Kalita | 0.3 + Gulba Kalle | 0.3 + Haikala | 0.3 + Kali Bakalan | 0.3 + Kali Purbakala | 0.3 + Kalibakal | 0.3 + Kalibakalako | 0.3 + Kalimundubakalan | 0.3 + Kamba-Kalele | 0.3 + Kimbakala | 0.3 + Kombakala | 0.3 + Kwala Bakala | 0.3 + Laikala | 0.3 + Maikala Range | 0.3 + Mambakala | 0.3 + Matamba-Kalenga | 0.3 + Matamba-Kalenge | 0.3 + Mbay Bakala | 0.3 + Mount Tohebakala | 0.3 + Naikala | 0.3 + Ngao Bakala | 0.3 + Purba Kalmegha | 0.3 + Sungai Bakala | 0.3 + Tagobikala | 0.3 + Tanjung Batikala | 0.3 + Tombakala | 0.3 + Tsibakala | 0.3 + Tumba-Kalumba | 0.3 + Tumba-Kalunga | 0.3 + Waikala | 0.3 +(89 rows) + +-- test unsatisfiable pattern +select * from test_trgm2 where t ~ '.*$x'; + t +--- +(0 rows) + diff --git a/contrib/pg_trgm/meson.build b/contrib/pg_trgm/meson.build new file mode 100644 index 0000000..093ac18 --- /dev/null +++ b/contrib/pg_trgm/meson.build @@ -0,0 +1,46 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pg_trgm_sources = files( + 'trgm_gin.c', + 'trgm_gist.c', + 'trgm_op.c', + 'trgm_regexp.c', +) + +if host_system == 'windows' + pg_trgm_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pg_trgm', + '--FILEDESC', 'pg_trgm - trigram matching',]) +endif + +pg_trgm = shared_module('pg_trgm', + pg_trgm_sources, + c_pch: pch_postgres_h, + kwargs: contrib_mod_args, +) +contrib_targets += pg_trgm + +install_data( + 'pg_trgm--1.0--1.1.sql', + 'pg_trgm--1.1--1.2.sql', + 'pg_trgm--1.2--1.3.sql', + 'pg_trgm--1.3--1.4.sql', + 'pg_trgm--1.3.sql', + 'pg_trgm--1.4--1.5.sql', + 'pg_trgm--1.5--1.6.sql', + 'pg_trgm.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'pg_trgm', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'pg_trgm', + 'pg_word_trgm', + 'pg_strict_word_trgm', + ], + }, +} diff --git a/contrib/pg_trgm/pg_trgm--1.0--1.1.sql b/contrib/pg_trgm/pg_trgm--1.0--1.1.sql new file mode 100644 index 0000000..b4e3e26 --- /dev/null +++ b/contrib/pg_trgm/pg_trgm--1.0--1.1.sql @@ -0,0 +1,12 @@ +/* contrib/pg_trgm/pg_trgm--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.1'" to load this file. \quit + +ALTER OPERATOR FAMILY gist_trgm_ops USING gist ADD + OPERATOR 5 pg_catalog.~ (text, text), + OPERATOR 6 pg_catalog.~* (text, text); + +ALTER OPERATOR FAMILY gin_trgm_ops USING gin ADD + OPERATOR 5 pg_catalog.~ (text, text), + OPERATOR 6 pg_catalog.~* (text, text); diff --git a/contrib/pg_trgm/pg_trgm--1.1--1.2.sql b/contrib/pg_trgm/pg_trgm--1.1--1.2.sql new file mode 100644 index 0000000..ff0f13f --- /dev/null +++ b/contrib/pg_trgm/pg_trgm--1.1--1.2.sql @@ -0,0 +1,74 @@ +/* contrib/pg_trgm/pg_trgm--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.2'" to load this file. \quit + +CREATE FUNCTION word_similarity(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION word_similarity_op(text,text) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE; -- stable because depends on pg_trgm.word_similarity_threshold + +CREATE FUNCTION word_similarity_commutator_op(text,text) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE; -- stable because depends on pg_trgm.word_similarity_threshold + +CREATE FUNCTION word_similarity_dist_op(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE FUNCTION word_similarity_dist_commutator_op(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE; + +CREATE OPERATOR <% ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = word_similarity_op, + COMMUTATOR = '%>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR %> ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = word_similarity_commutator_op, + COMMUTATOR = '<%', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR <<-> ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = word_similarity_dist_op, + COMMUTATOR = '<->>' +); + +CREATE OPERATOR <->> ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = word_similarity_dist_commutator_op, + COMMUTATOR = '<<->' +); + +CREATE FUNCTION gin_trgm_triconsistent(internal, int2, text, int4, internal, internal, internal) +RETURNS "char" +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT; + +ALTER OPERATOR FAMILY gist_trgm_ops USING gist ADD + OPERATOR 7 %> (text, text), + OPERATOR 8 <->> (text, text) FOR ORDER BY pg_catalog.float_ops; + +ALTER OPERATOR FAMILY gin_trgm_ops USING gin ADD + OPERATOR 7 %> (text, text), + FUNCTION 6 (text, text) gin_trgm_triconsistent (internal, int2, text, int4, internal, internal, internal); diff --git a/contrib/pg_trgm/pg_trgm--1.2--1.3.sql b/contrib/pg_trgm/pg_trgm--1.2--1.3.sql new file mode 100644 index 0000000..8dc772c --- /dev/null +++ b/contrib/pg_trgm/pg_trgm--1.2--1.3.sql @@ -0,0 +1,65 @@ +/* contrib/pg_trgm/pg_trgm--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.3'" to load this file. \quit + +-- Update procedure signatures the hard way. +-- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, +-- wherein the signatures have been updated already. In that case to_regprocedure() will +-- return NULL and no updates will happen. +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) +FROM (VALUES +(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types +('gtrgm_consistent(internal,text,int4,oid,internal)', '{internal,text,int2,oid,internal}'), +('gtrgm_distance(internal,text,int4,oid)', '{internal,text,int2,oid,internal}'), +('gtrgm_union(bytea,internal)', '{internal,internal}') +) AS update_data (oldproc, newtypestext), +LATERAL ( + SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ +) ls +WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + +UPDATE pg_catalog.pg_proc SET + prorettype = (my_schema || '.gtrgm')::pg_catalog.regtype +WHERE oid = pg_catalog.to_regprocedure(my_schema || '.gtrgm_union(internal,internal)'); + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; + +ALTER FUNCTION set_limit(float4) PARALLEL UNSAFE; +ALTER FUNCTION show_limit() PARALLEL SAFE; +ALTER FUNCTION show_trgm(text) PARALLEL SAFE; +ALTER FUNCTION similarity(text, text) PARALLEL SAFE; +ALTER FUNCTION similarity_op(text, text) PARALLEL SAFE; +ALTER FUNCTION word_similarity(text, text) PARALLEL SAFE; +ALTER FUNCTION word_similarity_op(text, text) PARALLEL SAFE; +ALTER FUNCTION word_similarity_commutator_op(text, text) PARALLEL SAFE; +ALTER FUNCTION similarity_dist(text, text) PARALLEL SAFE; +ALTER FUNCTION word_similarity_dist_op(text, text) PARALLEL SAFE; +ALTER FUNCTION word_similarity_dist_commutator_op(text, text) PARALLEL SAFE; +ALTER FUNCTION gtrgm_in(cstring) PARALLEL SAFE; +ALTER FUNCTION gtrgm_out(gtrgm) PARALLEL SAFE; +ALTER FUNCTION gtrgm_consistent(internal, text, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gtrgm_distance(internal, text, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gtrgm_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gtrgm_decompress(internal) PARALLEL SAFE; +ALTER FUNCTION gtrgm_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gtrgm_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gtrgm_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gtrgm_same(gtrgm, gtrgm, internal) PARALLEL SAFE; +ALTER FUNCTION gin_extract_value_trgm(text, internal) PARALLEL SAFE; +ALTER FUNCTION gin_extract_query_trgm(text, internal, int2, internal, internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gin_trgm_consistent(internal, int2, text, int4, internal, internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gin_trgm_triconsistent(internal, int2, text, int4, internal, internal, internal) PARALLEL SAFE; diff --git a/contrib/pg_trgm/pg_trgm--1.3--1.4.sql b/contrib/pg_trgm/pg_trgm--1.3--1.4.sql new file mode 100644 index 0000000..64a0c21 --- /dev/null +++ b/contrib/pg_trgm/pg_trgm--1.3--1.4.sql @@ -0,0 +1,68 @@ +/* contrib/pg_trgm/pg_trgm--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.4'" to load this file. \quit + +CREATE FUNCTION strict_word_similarity(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION strict_word_similarity_op(text,text) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; -- stable because depends on pg_trgm.word_similarity_threshold + +CREATE FUNCTION strict_word_similarity_commutator_op(text,text) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; -- stable because depends on pg_trgm.word_similarity_threshold + +CREATE OPERATOR <<% ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = strict_word_similarity_op, + COMMUTATOR = '%>>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR %>> ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = strict_word_similarity_commutator_op, + COMMUTATOR = '<<%', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION strict_word_similarity_dist_op(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION strict_word_similarity_dist_commutator_op(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR <<<-> ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = strict_word_similarity_dist_op, + COMMUTATOR = '<->>>' +); + +CREATE OPERATOR <->>> ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = strict_word_similarity_dist_commutator_op, + COMMUTATOR = '<<<->' +); + +ALTER OPERATOR FAMILY gist_trgm_ops USING gist ADD + OPERATOR 9 %>> (text, text), + OPERATOR 10 <->>> (text, text) FOR ORDER BY pg_catalog.float_ops; + +ALTER OPERATOR FAMILY gin_trgm_ops USING gin ADD + OPERATOR 9 %>> (text, text); diff --git a/contrib/pg_trgm/pg_trgm--1.3.sql b/contrib/pg_trgm/pg_trgm--1.3.sql new file mode 100644 index 0000000..4c6edf8 --- /dev/null +++ b/contrib/pg_trgm/pg_trgm--1.3.sql @@ -0,0 +1,254 @@ +/* contrib/pg_trgm/pg_trgm--1.3.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_trgm" to load this file. \quit + +-- Deprecated function +CREATE FUNCTION set_limit(float4) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; + +-- Deprecated function +CREATE FUNCTION show_limit() +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; + +CREATE FUNCTION show_trgm(text) +RETURNS _text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION similarity(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION similarity_op(text,text) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; -- stable because depends on pg_trgm.similarity_threshold + +CREATE OPERATOR % ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = similarity_op, + COMMUTATOR = '%', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION word_similarity(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION word_similarity_op(text,text) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; -- stable because depends on pg_trgm.word_similarity_threshold + +CREATE FUNCTION word_similarity_commutator_op(text,text) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; -- stable because depends on pg_trgm.word_similarity_threshold + +CREATE OPERATOR <% ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = word_similarity_op, + COMMUTATOR = '%>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR %> ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = word_similarity_commutator_op, + COMMUTATOR = '<%', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE FUNCTION similarity_dist(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR <-> ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = similarity_dist, + COMMUTATOR = '<->' +); + +CREATE FUNCTION word_similarity_dist_op(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION word_similarity_dist_commutator_op(text,text) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE OPERATOR <<-> ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = word_similarity_dist_op, + COMMUTATOR = '<->>' +); + +CREATE OPERATOR <->> ( + LEFTARG = text, + RIGHTARG = text, + PROCEDURE = word_similarity_dist_commutator_op, + COMMUTATOR = '<<->' +); + +-- gist key +CREATE FUNCTION gtrgm_in(cstring) +RETURNS gtrgm +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION gtrgm_out(gtrgm) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE TYPE gtrgm ( + INTERNALLENGTH = -1, + INPUT = gtrgm_in, + OUTPUT = gtrgm_out +); + +-- support functions for gist +CREATE FUNCTION gtrgm_consistent(internal,text,smallint,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gtrgm_distance(internal,text,smallint,oid,internal) +RETURNS float8 +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gtrgm_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gtrgm_decompress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gtrgm_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gtrgm_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gtrgm_union(internal, internal) +RETURNS gtrgm +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gtrgm_same(gtrgm, gtrgm, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- create the operator class for gist +CREATE OPERATOR CLASS gist_trgm_ops +FOR TYPE text USING gist +AS + OPERATOR 1 % (text, text), + FUNCTION 1 gtrgm_consistent (internal, text, smallint, oid, internal), + FUNCTION 2 gtrgm_union (internal, internal), + FUNCTION 3 gtrgm_compress (internal), + FUNCTION 4 gtrgm_decompress (internal), + FUNCTION 5 gtrgm_penalty (internal, internal, internal), + FUNCTION 6 gtrgm_picksplit (internal, internal), + FUNCTION 7 gtrgm_same (gtrgm, gtrgm, internal), + STORAGE gtrgm; + +-- Add operators and support functions that are new in 9.1. We do it like +-- this, leaving them "loose" in the operator family rather than bound into +-- the gist_trgm_ops opclass, because that's the only state that could be +-- reproduced during an upgrade from 9.0. + +ALTER OPERATOR FAMILY gist_trgm_ops USING gist ADD + OPERATOR 2 <-> (text, text) FOR ORDER BY pg_catalog.float_ops, + OPERATOR 3 pg_catalog.~~ (text, text), + OPERATOR 4 pg_catalog.~~* (text, text), + FUNCTION 8 (text, text) gtrgm_distance (internal, text, smallint, oid, internal); + +-- Add operators that are new in 9.3. + +ALTER OPERATOR FAMILY gist_trgm_ops USING gist ADD + OPERATOR 5 pg_catalog.~ (text, text), + OPERATOR 6 pg_catalog.~* (text, text); + +-- Add operators that are new in 9.6 (pg_trgm 1.2). + +ALTER OPERATOR FAMILY gist_trgm_ops USING gist ADD + OPERATOR 7 %> (text, text), + OPERATOR 8 <->> (text, text) FOR ORDER BY pg_catalog.float_ops; + +-- support functions for gin +CREATE FUNCTION gin_extract_value_trgm(text, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gin_extract_query_trgm(text, internal, int2, internal, internal, internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gin_trgm_consistent(internal, int2, text, int4, internal, internal, internal, internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- create the operator class for gin +CREATE OPERATOR CLASS gin_trgm_ops +FOR TYPE text USING gin +AS + OPERATOR 1 % (text, text), + FUNCTION 1 btint4cmp (int4, int4), + FUNCTION 2 gin_extract_value_trgm (text, internal), + FUNCTION 3 gin_extract_query_trgm (text, internal, int2, internal, internal, internal, internal), + FUNCTION 4 gin_trgm_consistent (internal, int2, text, int4, internal, internal, internal, internal), + STORAGE int4; + +-- Add operators that are new in 9.1. + +ALTER OPERATOR FAMILY gin_trgm_ops USING gin ADD + OPERATOR 3 pg_catalog.~~ (text, text), + OPERATOR 4 pg_catalog.~~* (text, text); + +-- Add operators that are new in 9.3. + +ALTER OPERATOR FAMILY gin_trgm_ops USING gin ADD + OPERATOR 5 pg_catalog.~ (text, text), + OPERATOR 6 pg_catalog.~* (text, text); + +-- Add functions that are new in 9.6 (pg_trgm 1.2). + +CREATE FUNCTION gin_trgm_triconsistent(internal, int2, text, int4, internal, internal, internal) +RETURNS "char" +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +ALTER OPERATOR FAMILY gin_trgm_ops USING gin ADD + OPERATOR 7 %> (text, text), + FUNCTION 6 (text,text) gin_trgm_triconsistent (internal, int2, text, int4, internal, internal, internal); diff --git a/contrib/pg_trgm/pg_trgm--1.4--1.5.sql b/contrib/pg_trgm/pg_trgm--1.4--1.5.sql new file mode 100644 index 0000000..db122fc --- /dev/null +++ b/contrib/pg_trgm/pg_trgm--1.4--1.5.sql @@ -0,0 +1,23 @@ +/* contrib/pg_trgm/pg_trgm--1.4--1.5.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.5'" to load this file. \quit + +CREATE FUNCTION gtrgm_options(internal) +RETURNS void +AS 'MODULE_PATHNAME', 'gtrgm_options' +LANGUAGE C IMMUTABLE PARALLEL SAFE; + +ALTER OPERATOR FAMILY gist_trgm_ops USING gist +ADD FUNCTION 10 (text) gtrgm_options (internal); + +ALTER OPERATOR % (text, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR <% (text, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR %> (text, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR <<% (text, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); +ALTER OPERATOR %>> (text, text) + SET (RESTRICT = matchingsel, JOIN = matchingjoinsel); diff --git a/contrib/pg_trgm/pg_trgm--1.5--1.6.sql b/contrib/pg_trgm/pg_trgm--1.5--1.6.sql new file mode 100644 index 0000000..9e74684 --- /dev/null +++ b/contrib/pg_trgm/pg_trgm--1.5--1.6.sql @@ -0,0 +1,10 @@ +/* contrib/pg_trgm/pg_trgm--1.5--1.6.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_trgm UPDATE TO '1.6'" to load this file. \quit + +ALTER OPERATOR FAMILY gin_trgm_ops USING gin ADD + OPERATOR 11 pg_catalog.= (text, text); + +ALTER OPERATOR FAMILY gist_trgm_ops USING gist ADD + OPERATOR 11 pg_catalog.= (text, text); diff --git a/contrib/pg_trgm/pg_trgm.control b/contrib/pg_trgm/pg_trgm.control new file mode 100644 index 0000000..1d6a9dd --- /dev/null +++ b/contrib/pg_trgm/pg_trgm.control @@ -0,0 +1,6 @@ +# pg_trgm extension +comment = 'text similarity measurement and index searching based on trigrams' +default_version = '1.6' +module_pathname = '$libdir/pg_trgm' +relocatable = true +trusted = true diff --git a/contrib/pg_trgm/sql/pg_strict_word_trgm.sql b/contrib/pg_trgm/sql/pg_strict_word_trgm.sql new file mode 100644 index 0000000..ce0791f --- /dev/null +++ b/contrib/pg_trgm/sql/pg_strict_word_trgm.sql @@ -0,0 +1,45 @@ +DROP INDEX trgm_idx2; + +\copy test_trgm3 from 'data/trgm2.data' + +-- reduce noise +set extra_float_digits = 0; + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; +select t <->>> 'Alaikallupoddakulam', t from test_trgm2 order by t <->>> 'Alaikallupoddakulam' limit 7; + +create index trgm_idx2 on test_trgm2 using gist (t gist_trgm_ops); +set enable_seqscan=off; + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + +explain (costs off) +select t <->>> 'Alaikallupoddakulam', t from test_trgm2 order by t <->>> 'Alaikallupoddakulam' limit 7; +select t <->>> 'Alaikallupoddakulam', t from test_trgm2 order by t <->>> 'Alaikallupoddakulam' limit 7; + +drop index trgm_idx2; +create index trgm_idx2 on test_trgm2 using gin (t gin_trgm_ops); +set enable_seqscan=off; + +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + +set "pg_trgm.strict_word_similarity_threshold" to 0.4; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; + +set "pg_trgm.strict_word_similarity_threshold" to 0.2; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <<% t order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <<% t order by sml desc, t; +select t,strict_word_similarity('Baykal',t) as sml from test_trgm2 where t %>> 'Baykal' order by sml desc, t; +select t,strict_word_similarity('Kabankala',t) as sml from test_trgm2 where t %>> 'Kabankala' order by sml desc, t; diff --git a/contrib/pg_trgm/sql/pg_trgm.sql b/contrib/pg_trgm/sql/pg_trgm.sql new file mode 100644 index 0000000..6a9da24 --- /dev/null +++ b/contrib/pg_trgm/sql/pg_trgm.sql @@ -0,0 +1,238 @@ +CREATE EXTENSION pg_trgm; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +--backslash is used in tests below, installcheck will fail if +--standard_conforming_string is off +set standard_conforming_strings=on; + +-- reduce noise +set extra_float_digits = 0; + +select show_trgm(''); +select show_trgm('(*&^$@%@'); +select show_trgm('a b c'); +select show_trgm(' a b c '); +select show_trgm('aA bB cC'); +select show_trgm(' aA bB cC '); +select show_trgm('a b C0*%^'); + +select similarity('wow','WOWa '); +select similarity('wow',' WOW '); + +select similarity('---', '####---'); + +CREATE TABLE test_trgm(t text COLLATE "C"); + +\copy test_trgm from 'data/trgm.data' + +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; + +create index trgm_idx on test_trgm using gist (t gist_trgm_ops); +set enable_seqscan=off; + +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; +explain (costs off) +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; + +drop index trgm_idx; +create index trgm_idx on test_trgm using gist (t gist_trgm_ops(siglen=0)); +create index trgm_idx on test_trgm using gist (t gist_trgm_ops(siglen=2025)); +create index trgm_idx on test_trgm using gist (t gist_trgm_ops(siglen=2024)); +set enable_seqscan=off; + +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; +explain (costs off) +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; +select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2; +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; + +drop index trgm_idx; +create index trgm_idx on test_trgm using gin (t gin_trgm_ops); +set enable_seqscan=off; + +select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t; +select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t; +select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}'; + +-- check handling of indexquals that generate no searchable conditions +explain (costs off) +select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; +select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; +explain (costs off) +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; +-- ensure that pending-list items are handled correctly, too +create temp table t_test_trgm(t text COLLATE "C"); +create index t_trgm_idx on t_test_trgm using gin (t gin_trgm_ops); +insert into t_test_trgm values ('qwerty99'), ('qwerty01'); +explain (costs off) +select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; +select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; +explain (costs off) +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; + +-- run the same queries with sequential scan to check the results +set enable_bitmapscan=off; +set enable_seqscan=on; +select count(*) from test_trgm where t like '%99%' and t like '%qwerty%'; +select count(*) from test_trgm where t like '%99%' and t like '%qw%'; +select count(*) from t_test_trgm where t like '%99%' and t like '%qwerty%'; +select count(*) from t_test_trgm where t like '%99%' and t like '%qw%'; +reset enable_bitmapscan; + +create table test2(t text COLLATE "C"); +insert into test2 values ('abcdef'); +insert into test2 values ('quark'); +insert into test2 values (' z foo bar'); +insert into test2 values ('/123/-45/'); +insert into test2 values ('line 1'); +insert into test2 values ('%line 2'); +insert into test2 values ('line 3%'); +insert into test2 values ('%line 4%'); +insert into test2 values ('%li%ne 5%'); +insert into test2 values ('li_e 6'); +create index test2_idx_gin on test2 using gin (t gin_trgm_ops); +set enable_seqscan=off; +explain (costs off) + select * from test2 where t like '%BCD%'; +explain (costs off) + select * from test2 where t ilike '%BCD%'; +select * from test2 where t like '%BCD%'; +select * from test2 where t like '%bcd%'; +select * from test2 where t like E'%\\bcd%'; +select * from test2 where t ilike '%BCD%'; +select * from test2 where t ilike 'qua%'; +select * from test2 where t like '%z foo bar%'; +select * from test2 where t like ' z foo%'; +explain (costs off) + select * from test2 where t ~ '[abc]{3}'; +explain (costs off) + select * from test2 where t ~* 'DEF'; +select * from test2 where t ~ '[abc]{3}'; +select * from test2 where t ~ 'a[bc]+d'; +select * from test2 where t ~ '(abc)*$'; +select * from test2 where t ~* 'DEF'; +select * from test2 where t ~ 'dEf'; +select * from test2 where t ~* '^q'; +select * from test2 where t ~* '[abc]{3}[def]{3}'; +select * from test2 where t ~* 'ab[a-z]{3}'; +select * from test2 where t ~* '(^| )qua'; +select * from test2 where t ~ 'q.*rk$'; +select * from test2 where t ~ 'q'; +select * from test2 where t ~ '[a-z]{3}'; +select * from test2 where t ~* '(a{10}|b{10}|c{10}){10}'; +select * from test2 where t ~ 'z foo bar'; +select * from test2 where t ~ ' z foo bar'; +select * from test2 where t ~ ' z foo bar'; +select * from test2 where t ~ ' z foo'; +select * from test2 where t ~ 'qua(?!foo)'; +select * from test2 where t ~ '/\d+/-\d'; +-- test = operator +explain (costs off) + select * from test2 where t = 'abcdef'; +select * from test2 where t = 'abcdef'; +explain (costs off) + select * from test2 where t = '%line%'; +select * from test2 where t = '%line%'; +select * from test2 where t = 'li_e 1'; +select * from test2 where t = '%line 2'; +select * from test2 where t = 'line 3%'; +select * from test2 where t = '%line 3%'; +select * from test2 where t = '%line 4%'; +select * from test2 where t = '%line 5%'; +select * from test2 where t = '%li_ne 5%'; +select * from test2 where t = '%li%ne 5%'; +select * from test2 where t = 'line 6'; +select * from test2 where t = 'li_e 6'; +drop index test2_idx_gin; + +create index test2_idx_gist on test2 using gist (t gist_trgm_ops); +set enable_seqscan=off; +explain (costs off) + select * from test2 where t like '%BCD%'; +explain (costs off) + select * from test2 where t ilike '%BCD%'; +select * from test2 where t like '%BCD%'; +select * from test2 where t like '%bcd%'; +select * from test2 where t like E'%\\bcd%'; +select * from test2 where t ilike '%BCD%'; +select * from test2 where t ilike 'qua%'; +select * from test2 where t like '%z foo bar%'; +select * from test2 where t like ' z foo%'; +explain (costs off) + select * from test2 where t ~ '[abc]{3}'; +explain (costs off) + select * from test2 where t ~* 'DEF'; +select * from test2 where t ~ '[abc]{3}'; +select * from test2 where t ~ 'a[bc]+d'; +select * from test2 where t ~ '(abc)*$'; +select * from test2 where t ~* 'DEF'; +select * from test2 where t ~ 'dEf'; +select * from test2 where t ~* '^q'; +select * from test2 where t ~* '[abc]{3}[def]{3}'; +select * from test2 where t ~* 'ab[a-z]{3}'; +select * from test2 where t ~* '(^| )qua'; +select * from test2 where t ~ 'q.*rk$'; +select * from test2 where t ~ 'q'; +select * from test2 where t ~ '[a-z]{3}'; +select * from test2 where t ~* '(a{10}|b{10}|c{10}){10}'; +select * from test2 where t ~ 'z foo bar'; +select * from test2 where t ~ ' z foo bar'; +select * from test2 where t ~ ' z foo bar'; +select * from test2 where t ~ ' z foo'; +select * from test2 where t ~ 'qua(?!foo)'; +select * from test2 where t ~ '/\d+/-\d'; +-- test = operator +explain (costs off) + select * from test2 where t = 'abcdef'; +select * from test2 where t = 'abcdef'; +explain (costs off) + select * from test2 where t = '%line%'; +select * from test2 where t = '%line%'; +select * from test2 where t = 'li_e 1'; +select * from test2 where t = '%line 2'; +select * from test2 where t = 'line 3%'; +select * from test2 where t = '%line 3%'; +select * from test2 where t = '%line 4%'; +select * from test2 where t = '%line 5%'; +select * from test2 where t = '%li_ne 5%'; +select * from test2 where t = '%li%ne 5%'; +select * from test2 where t = 'line 6'; +select * from test2 where t = 'li_e 6'; + +-- Check similarity threshold (bug #14202) + +CREATE TEMP TABLE restaurants (city text); +INSERT INTO restaurants SELECT 'Warsaw' FROM generate_series(1, 10000); +INSERT INTO restaurants SELECT 'Szczecin' FROM generate_series(1, 10000); +CREATE INDEX ON restaurants USING gist(city gist_trgm_ops); + +-- Similarity of the two names (for reference). +SELECT similarity('Szczecin', 'Warsaw'); + +-- Should get only 'Warsaw' for either setting of set_limit. +EXPLAIN (COSTS OFF) +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; +SELECT set_limit(0.3); +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; +SELECT set_limit(0.5); +SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit() + FROM restaurants WHERE city % 'Warsaw'; diff --git a/contrib/pg_trgm/sql/pg_word_trgm.sql b/contrib/pg_trgm/sql/pg_word_trgm.sql new file mode 100644 index 0000000..d2ada49 --- /dev/null +++ b/contrib/pg_trgm/sql/pg_word_trgm.sql @@ -0,0 +1,48 @@ +CREATE TABLE test_trgm2(t text COLLATE "C"); + +\copy test_trgm2 from 'data/trgm2.data' + +-- reduce noise +set extra_float_digits = 0; + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; +select t <->> 'Kabankala', t from test_trgm2 order by t <->> 'Kabankala' limit 7; + +create index trgm_idx2 on test_trgm2 using gist (t gist_trgm_ops); +set enable_seqscan=off; + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + +explain (costs off) +select t <->> 'Kabankala', t from test_trgm2 order by t <->> 'Kabankala' limit 7; +select t <->> 'Kabankala', t from test_trgm2 order by t <->> 'Kabankala' limit 7; + +drop index trgm_idx2; +create index trgm_idx2 on test_trgm2 using gin (t gin_trgm_ops); +set enable_seqscan=off; + +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + +set "pg_trgm.word_similarity_threshold" to 0.5; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + +set "pg_trgm.word_similarity_threshold" to 0.3; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where 'Baykal' <% t order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where 'Kabankala' <% t order by sml desc, t; +select t,word_similarity('Baykal',t) as sml from test_trgm2 where t %> 'Baykal' order by sml desc, t; +select t,word_similarity('Kabankala',t) as sml from test_trgm2 where t %> 'Kabankala' order by sml desc, t; + +-- test unsatisfiable pattern +select * from test_trgm2 where t ~ '.*$x'; diff --git a/contrib/pg_trgm/trgm.h b/contrib/pg_trgm/trgm.h new file mode 100644 index 0000000..afb0adb --- /dev/null +++ b/contrib/pg_trgm/trgm.h @@ -0,0 +1,140 @@ +/* + * contrib/pg_trgm/trgm.h + */ +#ifndef __TRGM_H__ +#define __TRGM_H__ + +#include "access/gist.h" +#include "access/itup.h" +#include "access/stratnum.h" +#include "storage/bufpage.h" + +/* + * Options ... but note that trgm_regexp.c effectively assumes these values + * of LPADDING and RPADDING. + */ +#define LPADDING 2 +#define RPADDING 1 +#define KEEPONLYALNUM +/* + * Caution: IGNORECASE macro means that trigrams are case-insensitive. + * If this macro is disabled, the ~* and ~~* operators must be removed from + * the operator classes, because we can't handle case-insensitive wildcard + * search with case-sensitive trigrams. Failure to do this will result in + * "cannot handle ~*(~~*) with case-sensitive trigrams" errors. + */ +#define IGNORECASE +#define DIVUNION + +/* operator strategy numbers */ +#define SimilarityStrategyNumber 1 +#define DistanceStrategyNumber 2 +#define LikeStrategyNumber 3 +#define ILikeStrategyNumber 4 +#define RegExpStrategyNumber 5 +#define RegExpICaseStrategyNumber 6 +#define WordSimilarityStrategyNumber 7 +#define WordDistanceStrategyNumber 8 +#define StrictWordSimilarityStrategyNumber 9 +#define StrictWordDistanceStrategyNumber 10 +#define EqualStrategyNumber 11 + +typedef char trgm[3]; + +#define CMPCHAR(a,b) ( ((a)==(b)) ? 0 : ( ((a)<(b)) ? -1 : 1 ) ) +#define CMPPCHAR(a,b,i) CMPCHAR( *(((const char*)(a))+i), *(((const char*)(b))+i) ) +#define CMPTRGM(a,b) ( CMPPCHAR(a,b,0) ? CMPPCHAR(a,b,0) : ( CMPPCHAR(a,b,1) ? CMPPCHAR(a,b,1) : CMPPCHAR(a,b,2) ) ) + +#define CPTRGM(a,b) do { \ + *(((char*)(a))+0) = *(((char*)(b))+0); \ + *(((char*)(a))+1) = *(((char*)(b))+1); \ + *(((char*)(a))+2) = *(((char*)(b))+2); \ +} while(0) + +#ifdef KEEPONLYALNUM +#define ISWORDCHR(c) (t_isalnum(c)) +#define ISPRINTABLECHAR(a) ( isascii( *(unsigned char*)(a) ) && (isalnum( *(unsigned char*)(a) ) || *(unsigned char*)(a)==' ') ) +#else +#define ISWORDCHR(c) (!t_isspace(c)) +#define ISPRINTABLECHAR(a) ( isascii( *(unsigned char*)(a) ) && isprint( *(unsigned char*)(a) ) ) +#endif +#define ISPRINTABLETRGM(t) ( ISPRINTABLECHAR( ((char*)(t)) ) && ISPRINTABLECHAR( ((char*)(t))+1 ) && ISPRINTABLECHAR( ((char*)(t))+2 ) ) + +#define ISESCAPECHAR(x) (*(x) == '\\') /* Wildcard escape character */ +#define ISWILDCARDCHAR(x) (*(x) == '_' || *(x) == '%') /* Wildcard + * meta-character */ + +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + uint8 flag; + char data[FLEXIBLE_ARRAY_MEMBER]; +} TRGM; + +#define TRGMHDRSIZE (VARHDRSZ + sizeof(uint8)) + +/* gist */ +#define SIGLEN_DEFAULT (sizeof(int) * 3) +#define SIGLEN_MAX GISTMaxIndexKeySize +#define BITBYTE 8 + +#define SIGLENBIT(siglen) ((siglen) * BITBYTE - 1) /* see makesign */ + +typedef char *BITVECP; + +#define LOOPBYTE(siglen) \ + for (i = 0; i < (siglen); i++) + +#define GETBYTE(x,i) ( *( (BITVECP)(x) + (int)( (i) / BITBYTE ) ) ) +#define GETBITBYTE(x,i) ( (((char)(x)) >> (i)) & 0x01 ) +#define CLRBIT(x,i) GETBYTE(x,i) &= ~( 0x01 << ( (i) % BITBYTE ) ) +#define SETBIT(x,i) GETBYTE(x,i) |= ( 0x01 << ( (i) % BITBYTE ) ) +#define GETBIT(x,i) ( (GETBYTE(x,i) >> ( (i) % BITBYTE )) & 0x01 ) + +#define HASHVAL(val, siglen) (((unsigned int)(val)) % SIGLENBIT(siglen)) +#define HASH(sign, val, siglen) SETBIT((sign), HASHVAL(val, siglen)) + +#define ARRKEY 0x01 +#define SIGNKEY 0x02 +#define ALLISTRUE 0x04 + +#define ISARRKEY(x) ( ((TRGM*)x)->flag & ARRKEY ) +#define ISSIGNKEY(x) ( ((TRGM*)x)->flag & SIGNKEY ) +#define ISALLTRUE(x) ( ((TRGM*)x)->flag & ALLISTRUE ) + +#define CALCGTSIZE(flag, len) ( TRGMHDRSIZE + ( ( (flag) & ARRKEY ) ? ((len)*sizeof(trgm)) : (((flag) & ALLISTRUE) ? 0 : (len)) ) ) +#define GETSIGN(x) ( (BITVECP)( (char*)x+TRGMHDRSIZE ) ) +#define GETARR(x) ( (trgm*)( (char*)x+TRGMHDRSIZE ) ) +#define ARRNELEM(x) ( ( VARSIZE(x) - TRGMHDRSIZE )/sizeof(trgm) ) + +/* + * If DIVUNION is defined then similarity formula is: + * count / (len1 + len2 - count) + * else if DIVUNION is not defined then similarity formula is: + * count / max(len1, len2) + */ +#ifdef DIVUNION +#define CALCSML(count, len1, len2) ((float4) (count)) / ((float4) ((len1) + (len2) - (count))) +#else +#define CALCSML(count, len1, len2) ((float4) (count)) / ((float4) (((len1) > (len2)) ? (len1) : (len2))) +#endif + +typedef struct TrgmPackedGraph TrgmPackedGraph; + +extern double similarity_threshold; +extern double word_similarity_threshold; +extern double strict_word_similarity_threshold; + +extern double index_strategy_get_limit(StrategyNumber strategy); +extern uint32 trgm2int(trgm *ptr); +extern void compact_trigram(trgm *tptr, char *str, int bytelen); +extern TRGM *generate_trgm(char *str, int slen); +extern TRGM *generate_wildcard_trgm(const char *str, int slen); +extern float4 cnt_sml(TRGM *trg1, TRGM *trg2, bool inexact); +extern bool trgm_contained_by(TRGM *trg1, TRGM *trg2); +extern bool *trgm_presence_map(TRGM *query, TRGM *key); +extern TRGM *createTrgmNFA(text *text_re, Oid collation, + TrgmPackedGraph **graph, MemoryContext rcontext); +extern bool trigramsMatchGraph(TrgmPackedGraph *graph, bool *check); + +#endif /* __TRGM_H__ */ diff --git a/contrib/pg_trgm/trgm_gin.c b/contrib/pg_trgm/trgm_gin.c new file mode 100644 index 0000000..29a52ea --- /dev/null +++ b/contrib/pg_trgm/trgm_gin.c @@ -0,0 +1,360 @@ +/* + * contrib/pg_trgm/trgm_gin.c + */ +#include "postgres.h" + +#include "access/gin.h" +#include "access/stratnum.h" +#include "fmgr.h" +#include "trgm.h" +#include "varatt.h" + +PG_FUNCTION_INFO_V1(gin_extract_trgm); +PG_FUNCTION_INFO_V1(gin_extract_value_trgm); +PG_FUNCTION_INFO_V1(gin_extract_query_trgm); +PG_FUNCTION_INFO_V1(gin_trgm_consistent); +PG_FUNCTION_INFO_V1(gin_trgm_triconsistent); + +/* + * This function can only be called if a pre-9.1 version of the GIN operator + * class definition is present in the catalogs (probably as a consequence + * of upgrade-in-place). Cope. + */ +Datum +gin_extract_trgm(PG_FUNCTION_ARGS) +{ + if (PG_NARGS() == 3) + return gin_extract_value_trgm(fcinfo); + if (PG_NARGS() == 7) + return gin_extract_query_trgm(fcinfo); + elog(ERROR, "unexpected number of arguments to gin_extract_trgm"); + PG_RETURN_NULL(); +} + +Datum +gin_extract_value_trgm(PG_FUNCTION_ARGS) +{ + text *val = (text *) PG_GETARG_TEXT_PP(0); + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + Datum *entries = NULL; + TRGM *trg; + int32 trglen; + + *nentries = 0; + + trg = generate_trgm(VARDATA_ANY(val), VARSIZE_ANY_EXHDR(val)); + trglen = ARRNELEM(trg); + + if (trglen > 0) + { + trgm *ptr; + int32 i; + + *nentries = trglen; + entries = (Datum *) palloc(sizeof(Datum) * trglen); + + ptr = GETARR(trg); + for (i = 0; i < trglen; i++) + { + int32 item = trgm2int(ptr); + + entries[i] = Int32GetDatum(item); + ptr++; + } + } + + PG_RETURN_POINTER(entries); +} + +Datum +gin_extract_query_trgm(PG_FUNCTION_ARGS) +{ + text *val = (text *) PG_GETARG_TEXT_PP(0); + int32 *nentries = (int32 *) PG_GETARG_POINTER(1); + StrategyNumber strategy = PG_GETARG_UINT16(2); + + /* bool **pmatch = (bool **) PG_GETARG_POINTER(3); */ + Pointer **extra_data = (Pointer **) PG_GETARG_POINTER(4); + + /* bool **nullFlags = (bool **) PG_GETARG_POINTER(5); */ + int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); + Datum *entries = NULL; + TRGM *trg; + int32 trglen; + trgm *ptr; + TrgmPackedGraph *graph; + int32 i; + + switch (strategy) + { + case SimilarityStrategyNumber: + case WordSimilarityStrategyNumber: + case StrictWordSimilarityStrategyNumber: + case EqualStrategyNumber: + trg = generate_trgm(VARDATA_ANY(val), VARSIZE_ANY_EXHDR(val)); + break; + case ILikeStrategyNumber: +#ifndef IGNORECASE + elog(ERROR, "cannot handle ~~* with case-sensitive trigrams"); +#endif + /* FALL THRU */ + case LikeStrategyNumber: + + /* + * For wildcard search we extract all the trigrams that every + * potentially-matching string must include. + */ + trg = generate_wildcard_trgm(VARDATA_ANY(val), + VARSIZE_ANY_EXHDR(val)); + break; + case RegExpICaseStrategyNumber: +#ifndef IGNORECASE + elog(ERROR, "cannot handle ~* with case-sensitive trigrams"); +#endif + /* FALL THRU */ + case RegExpStrategyNumber: + trg = createTrgmNFA(val, PG_GET_COLLATION(), + &graph, CurrentMemoryContext); + if (trg && ARRNELEM(trg) > 0) + { + /* + * Successful regex processing: store NFA-like graph as + * extra_data. GIN API requires an array of nentries + * Pointers, but we just put the same value in each element. + */ + trglen = ARRNELEM(trg); + *extra_data = (Pointer *) palloc(sizeof(Pointer) * trglen); + for (i = 0; i < trglen; i++) + (*extra_data)[i] = (Pointer) graph; + } + else + { + /* No result: have to do full index scan. */ + *nentries = 0; + *searchMode = GIN_SEARCH_MODE_ALL; + PG_RETURN_POINTER(entries); + } + break; + default: + elog(ERROR, "unrecognized strategy number: %d", strategy); + trg = NULL; /* keep compiler quiet */ + break; + } + + trglen = ARRNELEM(trg); + *nentries = trglen; + + if (trglen > 0) + { + entries = (Datum *) palloc(sizeof(Datum) * trglen); + ptr = GETARR(trg); + for (i = 0; i < trglen; i++) + { + int32 item = trgm2int(ptr); + + entries[i] = Int32GetDatum(item); + ptr++; + } + } + + /* + * If no trigram was extracted then we have to scan all the index. + */ + if (trglen == 0) + *searchMode = GIN_SEARCH_MODE_ALL; + + PG_RETURN_POINTER(entries); +} + +Datum +gin_trgm_consistent(PG_FUNCTION_ARGS) +{ + bool *check = (bool *) PG_GETARG_POINTER(0); + StrategyNumber strategy = PG_GETARG_UINT16(1); + + /* text *query = PG_GETARG_TEXT_PP(2); */ + int32 nkeys = PG_GETARG_INT32(3); + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); + bool *recheck = (bool *) PG_GETARG_POINTER(5); + bool res; + int32 i, + ntrue; + double nlimit; + + /* All cases served by this function are inexact */ + *recheck = true; + + switch (strategy) + { + case SimilarityStrategyNumber: + case WordSimilarityStrategyNumber: + case StrictWordSimilarityStrategyNumber: + nlimit = index_strategy_get_limit(strategy); + + /* Count the matches */ + ntrue = 0; + for (i = 0; i < nkeys; i++) + { + if (check[i]) + ntrue++; + } + + /*-------------------- + * If DIVUNION is defined then similarity formula is: + * c / (len1 + len2 - c) + * where c is number of common trigrams and it stands as ntrue in + * this code. Here we don't know value of len2 but we can assume + * that c (ntrue) is a lower bound of len2, so upper bound of + * similarity is: + * c / (len1 + c - c) => c / len1 + * If DIVUNION is not defined then similarity formula is: + * c / max(len1, len2) + * And again, c (ntrue) is a lower bound of len2, but c <= len1 + * just by definition and, consequently, upper bound of + * similarity is just c / len1. + * So, independently on DIVUNION the upper bound formula is the same. + */ + res = (nkeys == 0) ? false : + (((((float4) ntrue) / ((float4) nkeys))) >= nlimit); + break; + case ILikeStrategyNumber: +#ifndef IGNORECASE + elog(ERROR, "cannot handle ~~* with case-sensitive trigrams"); +#endif + /* FALL THRU */ + case LikeStrategyNumber: + case EqualStrategyNumber: + /* Check if all extracted trigrams are presented. */ + res = true; + for (i = 0; i < nkeys; i++) + { + if (!check[i]) + { + res = false; + break; + } + } + break; + case RegExpICaseStrategyNumber: +#ifndef IGNORECASE + elog(ERROR, "cannot handle ~* with case-sensitive trigrams"); +#endif + /* FALL THRU */ + case RegExpStrategyNumber: + if (nkeys < 1) + { + /* Regex processing gave no result: do full index scan */ + res = true; + } + else + res = trigramsMatchGraph((TrgmPackedGraph *) extra_data[0], + check); + break; + default: + elog(ERROR, "unrecognized strategy number: %d", strategy); + res = false; /* keep compiler quiet */ + break; + } + + PG_RETURN_BOOL(res); +} + +/* + * In all cases, GIN_TRUE is at least as favorable to inclusion as + * GIN_MAYBE. If no better option is available, simply treat + * GIN_MAYBE as if it were GIN_TRUE and apply the same test as the binary + * consistent function. + */ +Datum +gin_trgm_triconsistent(PG_FUNCTION_ARGS) +{ + GinTernaryValue *check = (GinTernaryValue *) PG_GETARG_POINTER(0); + StrategyNumber strategy = PG_GETARG_UINT16(1); + + /* text *query = PG_GETARG_TEXT_PP(2); */ + int32 nkeys = PG_GETARG_INT32(3); + Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); + GinTernaryValue res = GIN_MAYBE; + int32 i, + ntrue; + bool *boolcheck; + double nlimit; + + switch (strategy) + { + case SimilarityStrategyNumber: + case WordSimilarityStrategyNumber: + case StrictWordSimilarityStrategyNumber: + nlimit = index_strategy_get_limit(strategy); + + /* Count the matches */ + ntrue = 0; + for (i = 0; i < nkeys; i++) + { + if (check[i] != GIN_FALSE) + ntrue++; + } + + /* + * See comment in gin_trgm_consistent() about * upper bound + * formula + */ + res = (nkeys == 0) + ? GIN_FALSE : (((((float4) ntrue) / ((float4) nkeys)) >= nlimit) + ? GIN_MAYBE : GIN_FALSE); + break; + case ILikeStrategyNumber: +#ifndef IGNORECASE + elog(ERROR, "cannot handle ~~* with case-sensitive trigrams"); +#endif + /* FALL THRU */ + case LikeStrategyNumber: + case EqualStrategyNumber: + /* Check if all extracted trigrams are presented. */ + res = GIN_MAYBE; + for (i = 0; i < nkeys; i++) + { + if (check[i] == GIN_FALSE) + { + res = GIN_FALSE; + break; + } + } + break; + case RegExpICaseStrategyNumber: +#ifndef IGNORECASE + elog(ERROR, "cannot handle ~* with case-sensitive trigrams"); +#endif + /* FALL THRU */ + case RegExpStrategyNumber: + if (nkeys < 1) + { + /* Regex processing gave no result: do full index scan */ + res = GIN_MAYBE; + } + else + { + /* + * As trigramsMatchGraph implements a monotonic boolean + * function, promoting all GIN_MAYBE keys to GIN_TRUE will + * give a conservative result. + */ + boolcheck = (bool *) palloc(sizeof(bool) * nkeys); + for (i = 0; i < nkeys; i++) + boolcheck[i] = (check[i] != GIN_FALSE); + if (!trigramsMatchGraph((TrgmPackedGraph *) extra_data[0], + boolcheck)) + res = GIN_FALSE; + pfree(boolcheck); + } + break; + default: + elog(ERROR, "unrecognized strategy number: %d", strategy); + res = GIN_FALSE; /* keep compiler quiet */ + break; + } + + /* All cases served by this function are inexact */ + Assert(res != GIN_TRUE); + PG_RETURN_GIN_TERNARY_VALUE(res); +} diff --git a/contrib/pg_trgm/trgm_gist.c b/contrib/pg_trgm/trgm_gist.c new file mode 100644 index 0000000..9ef2e38 --- /dev/null +++ b/contrib/pg_trgm/trgm_gist.c @@ -0,0 +1,971 @@ +/* + * contrib/pg_trgm/trgm_gist.c + */ +#include "postgres.h" + +#include "access/reloptions.h" +#include "access/stratnum.h" +#include "fmgr.h" +#include "port/pg_bitutils.h" +#include "trgm.h" +#include "varatt.h" + +/* gist_trgm_ops opclass options */ +typedef struct +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int siglen; /* signature length in bytes */ +} TrgmGistOptions; + +#define GET_SIGLEN() (PG_HAS_OPCLASS_OPTIONS() ? \ + ((TrgmGistOptions *) PG_GET_OPCLASS_OPTIONS())->siglen : \ + SIGLEN_DEFAULT) + +typedef struct +{ + /* most recent inputs to gtrgm_consistent */ + StrategyNumber strategy; + text *query; + /* extracted trigrams for query */ + TRGM *trigrams; + /* if a regex operator, the extracted graph */ + TrgmPackedGraph *graph; + + /* + * The "query" and "trigrams" are stored in the same palloc block as this + * cache struct, at MAXALIGN'ed offsets. The graph however isn't. + */ +} gtrgm_consistent_cache; + +#define GETENTRY(vec,pos) ((TRGM *) DatumGetPointer((vec)->vector[(pos)].key)) + + +PG_FUNCTION_INFO_V1(gtrgm_in); +PG_FUNCTION_INFO_V1(gtrgm_out); +PG_FUNCTION_INFO_V1(gtrgm_compress); +PG_FUNCTION_INFO_V1(gtrgm_decompress); +PG_FUNCTION_INFO_V1(gtrgm_consistent); +PG_FUNCTION_INFO_V1(gtrgm_distance); +PG_FUNCTION_INFO_V1(gtrgm_union); +PG_FUNCTION_INFO_V1(gtrgm_same); +PG_FUNCTION_INFO_V1(gtrgm_penalty); +PG_FUNCTION_INFO_V1(gtrgm_picksplit); +PG_FUNCTION_INFO_V1(gtrgm_options); + + +Datum +gtrgm_in(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type %s", "gtrgm"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +Datum +gtrgm_out(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot display a value of type %s", "gtrgm"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +static TRGM * +gtrgm_alloc(bool isalltrue, int siglen, BITVECP sign) +{ + int flag = SIGNKEY | (isalltrue ? ALLISTRUE : 0); + int size = CALCGTSIZE(flag, siglen); + TRGM *res = palloc(size); + + SET_VARSIZE(res, size); + res->flag = flag; + + if (!isalltrue) + { + if (sign) + memcpy(GETSIGN(res), sign, siglen); + else + memset(GETSIGN(res), 0, siglen); + } + + return res; +} + +static void +makesign(BITVECP sign, TRGM *a, int siglen) +{ + int32 k, + len = ARRNELEM(a); + trgm *ptr = GETARR(a); + int32 tmp = 0; + + MemSet(sign, 0, siglen); + SETBIT(sign, SIGLENBIT(siglen)); /* set last unused bit */ + for (k = 0; k < len; k++) + { + CPTRGM(((char *) &tmp), ptr + k); + HASH(sign, tmp, siglen); + } +} + +Datum +gtrgm_compress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int siglen = GET_SIGLEN(); + GISTENTRY *retval = entry; + + if (entry->leafkey) + { /* trgm */ + TRGM *res; + text *val = DatumGetTextPP(entry->key); + + res = generate_trgm(VARDATA_ANY(val), VARSIZE_ANY_EXHDR(val)); + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(res), + entry->rel, entry->page, + entry->offset, false); + } + else if (ISSIGNKEY(DatumGetPointer(entry->key)) && + !ISALLTRUE(DatumGetPointer(entry->key))) + { + int32 i; + TRGM *res; + BITVECP sign = GETSIGN(DatumGetPointer(entry->key)); + + LOOPBYTE(siglen) + { + if ((sign[i] & 0xff) != 0xff) + PG_RETURN_POINTER(retval); + } + + res = gtrgm_alloc(true, siglen, sign); + retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(res), + entry->rel, entry->page, + entry->offset, false); + } + PG_RETURN_POINTER(retval); +} + +Datum +gtrgm_decompress(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *retval; + text *key; + + key = DatumGetTextPP(entry->key); + + if (key != (text *) DatumGetPointer(entry->key)) + { + /* need to pass back the decompressed item */ + retval = palloc(sizeof(GISTENTRY)); + gistentryinit(*retval, PointerGetDatum(key), + entry->rel, entry->page, entry->offset, entry->leafkey); + PG_RETURN_POINTER(retval); + } + else + { + /* we can return the entry as-is */ + PG_RETURN_POINTER(entry); + } +} + +static int32 +cnt_sml_sign_common(TRGM *qtrg, BITVECP sign, int siglen) +{ + int32 count = 0; + int32 k, + len = ARRNELEM(qtrg); + trgm *ptr = GETARR(qtrg); + int32 tmp = 0; + + for (k = 0; k < len; k++) + { + CPTRGM(((char *) &tmp), ptr + k); + count += GETBIT(sign, HASHVAL(tmp, siglen)); + } + + return count; +} + +Datum +gtrgm_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + text *query = PG_GETARG_TEXT_P(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + int siglen = GET_SIGLEN(); + TRGM *key = (TRGM *) DatumGetPointer(entry->key); + TRGM *qtrg; + bool res; + Size querysize = VARSIZE(query); + gtrgm_consistent_cache *cache; + double nlimit; + + /* + * We keep the extracted trigrams in cache, because trigram extraction is + * relatively CPU-expensive. When trying to reuse a cached value, check + * strategy number not just query itself, because trigram extraction + * depends on strategy. + * + * The cached structure is a single palloc chunk containing the + * gtrgm_consistent_cache header, then the input query (4-byte length + * word, uncompressed, starting at a MAXALIGN boundary), then the TRGM + * value (also starting at a MAXALIGN boundary). However we don't try to + * include the regex graph (if any) in that struct. (XXX currently, this + * approach can leak regex graphs across index rescans. Not clear if + * that's worth fixing.) + */ + cache = (gtrgm_consistent_cache *) fcinfo->flinfo->fn_extra; + if (cache == NULL || + cache->strategy != strategy || + VARSIZE(cache->query) != querysize || + memcmp((char *) cache->query, (char *) query, querysize) != 0) + { + gtrgm_consistent_cache *newcache; + TrgmPackedGraph *graph = NULL; + Size qtrgsize; + + switch (strategy) + { + case SimilarityStrategyNumber: + case WordSimilarityStrategyNumber: + case StrictWordSimilarityStrategyNumber: + case EqualStrategyNumber: + qtrg = generate_trgm(VARDATA(query), + querysize - VARHDRSZ); + break; + case ILikeStrategyNumber: +#ifndef IGNORECASE + elog(ERROR, "cannot handle ~~* with case-sensitive trigrams"); +#endif + /* FALL THRU */ + case LikeStrategyNumber: + qtrg = generate_wildcard_trgm(VARDATA(query), + querysize - VARHDRSZ); + break; + case RegExpICaseStrategyNumber: +#ifndef IGNORECASE + elog(ERROR, "cannot handle ~* with case-sensitive trigrams"); +#endif + /* FALL THRU */ + case RegExpStrategyNumber: + qtrg = createTrgmNFA(query, PG_GET_COLLATION(), + &graph, fcinfo->flinfo->fn_mcxt); + /* just in case an empty array is returned ... */ + if (qtrg && ARRNELEM(qtrg) <= 0) + { + pfree(qtrg); + qtrg = NULL; + } + break; + default: + elog(ERROR, "unrecognized strategy number: %d", strategy); + qtrg = NULL; /* keep compiler quiet */ + break; + } + + qtrgsize = qtrg ? VARSIZE(qtrg) : 0; + + newcache = (gtrgm_consistent_cache *) + MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + MAXALIGN(sizeof(gtrgm_consistent_cache)) + + MAXALIGN(querysize) + + qtrgsize); + + newcache->strategy = strategy; + newcache->query = (text *) + ((char *) newcache + MAXALIGN(sizeof(gtrgm_consistent_cache))); + memcpy((char *) newcache->query, (char *) query, querysize); + if (qtrg) + { + newcache->trigrams = (TRGM *) + ((char *) newcache->query + MAXALIGN(querysize)); + memcpy((char *) newcache->trigrams, (char *) qtrg, qtrgsize); + /* release qtrg in case it was made in fn_mcxt */ + pfree(qtrg); + } + else + newcache->trigrams = NULL; + newcache->graph = graph; + + if (cache) + pfree(cache); + fcinfo->flinfo->fn_extra = (void *) newcache; + cache = newcache; + } + + qtrg = cache->trigrams; + + switch (strategy) + { + case SimilarityStrategyNumber: + case WordSimilarityStrategyNumber: + case StrictWordSimilarityStrategyNumber: + + /* + * Similarity search is exact. (Strict) word similarity search is + * inexact + */ + *recheck = (strategy != SimilarityStrategyNumber); + + nlimit = index_strategy_get_limit(strategy); + + if (GIST_LEAF(entry)) + { /* all leafs contains orig trgm */ + double tmpsml = cnt_sml(qtrg, key, *recheck); + + res = (tmpsml >= nlimit); + } + else if (ISALLTRUE(key)) + { /* non-leaf contains signature */ + res = true; + } + else + { /* non-leaf contains signature */ + int32 count = cnt_sml_sign_common(qtrg, GETSIGN(key), siglen); + int32 len = ARRNELEM(qtrg); + + if (len == 0) + res = false; + else + res = (((((float8) count) / ((float8) len))) >= nlimit); + } + break; + case ILikeStrategyNumber: +#ifndef IGNORECASE + elog(ERROR, "cannot handle ~~* with case-sensitive trigrams"); +#endif + /* FALL THRU */ + case LikeStrategyNumber: + case EqualStrategyNumber: + /* Wildcard and equal search are inexact */ + *recheck = true; + + /* + * Check if all the extracted trigrams can be present in child + * nodes. + */ + if (GIST_LEAF(entry)) + { /* all leafs contains orig trgm */ + res = trgm_contained_by(qtrg, key); + } + else if (ISALLTRUE(key)) + { /* non-leaf contains signature */ + res = true; + } + else + { /* non-leaf contains signature */ + int32 k, + tmp = 0, + len = ARRNELEM(qtrg); + trgm *ptr = GETARR(qtrg); + BITVECP sign = GETSIGN(key); + + res = true; + for (k = 0; k < len; k++) + { + CPTRGM(((char *) &tmp), ptr + k); + if (!GETBIT(sign, HASHVAL(tmp, siglen))) + { + res = false; + break; + } + } + } + break; + case RegExpICaseStrategyNumber: +#ifndef IGNORECASE + elog(ERROR, "cannot handle ~* with case-sensitive trigrams"); +#endif + /* FALL THRU */ + case RegExpStrategyNumber: + /* Regexp search is inexact */ + *recheck = true; + + /* Check regex match as much as we can with available info */ + if (qtrg) + { + if (GIST_LEAF(entry)) + { /* all leafs contains orig trgm */ + bool *check; + + check = trgm_presence_map(qtrg, key); + res = trigramsMatchGraph(cache->graph, check); + pfree(check); + } + else if (ISALLTRUE(key)) + { /* non-leaf contains signature */ + res = true; + } + else + { /* non-leaf contains signature */ + int32 k, + tmp = 0, + len = ARRNELEM(qtrg); + trgm *ptr = GETARR(qtrg); + BITVECP sign = GETSIGN(key); + bool *check; + + /* + * GETBIT() tests may give false positives, due to limited + * size of the sign array. But since trigramsMatchGraph() + * implements a monotone boolean function, false positives + * in the check array can't lead to false negative answer. + * So we can apply trigramsMatchGraph despite uncertainty, + * and that usefully improves the quality of the search. + */ + check = (bool *) palloc(len * sizeof(bool)); + for (k = 0; k < len; k++) + { + CPTRGM(((char *) &tmp), ptr + k); + check[k] = GETBIT(sign, HASHVAL(tmp, siglen)); + } + res = trigramsMatchGraph(cache->graph, check); + pfree(check); + } + } + else + { + /* trigram-free query must be rechecked everywhere */ + res = true; + } + break; + default: + elog(ERROR, "unrecognized strategy number: %d", strategy); + res = false; /* keep compiler quiet */ + break; + } + + PG_RETURN_BOOL(res); +} + +Datum +gtrgm_distance(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + text *query = PG_GETARG_TEXT_P(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + int siglen = GET_SIGLEN(); + TRGM *key = (TRGM *) DatumGetPointer(entry->key); + TRGM *qtrg; + float8 res; + Size querysize = VARSIZE(query); + char *cache = (char *) fcinfo->flinfo->fn_extra; + + /* + * Cache the generated trigrams across multiple calls with the same query. + */ + if (cache == NULL || + VARSIZE(cache) != querysize || + memcmp(cache, query, querysize) != 0) + { + char *newcache; + + qtrg = generate_trgm(VARDATA(query), querysize - VARHDRSZ); + + newcache = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + MAXALIGN(querysize) + + VARSIZE(qtrg)); + + memcpy(newcache, query, querysize); + memcpy(newcache + MAXALIGN(querysize), qtrg, VARSIZE(qtrg)); + + if (cache) + pfree(cache); + fcinfo->flinfo->fn_extra = newcache; + cache = newcache; + } + + qtrg = (TRGM *) (cache + MAXALIGN(querysize)); + + switch (strategy) + { + case DistanceStrategyNumber: + case WordDistanceStrategyNumber: + case StrictWordDistanceStrategyNumber: + /* Only plain trigram distance is exact */ + *recheck = (strategy != DistanceStrategyNumber); + if (GIST_LEAF(entry)) + { /* all leafs contains orig trgm */ + + /* + * Prevent gcc optimizing the sml variable using volatile + * keyword. Otherwise res can differ from the + * word_similarity_dist_op() function. + */ + float4 volatile sml = cnt_sml(qtrg, key, *recheck); + + res = 1.0 - sml; + } + else if (ISALLTRUE(key)) + { /* all leafs contains orig trgm */ + res = 0.0; + } + else + { /* non-leaf contains signature */ + int32 count = cnt_sml_sign_common(qtrg, GETSIGN(key), siglen); + int32 len = ARRNELEM(qtrg); + + res = (len == 0) ? -1.0 : 1.0 - ((float8) count) / ((float8) len); + } + break; + default: + elog(ERROR, "unrecognized strategy number: %d", strategy); + res = 0; /* keep compiler quiet */ + break; + } + + PG_RETURN_FLOAT8(res); +} + +static int32 +unionkey(BITVECP sbase, TRGM *add, int siglen) +{ + int32 i; + + if (ISSIGNKEY(add)) + { + BITVECP sadd = GETSIGN(add); + + if (ISALLTRUE(add)) + return 1; + + LOOPBYTE(siglen) + sbase[i] |= sadd[i]; + } + else + { + trgm *ptr = GETARR(add); + int32 tmp = 0; + + for (i = 0; i < ARRNELEM(add); i++) + { + CPTRGM(((char *) &tmp), ptr + i); + HASH(sbase, tmp, siglen); + } + } + return 0; +} + + +Datum +gtrgm_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int32 len = entryvec->n; + int *size = (int *) PG_GETARG_POINTER(1); + int siglen = GET_SIGLEN(); + int32 i; + TRGM *result = gtrgm_alloc(false, siglen, NULL); + BITVECP base = GETSIGN(result); + + for (i = 0; i < len; i++) + { + if (unionkey(base, GETENTRY(entryvec, i), siglen)) + { + result->flag = ALLISTRUE; + SET_VARSIZE(result, CALCGTSIZE(ALLISTRUE, siglen)); + break; + } + } + + *size = VARSIZE(result); + + PG_RETURN_POINTER(result); +} + +Datum +gtrgm_same(PG_FUNCTION_ARGS) +{ + TRGM *a = (TRGM *) PG_GETARG_POINTER(0); + TRGM *b = (TRGM *) PG_GETARG_POINTER(1); + bool *result = (bool *) PG_GETARG_POINTER(2); + int siglen = GET_SIGLEN(); + + if (ISSIGNKEY(a)) + { /* then b also ISSIGNKEY */ + if (ISALLTRUE(a) && ISALLTRUE(b)) + *result = true; + else if (ISALLTRUE(a)) + *result = false; + else if (ISALLTRUE(b)) + *result = false; + else + { + int32 i; + BITVECP sa = GETSIGN(a), + sb = GETSIGN(b); + + *result = true; + LOOPBYTE(siglen) + { + if (sa[i] != sb[i]) + { + *result = false; + break; + } + } + } + } + else + { /* a and b ISARRKEY */ + int32 lena = ARRNELEM(a), + lenb = ARRNELEM(b); + + if (lena != lenb) + *result = false; + else + { + trgm *ptra = GETARR(a), + *ptrb = GETARR(b); + int32 i; + + *result = true; + for (i = 0; i < lena; i++) + if (CMPTRGM(ptra + i, ptrb + i)) + { + *result = false; + break; + } + } + } + + PG_RETURN_POINTER(result); +} + +static int32 +sizebitvec(BITVECP sign, int siglen) +{ + return pg_popcount(sign, siglen); +} + +static int +hemdistsign(BITVECP a, BITVECP b, int siglen) +{ + int i, + diff, + dist = 0; + + LOOPBYTE(siglen) + { + diff = (unsigned char) (a[i] ^ b[i]); + /* Using the popcount functions here isn't likely to win */ + dist += pg_number_of_ones[diff]; + } + return dist; +} + +static int +hemdist(TRGM *a, TRGM *b, int siglen) +{ + if (ISALLTRUE(a)) + { + if (ISALLTRUE(b)) + return 0; + else + return SIGLENBIT(siglen) - sizebitvec(GETSIGN(b), siglen); + } + else if (ISALLTRUE(b)) + return SIGLENBIT(siglen) - sizebitvec(GETSIGN(a), siglen); + + return hemdistsign(GETSIGN(a), GETSIGN(b), siglen); +} + +Datum +gtrgm_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); /* always ISSIGNKEY */ + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *penalty = (float *) PG_GETARG_POINTER(2); + int siglen = GET_SIGLEN(); + TRGM *origval = (TRGM *) DatumGetPointer(origentry->key); + TRGM *newval = (TRGM *) DatumGetPointer(newentry->key); + BITVECP orig = GETSIGN(origval); + + *penalty = 0.0; + + if (ISARRKEY(newval)) + { + char *cache = (char *) fcinfo->flinfo->fn_extra; + TRGM *cachedVal = (TRGM *) (cache + MAXALIGN(siglen)); + Size newvalsize = VARSIZE(newval); + BITVECP sign; + + /* + * Cache the sign data across multiple calls with the same newval. + */ + if (cache == NULL || + VARSIZE(cachedVal) != newvalsize || + memcmp(cachedVal, newval, newvalsize) != 0) + { + char *newcache; + + newcache = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + MAXALIGN(siglen) + + newvalsize); + + makesign((BITVECP) newcache, newval, siglen); + + cachedVal = (TRGM *) (newcache + MAXALIGN(siglen)); + memcpy(cachedVal, newval, newvalsize); + + if (cache) + pfree(cache); + fcinfo->flinfo->fn_extra = newcache; + cache = newcache; + } + + sign = (BITVECP) cache; + + if (ISALLTRUE(origval)) + *penalty = ((float) (SIGLENBIT(siglen) - sizebitvec(sign, siglen))) / (float) (SIGLENBIT(siglen) + 1); + else + *penalty = hemdistsign(sign, orig, siglen); + } + else + *penalty = hemdist(origval, newval, siglen); + PG_RETURN_POINTER(penalty); +} + +typedef struct +{ + bool allistrue; + BITVECP sign; +} CACHESIGN; + +static void +fillcache(CACHESIGN *item, TRGM *key, BITVECP sign, int siglen) +{ + item->allistrue = false; + item->sign = sign; + if (ISARRKEY(key)) + makesign(item->sign, key, siglen); + else if (ISALLTRUE(key)) + item->allistrue = true; + else + memcpy(item->sign, GETSIGN(key), siglen); +} + +#define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) ) +typedef struct +{ + OffsetNumber pos; + int32 cost; +} SPLITCOST; + +static int +comparecost(const void *a, const void *b) +{ + if (((const SPLITCOST *) a)->cost == ((const SPLITCOST *) b)->cost) + return 0; + else + return (((const SPLITCOST *) a)->cost > ((const SPLITCOST *) b)->cost) ? 1 : -1; +} + + +static int +hemdistcache(CACHESIGN *a, CACHESIGN *b, int siglen) +{ + if (a->allistrue) + { + if (b->allistrue) + return 0; + else + return SIGLENBIT(siglen) - sizebitvec(b->sign, siglen); + } + else if (b->allistrue) + return SIGLENBIT(siglen) - sizebitvec(a->sign, siglen); + + return hemdistsign(a->sign, b->sign, siglen); +} + +Datum +gtrgm_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + OffsetNumber maxoff = entryvec->n - 1; + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + int siglen = GET_SIGLEN(); + OffsetNumber k, + j; + TRGM *datum_l, + *datum_r; + BITVECP union_l, + union_r; + int32 size_alpha, + size_beta; + int32 size_waste, + waste = -1; + int32 nbytes; + OffsetNumber seed_1 = 0, + seed_2 = 0; + OffsetNumber *left, + *right; + BITVECP ptr; + int i; + CACHESIGN *cache; + char *cache_sign; + SPLITCOST *costvector; + + /* cache the sign data for each existing item */ + cache = (CACHESIGN *) palloc(sizeof(CACHESIGN) * (maxoff + 1)); + cache_sign = palloc(siglen * (maxoff + 1)); + + for (k = FirstOffsetNumber; k <= maxoff; k = OffsetNumberNext(k)) + fillcache(&cache[k], GETENTRY(entryvec, k), &cache_sign[siglen * k], + siglen); + + /* now find the two furthest-apart items */ + for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k)) + { + for (j = OffsetNumberNext(k); j <= maxoff; j = OffsetNumberNext(j)) + { + size_waste = hemdistcache(&(cache[j]), &(cache[k]), siglen); + if (size_waste > waste) + { + waste = size_waste; + seed_1 = k; + seed_2 = j; + } + } + } + + /* just in case we didn't make a selection ... */ + if (seed_1 == 0 || seed_2 == 0) + { + seed_1 = 1; + seed_2 = 2; + } + + /* initialize the result vectors */ + nbytes = maxoff * sizeof(OffsetNumber); + v->spl_left = left = (OffsetNumber *) palloc(nbytes); + v->spl_right = right = (OffsetNumber *) palloc(nbytes); + v->spl_nleft = 0; + v->spl_nright = 0; + + /* form initial .. */ + datum_l = gtrgm_alloc(cache[seed_1].allistrue, siglen, cache[seed_1].sign); + datum_r = gtrgm_alloc(cache[seed_2].allistrue, siglen, cache[seed_2].sign); + + union_l = GETSIGN(datum_l); + union_r = GETSIGN(datum_r); + + /* sort before ... */ + costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff); + for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) + { + costvector[j - 1].pos = j; + size_alpha = hemdistcache(&(cache[seed_1]), &(cache[j]), siglen); + size_beta = hemdistcache(&(cache[seed_2]), &(cache[j]), siglen); + costvector[j - 1].cost = abs(size_alpha - size_beta); + } + qsort(costvector, maxoff, sizeof(SPLITCOST), comparecost); + + for (k = 0; k < maxoff; k++) + { + j = costvector[k].pos; + if (j == seed_1) + { + *left++ = j; + v->spl_nleft++; + continue; + } + else if (j == seed_2) + { + *right++ = j; + v->spl_nright++; + continue; + } + + if (ISALLTRUE(datum_l) || cache[j].allistrue) + { + if (ISALLTRUE(datum_l) && cache[j].allistrue) + size_alpha = 0; + else + size_alpha = SIGLENBIT(siglen) - + sizebitvec((cache[j].allistrue) ? GETSIGN(datum_l) : + GETSIGN(cache[j].sign), + siglen); + } + else + size_alpha = hemdistsign(cache[j].sign, GETSIGN(datum_l), siglen); + + if (ISALLTRUE(datum_r) || cache[j].allistrue) + { + if (ISALLTRUE(datum_r) && cache[j].allistrue) + size_beta = 0; + else + size_beta = SIGLENBIT(siglen) - + sizebitvec((cache[j].allistrue) ? GETSIGN(datum_r) : + GETSIGN(cache[j].sign), + siglen); + } + else + size_beta = hemdistsign(cache[j].sign, GETSIGN(datum_r), siglen); + + if (size_alpha < size_beta + WISH_F(v->spl_nleft, v->spl_nright, 0.1)) + { + if (ISALLTRUE(datum_l) || cache[j].allistrue) + { + if (!ISALLTRUE(datum_l)) + memset(GETSIGN(datum_l), 0xff, siglen); + } + else + { + ptr = cache[j].sign; + LOOPBYTE(siglen) + union_l[i] |= ptr[i]; + } + *left++ = j; + v->spl_nleft++; + } + else + { + if (ISALLTRUE(datum_r) || cache[j].allistrue) + { + if (!ISALLTRUE(datum_r)) + memset(GETSIGN(datum_r), 0xff, siglen); + } + else + { + ptr = cache[j].sign; + LOOPBYTE(siglen) + union_r[i] |= ptr[i]; + } + *right++ = j; + v->spl_nright++; + } + } + + v->spl_ldatum = PointerGetDatum(datum_l); + v->spl_rdatum = PointerGetDatum(datum_r); + + PG_RETURN_POINTER(v); +} + +Datum +gtrgm_options(PG_FUNCTION_ARGS) +{ + local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0); + + init_local_reloptions(relopts, sizeof(TrgmGistOptions)); + add_local_int_reloption(relopts, "siglen", + "signature length in bytes", + SIGLEN_DEFAULT, 1, SIGLEN_MAX, + offsetof(TrgmGistOptions, siglen)); + + PG_RETURN_VOID(); +} diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c new file mode 100644 index 0000000..49d4497 --- /dev/null +++ b/contrib/pg_trgm/trgm_op.c @@ -0,0 +1,1323 @@ +/* + * contrib/pg_trgm/trgm_op.c + */ +#include "postgres.h" + +#include + +#include "catalog/pg_type.h" +#include "lib/qunique.h" +#include "miscadmin.h" +#include "trgm.h" +#include "tsearch/ts_locale.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/pg_crc.h" + +PG_MODULE_MAGIC; + +/* GUC variables */ +double similarity_threshold = 0.3f; +double word_similarity_threshold = 0.6f; +double strict_word_similarity_threshold = 0.5f; + +PG_FUNCTION_INFO_V1(set_limit); +PG_FUNCTION_INFO_V1(show_limit); +PG_FUNCTION_INFO_V1(show_trgm); +PG_FUNCTION_INFO_V1(similarity); +PG_FUNCTION_INFO_V1(word_similarity); +PG_FUNCTION_INFO_V1(strict_word_similarity); +PG_FUNCTION_INFO_V1(similarity_dist); +PG_FUNCTION_INFO_V1(similarity_op); +PG_FUNCTION_INFO_V1(word_similarity_op); +PG_FUNCTION_INFO_V1(word_similarity_commutator_op); +PG_FUNCTION_INFO_V1(word_similarity_dist_op); +PG_FUNCTION_INFO_V1(word_similarity_dist_commutator_op); +PG_FUNCTION_INFO_V1(strict_word_similarity_op); +PG_FUNCTION_INFO_V1(strict_word_similarity_commutator_op); +PG_FUNCTION_INFO_V1(strict_word_similarity_dist_op); +PG_FUNCTION_INFO_V1(strict_word_similarity_dist_commutator_op); + +/* Trigram with position */ +typedef struct +{ + trgm trg; + int index; +} pos_trgm; + +/* Trigram bound type */ +typedef uint8 TrgmBound; +#define TRGM_BOUND_LEFT 0x01 /* trigram is left bound of word */ +#define TRGM_BOUND_RIGHT 0x02 /* trigram is right bound of word */ + +/* Word similarity flags */ +#define WORD_SIMILARITY_CHECK_ONLY 0x01 /* only check existence of similar + * search pattern in text */ +#define WORD_SIMILARITY_STRICT 0x02 /* force bounds of extent to match + * word bounds */ + +/* + * Module load callback + */ +void +_PG_init(void) +{ + /* Define custom GUC variables. */ + DefineCustomRealVariable("pg_trgm.similarity_threshold", + "Sets the threshold used by the % operator.", + "Valid range is 0.0 .. 1.0.", + &similarity_threshold, + 0.3f, + 0.0, + 1.0, + PGC_USERSET, + 0, + NULL, + NULL, + NULL); + DefineCustomRealVariable("pg_trgm.word_similarity_threshold", + "Sets the threshold used by the <% operator.", + "Valid range is 0.0 .. 1.0.", + &word_similarity_threshold, + 0.6f, + 0.0, + 1.0, + PGC_USERSET, + 0, + NULL, + NULL, + NULL); + DefineCustomRealVariable("pg_trgm.strict_word_similarity_threshold", + "Sets the threshold used by the <<% operator.", + "Valid range is 0.0 .. 1.0.", + &strict_word_similarity_threshold, + 0.5f, + 0.0, + 1.0, + PGC_USERSET, + 0, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("pg_trgm"); +} + +/* + * Deprecated function. + * Use "pg_trgm.similarity_threshold" GUC variable instead of this function. + */ +Datum +set_limit(PG_FUNCTION_ARGS) +{ + float4 nlimit = PG_GETARG_FLOAT4(0); + char *nlimit_str; + Oid func_out_oid; + bool is_varlena; + + getTypeOutputInfo(FLOAT4OID, &func_out_oid, &is_varlena); + + nlimit_str = OidOutputFunctionCall(func_out_oid, Float4GetDatum(nlimit)); + + SetConfigOption("pg_trgm.similarity_threshold", nlimit_str, + PGC_USERSET, PGC_S_SESSION); + + PG_RETURN_FLOAT4(similarity_threshold); +} + + +/* + * Get similarity threshold for given index scan strategy number. + */ +double +index_strategy_get_limit(StrategyNumber strategy) +{ + switch (strategy) + { + case SimilarityStrategyNumber: + return similarity_threshold; + case WordSimilarityStrategyNumber: + return word_similarity_threshold; + case StrictWordSimilarityStrategyNumber: + return strict_word_similarity_threshold; + default: + elog(ERROR, "unrecognized strategy number: %d", strategy); + break; + } + + return 0.0; /* keep compiler quiet */ +} + +/* + * Deprecated function. + * Use "pg_trgm.similarity_threshold" GUC variable instead of this function. + */ +Datum +show_limit(PG_FUNCTION_ARGS) +{ + PG_RETURN_FLOAT4(similarity_threshold); +} + +static int +comp_trgm(const void *a, const void *b) +{ + return CMPTRGM(a, b); +} + +/* + * Finds first word in string, returns pointer to the word, + * endword points to the character after word + */ +static char * +find_word(char *str, int lenstr, char **endword, int *charlen) +{ + char *beginword = str; + + while (beginword - str < lenstr && !ISWORDCHR(beginword)) + beginword += pg_mblen(beginword); + + if (beginword - str >= lenstr) + return NULL; + + *endword = beginword; + *charlen = 0; + while (*endword - str < lenstr && ISWORDCHR(*endword)) + { + *endword += pg_mblen(*endword); + (*charlen)++; + } + + return beginword; +} + +/* + * Reduce a trigram (three possibly multi-byte characters) to a trgm, + * which is always exactly three bytes. If we have three single-byte + * characters, we just use them as-is; otherwise we form a hash value. + */ +void +compact_trigram(trgm *tptr, char *str, int bytelen) +{ + if (bytelen == 3) + { + CPTRGM(tptr, str); + } + else + { + pg_crc32 crc; + + INIT_LEGACY_CRC32(crc); + COMP_LEGACY_CRC32(crc, str, bytelen); + FIN_LEGACY_CRC32(crc); + + /* + * use only 3 upper bytes from crc, hope, it's good enough hashing + */ + CPTRGM(tptr, &crc); + } +} + +/* + * Adds trigrams from words (already padded). + */ +static trgm * +make_trigrams(trgm *tptr, char *str, int bytelen, int charlen) +{ + char *ptr = str; + + if (charlen < 3) + return tptr; + + if (bytelen > charlen) + { + /* Find multibyte character boundaries and apply compact_trigram */ + int lenfirst = pg_mblen(str), + lenmiddle = pg_mblen(str + lenfirst), + lenlast = pg_mblen(str + lenfirst + lenmiddle); + + while ((ptr - str) + lenfirst + lenmiddle + lenlast <= bytelen) + { + compact_trigram(tptr, ptr, lenfirst + lenmiddle + lenlast); + + ptr += lenfirst; + tptr++; + + lenfirst = lenmiddle; + lenmiddle = lenlast; + lenlast = pg_mblen(ptr + lenfirst + lenmiddle); + } + } + else + { + /* Fast path when there are no multibyte characters */ + Assert(bytelen == charlen); + + while (ptr - str < bytelen - 2 /* number of trigrams = strlen - 2 */ ) + { + CPTRGM(tptr, ptr); + ptr++; + tptr++; + } + } + + return tptr; +} + +/* + * Make array of trigrams without sorting and removing duplicate items. + * + * trg: where to return the array of trigrams. + * str: source string, of length slen bytes. + * bounds: where to return bounds of trigrams (if needed). + * + * Returns length of the generated array. + */ +static int +generate_trgm_only(trgm *trg, char *str, int slen, TrgmBound *bounds) +{ + trgm *tptr; + char *buf; + int charlen, + bytelen; + char *bword, + *eword; + + if (slen + LPADDING + RPADDING < 3 || slen == 0) + return 0; + + tptr = trg; + + /* Allocate a buffer for case-folded, blank-padded words */ + buf = (char *) palloc(slen * pg_database_encoding_max_length() + 4); + + if (LPADDING > 0) + { + *buf = ' '; + if (LPADDING > 1) + *(buf + 1) = ' '; + } + + eword = str; + while ((bword = find_word(eword, slen - (eword - str), &eword, &charlen)) != NULL) + { +#ifdef IGNORECASE + bword = lowerstr_with_len(bword, eword - bword); + bytelen = strlen(bword); +#else + bytelen = eword - bword; +#endif + + memcpy(buf + LPADDING, bword, bytelen); + +#ifdef IGNORECASE + pfree(bword); +#endif + + buf[LPADDING + bytelen] = ' '; + buf[LPADDING + bytelen + 1] = ' '; + + /* Calculate trigrams marking their bounds if needed */ + if (bounds) + bounds[tptr - trg] |= TRGM_BOUND_LEFT; + tptr = make_trigrams(tptr, buf, bytelen + LPADDING + RPADDING, + charlen + LPADDING + RPADDING); + if (bounds) + bounds[tptr - trg - 1] |= TRGM_BOUND_RIGHT; + } + + pfree(buf); + + return tptr - trg; +} + +/* + * Guard against possible overflow in the palloc requests below. (We + * don't worry about the additive constants, since palloc can detect + * requests that are a little above MaxAllocSize --- we just need to + * prevent integer overflow in the multiplications.) + */ +static void +protect_out_of_mem(int slen) +{ + if ((Size) (slen / 2) >= (MaxAllocSize / (sizeof(trgm) * 3)) || + (Size) slen >= (MaxAllocSize / pg_database_encoding_max_length())) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("out of memory"))); +} + +/* + * Make array of trigrams with sorting and removing duplicate items. + * + * str: source string, of length slen bytes. + * + * Returns the sorted array of unique trigrams. + */ +TRGM * +generate_trgm(char *str, int slen) +{ + TRGM *trg; + int len; + + protect_out_of_mem(slen); + + trg = (TRGM *) palloc(TRGMHDRSIZE + sizeof(trgm) * (slen / 2 + 1) * 3); + trg->flag = ARRKEY; + + len = generate_trgm_only(GETARR(trg), str, slen, NULL); + SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len)); + + if (len == 0) + return trg; + + /* + * Make trigrams unique. + */ + if (len > 1) + { + qsort(GETARR(trg), len, sizeof(trgm), comp_trgm); + len = qunique(GETARR(trg), len, sizeof(trgm), comp_trgm); + } + + SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len)); + + return trg; +} + +/* + * Make array of positional trigrams from two trigram arrays trg1 and trg2. + * + * trg1: trigram array of search pattern, of length len1. trg1 is required + * word which positions don't matter and replaced with -1. + * trg2: trigram array of text, of length len2. trg2 is haystack where we + * search and have to store its positions. + * + * Returns concatenated trigram array. + */ +static pos_trgm * +make_positional_trgm(trgm *trg1, int len1, trgm *trg2, int len2) +{ + pos_trgm *result; + int i, + len = len1 + len2; + + result = (pos_trgm *) palloc(sizeof(pos_trgm) * len); + + for (i = 0; i < len1; i++) + { + memcpy(&result[i].trg, &trg1[i], sizeof(trgm)); + result[i].index = -1; + } + + for (i = 0; i < len2; i++) + { + memcpy(&result[i + len1].trg, &trg2[i], sizeof(trgm)); + result[i + len1].index = i; + } + + return result; +} + +/* + * Compare position trigrams: compare trigrams first and position second. + */ +static int +comp_ptrgm(const void *v1, const void *v2) +{ + const pos_trgm *p1 = (const pos_trgm *) v1; + const pos_trgm *p2 = (const pos_trgm *) v2; + int cmp; + + cmp = CMPTRGM(p1->trg, p2->trg); + if (cmp != 0) + return cmp; + + if (p1->index < p2->index) + return -1; + else if (p1->index == p2->index) + return 0; + else + return 1; +} + +/* + * Iterative search function which calculates maximum similarity with word in + * the string. Maximum similarity is only calculated only if the flag + * WORD_SIMILARITY_CHECK_ONLY isn't set. + * + * trg2indexes: array which stores indexes of the array "found". + * found: array which stores true of false values. + * ulen1: count of unique trigrams of array "trg1". + * len2: length of array "trg2" and array "trg2indexes". + * len: length of the array "found". + * flags: set of boolean flags parameterizing similarity calculation. + * bounds: whether each trigram is left/right bound of word. + * + * Returns word similarity. + */ +static float4 +iterate_word_similarity(int *trg2indexes, + bool *found, + int ulen1, + int len2, + int len, + uint8 flags, + TrgmBound *bounds) +{ + int *lastpos, + i, + ulen2 = 0, + count = 0, + upper = -1, + lower; + float4 smlr_cur, + smlr_max = 0.0f; + double threshold; + + Assert(bounds || !(flags & WORD_SIMILARITY_STRICT)); + + /* Select appropriate threshold */ + threshold = (flags & WORD_SIMILARITY_STRICT) ? + strict_word_similarity_threshold : + word_similarity_threshold; + + /* + * Consider first trigram as initial lower bound for strict word + * similarity, or initialize it later with first trigram present for plain + * word similarity. + */ + lower = (flags & WORD_SIMILARITY_STRICT) ? 0 : -1; + + /* Memorise last position of each trigram */ + lastpos = (int *) palloc(sizeof(int) * len); + memset(lastpos, -1, sizeof(int) * len); + + for (i = 0; i < len2; i++) + { + int trgindex; + + CHECK_FOR_INTERRUPTS(); + + /* Get index of next trigram */ + trgindex = trg2indexes[i]; + + /* Update last position of this trigram */ + if (lower >= 0 || found[trgindex]) + { + if (lastpos[trgindex] < 0) + { + ulen2++; + if (found[trgindex]) + count++; + } + lastpos[trgindex] = i; + } + + /* + * Adjust upper bound if trigram is upper bound of word for strict + * word similarity, or if trigram is present in required substring for + * plain word similarity + */ + if ((flags & WORD_SIMILARITY_STRICT) ? (bounds[i] & TRGM_BOUND_RIGHT) + : found[trgindex]) + { + int prev_lower, + tmp_ulen2, + tmp_lower, + tmp_count; + + upper = i; + if (lower == -1) + { + lower = i; + ulen2 = 1; + } + + smlr_cur = CALCSML(count, ulen1, ulen2); + + /* Also try to adjust lower bound for greater similarity */ + tmp_count = count; + tmp_ulen2 = ulen2; + prev_lower = lower; + for (tmp_lower = lower; tmp_lower <= upper; tmp_lower++) + { + float smlr_tmp; + int tmp_trgindex; + + /* + * Adjust lower bound only if trigram is lower bound of word + * for strict word similarity, or consider every trigram as + * lower bound for plain word similarity. + */ + if (!(flags & WORD_SIMILARITY_STRICT) + || (bounds[tmp_lower] & TRGM_BOUND_LEFT)) + { + smlr_tmp = CALCSML(tmp_count, ulen1, tmp_ulen2); + if (smlr_tmp > smlr_cur) + { + smlr_cur = smlr_tmp; + ulen2 = tmp_ulen2; + lower = tmp_lower; + count = tmp_count; + } + + /* + * If we only check that word similarity is greater than + * threshold we do not need to calculate a maximum + * similarity. + */ + if ((flags & WORD_SIMILARITY_CHECK_ONLY) + && smlr_cur >= threshold) + break; + } + + tmp_trgindex = trg2indexes[tmp_lower]; + if (lastpos[tmp_trgindex] == tmp_lower) + { + tmp_ulen2--; + if (found[tmp_trgindex]) + tmp_count--; + } + } + + smlr_max = Max(smlr_max, smlr_cur); + + /* + * if we only check that word similarity is greater than threshold + * we do not need to calculate a maximum similarity. + */ + if ((flags & WORD_SIMILARITY_CHECK_ONLY) && smlr_max >= threshold) + break; + + for (tmp_lower = prev_lower; tmp_lower < lower; tmp_lower++) + { + int tmp_trgindex; + + tmp_trgindex = trg2indexes[tmp_lower]; + if (lastpos[tmp_trgindex] == tmp_lower) + lastpos[tmp_trgindex] = -1; + } + } + } + + pfree(lastpos); + + return smlr_max; +} + +/* + * Calculate word similarity. + * This function prepare two arrays: "trg2indexes" and "found". Then this arrays + * are used to calculate word similarity using iterate_word_similarity(). + * + * "trg2indexes" is array which stores indexes of the array "found". + * In other words: + * trg2indexes[j] = i; + * found[i] = true (or false); + * If found[i] == true then there is trigram trg2[j] in array "trg1". + * If found[i] == false then there is not trigram trg2[j] in array "trg1". + * + * str1: search pattern string, of length slen1 bytes. + * str2: text in which we are looking for a word, of length slen2 bytes. + * flags: set of boolean flags parameterizing similarity calculation. + * + * Returns word similarity. + */ +static float4 +calc_word_similarity(char *str1, int slen1, char *str2, int slen2, + uint8 flags) +{ + bool *found; + pos_trgm *ptrg; + trgm *trg1; + trgm *trg2; + int len1, + len2, + len, + i, + j, + ulen1; + int *trg2indexes; + float4 result; + TrgmBound *bounds; + + protect_out_of_mem(slen1 + slen2); + + /* Make positional trigrams */ + trg1 = (trgm *) palloc(sizeof(trgm) * (slen1 / 2 + 1) * 3); + trg2 = (trgm *) palloc(sizeof(trgm) * (slen2 / 2 + 1) * 3); + if (flags & WORD_SIMILARITY_STRICT) + bounds = (TrgmBound *) palloc0(sizeof(TrgmBound) * (slen2 / 2 + 1) * 3); + else + bounds = NULL; + + len1 = generate_trgm_only(trg1, str1, slen1, NULL); + len2 = generate_trgm_only(trg2, str2, slen2, bounds); + + ptrg = make_positional_trgm(trg1, len1, trg2, len2); + len = len1 + len2; + qsort(ptrg, len, sizeof(pos_trgm), comp_ptrgm); + + pfree(trg1); + pfree(trg2); + + /* + * Merge positional trigrams array: enumerate each trigram and find its + * presence in required word. + */ + trg2indexes = (int *) palloc(sizeof(int) * len2); + found = (bool *) palloc0(sizeof(bool) * len); + + ulen1 = 0; + j = 0; + for (i = 0; i < len; i++) + { + if (i > 0) + { + int cmp = CMPTRGM(ptrg[i - 1].trg, ptrg[i].trg); + + if (cmp != 0) + { + if (found[j]) + ulen1++; + j++; + } + } + + if (ptrg[i].index >= 0) + { + trg2indexes[ptrg[i].index] = j; + } + else + { + found[j] = true; + } + } + if (found[j]) + ulen1++; + + /* Run iterative procedure to find maximum similarity with word */ + result = iterate_word_similarity(trg2indexes, found, ulen1, len2, len, + flags, bounds); + + pfree(trg2indexes); + pfree(found); + pfree(ptrg); + + return result; +} + + +/* + * Extract the next non-wildcard part of a search string, i.e. a word bounded + * by '_' or '%' meta-characters, non-word characters or string end. + * + * str: source string, of length lenstr bytes (need not be null-terminated) + * buf: where to return the substring (must be long enough) + * *bytelen: receives byte length of the found substring + * *charlen: receives character length of the found substring + * + * Returns pointer to end+1 of the found substring in the source string. + * Returns NULL if no word found (in which case buf, bytelen, charlen not set) + * + * If the found word is bounded by non-word characters or string boundaries + * then this function will include corresponding padding spaces into buf. + */ +static const char * +get_wildcard_part(const char *str, int lenstr, + char *buf, int *bytelen, int *charlen) +{ + const char *beginword = str; + const char *endword; + char *s = buf; + bool in_leading_wildcard_meta = false; + bool in_trailing_wildcard_meta = false; + bool in_escape = false; + int clen; + + /* + * Find the first word character, remembering whether preceding character + * was wildcard meta-character. Note that the in_escape state persists + * from this loop to the next one, since we may exit at a word character + * that is in_escape. + */ + while (beginword - str < lenstr) + { + if (in_escape) + { + if (ISWORDCHR(beginword)) + break; + in_escape = false; + in_leading_wildcard_meta = false; + } + else + { + if (ISESCAPECHAR(beginword)) + in_escape = true; + else if (ISWILDCARDCHAR(beginword)) + in_leading_wildcard_meta = true; + else if (ISWORDCHR(beginword)) + break; + else + in_leading_wildcard_meta = false; + } + beginword += pg_mblen(beginword); + } + + /* + * Handle string end. + */ + if (beginword - str >= lenstr) + return NULL; + + /* + * Add left padding spaces if preceding character wasn't wildcard + * meta-character. + */ + *charlen = 0; + if (!in_leading_wildcard_meta) + { + if (LPADDING > 0) + { + *s++ = ' '; + (*charlen)++; + if (LPADDING > 1) + { + *s++ = ' '; + (*charlen)++; + } + } + } + + /* + * Copy data into buf until wildcard meta-character, non-word character or + * string boundary. Strip escapes during copy. + */ + endword = beginword; + while (endword - str < lenstr) + { + clen = pg_mblen(endword); + if (in_escape) + { + if (ISWORDCHR(endword)) + { + memcpy(s, endword, clen); + (*charlen)++; + s += clen; + } + else + { + /* + * Back up endword to the escape character when stopping at an + * escaped char, so that subsequent get_wildcard_part will + * restart from the escape character. We assume here that + * escape chars are single-byte. + */ + endword--; + break; + } + in_escape = false; + } + else + { + if (ISESCAPECHAR(endword)) + in_escape = true; + else if (ISWILDCARDCHAR(endword)) + { + in_trailing_wildcard_meta = true; + break; + } + else if (ISWORDCHR(endword)) + { + memcpy(s, endword, clen); + (*charlen)++; + s += clen; + } + else + break; + } + endword += clen; + } + + /* + * Add right padding spaces if next character isn't wildcard + * meta-character. + */ + if (!in_trailing_wildcard_meta) + { + if (RPADDING > 0) + { + *s++ = ' '; + (*charlen)++; + if (RPADDING > 1) + { + *s++ = ' '; + (*charlen)++; + } + } + } + + *bytelen = s - buf; + return endword; +} + +/* + * Generates trigrams for wildcard search string. + * + * Returns array of trigrams that must occur in any string that matches the + * wildcard string. For example, given pattern "a%bcd%" the trigrams + * " a", "bcd" would be extracted. + */ +TRGM * +generate_wildcard_trgm(const char *str, int slen) +{ + TRGM *trg; + char *buf, + *buf2; + trgm *tptr; + int len, + charlen, + bytelen; + const char *eword; + + protect_out_of_mem(slen); + + trg = (TRGM *) palloc(TRGMHDRSIZE + sizeof(trgm) * (slen / 2 + 1) * 3); + trg->flag = ARRKEY; + SET_VARSIZE(trg, TRGMHDRSIZE); + + if (slen + LPADDING + RPADDING < 3 || slen == 0) + return trg; + + tptr = GETARR(trg); + + /* Allocate a buffer for blank-padded, but not yet case-folded, words */ + buf = palloc(sizeof(char) * (slen + 4)); + + /* + * Extract trigrams from each substring extracted by get_wildcard_part. + */ + eword = str; + while ((eword = get_wildcard_part(eword, slen - (eword - str), + buf, &bytelen, &charlen)) != NULL) + { +#ifdef IGNORECASE + buf2 = lowerstr_with_len(buf, bytelen); + bytelen = strlen(buf2); +#else + buf2 = buf; +#endif + + /* + * count trigrams + */ + tptr = make_trigrams(tptr, buf2, bytelen, charlen); + +#ifdef IGNORECASE + pfree(buf2); +#endif + } + + pfree(buf); + + if ((len = tptr - GETARR(trg)) == 0) + return trg; + + /* + * Make trigrams unique. + */ + if (len > 1) + { + qsort(GETARR(trg), len, sizeof(trgm), comp_trgm); + len = qunique(GETARR(trg), len, sizeof(trgm), comp_trgm); + } + + SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, len)); + + return trg; +} + +uint32 +trgm2int(trgm *ptr) +{ + uint32 val = 0; + + val |= *(((unsigned char *) ptr)); + val <<= 8; + val |= *(((unsigned char *) ptr) + 1); + val <<= 8; + val |= *(((unsigned char *) ptr) + 2); + + return val; +} + +Datum +show_trgm(PG_FUNCTION_ARGS) +{ + text *in = PG_GETARG_TEXT_PP(0); + TRGM *trg; + Datum *d; + ArrayType *a; + trgm *ptr; + int i; + + trg = generate_trgm(VARDATA_ANY(in), VARSIZE_ANY_EXHDR(in)); + d = (Datum *) palloc(sizeof(Datum) * (1 + ARRNELEM(trg))); + + for (i = 0, ptr = GETARR(trg); i < ARRNELEM(trg); i++, ptr++) + { + text *item = (text *) palloc(VARHDRSZ + Max(12, pg_database_encoding_max_length() * 3)); + + if (pg_database_encoding_max_length() > 1 && !ISPRINTABLETRGM(ptr)) + { + snprintf(VARDATA(item), 12, "0x%06x", trgm2int(ptr)); + SET_VARSIZE(item, VARHDRSZ + strlen(VARDATA(item))); + } + else + { + SET_VARSIZE(item, VARHDRSZ + 3); + CPTRGM(VARDATA(item), ptr); + } + d[i] = PointerGetDatum(item); + } + + a = construct_array_builtin(d, ARRNELEM(trg), TEXTOID); + + for (i = 0; i < ARRNELEM(trg); i++) + pfree(DatumGetPointer(d[i])); + + pfree(d); + pfree(trg); + PG_FREE_IF_COPY(in, 0); + + PG_RETURN_POINTER(a); +} + +float4 +cnt_sml(TRGM *trg1, TRGM *trg2, bool inexact) +{ + trgm *ptr1, + *ptr2; + int count = 0; + int len1, + len2; + + ptr1 = GETARR(trg1); + ptr2 = GETARR(trg2); + + len1 = ARRNELEM(trg1); + len2 = ARRNELEM(trg2); + + /* explicit test is needed to avoid 0/0 division when both lengths are 0 */ + if (len1 <= 0 || len2 <= 0) + return (float4) 0.0; + + while (ptr1 - GETARR(trg1) < len1 && ptr2 - GETARR(trg2) < len2) + { + int res = CMPTRGM(ptr1, ptr2); + + if (res < 0) + ptr1++; + else if (res > 0) + ptr2++; + else + { + ptr1++; + ptr2++; + count++; + } + } + + /* + * If inexact then len2 is equal to count, because we don't know actual + * length of second string in inexact search and we can assume that count + * is a lower bound of len2. + */ + return CALCSML(count, len1, inexact ? count : len2); +} + + +/* + * Returns whether trg2 contains all trigrams in trg1. + * This relies on the trigram arrays being sorted. + */ +bool +trgm_contained_by(TRGM *trg1, TRGM *trg2) +{ + trgm *ptr1, + *ptr2; + int len1, + len2; + + ptr1 = GETARR(trg1); + ptr2 = GETARR(trg2); + + len1 = ARRNELEM(trg1); + len2 = ARRNELEM(trg2); + + while (ptr1 - GETARR(trg1) < len1 && ptr2 - GETARR(trg2) < len2) + { + int res = CMPTRGM(ptr1, ptr2); + + if (res < 0) + return false; + else if (res > 0) + ptr2++; + else + { + ptr1++; + ptr2++; + } + } + if (ptr1 - GETARR(trg1) < len1) + return false; + else + return true; +} + +/* + * Return a palloc'd boolean array showing, for each trigram in "query", + * whether it is present in the trigram array "key". + * This relies on the "key" array being sorted, but "query" need not be. + */ +bool * +trgm_presence_map(TRGM *query, TRGM *key) +{ + bool *result; + trgm *ptrq = GETARR(query), + *ptrk = GETARR(key); + int lenq = ARRNELEM(query), + lenk = ARRNELEM(key), + i; + + result = (bool *) palloc0(lenq * sizeof(bool)); + + /* for each query trigram, do a binary search in the key array */ + for (i = 0; i < lenq; i++) + { + int lo = 0; + int hi = lenk; + + while (lo < hi) + { + int mid = (lo + hi) / 2; + int res = CMPTRGM(ptrq, ptrk + mid); + + if (res < 0) + hi = mid; + else if (res > 0) + lo = mid + 1; + else + { + result[i] = true; + break; + } + } + ptrq++; + } + + return result; +} + +Datum +similarity(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + TRGM *trg1, + *trg2; + float4 res; + + trg1 = generate_trgm(VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1)); + trg2 = generate_trgm(VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2)); + + res = cnt_sml(trg1, trg2, false); + + pfree(trg1); + pfree(trg2); + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + + PG_RETURN_FLOAT4(res); +} + +Datum +word_similarity(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + float4 res; + + res = calc_word_similarity(VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1), + VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2), + 0); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_FLOAT4(res); +} + +Datum +strict_word_similarity(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + float4 res; + + res = calc_word_similarity(VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1), + VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2), + WORD_SIMILARITY_STRICT); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_FLOAT4(res); +} + +Datum +similarity_dist(PG_FUNCTION_ARGS) +{ + float4 res = DatumGetFloat4(DirectFunctionCall2(similarity, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_FLOAT4(1.0 - res); +} + +Datum +similarity_op(PG_FUNCTION_ARGS) +{ + float4 res = DatumGetFloat4(DirectFunctionCall2(similarity, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(res >= similarity_threshold); +} + +Datum +word_similarity_op(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + float4 res; + + res = calc_word_similarity(VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1), + VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2), + WORD_SIMILARITY_CHECK_ONLY); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_BOOL(res >= word_similarity_threshold); +} + +Datum +word_similarity_commutator_op(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + float4 res; + + res = calc_word_similarity(VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2), + VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1), + WORD_SIMILARITY_CHECK_ONLY); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_BOOL(res >= word_similarity_threshold); +} + +Datum +word_similarity_dist_op(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + float4 res; + + res = calc_word_similarity(VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1), + VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2), + 0); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_FLOAT4(1.0 - res); +} + +Datum +word_similarity_dist_commutator_op(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + float4 res; + + res = calc_word_similarity(VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2), + VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1), + 0); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_FLOAT4(1.0 - res); +} + +Datum +strict_word_similarity_op(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + float4 res; + + res = calc_word_similarity(VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1), + VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2), + WORD_SIMILARITY_CHECK_ONLY | WORD_SIMILARITY_STRICT); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_BOOL(res >= strict_word_similarity_threshold); +} + +Datum +strict_word_similarity_commutator_op(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + float4 res; + + res = calc_word_similarity(VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2), + VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1), + WORD_SIMILARITY_CHECK_ONLY | WORD_SIMILARITY_STRICT); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_BOOL(res >= strict_word_similarity_threshold); +} + +Datum +strict_word_similarity_dist_op(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + float4 res; + + res = calc_word_similarity(VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1), + VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2), + WORD_SIMILARITY_STRICT); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_FLOAT4(1.0 - res); +} + +Datum +strict_word_similarity_dist_commutator_op(PG_FUNCTION_ARGS) +{ + text *in1 = PG_GETARG_TEXT_PP(0); + text *in2 = PG_GETARG_TEXT_PP(1); + float4 res; + + res = calc_word_similarity(VARDATA_ANY(in2), VARSIZE_ANY_EXHDR(in2), + VARDATA_ANY(in1), VARSIZE_ANY_EXHDR(in1), + WORD_SIMILARITY_STRICT); + + PG_FREE_IF_COPY(in1, 0); + PG_FREE_IF_COPY(in2, 1); + PG_RETURN_FLOAT4(1.0 - res); +} diff --git a/contrib/pg_trgm/trgm_regexp.c b/contrib/pg_trgm/trgm_regexp.c new file mode 100644 index 0000000..1d36946 --- /dev/null +++ b/contrib/pg_trgm/trgm_regexp.c @@ -0,0 +1,2358 @@ +/*------------------------------------------------------------------------- + * + * trgm_regexp.c + * Regular expression matching using trigrams. + * + * The general idea of trigram index support for a regular expression (regex) + * search is to transform the regex into a logical expression on trigrams. + * For example: + * + * (ab|cd)efg => ((abe & bef) | (cde & def)) & efg + * + * If a string matches the regex, then it must match the logical expression on + * trigrams. The opposite is not necessarily true, however: a string that + * matches the logical expression might not match the original regex. Such + * false positives are removed via recheck, by running the regular regex match + * operator on the retrieved heap tuple. + * + * Since the trigram expression involves both AND and OR operators, we can't + * expect the core index machinery to evaluate it completely. Instead, the + * result of regex analysis is a list of trigrams to be sought in the index, + * plus a simplified graph that is used by trigramsMatchGraph() to determine + * whether a particular indexed value matches the expression. + * + * Converting a regex to a trigram expression is based on analysis of an + * automaton corresponding to the regex. The algorithm consists of four + * stages: + * + * 1) Compile the regexp to NFA form. This is handled by the PostgreSQL + * regexp library, which provides accessors for its opaque regex_t struct + * to expose the NFA state graph and the "colors" (sets of equivalent + * characters) used as state transition labels. + * + * 2) Transform the original NFA into an expanded graph, where arcs + * are labeled with trigrams that must be present in order to move from + * one state to another via the arcs. The trigrams used in this stage + * consist of colors, not characters, as in the original NFA. + * + * 3) Expand the color trigrams into regular trigrams consisting of + * characters. If too many distinct trigrams are produced, trigrams are + * eliminated and the graph is simplified until it's simple enough. + * + * 4) Finally, the resulting graph is packed into a TrgmPackedGraph struct, + * and returned to the caller. + * + * 1) Compile the regexp to NFA form + * --------------------------------- + * The automaton returned by the regexp compiler is a graph where vertices + * are "states" and arcs are labeled with colors. Each color represents + * a set of characters, so that all characters assigned to the same color + * are interchangeable, so far as matching the regexp is concerned. There + * are two special states: "initial" and "final". A state can have multiple + * outgoing arcs labeled with the same color, which makes the automaton + * non-deterministic, because it can be in many states simultaneously. + * + * Note that this NFA is already lossy compared to the original regexp, + * since it ignores some regex features such as lookahead constraints and + * backref matching. This is OK for our purposes since it's still the case + * that only strings matching the NFA can possibly satisfy the regexp. + * + * 2) Transform the original NFA into an expanded graph + * ---------------------------------------------------- + * In the 2nd stage, the automaton is transformed into a graph based on the + * original NFA. Each state in the expanded graph represents a state from + * the original NFA, plus a prefix identifying the last two characters + * (colors, to be precise) seen before entering the state. There can be + * multiple states in the expanded graph for each state in the original NFA, + * depending on what characters can precede it. A prefix position can be + * "unknown" if it's uncertain what the preceding character was, or "blank" + * if the character was a non-word character (we don't need to distinguish + * which non-word character it was, so just think of all of them as blanks). + * + * For convenience in description, call an expanded-state identifier + * (two prefix colors plus a state number from the original NFA) an + * "enter key". + * + * Each arc of the expanded graph is labeled with a trigram that must be + * present in the string to match. We can construct this from an out-arc of + * the underlying NFA state by combining the expanded state's prefix with the + * color label of the underlying out-arc, if neither prefix position is + * "unknown". But note that some of the colors in the trigram might be + * "blank". This is OK since we want to generate word-boundary trigrams as + * the regular trigram machinery would, if we know that some word characters + * must be adjacent to a word boundary in all strings matching the NFA. + * + * The expanded graph can also have fewer states than the original NFA, + * because we don't bother to make a separate state entry unless the state + * is reachable by a valid arc. When an enter key is reachable from a state + * of the expanded graph, but we do not know a complete trigram associated + * with that transition, we cannot make a valid arc; instead we insert the + * enter key into the enterKeys list of the source state. This effectively + * means that the two expanded states are not reliably distinguishable based + * on examining trigrams. + * + * So the expanded graph resembles the original NFA, but the arcs are + * labeled with trigrams instead of individual characters, and there may be + * more or fewer states. It is a lossy representation of the original NFA: + * any string that matches the original regexp must match the expanded graph, + * but the reverse is not true. + * + * We build the expanded graph through a breadth-first traversal of states + * reachable from the initial state. At each reachable state, we identify the + * states reachable from it without traversing a predictable trigram, and add + * those states' enter keys to the current state. Then we generate all + * out-arcs leading out of this collection of states that have predictable + * trigrams, adding their target states to the queue of states to examine. + * + * When building the graph, if the number of states or arcs exceed pre-defined + * limits, we give up and simply mark any states not yet processed as final + * states. Roughly speaking, that means that we make use of some portion from + * the beginning of the regexp. Also, any colors that have too many member + * characters are treated as "unknown", so that we can't derive trigrams + * from them. + * + * 3) Expand the color trigrams into regular trigrams + * -------------------------------------------------- + * The trigrams in the expanded graph are "color trigrams", consisting + * of three consecutive colors that must be present in the string. But for + * search, we need regular trigrams consisting of characters. In the 3rd + * stage, the color trigrams are expanded into regular trigrams. Since each + * color can represent many characters, the total number of regular trigrams + * after expansion could be very large. Because searching the index for + * thousands of trigrams would be slow, and would likely produce so many + * false positives that we would have to traverse a large fraction of the + * index, the graph is simplified further in a lossy fashion by removing + * color trigrams. When a color trigram is removed, the states connected by + * any arcs labeled with that trigram are merged. + * + * Trigrams do not all have equivalent value for searching: some of them are + * more frequent and some of them are less frequent. Ideally, we would like + * to know the distribution of trigrams, but we don't. But because of padding + * we know for sure that the empty character is more frequent than others, + * so we can penalize trigrams according to presence of whitespace. The + * penalty assigned to each color trigram is the number of simple trigrams + * it would produce, times the penalties[] multiplier associated with its + * whitespace content. (The penalties[] constants were calculated by analysis + * of some real-life text.) We eliminate color trigrams starting with the + * highest-penalty one, until we get to a total penalty of no more than + * WISH_TRGM_PENALTY. However, we cannot remove a color trigram if that would + * lead to merging the initial and final states, so we may not be able to + * reach WISH_TRGM_PENALTY. It's still okay so long as we have no more than + * MAX_TRGM_COUNT simple trigrams in total, otherwise we fail. + * + * 4) Pack the graph into a compact representation + * ----------------------------------------------- + * The 2nd and 3rd stages might have eliminated or merged many of the states + * and trigrams created earlier, so in this final stage, the graph is + * compacted and packed into a simpler struct that contains only the + * information needed to evaluate it. + * + * ALGORITHM EXAMPLE: + * + * Consider the example regex "ab[cd]". This regex is transformed into the + * following NFA (for simplicity we show colors as their single members): + * + * 4# + * c/ + * a b / + * 1* --- 2 ---- 3 + * \ + * d\ + * 5# + * + * We use * to mark initial state and # to mark final state. It's not depicted, + * but states 1, 4, 5 have self-referencing arcs for all possible characters, + * because this pattern can match to any part of a string. + * + * As the result of stage 2 we will have the following graph: + * + * abc abd + * 2# <---- 1* ----> 3# + * + * The process for generating this graph is: + * 1) Create state 1 with enter key (UNKNOWN, UNKNOWN, 1). + * 2) Add key (UNKNOWN, "a", 2) to state 1. + * 3) Add key ("a", "b", 3) to state 1. + * 4) Create new state 2 with enter key ("b", "c", 4). Add an arc + * from state 1 to state 2 with label trigram "abc". + * 5) Mark state 2 final because state 4 of source NFA is marked as final. + * 6) Create new state 3 with enter key ("b", "d", 5). Add an arc + * from state 1 to state 3 with label trigram "abd". + * 7) Mark state 3 final because state 5 of source NFA is marked as final. + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * contrib/pg_trgm/trgm_regexp.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "regex/regexport.h" +#include "trgm.h" +#include "tsearch/ts_locale.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "varatt.h" + +/* + * Uncomment (or use -DTRGM_REGEXP_DEBUG) to print debug info, + * for exploring and debugging the algorithm implementation. + * This produces three graph files in /tmp, in Graphviz .gv format. + * Some progress information is also printed to postmaster stderr. + */ +/* #define TRGM_REGEXP_DEBUG */ + +/* + * These parameters are used to limit the amount of work done. + * Otherwise regex processing could be too slow and memory-consuming. + * + * MAX_EXPANDED_STATES - How many states we allow in expanded graph + * MAX_EXPANDED_ARCS - How many arcs we allow in expanded graph + * MAX_TRGM_COUNT - How many simple trigrams we allow to be extracted + * WISH_TRGM_PENALTY - Maximum desired sum of color trigram penalties + * COLOR_COUNT_LIMIT - Maximum number of characters per color + */ +#define MAX_EXPANDED_STATES 128 +#define MAX_EXPANDED_ARCS 1024 +#define MAX_TRGM_COUNT 256 +#define WISH_TRGM_PENALTY 16 +#define COLOR_COUNT_LIMIT 256 + +/* + * Penalty multipliers for trigram counts depending on whitespace contents. + * Numbers based on analysis of real-life texts. + */ +static const float4 penalties[8] = { + 1.0f, /* "aaa" */ + 3.5f, /* "aa " */ + 0.0f, /* "a a" (impossible) */ + 0.0f, /* "a " (impossible) */ + 4.2f, /* " aa" */ + 2.1f, /* " a " */ + 25.0f, /* " a" */ + 0.0f /* " " (impossible) */ +}; + +/* Struct representing a single pg_wchar, converted back to multibyte form */ +typedef struct +{ + char bytes[MAX_MULTIBYTE_CHAR_LEN]; +} trgm_mb_char; + +/* + * Attributes of NFA colors: + * + * expandable - we know the character expansion of this color + * containsNonWord - color contains non-word characters + * (which will not be extracted into trigrams) + * wordCharsCount - count of word characters in color + * wordChars - array of this color's word characters + * (which can be extracted into trigrams) + * + * When expandable is false, the other attributes don't matter; we just + * assume this color represents unknown character(s). + */ +typedef struct +{ + bool expandable; + bool containsNonWord; + int wordCharsCount; + trgm_mb_char *wordChars; +} TrgmColorInfo; + +/* + * A "prefix" is information about the colors of the last two characters read + * before reaching a specific NFA state. These colors can have special values + * COLOR_UNKNOWN and COLOR_BLANK. COLOR_UNKNOWN means that we have no + * information, for example because we read some character of an unexpandable + * color. COLOR_BLANK means that we read a non-word character. + * + * We call a prefix ambiguous if at least one of its colors is unknown. It's + * fully ambiguous if both are unknown, partially ambiguous if only the first + * is unknown. (The case of first color known, second unknown is not valid.) + * + * Wholly- or partly-blank prefixes are mostly handled the same as regular + * color prefixes. This allows us to generate appropriate partly-blank + * trigrams when the NFA requires word character(s) to appear adjacent to + * non-word character(s). + */ +typedef int TrgmColor; + +/* We assume that colors returned by the regexp engine cannot be these: */ +#define COLOR_UNKNOWN (-3) +#define COLOR_BLANK (-4) + +typedef struct +{ + TrgmColor colors[2]; +} TrgmPrefix; + +/* + * Color-trigram data type. Note that some elements of the trigram can be + * COLOR_BLANK, but we don't allow COLOR_UNKNOWN. + */ +typedef struct +{ + TrgmColor colors[3]; +} ColorTrgm; + +/* + * Key identifying a state of our expanded graph: color prefix, and number + * of the corresponding state in the underlying regex NFA. The color prefix + * shows how we reached the regex state (to the extent that we know it). + */ +typedef struct +{ + TrgmPrefix prefix; + int nstate; +} TrgmStateKey; + +/* + * One state of the expanded graph. + * + * stateKey - ID of this state + * arcs - outgoing arcs of this state (List of TrgmArc) + * enterKeys - enter keys reachable from this state without reading any + * predictable trigram (List of TrgmStateKey) + * flags - flag bits + * snumber - number of this state (initially assigned as -1, -2, etc, + * for debugging purposes only; then at the packaging stage, + * surviving states are renumbered with positive numbers) + * parent - parent state, if this state has been merged into another + * tentFlags - flags this state would acquire via planned merges + * tentParent - planned parent state, if considering a merge + */ +#define TSTATE_INIT 0x01 /* flag indicating this state is initial */ +#define TSTATE_FIN 0x02 /* flag indicating this state is final */ + +typedef struct TrgmState +{ + TrgmStateKey stateKey; /* hashtable key: must be first field */ + List *arcs; + List *enterKeys; + int flags; + int snumber; + struct TrgmState *parent; + int tentFlags; + struct TrgmState *tentParent; +} TrgmState; + +/* + * One arc in the expanded graph. + */ +typedef struct +{ + ColorTrgm ctrgm; /* trigram needed to traverse arc */ + TrgmState *target; /* next state */ +} TrgmArc; + +/* + * Information about arc of specific color trigram (used in stage 3) + * + * Contains pointers to the source and target states. + */ +typedef struct +{ + TrgmState *source; + TrgmState *target; +} TrgmArcInfo; + +/* + * Information about color trigram (used in stage 3) + * + * ctrgm - trigram itself + * cnumber - number of this trigram (used in the packaging stage) + * count - number of simple trigrams created from this color trigram + * expanded - indicates this color trigram is expanded into simple trigrams + * arcs - list of all arcs labeled with this color trigram. + */ +typedef struct +{ + ColorTrgm ctrgm; + int cnumber; + int count; + float4 penalty; + bool expanded; + List *arcs; +} ColorTrgmInfo; + +/* + * Data structure representing all the data we need during regex processing. + * + * regex - compiled regex + * colorInfo - extracted information about regex's colors + * ncolors - number of colors in colorInfo[] + * states - hashtable of TrgmStates (states of expanded graph) + * initState - pointer to initial state of expanded graph + * queue - queue of to-be-processed TrgmStates + * keysQueue - queue of to-be-processed TrgmStateKeys + * arcsCount - total number of arcs of expanded graph (for resource + * limiting) + * overflowed - we have exceeded resource limit for transformation + * colorTrgms - array of all color trigrams present in graph + * colorTrgmsCount - count of those color trigrams + * totalTrgmCount - total count of extracted simple trigrams + */ +typedef struct +{ + /* Source regexp, and color information extracted from it (stage 1) */ + regex_t *regex; + TrgmColorInfo *colorInfo; + int ncolors; + + /* Expanded graph (stage 2) */ + HTAB *states; + TrgmState *initState; + int nstates; + + /* Workspace for stage 2 */ + List *queue; + List *keysQueue; + int arcsCount; + bool overflowed; + + /* Information about distinct color trigrams in the graph (stage 3) */ + ColorTrgmInfo *colorTrgms; + int colorTrgmsCount; + int totalTrgmCount; +} TrgmNFA; + +/* + * Final, compact representation of expanded graph. + */ +typedef struct +{ + int targetState; /* index of target state (zero-based) */ + int colorTrgm; /* index of color trigram for transition */ +} TrgmPackedArc; + +typedef struct +{ + int arcsCount; /* number of out-arcs for this state */ + TrgmPackedArc *arcs; /* array of arcsCount packed arcs */ +} TrgmPackedState; + +/* "typedef struct TrgmPackedGraph TrgmPackedGraph" appears in trgm.h */ +struct TrgmPackedGraph +{ + /* + * colorTrigramsCount and colorTrigramGroups contain information about how + * trigrams are grouped into color trigrams. "colorTrigramsCount" is the + * count of color trigrams and "colorTrigramGroups" contains number of + * simple trigrams for each color trigram. The array of simple trigrams + * (stored separately from this struct) is ordered so that the simple + * trigrams for each color trigram are consecutive, and they're in order + * by color trigram number. + */ + int colorTrigramsCount; + int *colorTrigramGroups; /* array of size colorTrigramsCount */ + + /* + * The states of the simplified NFA. State number 0 is always initial + * state and state number 1 is always final state. + */ + int statesCount; + TrgmPackedState *states; /* array of size statesCount */ + + /* Temporary work space for trigramsMatchGraph() */ + bool *colorTrigramsActive; /* array of size colorTrigramsCount */ + bool *statesActive; /* array of size statesCount */ + int *statesQueue; /* array of size statesCount */ +}; + +/* + * Temporary structure for representing an arc during packaging. + */ +typedef struct +{ + int sourceState; + int targetState; + int colorTrgm; +} TrgmPackArcInfo; + + +/* prototypes for private functions */ +static TRGM *createTrgmNFAInternal(regex_t *regex, TrgmPackedGraph **graph, + MemoryContext rcontext); +static void RE_compile(regex_t *regex, text *text_re, + int cflags, Oid collation); +static void getColorInfo(regex_t *regex, TrgmNFA *trgmNFA); +static bool convertPgWchar(pg_wchar c, trgm_mb_char *result); +static void transformGraph(TrgmNFA *trgmNFA); +static void processState(TrgmNFA *trgmNFA, TrgmState *state); +static void addKey(TrgmNFA *trgmNFA, TrgmState *state, TrgmStateKey *key); +static void addKeyToQueue(TrgmNFA *trgmNFA, TrgmStateKey *key); +static void addArcs(TrgmNFA *trgmNFA, TrgmState *state); +static void addArc(TrgmNFA *trgmNFA, TrgmState *state, TrgmStateKey *key, + TrgmColor co, TrgmStateKey *destKey); +static bool validArcLabel(TrgmStateKey *key, TrgmColor co); +static TrgmState *getState(TrgmNFA *trgmNFA, TrgmStateKey *key); +static bool prefixContains(TrgmPrefix *prefix1, TrgmPrefix *prefix2); +static bool selectColorTrigrams(TrgmNFA *trgmNFA); +static TRGM *expandColorTrigrams(TrgmNFA *trgmNFA, MemoryContext rcontext); +static void fillTrgm(trgm *ptrgm, trgm_mb_char s[3]); +static void mergeStates(TrgmState *state1, TrgmState *state2); +static int colorTrgmInfoCmp(const void *p1, const void *p2); +static int colorTrgmInfoPenaltyCmp(const void *p1, const void *p2); +static TrgmPackedGraph *packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext); +static int packArcInfoCmp(const void *a1, const void *a2); + +#ifdef TRGM_REGEXP_DEBUG +static void printSourceNFA(regex_t *regex, TrgmColorInfo *colors, int ncolors); +static void printTrgmNFA(TrgmNFA *trgmNFA); +static void printTrgmColor(StringInfo buf, TrgmColor co); +static void printTrgmPackedGraph(TrgmPackedGraph *packedGraph, TRGM *trigrams); +#endif + + +/* + * Main entry point to process a regular expression. + * + * Returns an array of trigrams required by the regular expression, or NULL if + * the regular expression was too complex to analyze. In addition, a packed + * graph representation of the regex is returned into *graph. The results + * must be allocated in rcontext (which might or might not be the current + * context). + */ +TRGM * +createTrgmNFA(text *text_re, Oid collation, + TrgmPackedGraph **graph, MemoryContext rcontext) +{ + TRGM *trg; + regex_t regex; + MemoryContext tmpcontext; + MemoryContext oldcontext; + + /* + * This processing generates a great deal of cruft, which we'd like to + * clean up before returning (since this function may be called in a + * query-lifespan memory context). Make a temp context we can work in so + * that cleanup is easy. + */ + tmpcontext = AllocSetContextCreate(CurrentMemoryContext, + "createTrgmNFA temporary context", + ALLOCSET_DEFAULT_SIZES); + oldcontext = MemoryContextSwitchTo(tmpcontext); + + /* + * Stage 1: Compile the regexp into a NFA, using the regexp library. + */ +#ifdef IGNORECASE + RE_compile(®ex, text_re, + REG_ADVANCED | REG_NOSUB | REG_ICASE, collation); +#else + RE_compile(®ex, text_re, + REG_ADVANCED | REG_NOSUB, collation); +#endif + + trg = createTrgmNFAInternal(®ex, graph, rcontext); + + /* Clean up all the cruft we created (including regex) */ + MemoryContextSwitchTo(oldcontext); + MemoryContextDelete(tmpcontext); + + return trg; +} + +/* + * Body of createTrgmNFA, exclusive of regex compilation/freeing. + */ +static TRGM * +createTrgmNFAInternal(regex_t *regex, TrgmPackedGraph **graph, + MemoryContext rcontext) +{ + TRGM *trg; + TrgmNFA trgmNFA; + + trgmNFA.regex = regex; + + /* Collect color information from the regex */ + getColorInfo(regex, &trgmNFA); + +#ifdef TRGM_REGEXP_DEBUG + printSourceNFA(regex, trgmNFA.colorInfo, trgmNFA.ncolors); +#endif + + /* + * Stage 2: Create an expanded graph from the source NFA. + */ + transformGraph(&trgmNFA); + +#ifdef TRGM_REGEXP_DEBUG + printTrgmNFA(&trgmNFA); +#endif + + /* + * Fail if we were unable to make a nontrivial graph, ie it is possible to + * get from the initial state to the final state without reading any + * predictable trigram. + */ + if (trgmNFA.initState->flags & TSTATE_FIN) + return NULL; + + /* + * Stage 3: Select color trigrams to expand. Fail if too many trigrams. + */ + if (!selectColorTrigrams(&trgmNFA)) + return NULL; + + /* + * Stage 4: Expand color trigrams and pack graph into final + * representation. + */ + trg = expandColorTrigrams(&trgmNFA, rcontext); + + *graph = packGraph(&trgmNFA, rcontext); + +#ifdef TRGM_REGEXP_DEBUG + printTrgmPackedGraph(*graph, trg); +#endif + + return trg; +} + +/* + * Main entry point for evaluating a graph during index scanning. + * + * The check[] array is indexed by trigram number (in the array of simple + * trigrams returned by createTrgmNFA), and holds true for those trigrams + * that are present in the index entry being checked. + */ +bool +trigramsMatchGraph(TrgmPackedGraph *graph, bool *check) +{ + int i, + j, + k, + queueIn, + queueOut; + + /* + * Reset temporary working areas. + */ + memset(graph->colorTrigramsActive, 0, + sizeof(bool) * graph->colorTrigramsCount); + memset(graph->statesActive, 0, sizeof(bool) * graph->statesCount); + + /* + * Check which color trigrams were matched. A match for any simple + * trigram associated with a color trigram counts as a match of the color + * trigram. + */ + j = 0; + for (i = 0; i < graph->colorTrigramsCount; i++) + { + int cnt = graph->colorTrigramGroups[i]; + + for (k = j; k < j + cnt; k++) + { + if (check[k]) + { + /* + * Found one matched trigram in the group. Can skip the rest + * of them and go to the next group. + */ + graph->colorTrigramsActive[i] = true; + break; + } + } + j = j + cnt; + } + + /* + * Initialize the statesQueue to hold just the initial state. Note: + * statesQueue has room for statesCount entries, which is certainly enough + * since no state will be put in the queue more than once. The + * statesActive array marks which states have been queued. + */ + graph->statesActive[0] = true; + graph->statesQueue[0] = 0; + queueIn = 0; + queueOut = 1; + + /* Process queued states as long as there are any. */ + while (queueIn < queueOut) + { + int stateno = graph->statesQueue[queueIn++]; + TrgmPackedState *state = &graph->states[stateno]; + int cnt = state->arcsCount; + + /* Loop over state's out-arcs */ + for (i = 0; i < cnt; i++) + { + TrgmPackedArc *arc = &state->arcs[i]; + + /* + * If corresponding color trigram is present then activate the + * corresponding state. We're done if that's the final state, + * otherwise queue the state if it's not been queued already. + */ + if (graph->colorTrigramsActive[arc->colorTrgm]) + { + int nextstate = arc->targetState; + + if (nextstate == 1) + return true; /* success: final state is reachable */ + + if (!graph->statesActive[nextstate]) + { + graph->statesActive[nextstate] = true; + graph->statesQueue[queueOut++] = nextstate; + } + } + } + } + + /* Queue is empty, so match fails. */ + return false; +} + +/* + * Compile regex string into struct at *regex. + * NB: pg_regfree must be applied to regex if this completes successfully. + */ +static void +RE_compile(regex_t *regex, text *text_re, int cflags, Oid collation) +{ + 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); + + /* Compile regex */ + regcomp_result = pg_regcomp(regex, + pattern, + pattern_len, + cflags, + collation); + + pfree(pattern); + + if (regcomp_result != REG_OKAY) + { + /* re didn't compile (no need for pg_regfree, if so) */ + pg_regerror(regcomp_result, regex, errMsg, sizeof(errMsg)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION), + errmsg("invalid regular expression: %s", errMsg))); + } +} + + +/*--------------------- + * Subroutines for pre-processing the color map (stage 1). + *--------------------- + */ + +/* + * Fill TrgmColorInfo structure for each color using regex export functions. + */ +static void +getColorInfo(regex_t *regex, TrgmNFA *trgmNFA) +{ + int colorsCount = pg_reg_getnumcolors(regex); + int i; + + trgmNFA->ncolors = colorsCount; + trgmNFA->colorInfo = (TrgmColorInfo *) + palloc0(colorsCount * sizeof(TrgmColorInfo)); + + /* + * Loop over colors, filling TrgmColorInfo about each. Note we include + * WHITE (0) even though we know it'll be reported as non-expandable. + */ + for (i = 0; i < colorsCount; i++) + { + TrgmColorInfo *colorInfo = &trgmNFA->colorInfo[i]; + int charsCount = pg_reg_getnumcharacters(regex, i); + pg_wchar *chars; + int j; + + if (charsCount < 0 || charsCount > COLOR_COUNT_LIMIT) + { + /* Non expandable, or too large to work with */ + colorInfo->expandable = false; + continue; + } + + colorInfo->expandable = true; + colorInfo->containsNonWord = false; + colorInfo->wordChars = (trgm_mb_char *) + palloc(sizeof(trgm_mb_char) * charsCount); + colorInfo->wordCharsCount = 0; + + /* Extract all the chars in this color */ + chars = (pg_wchar *) palloc(sizeof(pg_wchar) * charsCount); + pg_reg_getcharacters(regex, i, chars, charsCount); + + /* + * Convert characters back to multibyte form, and save only those that + * are word characters. Set "containsNonWord" if any non-word + * character. (Note: it'd probably be nicer to keep the chars in + * pg_wchar format for now, but ISWORDCHR wants to see multibyte.) + */ + for (j = 0; j < charsCount; j++) + { + trgm_mb_char c; + + if (!convertPgWchar(chars[j], &c)) + continue; /* ok to ignore it altogether */ + if (ISWORDCHR(c.bytes)) + colorInfo->wordChars[colorInfo->wordCharsCount++] = c; + else + colorInfo->containsNonWord = true; + } + + pfree(chars); + } +} + +/* + * Convert pg_wchar to multibyte format. + * Returns false if the character should be ignored completely. + */ +static bool +convertPgWchar(pg_wchar c, trgm_mb_char *result) +{ + /* "s" has enough space for a multibyte character and a trailing NUL */ + char s[MAX_MULTIBYTE_CHAR_LEN + 1]; + + /* + * We can ignore the NUL character, since it can never appear in a PG text + * string. This avoids the need for various special cases when + * reconstructing trigrams. + */ + if (c == 0) + return false; + + /* Do the conversion, making sure the result is NUL-terminated */ + memset(s, 0, sizeof(s)); + pg_wchar2mb_with_len(&c, s, 1); + + /* + * In IGNORECASE mode, we can ignore uppercase characters. We assume that + * the regex engine generated both uppercase and lowercase equivalents + * within each color, since we used the REG_ICASE option; so there's no + * need to process the uppercase version. + * + * XXX this code is dependent on the assumption that lowerstr() works the + * same as the regex engine's internal case folding machinery. Might be + * wiser to expose pg_wc_tolower and test whether c == pg_wc_tolower(c). + * On the other hand, the trigrams in the index were created using + * lowerstr(), so we're probably screwed if there's any incompatibility + * anyway. + */ +#ifdef IGNORECASE + { + char *lowerCased = lowerstr(s); + + if (strcmp(lowerCased, s) != 0) + { + pfree(lowerCased); + return false; + } + pfree(lowerCased); + } +#endif + + /* Fill result with exactly MAX_MULTIBYTE_CHAR_LEN bytes */ + memcpy(result->bytes, s, MAX_MULTIBYTE_CHAR_LEN); + return true; +} + + +/*--------------------- + * Subroutines for expanding original NFA graph into a trigram graph (stage 2). + *--------------------- + */ + +/* + * Transform the graph, given a regex and extracted color information. + * + * We create and process a queue of expanded-graph states until all the states + * are processed. + * + * This algorithm may be stopped due to resource limitation. In this case we + * force every unprocessed branch to immediately finish with matching (this + * can give us false positives but no false negatives) by marking all + * unprocessed states as final. + */ +static void +transformGraph(TrgmNFA *trgmNFA) +{ + HASHCTL hashCtl; + TrgmStateKey initkey; + TrgmState *initstate; + ListCell *lc; + + /* Initialize this stage's workspace in trgmNFA struct */ + trgmNFA->queue = NIL; + trgmNFA->keysQueue = NIL; + trgmNFA->arcsCount = 0; + trgmNFA->overflowed = false; + + /* Create hashtable for states */ + hashCtl.keysize = sizeof(TrgmStateKey); + hashCtl.entrysize = sizeof(TrgmState); + hashCtl.hcxt = CurrentMemoryContext; + trgmNFA->states = hash_create("Trigram NFA", + 1024, + &hashCtl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + trgmNFA->nstates = 0; + + /* Create initial state: ambiguous prefix, NFA's initial state */ + MemSet(&initkey, 0, sizeof(initkey)); + initkey.prefix.colors[0] = COLOR_UNKNOWN; + initkey.prefix.colors[1] = COLOR_UNKNOWN; + initkey.nstate = pg_reg_getinitialstate(trgmNFA->regex); + + initstate = getState(trgmNFA, &initkey); + initstate->flags |= TSTATE_INIT; + trgmNFA->initState = initstate; + + /* + * Recursively build the expanded graph by processing queue of states + * (breadth-first search). getState already put initstate in the queue. + * Note that getState will append new states to the queue within the loop, + * too; this works as long as we don't do repeat fetches using the "lc" + * pointer. + */ + foreach(lc, trgmNFA->queue) + { + TrgmState *state = (TrgmState *) lfirst(lc); + + /* + * If we overflowed then just mark state as final. Otherwise do + * actual processing. + */ + if (trgmNFA->overflowed) + state->flags |= TSTATE_FIN; + else + processState(trgmNFA, state); + + /* Did we overflow? */ + if (trgmNFA->arcsCount > MAX_EXPANDED_ARCS || + hash_get_num_entries(trgmNFA->states) > MAX_EXPANDED_STATES) + trgmNFA->overflowed = true; + } +} + +/* + * Process one state: add enter keys and then add outgoing arcs. + */ +static void +processState(TrgmNFA *trgmNFA, TrgmState *state) +{ + ListCell *lc; + + /* keysQueue should be NIL already, but make sure */ + trgmNFA->keysQueue = NIL; + + /* + * Add state's own key, and then process all keys added to keysQueue until + * queue is finished. But we can quit if the state gets marked final. + */ + addKey(trgmNFA, state, &state->stateKey); + foreach(lc, trgmNFA->keysQueue) + { + TrgmStateKey *key = (TrgmStateKey *) lfirst(lc); + + if (state->flags & TSTATE_FIN) + break; + addKey(trgmNFA, state, key); + } + + /* Release keysQueue to clean up for next cycle */ + list_free(trgmNFA->keysQueue); + trgmNFA->keysQueue = NIL; + + /* + * Add outgoing arcs only if state isn't final (we have no interest in + * outgoing arcs if we already match) + */ + if (!(state->flags & TSTATE_FIN)) + addArcs(trgmNFA, state); +} + +/* + * Add the given enter key into the state's enterKeys list, and determine + * whether this should result in any further enter keys being added. + * If so, add those keys to keysQueue so that processState will handle them. + * + * If the enter key is for the NFA's final state, mark state as TSTATE_FIN. + * This situation means that we can reach the final state from this expanded + * state without reading any predictable trigram, so we must consider this + * state as an accepting one. + * + * The given key could be a duplicate of one already in enterKeys, or be + * redundant with some enterKeys. So we check that before doing anything. + * + * Note that we don't generate any actual arcs here. addArcs will do that + * later, after we have identified all the enter keys for this state. + */ +static void +addKey(TrgmNFA *trgmNFA, TrgmState *state, TrgmStateKey *key) +{ + regex_arc_t *arcs; + TrgmStateKey destKey; + ListCell *cell; + int i, + arcsCount; + + /* + * Ensure any pad bytes in destKey are zero, since it may get used as a + * hashtable key by getState. + */ + MemSet(&destKey, 0, sizeof(destKey)); + + /* + * Compare key to each existing enter key of the state to check for + * redundancy. We can drop either old key(s) or the new key if we find + * redundancy. + */ + foreach(cell, state->enterKeys) + { + TrgmStateKey *existingKey = (TrgmStateKey *) lfirst(cell); + + if (existingKey->nstate == key->nstate) + { + if (prefixContains(&existingKey->prefix, &key->prefix)) + { + /* This old key already covers the new key. Nothing to do */ + return; + } + if (prefixContains(&key->prefix, &existingKey->prefix)) + { + /* + * The new key covers this old key. Remove the old key, it's + * no longer needed once we add this key to the list. + */ + state->enterKeys = foreach_delete_current(state->enterKeys, + cell); + } + } + } + + /* No redundancy, so add this key to the state's list */ + state->enterKeys = lappend(state->enterKeys, key); + + /* If state is now known final, mark it and we're done */ + if (key->nstate == pg_reg_getfinalstate(trgmNFA->regex)) + { + state->flags |= TSTATE_FIN; + return; + } + + /* + * Loop through all outgoing arcs of the corresponding state in the + * original NFA. + */ + arcsCount = pg_reg_getnumoutarcs(trgmNFA->regex, key->nstate); + arcs = (regex_arc_t *) palloc(sizeof(regex_arc_t) * arcsCount); + pg_reg_getoutarcs(trgmNFA->regex, key->nstate, arcs, arcsCount); + + for (i = 0; i < arcsCount; i++) + { + regex_arc_t *arc = &arcs[i]; + + if (pg_reg_colorisbegin(trgmNFA->regex, arc->co)) + { + /* + * Start of line/string (^). Trigram extraction treats start of + * line same as start of word: double space prefix is added. + * Hence, make an enter key showing we can reach the arc + * destination with all-blank prefix. + */ + destKey.prefix.colors[0] = COLOR_BLANK; + destKey.prefix.colors[1] = COLOR_BLANK; + destKey.nstate = arc->to; + + /* Add enter key to this state */ + addKeyToQueue(trgmNFA, &destKey); + } + else if (pg_reg_colorisend(trgmNFA->regex, arc->co)) + { + /* + * End of line/string ($). We must consider this arc as a + * transition that doesn't read anything. The reason for adding + * this enter key to the state is that if the arc leads to the + * NFA's final state, we must mark this expanded state as final. + */ + destKey.prefix.colors[0] = COLOR_UNKNOWN; + destKey.prefix.colors[1] = COLOR_UNKNOWN; + destKey.nstate = arc->to; + + /* Add enter key to this state */ + addKeyToQueue(trgmNFA, &destKey); + } + else if (arc->co >= 0) + { + /* Regular color (including WHITE) */ + TrgmColorInfo *colorInfo = &trgmNFA->colorInfo[arc->co]; + + if (colorInfo->expandable) + { + if (colorInfo->containsNonWord && + !validArcLabel(key, COLOR_BLANK)) + { + /* + * We can reach the arc destination after reading a + * non-word character, but the prefix is not something + * that addArc will accept with COLOR_BLANK, so no trigram + * arc can get made for this transition. We must make an + * enter key to show that the arc destination is + * reachable. Set it up with an all-blank prefix, since + * that corresponds to what the trigram extraction code + * will do at a word starting boundary. + */ + destKey.prefix.colors[0] = COLOR_BLANK; + destKey.prefix.colors[1] = COLOR_BLANK; + destKey.nstate = arc->to; + addKeyToQueue(trgmNFA, &destKey); + } + + if (colorInfo->wordCharsCount > 0 && + !validArcLabel(key, arc->co)) + { + /* + * We can reach the arc destination after reading a word + * character, but the prefix is not something that addArc + * will accept, so no trigram arc can get made for this + * transition. We must make an enter key to show that the + * arc destination is reachable. The prefix for the enter + * key should reflect the info we have for this arc. + */ + destKey.prefix.colors[0] = key->prefix.colors[1]; + destKey.prefix.colors[1] = arc->co; + destKey.nstate = arc->to; + addKeyToQueue(trgmNFA, &destKey); + } + } + else + { + /* + * Unexpandable color. Add enter key with ambiguous prefix, + * showing we can reach the destination from this state, but + * the preceding colors will be uncertain. (We do not set the + * first prefix color to key->prefix.colors[1], because a + * prefix of known followed by unknown is invalid.) + */ + destKey.prefix.colors[0] = COLOR_UNKNOWN; + destKey.prefix.colors[1] = COLOR_UNKNOWN; + destKey.nstate = arc->to; + addKeyToQueue(trgmNFA, &destKey); + } + } + else + { + /* RAINBOW: treat as unexpandable color */ + destKey.prefix.colors[0] = COLOR_UNKNOWN; + destKey.prefix.colors[1] = COLOR_UNKNOWN; + destKey.nstate = arc->to; + addKeyToQueue(trgmNFA, &destKey); + } + } + + pfree(arcs); +} + +/* + * Add copy of given key to keysQueue for later processing. + */ +static void +addKeyToQueue(TrgmNFA *trgmNFA, TrgmStateKey *key) +{ + TrgmStateKey *keyCopy = (TrgmStateKey *) palloc(sizeof(TrgmStateKey)); + + memcpy(keyCopy, key, sizeof(TrgmStateKey)); + trgmNFA->keysQueue = lappend(trgmNFA->keysQueue, keyCopy); +} + +/* + * Add outgoing arcs from given state, whose enter keys are all now known. + */ +static void +addArcs(TrgmNFA *trgmNFA, TrgmState *state) +{ + TrgmStateKey destKey; + ListCell *cell; + regex_arc_t *arcs; + int arcsCount, + i; + + /* + * Ensure any pad bytes in destKey are zero, since it may get used as a + * hashtable key by getState. + */ + MemSet(&destKey, 0, sizeof(destKey)); + + /* + * Iterate over enter keys associated with this expanded-graph state. This + * includes both the state's own stateKey, and any enter keys we added to + * it during addKey (which represent expanded-graph states that are not + * distinguishable from this one by means of trigrams). For each such + * enter key, examine all the out-arcs of the key's underlying NFA state, + * and try to make a trigram arc leading to where the out-arc leads. + * (addArc will deal with whether the arc is valid or not.) + */ + foreach(cell, state->enterKeys) + { + TrgmStateKey *key = (TrgmStateKey *) lfirst(cell); + + arcsCount = pg_reg_getnumoutarcs(trgmNFA->regex, key->nstate); + arcs = (regex_arc_t *) palloc(sizeof(regex_arc_t) * arcsCount); + pg_reg_getoutarcs(trgmNFA->regex, key->nstate, arcs, arcsCount); + + for (i = 0; i < arcsCount; i++) + { + regex_arc_t *arc = &arcs[i]; + TrgmColorInfo *colorInfo; + + /* + * Ignore non-expandable colors; addKey already handled the case. + * + * We need no special check for WHITE or begin/end pseudocolors + * here. We don't need to do any processing for them, and they + * will be marked non-expandable since the regex engine will have + * reported them that way. We do have to watch out for RAINBOW, + * which has a negative color number. + */ + if (arc->co < 0) + continue; + Assert(arc->co < trgmNFA->ncolors); + + colorInfo = &trgmNFA->colorInfo[arc->co]; + if (!colorInfo->expandable) + continue; + + if (colorInfo->containsNonWord) + { + /* + * Color includes non-word character(s). + * + * Generate an arc, treating this transition as occurring on + * BLANK. This allows word-ending trigrams to be manufactured + * if possible. + */ + destKey.prefix.colors[0] = key->prefix.colors[1]; + destKey.prefix.colors[1] = COLOR_BLANK; + destKey.nstate = arc->to; + + addArc(trgmNFA, state, key, COLOR_BLANK, &destKey); + } + + if (colorInfo->wordCharsCount > 0) + { + /* + * Color includes word character(s). + * + * Generate an arc. Color is pushed into prefix of target + * state. + */ + destKey.prefix.colors[0] = key->prefix.colors[1]; + destKey.prefix.colors[1] = arc->co; + destKey.nstate = arc->to; + + addArc(trgmNFA, state, key, arc->co, &destKey); + } + } + + pfree(arcs); + } +} + +/* + * Generate an out-arc of the expanded graph, if it's valid and not redundant. + * + * state: expanded-graph state we want to add an out-arc to + * key: provides prefix colors (key->nstate is not used) + * co: transition color + * destKey: identifier for destination state of expanded graph + */ +static void +addArc(TrgmNFA *trgmNFA, TrgmState *state, TrgmStateKey *key, + TrgmColor co, TrgmStateKey *destKey) +{ + TrgmArc *arc; + ListCell *cell; + + /* Do nothing if this wouldn't be a valid arc label trigram */ + if (!validArcLabel(key, co)) + return; + + /* + * Check if we are going to reach key which is covered by a key which is + * already listed in this state. If so arc is useless: the NFA can bypass + * it through a path that doesn't require any predictable trigram, so + * whether the arc's trigram is present or not doesn't really matter. + */ + foreach(cell, state->enterKeys) + { + TrgmStateKey *existingKey = (TrgmStateKey *) lfirst(cell); + + if (existingKey->nstate == destKey->nstate && + prefixContains(&existingKey->prefix, &destKey->prefix)) + return; + } + + /* Checks were successful, add new arc */ + arc = (TrgmArc *) palloc(sizeof(TrgmArc)); + arc->target = getState(trgmNFA, destKey); + arc->ctrgm.colors[0] = key->prefix.colors[0]; + arc->ctrgm.colors[1] = key->prefix.colors[1]; + arc->ctrgm.colors[2] = co; + + state->arcs = lappend(state->arcs, arc); + trgmNFA->arcsCount++; +} + +/* + * Can we make a valid trigram arc label from the given prefix and arc color? + * + * This is split out so that tests in addKey and addArc will stay in sync. + */ +static bool +validArcLabel(TrgmStateKey *key, TrgmColor co) +{ + /* + * We have to know full trigram in order to add outgoing arc. So we can't + * do it if prefix is ambiguous. + */ + if (key->prefix.colors[0] == COLOR_UNKNOWN) + return false; + + /* If key->prefix.colors[0] isn't unknown, its second color isn't either */ + Assert(key->prefix.colors[1] != COLOR_UNKNOWN); + /* And we should not be called with an unknown arc color anytime */ + Assert(co != COLOR_UNKNOWN); + + /* + * We don't bother with making arcs representing three non-word + * characters, since that's useless for trigram extraction. + */ + if (key->prefix.colors[0] == COLOR_BLANK && + key->prefix.colors[1] == COLOR_BLANK && + co == COLOR_BLANK) + return false; + + /* + * We also reject nonblank-blank-anything. The nonblank-blank-nonblank + * case doesn't correspond to any trigram the trigram extraction code + * would make. The nonblank-blank-blank case is also not possible with + * RPADDING = 1. (Note that in many cases we'd fail to generate such a + * trigram even if it were valid, for example processing "foo bar" will + * not result in considering the trigram "o ". So if you want to support + * RPADDING = 2, there's more to do than just twiddle this test.) + */ + if (key->prefix.colors[0] != COLOR_BLANK && + key->prefix.colors[1] == COLOR_BLANK) + return false; + + /* + * Other combinations involving blank are valid, in particular we assume + * blank-blank-nonblank is valid, which presumes that LPADDING is 2. + * + * Note: Using again the example "foo bar", we will not consider the + * trigram " b", though this trigram would be found by the trigram + * extraction code. Since we will find " ba", it doesn't seem worth + * trying to hack the algorithm to generate the additional trigram. + */ + + /* arc label is valid */ + return true; +} + +/* + * Get state of expanded graph for given state key, + * and queue the state for processing if it didn't already exist. + */ +static TrgmState * +getState(TrgmNFA *trgmNFA, TrgmStateKey *key) +{ + TrgmState *state; + bool found; + + state = (TrgmState *) hash_search(trgmNFA->states, key, HASH_ENTER, + &found); + if (!found) + { + /* New state: initialize and queue it */ + state->arcs = NIL; + state->enterKeys = NIL; + state->flags = 0; + /* states are initially given negative numbers */ + state->snumber = -(++trgmNFA->nstates); + state->parent = NULL; + state->tentFlags = 0; + state->tentParent = NULL; + + trgmNFA->queue = lappend(trgmNFA->queue, state); + } + return state; +} + +/* + * Check if prefix1 "contains" prefix2. + * + * "contains" means that any exact prefix (with no ambiguity) that satisfies + * prefix2 also satisfies prefix1. + */ +static bool +prefixContains(TrgmPrefix *prefix1, TrgmPrefix *prefix2) +{ + if (prefix1->colors[1] == COLOR_UNKNOWN) + { + /* Fully ambiguous prefix contains everything */ + return true; + } + else if (prefix1->colors[0] == COLOR_UNKNOWN) + { + /* + * Prefix with only first unknown color contains every prefix with + * same second color. + */ + if (prefix1->colors[1] == prefix2->colors[1]) + return true; + else + return false; + } + else + { + /* Exact prefix contains only the exact same prefix */ + if (prefix1->colors[0] == prefix2->colors[0] && + prefix1->colors[1] == prefix2->colors[1]) + return true; + else + return false; + } +} + + +/*--------------------- + * Subroutines for expanding color trigrams into regular trigrams (stage 3). + *--------------------- + */ + +/* + * Get vector of all color trigrams in graph and select which of them + * to expand into simple trigrams. + * + * Returns true if OK, false if exhausted resource limits. + */ +static bool +selectColorTrigrams(TrgmNFA *trgmNFA) +{ + HASH_SEQ_STATUS scan_status; + int arcsCount = trgmNFA->arcsCount, + i; + TrgmState *state; + ColorTrgmInfo *colorTrgms; + int64 totalTrgmCount; + float4 totalTrgmPenalty; + int cnumber; + + /* Collect color trigrams from all arcs */ + colorTrgms = (ColorTrgmInfo *) palloc0(sizeof(ColorTrgmInfo) * arcsCount); + trgmNFA->colorTrgms = colorTrgms; + + i = 0; + hash_seq_init(&scan_status, trgmNFA->states); + while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL) + { + ListCell *cell; + + foreach(cell, state->arcs) + { + TrgmArc *arc = (TrgmArc *) lfirst(cell); + TrgmArcInfo *arcInfo = (TrgmArcInfo *) palloc(sizeof(TrgmArcInfo)); + ColorTrgmInfo *trgmInfo = &colorTrgms[i]; + + arcInfo->source = state; + arcInfo->target = arc->target; + trgmInfo->ctrgm = arc->ctrgm; + trgmInfo->cnumber = -1; + /* count and penalty will be set below */ + trgmInfo->expanded = true; + trgmInfo->arcs = list_make1(arcInfo); + i++; + } + } + Assert(i == arcsCount); + + /* Remove duplicates, merging their arcs lists */ + if (arcsCount >= 2) + { + ColorTrgmInfo *p1, + *p2; + + /* Sort trigrams to ease duplicate detection */ + qsort(colorTrgms, arcsCount, sizeof(ColorTrgmInfo), colorTrgmInfoCmp); + + /* p1 is probe point, p2 is last known non-duplicate. */ + p2 = colorTrgms; + for (p1 = colorTrgms + 1; p1 < colorTrgms + arcsCount; p1++) + { + if (colorTrgmInfoCmp(p1, p2) > 0) + { + p2++; + *p2 = *p1; + } + else + { + p2->arcs = list_concat(p2->arcs, p1->arcs); + } + } + trgmNFA->colorTrgmsCount = (p2 - colorTrgms) + 1; + } + else + { + trgmNFA->colorTrgmsCount = arcsCount; + } + + /* + * Count number of simple trigrams generated by each color trigram, and + * also compute a penalty value, which is the number of simple trigrams + * times a multiplier that depends on its whitespace content. + * + * Note: per-color-trigram counts cannot overflow an int so long as + * COLOR_COUNT_LIMIT is not more than the cube root of INT_MAX, ie about + * 1290. However, the grand total totalTrgmCount might conceivably + * overflow an int, so we use int64 for that within this routine. Also, + * penalties are calculated in float4 arithmetic to avoid any overflow + * worries. + */ + totalTrgmCount = 0; + totalTrgmPenalty = 0.0f; + for (i = 0; i < trgmNFA->colorTrgmsCount; i++) + { + ColorTrgmInfo *trgmInfo = &colorTrgms[i]; + int j, + count = 1, + typeIndex = 0; + + for (j = 0; j < 3; j++) + { + TrgmColor c = trgmInfo->ctrgm.colors[j]; + + typeIndex *= 2; + if (c == COLOR_BLANK) + typeIndex++; + else + count *= trgmNFA->colorInfo[c].wordCharsCount; + } + trgmInfo->count = count; + totalTrgmCount += count; + trgmInfo->penalty = penalties[typeIndex] * (float4) count; + totalTrgmPenalty += trgmInfo->penalty; + } + + /* Sort color trigrams in descending order of their penalties */ + qsort(colorTrgms, trgmNFA->colorTrgmsCount, sizeof(ColorTrgmInfo), + colorTrgmInfoPenaltyCmp); + + /* + * Remove color trigrams from the graph so long as total penalty of color + * trigrams exceeds WISH_TRGM_PENALTY. (If we fail to get down to + * WISH_TRGM_PENALTY, it's OK so long as total count is no more than + * MAX_TRGM_COUNT.) We prefer to remove color trigrams with higher + * penalty, since those are the most promising for reducing the total + * penalty. When removing a color trigram we have to merge states + * connected by arcs labeled with that trigram. It's necessary to not + * merge initial and final states, because our graph becomes useless if + * that happens; so we cannot always remove the trigram we'd prefer to. + */ + for (i = 0; i < trgmNFA->colorTrgmsCount; i++) + { + ColorTrgmInfo *trgmInfo = &colorTrgms[i]; + bool canRemove = true; + ListCell *cell; + + /* Done if we've reached the target */ + if (totalTrgmPenalty <= WISH_TRGM_PENALTY) + break; + +#ifdef TRGM_REGEXP_DEBUG + fprintf(stderr, "considering ctrgm %d %d %d, penalty %f, %d arcs\n", + trgmInfo->ctrgm.colors[0], + trgmInfo->ctrgm.colors[1], + trgmInfo->ctrgm.colors[2], + trgmInfo->penalty, + list_length(trgmInfo->arcs)); +#endif + + /* + * Does any arc of this color trigram connect initial and final + * states? If so we can't remove it. + */ + foreach(cell, trgmInfo->arcs) + { + TrgmArcInfo *arcInfo = (TrgmArcInfo *) lfirst(cell); + TrgmState *source = arcInfo->source, + *target = arcInfo->target; + int source_flags, + target_flags; + +#ifdef TRGM_REGEXP_DEBUG + fprintf(stderr, "examining arc to s%d (%x) from s%d (%x)\n", + -target->snumber, target->flags, + -source->snumber, source->flags); +#endif + + /* examine parent states, if any merging has already happened */ + while (source->parent) + source = source->parent; + while (target->parent) + target = target->parent; + +#ifdef TRGM_REGEXP_DEBUG + fprintf(stderr, " ... after completed merges: to s%d (%x) from s%d (%x)\n", + -target->snumber, target->flags, + -source->snumber, source->flags); +#endif + + /* we must also consider merges we are planning right now */ + source_flags = source->flags | source->tentFlags; + while (source->tentParent) + { + source = source->tentParent; + source_flags |= source->flags | source->tentFlags; + } + target_flags = target->flags | target->tentFlags; + while (target->tentParent) + { + target = target->tentParent; + target_flags |= target->flags | target->tentFlags; + } + +#ifdef TRGM_REGEXP_DEBUG + fprintf(stderr, " ... after tentative merges: to s%d (%x) from s%d (%x)\n", + -target->snumber, target_flags, + -source->snumber, source_flags); +#endif + + /* would fully-merged state have both INIT and FIN set? */ + if (((source_flags | target_flags) & (TSTATE_INIT | TSTATE_FIN)) == + (TSTATE_INIT | TSTATE_FIN)) + { + canRemove = false; + break; + } + + /* ok so far, so remember planned merge */ + if (source != target) + { +#ifdef TRGM_REGEXP_DEBUG + fprintf(stderr, " ... tentatively merging s%d into s%d\n", + -target->snumber, -source->snumber); +#endif + target->tentParent = source; + source->tentFlags |= target_flags; + } + } + + /* + * We must reset all the tentFlags/tentParent fields before + * continuing. tentFlags could only have become set in states that + * are the source or parent or tentative parent of one of the current + * arcs; likewise tentParent could only have become set in states that + * are the target or parent or tentative parent of one of the current + * arcs. There might be some overlap between those sets, but if we + * clear tentFlags in target states as well as source states, we + * should be okay even if we visit a state as target before visiting + * it as a source. + */ + foreach(cell, trgmInfo->arcs) + { + TrgmArcInfo *arcInfo = (TrgmArcInfo *) lfirst(cell); + TrgmState *source = arcInfo->source, + *target = arcInfo->target; + TrgmState *ttarget; + + /* no need to touch previously-merged states */ + while (source->parent) + source = source->parent; + while (target->parent) + target = target->parent; + + while (source) + { + source->tentFlags = 0; + source = source->tentParent; + } + + while ((ttarget = target->tentParent) != NULL) + { + target->tentParent = NULL; + target->tentFlags = 0; /* in case it was also a source */ + target = ttarget; + } + } + + /* Now, move on if we can't drop this trigram */ + if (!canRemove) + { +#ifdef TRGM_REGEXP_DEBUG + fprintf(stderr, " ... not ok to merge\n"); +#endif + continue; + } + + /* OK, merge states linked by each arc labeled by the trigram */ + foreach(cell, trgmInfo->arcs) + { + TrgmArcInfo *arcInfo = (TrgmArcInfo *) lfirst(cell); + TrgmState *source = arcInfo->source, + *target = arcInfo->target; + + while (source->parent) + source = source->parent; + while (target->parent) + target = target->parent; + if (source != target) + { +#ifdef TRGM_REGEXP_DEBUG + fprintf(stderr, "merging s%d into s%d\n", + -target->snumber, -source->snumber); +#endif + mergeStates(source, target); + /* Assert we didn't merge initial and final states */ + Assert((source->flags & (TSTATE_INIT | TSTATE_FIN)) != + (TSTATE_INIT | TSTATE_FIN)); + } + } + + /* Mark trigram unexpanded, and update totals */ + trgmInfo->expanded = false; + totalTrgmCount -= trgmInfo->count; + totalTrgmPenalty -= trgmInfo->penalty; + } + + /* Did we succeed in fitting into MAX_TRGM_COUNT? */ + if (totalTrgmCount > MAX_TRGM_COUNT) + return false; + + trgmNFA->totalTrgmCount = (int) totalTrgmCount; + + /* + * Sort color trigrams by colors (will be useful for bsearch in packGraph) + * and enumerate the color trigrams that are expanded. + */ + cnumber = 0; + qsort(colorTrgms, trgmNFA->colorTrgmsCount, sizeof(ColorTrgmInfo), + colorTrgmInfoCmp); + for (i = 0; i < trgmNFA->colorTrgmsCount; i++) + { + if (colorTrgms[i].expanded) + { + colorTrgms[i].cnumber = cnumber; + cnumber++; + } + } + + return true; +} + +/* + * Expand selected color trigrams into regular trigrams. + * + * Returns the TRGM array to be passed to the index machinery. + * The array must be allocated in rcontext. + */ +static TRGM * +expandColorTrigrams(TrgmNFA *trgmNFA, MemoryContext rcontext) +{ + TRGM *trg; + trgm *p; + int i; + TrgmColorInfo blankColor; + trgm_mb_char blankChar; + + /* Set up "blank" color structure containing a single zero character */ + memset(blankChar.bytes, 0, sizeof(blankChar.bytes)); + blankColor.wordCharsCount = 1; + blankColor.wordChars = &blankChar; + + /* Construct the trgm array */ + trg = (TRGM *) + MemoryContextAllocZero(rcontext, + TRGMHDRSIZE + + trgmNFA->totalTrgmCount * sizeof(trgm)); + trg->flag = ARRKEY; + SET_VARSIZE(trg, CALCGTSIZE(ARRKEY, trgmNFA->totalTrgmCount)); + p = GETARR(trg); + for (i = 0; i < trgmNFA->colorTrgmsCount; i++) + { + ColorTrgmInfo *colorTrgm = &trgmNFA->colorTrgms[i]; + TrgmColorInfo *c[3]; + trgm_mb_char s[3]; + int j, + i1, + i2, + i3; + + /* Ignore any unexpanded trigrams ... */ + if (!colorTrgm->expanded) + continue; + + /* Get colors, substituting the dummy struct for COLOR_BLANK */ + for (j = 0; j < 3; j++) + { + if (colorTrgm->ctrgm.colors[j] != COLOR_BLANK) + c[j] = &trgmNFA->colorInfo[colorTrgm->ctrgm.colors[j]]; + else + c[j] = &blankColor; + } + + /* Iterate over all possible combinations of colors' characters */ + for (i1 = 0; i1 < c[0]->wordCharsCount; i1++) + { + s[0] = c[0]->wordChars[i1]; + for (i2 = 0; i2 < c[1]->wordCharsCount; i2++) + { + s[1] = c[1]->wordChars[i2]; + for (i3 = 0; i3 < c[2]->wordCharsCount; i3++) + { + s[2] = c[2]->wordChars[i3]; + fillTrgm(p, s); + p++; + } + } + } + } + + return trg; +} + +/* + * Convert trigram into trgm datatype. + */ +static void +fillTrgm(trgm *ptrgm, trgm_mb_char s[3]) +{ + char str[3 * MAX_MULTIBYTE_CHAR_LEN], + *p; + int i, + j; + + /* Write multibyte string into "str" (we don't need null termination) */ + p = str; + + for (i = 0; i < 3; i++) + { + if (s[i].bytes[0] != 0) + { + for (j = 0; j < MAX_MULTIBYTE_CHAR_LEN && s[i].bytes[j]; j++) + *p++ = s[i].bytes[j]; + } + else + { + /* Emit a space in place of COLOR_BLANK */ + *p++ = ' '; + } + } + + /* Convert "str" to a standard trigram (possibly hashing it) */ + compact_trigram(ptrgm, str, p - str); +} + +/* + * Merge two states of graph. + */ +static void +mergeStates(TrgmState *state1, TrgmState *state2) +{ + Assert(state1 != state2); + Assert(!state1->parent); + Assert(!state2->parent); + + /* state1 absorbs state2's flags */ + state1->flags |= state2->flags; + + /* state2, and indirectly all its children, become children of state1 */ + state2->parent = state1; +} + +/* + * Compare function for sorting of color trigrams by their colors. + */ +static int +colorTrgmInfoCmp(const void *p1, const void *p2) +{ + const ColorTrgmInfo *c1 = (const ColorTrgmInfo *) p1; + const ColorTrgmInfo *c2 = (const ColorTrgmInfo *) p2; + + return memcmp(&c1->ctrgm, &c2->ctrgm, sizeof(ColorTrgm)); +} + +/* + * Compare function for sorting color trigrams in descending order of + * their penalty fields. + */ +static int +colorTrgmInfoPenaltyCmp(const void *p1, const void *p2) +{ + float4 penalty1 = ((const ColorTrgmInfo *) p1)->penalty; + float4 penalty2 = ((const ColorTrgmInfo *) p2)->penalty; + + if (penalty1 < penalty2) + return 1; + else if (penalty1 == penalty2) + return 0; + else + return -1; +} + + +/*--------------------- + * Subroutines for packing the graph into final representation (stage 4). + *--------------------- + */ + +/* + * Pack expanded graph into final representation. + * + * The result data must be allocated in rcontext. + */ +static TrgmPackedGraph * +packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext) +{ + int snumber = 2, + arcIndex, + arcsCount; + HASH_SEQ_STATUS scan_status; + TrgmState *state; + TrgmPackArcInfo *arcs; + TrgmPackedArc *packedArcs; + TrgmPackedGraph *result; + int i, + j; + + /* Enumerate surviving states, giving init and fin reserved numbers */ + hash_seq_init(&scan_status, trgmNFA->states); + while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL) + { + while (state->parent) + state = state->parent; + + if (state->snumber < 0) + { + if (state->flags & TSTATE_INIT) + state->snumber = 0; + else if (state->flags & TSTATE_FIN) + state->snumber = 1; + else + { + state->snumber = snumber; + snumber++; + } + } + } + + /* Collect array of all arcs */ + arcs = (TrgmPackArcInfo *) + palloc(sizeof(TrgmPackArcInfo) * trgmNFA->arcsCount); + arcIndex = 0; + hash_seq_init(&scan_status, trgmNFA->states); + while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL) + { + TrgmState *source = state; + ListCell *cell; + + while (source->parent) + source = source->parent; + + foreach(cell, state->arcs) + { + TrgmArc *arc = (TrgmArc *) lfirst(cell); + TrgmState *target = arc->target; + + while (target->parent) + target = target->parent; + + if (source->snumber != target->snumber) + { + ColorTrgmInfo *ctrgm; + + ctrgm = (ColorTrgmInfo *) bsearch(&arc->ctrgm, + trgmNFA->colorTrgms, + trgmNFA->colorTrgmsCount, + sizeof(ColorTrgmInfo), + colorTrgmInfoCmp); + Assert(ctrgm != NULL); + Assert(ctrgm->expanded); + + arcs[arcIndex].sourceState = source->snumber; + arcs[arcIndex].targetState = target->snumber; + arcs[arcIndex].colorTrgm = ctrgm->cnumber; + arcIndex++; + } + } + } + + /* Sort arcs to ease duplicate detection */ + qsort(arcs, arcIndex, sizeof(TrgmPackArcInfo), packArcInfoCmp); + + /* We could have duplicates because states were merged. Remove them. */ + if (arcIndex > 1) + { + /* p1 is probe point, p2 is last known non-duplicate. */ + TrgmPackArcInfo *p1, + *p2; + + p2 = arcs; + for (p1 = arcs + 1; p1 < arcs + arcIndex; p1++) + { + if (packArcInfoCmp(p1, p2) > 0) + { + p2++; + *p2 = *p1; + } + } + arcsCount = (p2 - arcs) + 1; + } + else + arcsCount = arcIndex; + + /* Create packed representation */ + result = (TrgmPackedGraph *) + MemoryContextAlloc(rcontext, sizeof(TrgmPackedGraph)); + + /* Pack color trigrams information */ + result->colorTrigramsCount = 0; + for (i = 0; i < trgmNFA->colorTrgmsCount; i++) + { + if (trgmNFA->colorTrgms[i].expanded) + result->colorTrigramsCount++; + } + result->colorTrigramGroups = (int *) + MemoryContextAlloc(rcontext, sizeof(int) * result->colorTrigramsCount); + j = 0; + for (i = 0; i < trgmNFA->colorTrgmsCount; i++) + { + if (trgmNFA->colorTrgms[i].expanded) + { + result->colorTrigramGroups[j] = trgmNFA->colorTrgms[i].count; + j++; + } + } + + /* Pack states and arcs information */ + result->statesCount = snumber; + result->states = (TrgmPackedState *) + MemoryContextAlloc(rcontext, snumber * sizeof(TrgmPackedState)); + packedArcs = (TrgmPackedArc *) + MemoryContextAlloc(rcontext, arcsCount * sizeof(TrgmPackedArc)); + j = 0; + for (i = 0; i < snumber; i++) + { + int cnt = 0; + + result->states[i].arcs = &packedArcs[j]; + while (j < arcsCount && arcs[j].sourceState == i) + { + packedArcs[j].targetState = arcs[j].targetState; + packedArcs[j].colorTrgm = arcs[j].colorTrgm; + cnt++; + j++; + } + result->states[i].arcsCount = cnt; + } + + /* Allocate working memory for trigramsMatchGraph() */ + result->colorTrigramsActive = (bool *) + MemoryContextAlloc(rcontext, sizeof(bool) * result->colorTrigramsCount); + result->statesActive = (bool *) + MemoryContextAlloc(rcontext, sizeof(bool) * result->statesCount); + result->statesQueue = (int *) + MemoryContextAlloc(rcontext, sizeof(int) * result->statesCount); + + return result; +} + +/* + * Comparison function for sorting TrgmPackArcInfos. + * + * Compares arcs in following order: sourceState, colorTrgm, targetState. + */ +static int +packArcInfoCmp(const void *a1, const void *a2) +{ + const TrgmPackArcInfo *p1 = (const TrgmPackArcInfo *) a1; + const TrgmPackArcInfo *p2 = (const TrgmPackArcInfo *) a2; + + if (p1->sourceState < p2->sourceState) + return -1; + if (p1->sourceState > p2->sourceState) + return 1; + if (p1->colorTrgm < p2->colorTrgm) + return -1; + if (p1->colorTrgm > p2->colorTrgm) + return 1; + if (p1->targetState < p2->targetState) + return -1; + if (p1->targetState > p2->targetState) + return 1; + return 0; +} + + +/*--------------------- + * Debugging functions + * + * These are designed to emit GraphViz files. + *--------------------- + */ + +#ifdef TRGM_REGEXP_DEBUG + +/* + * Print initial NFA, in regexp library's representation + */ +static void +printSourceNFA(regex_t *regex, TrgmColorInfo *colors, int ncolors) +{ + StringInfoData buf; + int nstates = pg_reg_getnumstates(regex); + int state; + int i; + + initStringInfo(&buf); + + appendStringInfoString(&buf, "\ndigraph sourceNFA {\n"); + + for (state = 0; state < nstates; state++) + { + regex_arc_t *arcs; + int i, + arcsCount; + + appendStringInfo(&buf, "s%d", state); + if (pg_reg_getfinalstate(regex) == state) + appendStringInfoString(&buf, " [shape = doublecircle]"); + appendStringInfoString(&buf, ";\n"); + + arcsCount = pg_reg_getnumoutarcs(regex, state); + arcs = (regex_arc_t *) palloc(sizeof(regex_arc_t) * arcsCount); + pg_reg_getoutarcs(regex, state, arcs, arcsCount); + + for (i = 0; i < arcsCount; i++) + { + appendStringInfo(&buf, " s%d -> s%d [label = \"%d\"];\n", + state, arcs[i].to, arcs[i].co); + } + + pfree(arcs); + } + + appendStringInfoString(&buf, " node [shape = point ]; initial;\n"); + appendStringInfo(&buf, " initial -> s%d;\n", + pg_reg_getinitialstate(regex)); + + /* Print colors */ + appendStringInfoString(&buf, " { rank = sink;\n"); + appendStringInfoString(&buf, " Colors [shape = none, margin=0, label=<\n"); + + for (i = 0; i < ncolors; i++) + { + TrgmColorInfo *color = &colors[i]; + int j; + + appendStringInfo(&buf, "
Color %d: ", i); + if (color->expandable) + { + for (j = 0; j < color->wordCharsCount; j++) + { + char s[MAX_MULTIBYTE_CHAR_LEN + 1]; + + memcpy(s, color->wordChars[j].bytes, MAX_MULTIBYTE_CHAR_LEN); + s[MAX_MULTIBYTE_CHAR_LEN] = '\0'; + appendStringInfoString(&buf, s); + } + } + else + appendStringInfoString(&buf, "not expandable"); + appendStringInfoChar(&buf, '\n'); + } + + appendStringInfoString(&buf, " >];\n"); + appendStringInfoString(&buf, " }\n"); + appendStringInfoString(&buf, "}\n"); + + { + /* dot -Tpng -o /tmp/source.png < /tmp/source.gv */ + FILE *fp = fopen("/tmp/source.gv", "w"); + + fprintf(fp, "%s", buf.data); + fclose(fp); + } + + pfree(buf.data); +} + +/* + * Print expanded graph. + */ +static void +printTrgmNFA(TrgmNFA *trgmNFA) +{ + StringInfoData buf; + HASH_SEQ_STATUS scan_status; + TrgmState *state; + TrgmState *initstate = NULL; + + initStringInfo(&buf); + + appendStringInfoString(&buf, "\ndigraph transformedNFA {\n"); + + hash_seq_init(&scan_status, trgmNFA->states); + while ((state = (TrgmState *) hash_seq_search(&scan_status)) != NULL) + { + ListCell *cell; + + appendStringInfo(&buf, "s%d", -state->snumber); + if (state->flags & TSTATE_FIN) + appendStringInfoString(&buf, " [shape = doublecircle]"); + if (state->flags & TSTATE_INIT) + initstate = state; + appendStringInfo(&buf, " [label = \"%d\"]", state->stateKey.nstate); + appendStringInfoString(&buf, ";\n"); + + foreach(cell, state->arcs) + { + TrgmArc *arc = (TrgmArc *) lfirst(cell); + + appendStringInfo(&buf, " s%d -> s%d [label = \"", + -state->snumber, -arc->target->snumber); + printTrgmColor(&buf, arc->ctrgm.colors[0]); + appendStringInfoChar(&buf, ' '); + printTrgmColor(&buf, arc->ctrgm.colors[1]); + appendStringInfoChar(&buf, ' '); + printTrgmColor(&buf, arc->ctrgm.colors[2]); + appendStringInfoString(&buf, "\"];\n"); + } + } + + if (initstate) + { + appendStringInfoString(&buf, " node [shape = point ]; initial;\n"); + appendStringInfo(&buf, " initial -> s%d;\n", -initstate->snumber); + } + + appendStringInfoString(&buf, "}\n"); + + { + /* dot -Tpng -o /tmp/transformed.png < /tmp/transformed.gv */ + FILE *fp = fopen("/tmp/transformed.gv", "w"); + + fprintf(fp, "%s", buf.data); + fclose(fp); + } + + pfree(buf.data); +} + +/* + * Print a TrgmColor readably. + */ +static void +printTrgmColor(StringInfo buf, TrgmColor co) +{ + if (co == COLOR_UNKNOWN) + appendStringInfoChar(buf, 'u'); + else if (co == COLOR_BLANK) + appendStringInfoChar(buf, 'b'); + else + appendStringInfo(buf, "%d", (int) co); +} + +/* + * Print final packed representation of trigram-based expanded graph. + */ +static void +printTrgmPackedGraph(TrgmPackedGraph *packedGraph, TRGM *trigrams) +{ + StringInfoData buf; + trgm *p; + int i; + + initStringInfo(&buf); + + appendStringInfoString(&buf, "\ndigraph packedGraph {\n"); + + for (i = 0; i < packedGraph->statesCount; i++) + { + TrgmPackedState *state = &packedGraph->states[i]; + int j; + + appendStringInfo(&buf, " s%d", i); + if (i == 1) + appendStringInfoString(&buf, " [shape = doublecircle]"); + + appendStringInfo(&buf, " [label = ];\n", i); + + for (j = 0; j < state->arcsCount; j++) + { + TrgmPackedArc *arc = &state->arcs[j]; + + appendStringInfo(&buf, " s%d -> s%d [label = \"trigram %d\"];\n", + i, arc->targetState, arc->colorTrgm); + } + } + + appendStringInfoString(&buf, " node [shape = point ]; initial;\n"); + appendStringInfo(&buf, " initial -> s%d;\n", 0); + + /* Print trigrams */ + appendStringInfoString(&buf, " { rank = sink;\n"); + appendStringInfoString(&buf, " Trigrams [shape = none, margin=0, label=<\n"); + + p = GETARR(trigrams); + for (i = 0; i < packedGraph->colorTrigramsCount; i++) + { + int count = packedGraph->colorTrigramGroups[i]; + int j; + + appendStringInfo(&buf, "
Trigram %d: ", i); + + for (j = 0; j < count; j++) + { + if (j > 0) + appendStringInfoString(&buf, ", "); + + /* + * XXX This representation is nice only for all-ASCII trigrams. + */ + appendStringInfo(&buf, "\"%c%c%c\"", (*p)[0], (*p)[1], (*p)[2]); + p++; + } + } + + appendStringInfoString(&buf, " >];\n"); + appendStringInfoString(&buf, " }\n"); + appendStringInfoString(&buf, "}\n"); + + { + /* dot -Tpng -o /tmp/packed.png < /tmp/packed.gv */ + FILE *fp = fopen("/tmp/packed.gv", "w"); + + fprintf(fp, "%s", buf.data); + fclose(fp); + } + + pfree(buf.data); +} + +#endif /* TRGM_REGEXP_DEBUG */ diff --git a/contrib/pg_visibility/.gitignore b/contrib/pg_visibility/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pg_visibility/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pg_visibility/Makefile b/contrib/pg_visibility/Makefile new file mode 100644 index 0000000..b3b1a89 --- /dev/null +++ b/contrib/pg_visibility/Makefile @@ -0,0 +1,24 @@ +# contrib/pg_visibility/Makefile + +MODULE_big = pg_visibility +OBJS = \ + $(WIN32RES) \ + pg_visibility.o + +EXTENSION = pg_visibility +DATA = pg_visibility--1.1.sql pg_visibility--1.1--1.2.sql \ + pg_visibility--1.0--1.1.sql +PGFILEDESC = "pg_visibility - page visibility information" + +REGRESS = pg_visibility + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pg_visibility +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pg_visibility/expected/pg_visibility.out b/contrib/pg_visibility/expected/pg_visibility.out new file mode 100644 index 0000000..9de54db --- /dev/null +++ b/contrib/pg_visibility/expected/pg_visibility.out @@ -0,0 +1,279 @@ +CREATE EXTENSION pg_visibility; +-- +-- recently-dropped table +-- +\set VERBOSITY sqlstate +BEGIN; +CREATE TABLE droppedtest (c int); +SELECT 'droppedtest'::regclass::oid AS oid \gset +SAVEPOINT q; DROP TABLE droppedtest; RELEASE q; +SAVEPOINT q; SELECT * FROM pg_visibility_map(:oid); ROLLBACK TO q; +ERROR: XX000 +-- ERROR: could not open relation with OID 16xxx +SAVEPOINT q; SELECT 1; ROLLBACK TO q; + ?column? +---------- + 1 +(1 row) + +SAVEPOINT q; SELECT 1; ROLLBACK TO q; + ?column? +---------- + 1 +(1 row) + +SELECT pg_relation_size(:oid), pg_relation_filepath(:oid), + has_table_privilege(:oid, 'SELECT'); + pg_relation_size | pg_relation_filepath | has_table_privilege +------------------+----------------------+--------------------- + | | +(1 row) + +SELECT * FROM pg_visibility_map(:oid); +ERROR: XX000 +-- ERROR: could not open relation with OID 16xxx +ROLLBACK; +\set VERBOSITY default +-- +-- check that using the module's functions with unsupported relations will fail +-- +-- partitioned tables (the parent ones) don't have visibility maps +create table test_partitioned (a int) partition by list (a); +-- these should all fail +select pg_visibility('test_partitioned', 0); +ERROR: relation "test_partitioned" is of wrong relation kind +DETAIL: This operation is not supported for partitioned tables. +select pg_visibility_map('test_partitioned'); +ERROR: relation "test_partitioned" is of wrong relation kind +DETAIL: This operation is not supported for partitioned tables. +select pg_visibility_map_summary('test_partitioned'); +ERROR: relation "test_partitioned" is of wrong relation kind +DETAIL: This operation is not supported for partitioned tables. +select pg_check_frozen('test_partitioned'); +ERROR: relation "test_partitioned" is of wrong relation kind +DETAIL: This operation is not supported for partitioned tables. +select pg_truncate_visibility_map('test_partitioned'); +ERROR: relation "test_partitioned" is of wrong relation kind +DETAIL: This operation is not supported for partitioned tables. +create table test_partition partition of test_partitioned for values in (1); +create index test_index on test_partition (a); +-- indexes do not, so these all fail +select pg_visibility('test_index', 0); +ERROR: relation "test_index" is of wrong relation kind +DETAIL: This operation is not supported for indexes. +select pg_visibility_map('test_index'); +ERROR: relation "test_index" is of wrong relation kind +DETAIL: This operation is not supported for indexes. +select pg_visibility_map_summary('test_index'); +ERROR: relation "test_index" is of wrong relation kind +DETAIL: This operation is not supported for indexes. +select pg_check_frozen('test_index'); +ERROR: relation "test_index" is of wrong relation kind +DETAIL: This operation is not supported for indexes. +select pg_truncate_visibility_map('test_index'); +ERROR: relation "test_index" is of wrong relation kind +DETAIL: This operation is not supported for indexes. +create view test_view as select 1; +-- views do not have VMs, so these all fail +select pg_visibility('test_view', 0); +ERROR: relation "test_view" is of wrong relation kind +DETAIL: This operation is not supported for views. +select pg_visibility_map('test_view'); +ERROR: relation "test_view" is of wrong relation kind +DETAIL: This operation is not supported for views. +select pg_visibility_map_summary('test_view'); +ERROR: relation "test_view" is of wrong relation kind +DETAIL: This operation is not supported for views. +select pg_check_frozen('test_view'); +ERROR: relation "test_view" is of wrong relation kind +DETAIL: This operation is not supported for views. +select pg_truncate_visibility_map('test_view'); +ERROR: relation "test_view" is of wrong relation kind +DETAIL: This operation is not supported for views. +create sequence test_sequence; +-- sequences do not have VMs, so these all fail +select pg_visibility('test_sequence', 0); +ERROR: relation "test_sequence" is of wrong relation kind +DETAIL: This operation is not supported for sequences. +select pg_visibility_map('test_sequence'); +ERROR: relation "test_sequence" is of wrong relation kind +DETAIL: This operation is not supported for sequences. +select pg_visibility_map_summary('test_sequence'); +ERROR: relation "test_sequence" is of wrong relation kind +DETAIL: This operation is not supported for sequences. +select pg_check_frozen('test_sequence'); +ERROR: relation "test_sequence" is of wrong relation kind +DETAIL: This operation is not supported for sequences. +select pg_truncate_visibility_map('test_sequence'); +ERROR: relation "test_sequence" is of wrong relation kind +DETAIL: This operation is not supported for sequences. +create foreign data wrapper dummy; +create server dummy_server foreign data wrapper dummy; +create foreign table test_foreign_table () server dummy_server; +-- foreign tables do not have VMs, so these all fail +select pg_visibility('test_foreign_table', 0); +ERROR: relation "test_foreign_table" is of wrong relation kind +DETAIL: This operation is not supported for foreign tables. +select pg_visibility_map('test_foreign_table'); +ERROR: relation "test_foreign_table" is of wrong relation kind +DETAIL: This operation is not supported for foreign tables. +select pg_visibility_map_summary('test_foreign_table'); +ERROR: relation "test_foreign_table" is of wrong relation kind +DETAIL: This operation is not supported for foreign tables. +select pg_check_frozen('test_foreign_table'); +ERROR: relation "test_foreign_table" is of wrong relation kind +DETAIL: This operation is not supported for foreign tables. +select pg_truncate_visibility_map('test_foreign_table'); +ERROR: relation "test_foreign_table" is of wrong relation kind +DETAIL: This operation is not supported for foreign tables. +-- check some of the allowed relkinds +create table regular_table (a int, b text); +alter table regular_table alter column b set storage external; +insert into regular_table values (1, repeat('one', 1000)), (2, repeat('two', 1000)); +vacuum (disable_page_skipping) regular_table; +select count(*) > 0 from pg_visibility('regular_table'); + ?column? +---------- + t +(1 row) + +select count(*) > 0 from pg_visibility((select reltoastrelid from pg_class where relname = 'regular_table')); + ?column? +---------- + t +(1 row) + +truncate regular_table; +select count(*) > 0 from pg_visibility('regular_table'); + ?column? +---------- + f +(1 row) + +select count(*) > 0 from pg_visibility((select reltoastrelid from pg_class where relname = 'regular_table')); + ?column? +---------- + f +(1 row) + +create materialized view matview_visibility_test as select * from regular_table; +vacuum (disable_page_skipping) matview_visibility_test; +select count(*) > 0 from pg_visibility('matview_visibility_test'); + ?column? +---------- + f +(1 row) + +insert into regular_table values (1), (2); +refresh materialized view matview_visibility_test; +select count(*) > 0 from pg_visibility('matview_visibility_test'); + ?column? +---------- + t +(1 row) + +-- regular tables which are part of a partition *do* have visibility maps +insert into test_partition values (1); +vacuum (disable_page_skipping) test_partition; +select count(*) > 0 from pg_visibility('test_partition', 0); + ?column? +---------- + t +(1 row) + +select count(*) > 0 from pg_visibility_map('test_partition'); + ?column? +---------- + t +(1 row) + +select count(*) > 0 from pg_visibility_map_summary('test_partition'); + ?column? +---------- + t +(1 row) + +select * from pg_check_frozen('test_partition'); -- hopefully none + t_ctid +-------- +(0 rows) + +select pg_truncate_visibility_map('test_partition'); + pg_truncate_visibility_map +---------------------------- + +(1 row) + +-- test copy freeze +create table copyfreeze (a int, b char(1500)); +-- load all rows via COPY FREEZE and ensure that all pages are set all-visible +-- and all-frozen. +begin; +truncate copyfreeze; +copy copyfreeze from stdin freeze; +commit; +select * from pg_visibility_map('copyfreeze'); + blkno | all_visible | all_frozen +-------+-------------+------------ + 0 | t | t + 1 | t | t + 2 | t | t +(3 rows) + +select * from pg_check_frozen('copyfreeze'); + t_ctid +-------- +(0 rows) + +-- load half the rows via regular COPY and rest via COPY FREEZE. The pages +-- which are touched by regular COPY must not be set all-visible/all-frozen. On +-- the other hand, pages allocated by COPY FREEZE should be marked +-- all-frozen/all-visible. +begin; +truncate copyfreeze; +copy copyfreeze from stdin; +copy copyfreeze from stdin freeze; +commit; +select * from pg_visibility_map('copyfreeze'); + blkno | all_visible | all_frozen +-------+-------------+------------ + 0 | f | f + 1 | f | f + 2 | t | t +(3 rows) + +select * from pg_check_frozen('copyfreeze'); + t_ctid +-------- +(0 rows) + +-- Try a mix of regular COPY and COPY FREEZE. +begin; +truncate copyfreeze; +copy copyfreeze from stdin freeze; +copy copyfreeze from stdin; +copy copyfreeze from stdin freeze; +commit; +select * from pg_visibility_map('copyfreeze'); + blkno | all_visible | all_frozen +-------+-------------+------------ + 0 | t | t + 1 | f | f + 2 | t | t +(3 rows) + +select * from pg_check_frozen('copyfreeze'); + t_ctid +-------- +(0 rows) + +-- cleanup +drop table test_partitioned; +drop view test_view; +drop sequence test_sequence; +drop foreign table test_foreign_table; +drop server dummy_server; +drop foreign data wrapper dummy; +drop materialized view matview_visibility_test; +drop table regular_table; +drop table copyfreeze; diff --git a/contrib/pg_visibility/meson.build b/contrib/pg_visibility/meson.build new file mode 100644 index 0000000..73c708d --- /dev/null +++ b/contrib/pg_visibility/meson.build @@ -0,0 +1,36 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pg_visibility_sources = files( + 'pg_visibility.c', +) + +if host_system == 'windows' + pg_visibility_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pg_visibility', + '--FILEDESC', 'pg_visibility - page visibility information',]) +endif + +pg_visibility = shared_module('pg_visibility', + pg_visibility_sources, + kwargs: contrib_mod_args, +) +contrib_targets += pg_visibility + +install_data( + 'pg_visibility--1.0--1.1.sql', + 'pg_visibility--1.1--1.2.sql', + 'pg_visibility--1.1.sql', + 'pg_visibility.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'pg_visibility', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'pg_visibility', + ], + }, +} diff --git a/contrib/pg_visibility/pg_visibility--1.0--1.1.sql b/contrib/pg_visibility/pg_visibility--1.0--1.1.sql new file mode 100644 index 0000000..378824c --- /dev/null +++ b/contrib/pg_visibility/pg_visibility--1.0--1.1.sql @@ -0,0 +1,24 @@ +/* contrib/pg_visibility/pg_visibility--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_visibility UPDATE TO '1.1'" to load this file. \quit + +CREATE FUNCTION pg_check_frozen(regclass, t_ctid OUT tid) +RETURNS SETOF tid +AS 'MODULE_PATHNAME', 'pg_check_frozen' +LANGUAGE C STRICT; + +CREATE FUNCTION pg_check_visible(regclass, t_ctid OUT tid) +RETURNS SETOF tid +AS 'MODULE_PATHNAME', 'pg_check_visible' +LANGUAGE C STRICT; + +CREATE FUNCTION pg_truncate_visibility_map(regclass) +RETURNS void +AS 'MODULE_PATHNAME', 'pg_truncate_visibility_map' +LANGUAGE C STRICT +PARALLEL UNSAFE; -- let's not make this any more dangerous + +REVOKE ALL ON FUNCTION pg_check_frozen(regclass) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_check_visible(regclass) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_truncate_visibility_map(regclass) FROM PUBLIC; diff --git a/contrib/pg_visibility/pg_visibility--1.1--1.2.sql b/contrib/pg_visibility/pg_visibility--1.1--1.2.sql new file mode 100644 index 0000000..a5a4fe7 --- /dev/null +++ b/contrib/pg_visibility/pg_visibility--1.1--1.2.sql @@ -0,0 +1,13 @@ +/* contrib/pg_visibility/pg_visibility--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_visibility UPDATE TO '1.2'" to load this file. \quit + +-- Allow use of monitoring functions by pg_monitor members +GRANT EXECUTE ON FUNCTION pg_visibility_map(regclass, bigint) TO pg_stat_scan_tables; +GRANT EXECUTE ON FUNCTION pg_visibility(regclass, bigint) TO pg_stat_scan_tables; +GRANT EXECUTE ON FUNCTION pg_visibility_map(regclass) TO pg_stat_scan_tables; +GRANT EXECUTE ON FUNCTION pg_visibility(regclass) TO pg_stat_scan_tables; +GRANT EXECUTE ON FUNCTION pg_visibility_map_summary(regclass) TO pg_stat_scan_tables; +GRANT EXECUTE ON FUNCTION pg_check_frozen(regclass) TO pg_stat_scan_tables; +GRANT EXECUTE ON FUNCTION pg_check_visible(regclass) TO pg_stat_scan_tables; diff --git a/contrib/pg_visibility/pg_visibility--1.1.sql b/contrib/pg_visibility/pg_visibility--1.1.sql new file mode 100644 index 0000000..0a29967 --- /dev/null +++ b/contrib/pg_visibility/pg_visibility--1.1.sql @@ -0,0 +1,75 @@ +/* contrib/pg_visibility/pg_visibility--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_visibility" to load this file. \quit + +-- Show visibility map information. +CREATE FUNCTION pg_visibility_map(regclass, blkno bigint, + all_visible OUT boolean, + all_frozen OUT boolean) +RETURNS record +AS 'MODULE_PATHNAME', 'pg_visibility_map' +LANGUAGE C STRICT; + +-- Show visibility map and page-level visibility information. +CREATE FUNCTION pg_visibility(regclass, blkno bigint, + all_visible OUT boolean, + all_frozen OUT boolean, + pd_all_visible OUT boolean) +RETURNS record +AS 'MODULE_PATHNAME', 'pg_visibility' +LANGUAGE C STRICT; + +-- Show visibility map information for each block in a relation. +CREATE FUNCTION pg_visibility_map(regclass, blkno OUT bigint, + all_visible OUT boolean, + all_frozen OUT boolean) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_visibility_map_rel' +LANGUAGE C STRICT; + +-- Show visibility map and page-level visibility information for each block. +CREATE FUNCTION pg_visibility(regclass, blkno OUT bigint, + all_visible OUT boolean, + all_frozen OUT boolean, + pd_all_visible OUT boolean) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_visibility_rel' +LANGUAGE C STRICT; + +-- Show summary of visibility map bits for a relation. +CREATE FUNCTION pg_visibility_map_summary(regclass, + OUT all_visible bigint, OUT all_frozen bigint) +RETURNS record +AS 'MODULE_PATHNAME', 'pg_visibility_map_summary' +LANGUAGE C STRICT; + +-- Show tupleids of non-frozen tuples if any in all_frozen pages +-- for a relation. +CREATE FUNCTION pg_check_frozen(regclass, t_ctid OUT tid) +RETURNS SETOF tid +AS 'MODULE_PATHNAME', 'pg_check_frozen' +LANGUAGE C STRICT; + +-- Show tupleids of dead tuples if any in all_visible pages for a relation. +CREATE FUNCTION pg_check_visible(regclass, t_ctid OUT tid) +RETURNS SETOF tid +AS 'MODULE_PATHNAME', 'pg_check_visible' +LANGUAGE C STRICT; + +-- Truncate the visibility map fork. +CREATE FUNCTION pg_truncate_visibility_map(regclass) +RETURNS void +AS 'MODULE_PATHNAME', 'pg_truncate_visibility_map' +LANGUAGE C STRICT +PARALLEL UNSAFE; -- let's not make this any more dangerous + +-- Don't want these to be available to public. +REVOKE ALL ON FUNCTION pg_visibility_map(regclass, bigint) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_visibility(regclass, bigint) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_visibility_map(regclass) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_visibility(regclass) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_visibility_map_summary(regclass) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_check_frozen(regclass) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_check_visible(regclass) FROM PUBLIC; +REVOKE ALL ON FUNCTION pg_truncate_visibility_map(regclass) FROM PUBLIC; diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c new file mode 100644 index 0000000..2a4acfd --- /dev/null +++ b/contrib/pg_visibility/pg_visibility.c @@ -0,0 +1,779 @@ +/*------------------------------------------------------------------------- + * + * pg_visibility.c + * display visibility map information and page-level visibility bits + * + * Copyright (c) 2016-2023, PostgreSQL Global Development Group + * + * contrib/pg_visibility/pg_visibility.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/visibilitymap.h" +#include "access/xloginsert.h" +#include "catalog/pg_type.h" +#include "catalog/storage_xlog.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/procarray.h" +#include "storage/smgr.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" + +PG_MODULE_MAGIC; + +typedef struct vbits +{ + BlockNumber next; + BlockNumber count; + uint8 bits[FLEXIBLE_ARRAY_MEMBER]; +} vbits; + +typedef struct corrupt_items +{ + BlockNumber next; + BlockNumber count; + ItemPointer tids; +} corrupt_items; + +PG_FUNCTION_INFO_V1(pg_visibility_map); +PG_FUNCTION_INFO_V1(pg_visibility_map_rel); +PG_FUNCTION_INFO_V1(pg_visibility); +PG_FUNCTION_INFO_V1(pg_visibility_rel); +PG_FUNCTION_INFO_V1(pg_visibility_map_summary); +PG_FUNCTION_INFO_V1(pg_check_frozen); +PG_FUNCTION_INFO_V1(pg_check_visible); +PG_FUNCTION_INFO_V1(pg_truncate_visibility_map); + +static TupleDesc pg_visibility_tupdesc(bool include_blkno, bool include_pd); +static vbits *collect_visibility_data(Oid relid, bool include_pd); +static corrupt_items *collect_corrupt_items(Oid relid, bool all_visible, + bool all_frozen); +static void record_corrupt_item(corrupt_items *items, ItemPointer tid); +static bool tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, + Buffer buffer); +static void check_relation_relkind(Relation rel); + +/* + * Visibility map information for a single block of a relation. + * + * Note: the VM code will silently return zeroes for pages past the end + * of the map, so we allow probes up to MaxBlockNumber regardless of the + * actual relation size. + */ +Datum +pg_visibility_map(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int64 blkno = PG_GETARG_INT64(1); + int32 mapbits; + Relation rel; + Buffer vmbuffer = InvalidBuffer; + TupleDesc tupdesc; + Datum values[2]; + bool nulls[2] = {0}; + + rel = relation_open(relid, AccessShareLock); + + /* Only some relkinds have a visibility map */ + check_relation_relkind(rel); + + if (blkno < 0 || blkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number"))); + + tupdesc = pg_visibility_tupdesc(false, false); + + mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer); + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + values[0] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0); + values[1] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0); + + relation_close(rel, AccessShareLock); + + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Visibility map information for a single block of a relation, plus the + * page-level information for the same block. + */ +Datum +pg_visibility(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + int64 blkno = PG_GETARG_INT64(1); + int32 mapbits; + Relation rel; + Buffer vmbuffer = InvalidBuffer; + Buffer buffer; + Page page; + TupleDesc tupdesc; + Datum values[3]; + bool nulls[3] = {0}; + + rel = relation_open(relid, AccessShareLock); + + /* Only some relkinds have a visibility map */ + check_relation_relkind(rel); + + if (blkno < 0 || blkno > MaxBlockNumber) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid block number"))); + + tupdesc = pg_visibility_tupdesc(false, true); + + mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer); + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + values[0] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0); + values[1] = BoolGetDatum((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0); + + /* Here we have to explicitly check rel size ... */ + if (blkno < RelationGetNumberOfBlocks(rel)) + { + buffer = ReadBuffer(rel, blkno); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + page = BufferGetPage(buffer); + values[2] = BoolGetDatum(PageIsAllVisible(page)); + + UnlockReleaseBuffer(buffer); + } + else + { + /* As with the vismap, silently return 0 for pages past EOF */ + values[2] = BoolGetDatum(false); + } + + relation_close(rel, AccessShareLock); + + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Visibility map information for every block in a relation. + */ +Datum +pg_visibility_map_rel(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + vbits *info; + + if (SRF_IS_FIRSTCALL()) + { + Oid relid = PG_GETARG_OID(0); + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + funcctx->tuple_desc = pg_visibility_tupdesc(true, false); + /* collect_visibility_data will verify the relkind */ + funcctx->user_fctx = collect_visibility_data(relid, false); + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + info = (vbits *) funcctx->user_fctx; + + if (info->next < info->count) + { + Datum values[3]; + bool nulls[3] = {0}; + HeapTuple tuple; + + values[0] = Int64GetDatum(info->next); + values[1] = BoolGetDatum((info->bits[info->next] & (1 << 0)) != 0); + values[2] = BoolGetDatum((info->bits[info->next] & (1 << 1)) != 0); + info->next++; + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + + SRF_RETURN_DONE(funcctx); +} + +/* + * Visibility map information for every block in a relation, plus the page + * level information for each block. + */ +Datum +pg_visibility_rel(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + vbits *info; + + if (SRF_IS_FIRSTCALL()) + { + Oid relid = PG_GETARG_OID(0); + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + funcctx->tuple_desc = pg_visibility_tupdesc(true, true); + /* collect_visibility_data will verify the relkind */ + funcctx->user_fctx = collect_visibility_data(relid, true); + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + info = (vbits *) funcctx->user_fctx; + + if (info->next < info->count) + { + Datum values[4]; + bool nulls[4] = {0}; + HeapTuple tuple; + + values[0] = Int64GetDatum(info->next); + values[1] = BoolGetDatum((info->bits[info->next] & (1 << 0)) != 0); + values[2] = BoolGetDatum((info->bits[info->next] & (1 << 1)) != 0); + values[3] = BoolGetDatum((info->bits[info->next] & (1 << 2)) != 0); + info->next++; + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + + SRF_RETURN_DONE(funcctx); +} + +/* + * Count the number of all-visible and all-frozen pages in the visibility + * map for a particular relation. + */ +Datum +pg_visibility_map_summary(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Relation rel; + BlockNumber nblocks; + BlockNumber blkno; + Buffer vmbuffer = InvalidBuffer; + int64 all_visible = 0; + int64 all_frozen = 0; + TupleDesc tupdesc; + Datum values[2]; + bool nulls[2] = {0}; + + rel = relation_open(relid, AccessShareLock); + + /* Only some relkinds have a visibility map */ + check_relation_relkind(rel); + + nblocks = RelationGetNumberOfBlocks(rel); + + for (blkno = 0; blkno < nblocks; ++blkno) + { + int32 mapbits; + + /* Make sure we are interruptible. */ + CHECK_FOR_INTERRUPTS(); + + /* Get map info. */ + mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer); + if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0) + ++all_visible; + if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0) + ++all_frozen; + } + + /* Clean up. */ + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + relation_close(rel, AccessShareLock); + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + values[0] = Int64GetDatum(all_visible); + values[1] = Int64GetDatum(all_frozen); + + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* + * Return the TIDs of non-frozen tuples present in pages marked all-frozen + * in the visibility map. We hope no one will ever find any, but there could + * be bugs, database corruption, etc. + */ +Datum +pg_check_frozen(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + corrupt_items *items; + + if (SRF_IS_FIRSTCALL()) + { + Oid relid = PG_GETARG_OID(0); + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + /* collect_corrupt_items will verify the relkind */ + funcctx->user_fctx = collect_corrupt_items(relid, false, true); + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + items = (corrupt_items *) funcctx->user_fctx; + + if (items->next < items->count) + SRF_RETURN_NEXT(funcctx, PointerGetDatum(&items->tids[items->next++])); + + SRF_RETURN_DONE(funcctx); +} + +/* + * Return the TIDs of not-all-visible tuples in pages marked all-visible + * in the visibility map. We hope no one will ever find any, but there could + * be bugs, database corruption, etc. + */ +Datum +pg_check_visible(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + corrupt_items *items; + + if (SRF_IS_FIRSTCALL()) + { + Oid relid = PG_GETARG_OID(0); + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + /* collect_corrupt_items will verify the relkind */ + funcctx->user_fctx = collect_corrupt_items(relid, true, false); + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + items = (corrupt_items *) funcctx->user_fctx; + + if (items->next < items->count) + SRF_RETURN_NEXT(funcctx, PointerGetDatum(&items->tids[items->next++])); + + SRF_RETURN_DONE(funcctx); +} + +/* + * Remove the visibility map fork for a relation. If there turn out to be + * any bugs in the visibility map code that require rebuilding the VM, this + * provides users with a way to do it that is cleaner than shutting down the + * server and removing files by hand. + * + * This is a cut-down version of RelationTruncate. + */ +Datum +pg_truncate_visibility_map(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Relation rel; + ForkNumber fork; + BlockNumber block; + + rel = relation_open(relid, AccessExclusiveLock); + + /* Only some relkinds have a visibility map */ + check_relation_relkind(rel); + + /* Forcibly reset cached file size */ + RelationGetSmgr(rel)->smgr_cached_nblocks[VISIBILITYMAP_FORKNUM] = InvalidBlockNumber; + + block = visibilitymap_prepare_truncate(rel, 0); + if (BlockNumberIsValid(block)) + { + fork = VISIBILITYMAP_FORKNUM; + smgrtruncate(RelationGetSmgr(rel), &fork, 1, &block); + } + + if (RelationNeedsWAL(rel)) + { + xl_smgr_truncate xlrec; + + xlrec.blkno = 0; + xlrec.rlocator = rel->rd_locator; + xlrec.flags = SMGR_TRUNCATE_VM; + + XLogBeginInsert(); + XLogRegisterData((char *) &xlrec, sizeof(xlrec)); + + XLogInsert(RM_SMGR_ID, XLOG_SMGR_TRUNCATE | XLR_SPECIAL_REL_UPDATE); + } + + /* + * Release the lock right away, not at commit time. + * + * It would be a problem to release the lock prior to commit if this + * truncate operation sends any transactional invalidation messages. Other + * backends would potentially be able to lock the relation without + * processing them in the window of time between when we release the lock + * here and when we sent the messages at our eventual commit. However, + * we're currently only sending a non-transactional smgr invalidation, + * which will have been posted to shared memory immediately from within + * smgr_truncate. Therefore, there should be no race here. + * + * The reason why it's desirable to release the lock early here is because + * of the possibility that someone will need to use this to blow away many + * visibility map forks at once. If we can't release the lock until + * commit time, the transaction doing this will accumulate + * AccessExclusiveLocks on all of those relations at the same time, which + * is undesirable. However, if this turns out to be unsafe we may have no + * choice... + */ + relation_close(rel, AccessExclusiveLock); + + /* Nothing to return. */ + PG_RETURN_VOID(); +} + +/* + * Helper function to construct whichever TupleDesc we need for a particular + * call. + */ +static TupleDesc +pg_visibility_tupdesc(bool include_blkno, bool include_pd) +{ + TupleDesc tupdesc; + AttrNumber maxattr = 2; + AttrNumber a = 0; + + if (include_blkno) + ++maxattr; + if (include_pd) + ++maxattr; + tupdesc = CreateTemplateTupleDesc(maxattr); + if (include_blkno) + TupleDescInitEntry(tupdesc, ++a, "blkno", INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, ++a, "all_visible", BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, ++a, "all_frozen", BOOLOID, -1, 0); + if (include_pd) + TupleDescInitEntry(tupdesc, ++a, "pd_all_visible", BOOLOID, -1, 0); + Assert(a == maxattr); + + return BlessTupleDesc(tupdesc); +} + +/* + * Collect visibility data about a relation. + * + * Checks relkind of relid and will throw an error if the relation does not + * have a VM. + */ +static vbits * +collect_visibility_data(Oid relid, bool include_pd) +{ + Relation rel; + BlockNumber nblocks; + vbits *info; + BlockNumber blkno; + Buffer vmbuffer = InvalidBuffer; + BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD); + + rel = relation_open(relid, AccessShareLock); + + /* Only some relkinds have a visibility map */ + check_relation_relkind(rel); + + nblocks = RelationGetNumberOfBlocks(rel); + info = palloc0(offsetof(vbits, bits) + nblocks); + info->next = 0; + info->count = nblocks; + + for (blkno = 0; blkno < nblocks; ++blkno) + { + int32 mapbits; + + /* Make sure we are interruptible. */ + CHECK_FOR_INTERRUPTS(); + + /* Get map info. */ + mapbits = (int32) visibilitymap_get_status(rel, blkno, &vmbuffer); + if ((mapbits & VISIBILITYMAP_ALL_VISIBLE) != 0) + info->bits[blkno] |= (1 << 0); + if ((mapbits & VISIBILITYMAP_ALL_FROZEN) != 0) + info->bits[blkno] |= (1 << 1); + + /* + * Page-level data requires reading every block, so only get it if the + * caller needs it. Use a buffer access strategy, too, to prevent + * cache-trashing. + */ + if (include_pd) + { + Buffer buffer; + Page page; + + buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, + bstrategy); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + page = BufferGetPage(buffer); + if (PageIsAllVisible(page)) + info->bits[blkno] |= (1 << 2); + + UnlockReleaseBuffer(buffer); + } + } + + /* Clean up. */ + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + relation_close(rel, AccessShareLock); + + return info; +} + +/* + * Returns a list of items whose visibility map information does not match + * the status of the tuples on the page. + * + * If all_visible is passed as true, this will include all items which are + * on pages marked as all-visible in the visibility map but which do not + * seem to in fact be all-visible. + * + * If all_frozen is passed as true, this will include all items which are + * on pages marked as all-frozen but which do not seem to in fact be frozen. + * + * Checks relkind of relid and will throw an error if the relation does not + * have a VM. + */ +static corrupt_items * +collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen) +{ + Relation rel; + BlockNumber nblocks; + corrupt_items *items; + BlockNumber blkno; + Buffer vmbuffer = InvalidBuffer; + BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD); + TransactionId OldestXmin = InvalidTransactionId; + + rel = relation_open(relid, AccessShareLock); + + /* Only some relkinds have a visibility map */ + check_relation_relkind(rel); + + if (all_visible) + OldestXmin = GetOldestNonRemovableTransactionId(rel); + + nblocks = RelationGetNumberOfBlocks(rel); + + /* + * Guess an initial array size. We don't expect many corrupted tuples, so + * start with a small array. This function uses the "next" field to track + * the next offset where we can store an item (which is the same thing as + * the number of items found so far) and the "count" field to track the + * number of entries allocated. We'll repurpose these fields before + * returning. + */ + items = palloc0(sizeof(corrupt_items)); + items->next = 0; + items->count = 64; + items->tids = palloc(items->count * sizeof(ItemPointerData)); + + /* Loop over every block in the relation. */ + for (blkno = 0; blkno < nblocks; ++blkno) + { + bool check_frozen = false; + bool check_visible = false; + Buffer buffer; + Page page; + OffsetNumber offnum, + maxoff; + + /* Make sure we are interruptible. */ + CHECK_FOR_INTERRUPTS(); + + /* Use the visibility map to decide whether to check this page. */ + if (all_frozen && VM_ALL_FROZEN(rel, blkno, &vmbuffer)) + check_frozen = true; + if (all_visible && VM_ALL_VISIBLE(rel, blkno, &vmbuffer)) + check_visible = true; + if (!check_visible && !check_frozen) + continue; + + /* Read and lock the page. */ + buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, + bstrategy); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + page = BufferGetPage(buffer); + maxoff = PageGetMaxOffsetNumber(page); + + /* + * The visibility map bits might have changed while we were acquiring + * the page lock. Recheck to avoid returning spurious results. + */ + if (check_frozen && !VM_ALL_FROZEN(rel, blkno, &vmbuffer)) + check_frozen = false; + if (check_visible && !VM_ALL_VISIBLE(rel, blkno, &vmbuffer)) + check_visible = false; + if (!check_visible && !check_frozen) + { + UnlockReleaseBuffer(buffer); + continue; + } + + /* Iterate over each tuple on the page. */ + for (offnum = FirstOffsetNumber; + offnum <= maxoff; + offnum = OffsetNumberNext(offnum)) + { + HeapTupleData tuple; + ItemId itemid; + + itemid = PageGetItemId(page, offnum); + + /* Unused or redirect line pointers are of no interest. */ + if (!ItemIdIsUsed(itemid) || ItemIdIsRedirected(itemid)) + continue; + + /* Dead line pointers are neither all-visible nor frozen. */ + if (ItemIdIsDead(itemid)) + { + ItemPointerSet(&(tuple.t_self), blkno, offnum); + record_corrupt_item(items, &tuple.t_self); + continue; + } + + /* Initialize a HeapTupleData structure for checks below. */ + ItemPointerSet(&(tuple.t_self), blkno, offnum); + tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid); + tuple.t_len = ItemIdGetLength(itemid); + tuple.t_tableOid = relid; + + /* + * If we're checking whether the page is all-visible, we expect + * the tuple to be all-visible. + */ + if (check_visible && + !tuple_all_visible(&tuple, OldestXmin, buffer)) + { + TransactionId RecomputedOldestXmin; + + /* + * Time has passed since we computed OldestXmin, so it's + * possible that this tuple is all-visible in reality even + * though it doesn't appear so based on our + * previously-computed value. Let's compute a new value so we + * can be certain whether there is a problem. + * + * From a concurrency point of view, it sort of sucks to + * retake ProcArrayLock here while we're holding the buffer + * exclusively locked, but it should be safe against + * deadlocks, because surely + * GetOldestNonRemovableTransactionId() should never take a + * buffer lock. And this shouldn't happen often, so it's worth + * being careful so as to avoid false positives. + */ + RecomputedOldestXmin = GetOldestNonRemovableTransactionId(rel); + + if (!TransactionIdPrecedes(OldestXmin, RecomputedOldestXmin)) + record_corrupt_item(items, &tuple.t_self); + else + { + OldestXmin = RecomputedOldestXmin; + if (!tuple_all_visible(&tuple, OldestXmin, buffer)) + record_corrupt_item(items, &tuple.t_self); + } + } + + /* + * If we're checking whether the page is all-frozen, we expect the + * tuple to be in a state where it will never need freezing. + */ + if (check_frozen) + { + if (heap_tuple_needs_eventual_freeze(tuple.t_data)) + record_corrupt_item(items, &tuple.t_self); + } + } + + UnlockReleaseBuffer(buffer); + } + + /* Clean up. */ + if (vmbuffer != InvalidBuffer) + ReleaseBuffer(vmbuffer); + relation_close(rel, AccessShareLock); + + /* + * Before returning, repurpose the fields to match caller's expectations. + * next is now the next item that should be read (rather than written) and + * count is now the number of items we wrote (rather than the number we + * allocated). + */ + items->count = items->next; + items->next = 0; + + return items; +} + +/* + * Remember one corrupt item. + */ +static void +record_corrupt_item(corrupt_items *items, ItemPointer tid) +{ + /* enlarge output array if needed. */ + if (items->next >= items->count) + { + items->count *= 2; + items->tids = repalloc(items->tids, + items->count * sizeof(ItemPointerData)); + } + /* and add the new item */ + items->tids[items->next++] = *tid; +} + +/* + * Check whether a tuple is all-visible relative to a given OldestXmin value. + * The buffer should contain the tuple and should be locked and pinned. + */ +static bool +tuple_all_visible(HeapTuple tup, TransactionId OldestXmin, Buffer buffer) +{ + HTSV_Result state; + TransactionId xmin; + + state = HeapTupleSatisfiesVacuum(tup, OldestXmin, buffer); + if (state != HEAPTUPLE_LIVE) + return false; /* all-visible implies live */ + + /* + * Neither lazy_scan_heap nor heap_page_is_all_visible will mark a page + * all-visible unless every tuple is hinted committed. However, those hint + * bits could be lost after a crash, so we can't be certain that they'll + * be set here. So just check the xmin. + */ + + xmin = HeapTupleHeaderGetXmin(tup->t_data); + if (!TransactionIdPrecedes(xmin, OldestXmin)) + return false; /* xmin not old enough for all to see */ + + return true; +} + +/* + * check_relation_relkind - convenience routine to check that relation + * is of the relkind supported by the callers + */ +static void +check_relation_relkind(Relation rel) +{ + if (!RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("relation \"%s\" is of wrong relation kind", + RelationGetRelationName(rel)), + errdetail_relkind_not_supported(rel->rd_rel->relkind))); +} diff --git a/contrib/pg_visibility/pg_visibility.control b/contrib/pg_visibility/pg_visibility.control new file mode 100644 index 0000000..3cffa08 --- /dev/null +++ b/contrib/pg_visibility/pg_visibility.control @@ -0,0 +1,5 @@ +# pg_visibility extension +comment = 'examine the visibility map (VM) and page-level visibility info' +default_version = '1.2' +module_pathname = '$libdir/pg_visibility' +relocatable = true diff --git a/contrib/pg_visibility/sql/pg_visibility.sql b/contrib/pg_visibility/sql/pg_visibility.sql new file mode 100644 index 0000000..ff3538f --- /dev/null +++ b/contrib/pg_visibility/sql/pg_visibility.sql @@ -0,0 +1,182 @@ +CREATE EXTENSION pg_visibility; + +-- +-- recently-dropped table +-- +\set VERBOSITY sqlstate +BEGIN; +CREATE TABLE droppedtest (c int); +SELECT 'droppedtest'::regclass::oid AS oid \gset +SAVEPOINT q; DROP TABLE droppedtest; RELEASE q; +SAVEPOINT q; SELECT * FROM pg_visibility_map(:oid); ROLLBACK TO q; +-- ERROR: could not open relation with OID 16xxx +SAVEPOINT q; SELECT 1; ROLLBACK TO q; +SAVEPOINT q; SELECT 1; ROLLBACK TO q; +SELECT pg_relation_size(:oid), pg_relation_filepath(:oid), + has_table_privilege(:oid, 'SELECT'); +SELECT * FROM pg_visibility_map(:oid); +-- ERROR: could not open relation with OID 16xxx +ROLLBACK; +\set VERBOSITY default + +-- +-- check that using the module's functions with unsupported relations will fail +-- + +-- partitioned tables (the parent ones) don't have visibility maps +create table test_partitioned (a int) partition by list (a); +-- these should all fail +select pg_visibility('test_partitioned', 0); +select pg_visibility_map('test_partitioned'); +select pg_visibility_map_summary('test_partitioned'); +select pg_check_frozen('test_partitioned'); +select pg_truncate_visibility_map('test_partitioned'); + +create table test_partition partition of test_partitioned for values in (1); +create index test_index on test_partition (a); +-- indexes do not, so these all fail +select pg_visibility('test_index', 0); +select pg_visibility_map('test_index'); +select pg_visibility_map_summary('test_index'); +select pg_check_frozen('test_index'); +select pg_truncate_visibility_map('test_index'); + +create view test_view as select 1; +-- views do not have VMs, so these all fail +select pg_visibility('test_view', 0); +select pg_visibility_map('test_view'); +select pg_visibility_map_summary('test_view'); +select pg_check_frozen('test_view'); +select pg_truncate_visibility_map('test_view'); + +create sequence test_sequence; +-- sequences do not have VMs, so these all fail +select pg_visibility('test_sequence', 0); +select pg_visibility_map('test_sequence'); +select pg_visibility_map_summary('test_sequence'); +select pg_check_frozen('test_sequence'); +select pg_truncate_visibility_map('test_sequence'); + +create foreign data wrapper dummy; +create server dummy_server foreign data wrapper dummy; +create foreign table test_foreign_table () server dummy_server; +-- foreign tables do not have VMs, so these all fail +select pg_visibility('test_foreign_table', 0); +select pg_visibility_map('test_foreign_table'); +select pg_visibility_map_summary('test_foreign_table'); +select pg_check_frozen('test_foreign_table'); +select pg_truncate_visibility_map('test_foreign_table'); + +-- check some of the allowed relkinds +create table regular_table (a int, b text); +alter table regular_table alter column b set storage external; +insert into regular_table values (1, repeat('one', 1000)), (2, repeat('two', 1000)); +vacuum (disable_page_skipping) regular_table; +select count(*) > 0 from pg_visibility('regular_table'); +select count(*) > 0 from pg_visibility((select reltoastrelid from pg_class where relname = 'regular_table')); +truncate regular_table; +select count(*) > 0 from pg_visibility('regular_table'); +select count(*) > 0 from pg_visibility((select reltoastrelid from pg_class where relname = 'regular_table')); + +create materialized view matview_visibility_test as select * from regular_table; +vacuum (disable_page_skipping) matview_visibility_test; +select count(*) > 0 from pg_visibility('matview_visibility_test'); +insert into regular_table values (1), (2); +refresh materialized view matview_visibility_test; +select count(*) > 0 from pg_visibility('matview_visibility_test'); + +-- regular tables which are part of a partition *do* have visibility maps +insert into test_partition values (1); +vacuum (disable_page_skipping) test_partition; +select count(*) > 0 from pg_visibility('test_partition', 0); +select count(*) > 0 from pg_visibility_map('test_partition'); +select count(*) > 0 from pg_visibility_map_summary('test_partition'); +select * from pg_check_frozen('test_partition'); -- hopefully none +select pg_truncate_visibility_map('test_partition'); + +-- test copy freeze +create table copyfreeze (a int, b char(1500)); + +-- load all rows via COPY FREEZE and ensure that all pages are set all-visible +-- and all-frozen. +begin; +truncate copyfreeze; +copy copyfreeze from stdin freeze; +1 '1' +2 '2' +3 '3' +4 '4' +5 '5' +6 '6' +7 '7' +8 '8' +9 '9' +10 '10' +11 '11' +12 '12' +\. +commit; +select * from pg_visibility_map('copyfreeze'); +select * from pg_check_frozen('copyfreeze'); + +-- load half the rows via regular COPY and rest via COPY FREEZE. The pages +-- which are touched by regular COPY must not be set all-visible/all-frozen. On +-- the other hand, pages allocated by COPY FREEZE should be marked +-- all-frozen/all-visible. +begin; +truncate copyfreeze; +copy copyfreeze from stdin; +1 '1' +2 '2' +3 '3' +4 '4' +5 '5' +6 '6' +\. +copy copyfreeze from stdin freeze; +7 '7' +8 '8' +9 '9' +10 '10' +11 '11' +12 '12' +\. +commit; +select * from pg_visibility_map('copyfreeze'); +select * from pg_check_frozen('copyfreeze'); + +-- Try a mix of regular COPY and COPY FREEZE. +begin; +truncate copyfreeze; +copy copyfreeze from stdin freeze; +1 '1' +2 '2' +3 '3' +4 '4' +5 '5' +\. +copy copyfreeze from stdin; +6 '6' +\. +copy copyfreeze from stdin freeze; +7 '7' +8 '8' +9 '9' +10 '10' +11 '11' +12 '12' +\. +commit; +select * from pg_visibility_map('copyfreeze'); +select * from pg_check_frozen('copyfreeze'); + +-- cleanup +drop table test_partitioned; +drop view test_view; +drop sequence test_sequence; +drop foreign table test_foreign_table; +drop server dummy_server; +drop foreign data wrapper dummy; +drop materialized view matview_visibility_test; +drop table regular_table; +drop table copyfreeze; diff --git a/contrib/pg_walinspect/.gitignore b/contrib/pg_walinspect/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pg_walinspect/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pg_walinspect/Makefile b/contrib/pg_walinspect/Makefile new file mode 100644 index 0000000..22090f7 --- /dev/null +++ b/contrib/pg_walinspect/Makefile @@ -0,0 +1,29 @@ +# contrib/pg_walinspect/Makefile + +MODULE_big = pg_walinspect +OBJS = \ + $(WIN32RES) \ + pg_walinspect.o +PGFILEDESC = "pg_walinspect - functions to inspect contents of PostgreSQL Write-Ahead Log" + +EXTENSION = pg_walinspect +DATA = pg_walinspect--1.0.sql pg_walinspect--1.0--1.1.sql + +REGRESS = pg_walinspect oldextversions + +REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/pg_walinspect/walinspect.conf + +# Disabled because these tests require "wal_level=replica", which +# some 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 = contrib/pg_walinspect +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pg_walinspect/expected/oldextversions.out b/contrib/pg_walinspect/expected/oldextversions.out new file mode 100644 index 0000000..89953af --- /dev/null +++ b/contrib/pg_walinspect/expected/oldextversions.out @@ -0,0 +1,69 @@ +-- Test old extension version entry points. +CREATE EXTENSION pg_walinspect WITH VERSION '1.0'; +-- Mask DETAIL messages as these could refer to current LSN positions. +\set VERBOSITY terse +-- List what version 1.0 contains, using a locale-independent sorting. +SELECT pg_describe_object(classid, objid, 0) AS obj + FROM pg_depend + WHERE refclassid = 'pg_extension'::regclass AND + refobjid = (SELECT oid FROM pg_extension + WHERE extname = 'pg_walinspect') AND deptype = 'e' + ORDER BY pg_describe_object(classid, objid, 0) COLLATE "C"; + obj +----------------------------------------------------------- + function pg_get_wal_record_info(pg_lsn) + function pg_get_wal_records_info(pg_lsn,pg_lsn) + function pg_get_wal_records_info_till_end_of_wal(pg_lsn) + function pg_get_wal_stats(pg_lsn,pg_lsn,boolean) + function pg_get_wal_stats_till_end_of_wal(pg_lsn,boolean) +(5 rows) + +-- Make sure checkpoints don't interfere with the test. +SELECT 'init' FROM pg_create_physical_replication_slot('regress_pg_walinspect_slot', true, false); + ?column? +---------- + init +(1 row) + +CREATE TABLE sample_tbl(col1 int, col2 int); +SELECT pg_current_wal_lsn() AS wal_lsn1 \gset +INSERT INTO sample_tbl SELECT * FROM generate_series(1, 2); +-- Tests for the past functions. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info_till_end_of_wal(:'wal_lsn1'); + ok +---- + t +(1 row) + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats_till_end_of_wal(:'wal_lsn1'); + ok +---- + t +(1 row) + +-- Failures with start LSNs. +SELECT * FROM pg_get_wal_records_info_till_end_of_wal('FFFFFFFF/FFFFFFFF'); +ERROR: WAL start LSN must be less than current LSN +SELECT * FROM pg_get_wal_stats_till_end_of_wal('FFFFFFFF/FFFFFFFF'); +ERROR: WAL start LSN must be less than current LSN +-- Move to new version 1.1. +ALTER EXTENSION pg_walinspect UPDATE TO '1.1'; +-- List what version 1.1 contains. +\dx+ pg_walinspect + Objects in extension "pg_walinspect" + Object description +------------------------------------------------------- + function pg_get_wal_block_info(pg_lsn,pg_lsn,boolean) + function pg_get_wal_record_info(pg_lsn) + function pg_get_wal_records_info(pg_lsn,pg_lsn) + function pg_get_wal_stats(pg_lsn,pg_lsn,boolean) +(4 rows) + +SELECT pg_drop_replication_slot('regress_pg_walinspect_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +DROP TABLE sample_tbl; +DROP EXTENSION pg_walinspect; diff --git a/contrib/pg_walinspect/expected/pg_walinspect.out b/contrib/pg_walinspect/expected/pg_walinspect.out new file mode 100644 index 0000000..a8f4c91 --- /dev/null +++ b/contrib/pg_walinspect/expected/pg_walinspect.out @@ -0,0 +1,262 @@ +CREATE EXTENSION pg_walinspect; +-- Mask DETAIL messages as these could refer to current LSN positions. +\set VERBOSITY terse +-- Make sure checkpoints don't interfere with the test. +SELECT 'init' FROM pg_create_physical_replication_slot('regress_pg_walinspect_slot', true, false); + ?column? +---------- + init +(1 row) + +CREATE TABLE sample_tbl(col1 int, col2 int); +-- Save some LSNs for comparisons. +SELECT pg_current_wal_lsn() AS wal_lsn1 \gset +INSERT INTO sample_tbl SELECT * FROM generate_series(1, 2); +SELECT pg_current_wal_lsn() AS wal_lsn2 \gset +INSERT INTO sample_tbl SELECT * FROM generate_series(3, 4); +-- =================================================================== +-- Tests for input validation +-- =================================================================== +-- Invalid input LSN. +SELECT * FROM pg_get_wal_record_info('0/0'); +ERROR: could not read WAL at LSN 0/0 +-- Invalid start LSN. +SELECT * FROM pg_get_wal_records_info('0/0', :'wal_lsn1'); +ERROR: could not read WAL at LSN 0/0 +SELECT * FROM pg_get_wal_stats('0/0', :'wal_lsn1'); +ERROR: could not read WAL at LSN 0/0 +SELECT * FROM pg_get_wal_block_info('0/0', :'wal_lsn1'); +ERROR: could not read WAL at LSN 0/0 +-- Start LSN > End LSN. +SELECT * FROM pg_get_wal_records_info(:'wal_lsn2', :'wal_lsn1'); +ERROR: WAL start LSN must be less than end LSN +SELECT * FROM pg_get_wal_stats(:'wal_lsn2', :'wal_lsn1'); +ERROR: WAL start LSN must be less than end LSN +SELECT * FROM pg_get_wal_block_info(:'wal_lsn2', :'wal_lsn1'); +ERROR: WAL start LSN must be less than end LSN +-- LSNs with the highest value possible. +SELECT * FROM pg_get_wal_record_info('FFFFFFFF/FFFFFFFF'); +ERROR: WAL input LSN must be less than current LSN +-- Success with end LSNs. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', 'FFFFFFFF/FFFFFFFF'); + ok +---- + t +(1 row) + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats(:'wal_lsn1', 'FFFFFFFF/FFFFFFFF'); + ok +---- + t +(1 row) + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn1', 'FFFFFFFF/FFFFFFFF'); + ok +---- + t +(1 row) + +-- Failures with start LSNs. +SELECT * FROM pg_get_wal_records_info('FFFFFFFF/FFFFFFFE', 'FFFFFFFF/FFFFFFFF'); +ERROR: WAL start LSN must be less than current LSN +SELECT * FROM pg_get_wal_stats('FFFFFFFF/FFFFFFFE', 'FFFFFFFF/FFFFFFFF'); +ERROR: WAL start LSN must be less than current LSN +SELECT * FROM pg_get_wal_block_info('FFFFFFFF/FFFFFFFE', 'FFFFFFFF/FFFFFFFF'); +ERROR: WAL start LSN must be less than current LSN +-- =================================================================== +-- Tests for all function executions +-- =================================================================== +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_record_info(:'wal_lsn1'); + ok +---- + t +(1 row) + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2'); + ok +---- + t +(1 row) + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats(:'wal_lsn1', :'wal_lsn2'); + ok +---- + t +(1 row) + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn1', :'wal_lsn2'); + ok +---- + t +(1 row) + +-- =================================================================== +-- Test for filtering out WAL records of a particular table +-- =================================================================== +SELECT oid AS sample_tbl_oid FROM pg_class WHERE relname = 'sample_tbl' \gset +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2') + WHERE block_ref LIKE concat('%', :'sample_tbl_oid', '%') AND resource_manager = 'Heap'; + ok +---- + t +(1 row) + +-- =================================================================== +-- Test for filtering out WAL records based on resource_manager and +-- record_type +-- =================================================================== +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2') + WHERE resource_manager = 'Heap' AND record_type = 'INSERT'; + ok +---- + t +(1 row) + +-- =================================================================== +-- Tests to get block information from WAL record +-- =================================================================== +-- Update table to generate some block data. +SELECT pg_current_wal_lsn() AS wal_lsn3 \gset +UPDATE sample_tbl SET col1 = col1 + 1 WHERE col1 = 1; +SELECT pg_current_wal_lsn() AS wal_lsn4 \gset +-- Check if we get block data from WAL record. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn3', :'wal_lsn4') + WHERE relfilenode = :'sample_tbl_oid' AND block_data IS NOT NULL; + ok +---- + t +(1 row) + +-- Force full-page image on the next update. +SELECT pg_current_wal_lsn() AS wal_lsn5 \gset +CHECKPOINT; +UPDATE sample_tbl SET col1 = col1 + 1 WHERE col1 = 2; +SELECT pg_current_wal_lsn() AS wal_lsn6 \gset +-- Check if we get FPI from WAL record. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn5', :'wal_lsn6') + WHERE relfilenode = :'sample_tbl_oid' AND block_fpi_data IS NOT NULL; + ok +---- + t +(1 row) + +-- =================================================================== +-- Tests for permissions +-- =================================================================== +CREATE ROLE regress_pg_walinspect; +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_record_info(pg_lsn)', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_records_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_stats(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- no + has_function_privilege +------------------------ + f +(1 row) + +-- Functions accessible by users with role pg_read_server_files. +GRANT pg_read_server_files TO regress_pg_walinspect; +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_record_info(pg_lsn)', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_records_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_stats(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +REVOKE pg_read_server_files FROM regress_pg_walinspect; +-- Superuser can grant execute to other users. +GRANT EXECUTE ON FUNCTION pg_get_wal_record_info(pg_lsn) + TO regress_pg_walinspect; +GRANT EXECUTE ON FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn) + TO regress_pg_walinspect; +GRANT EXECUTE ON FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean) + TO regress_pg_walinspect; +GRANT EXECUTE ON FUNCTION pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) + TO regress_pg_walinspect; +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_record_info(pg_lsn)', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_records_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_stats(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- yes + has_function_privilege +------------------------ + t +(1 row) + +REVOKE EXECUTE ON FUNCTION pg_get_wal_record_info(pg_lsn) + FROM regress_pg_walinspect; +REVOKE EXECUTE ON FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn) + FROM regress_pg_walinspect; +REVOKE EXECUTE ON FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean) + FROM regress_pg_walinspect; +REVOKE EXECUTE ON FUNCTION pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) + FROM regress_pg_walinspect; +-- =================================================================== +-- Clean up +-- =================================================================== +DROP ROLE regress_pg_walinspect; +SELECT pg_drop_replication_slot('regress_pg_walinspect_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +DROP TABLE sample_tbl; +DROP EXTENSION pg_walinspect; diff --git a/contrib/pg_walinspect/meson.build b/contrib/pg_walinspect/meson.build new file mode 100644 index 0000000..80059f6 --- /dev/null +++ b/contrib/pg_walinspect/meson.build @@ -0,0 +1,40 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pg_walinspect_sources = files('pg_walinspect.c') + +if host_system == 'windows' + pg_walinspect_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pg_walinspect', + '--FILEDESC', 'pg_walinspect - functions to inspect contents of PostgreSQL Write-Ahead Log',]) +endif + +pg_walinspect = shared_module('pg_walinspect', + pg_walinspect_sources, + kwargs: contrib_mod_args + { + 'dependencies': contrib_mod_args['dependencies'], + }, +) +contrib_targets += pg_walinspect + +install_data( + 'pg_walinspect.control', + 'pg_walinspect--1.0.sql', + 'pg_walinspect--1.0--1.1.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'pg_walinspect', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'pg_walinspect', + 'oldextversions', + ], + # Disabled because these tests require "wal_level=replica", which + # some runningcheck users do not have (e.g. buildfarm clients). + 'regress_args': ['--temp-config', files('walinspect.conf')], + 'runningcheck': false, + }, +} diff --git a/contrib/pg_walinspect/pg_walinspect--1.0--1.1.sql b/contrib/pg_walinspect/pg_walinspect--1.0--1.1.sql new file mode 100644 index 0000000..a4d50d3 --- /dev/null +++ b/contrib/pg_walinspect/pg_walinspect--1.0--1.1.sql @@ -0,0 +1,42 @@ +/* contrib/pg_walinspect/pg_walinspect--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pg_walinspect UPDATE TO '1.1'" to load this file. \quit + +-- Unsupported functions after 1.1. +DROP FUNCTION pg_get_wal_records_info_till_end_of_wal(pg_lsn); +DROP FUNCTION pg_get_wal_stats_till_end_of_wal(pg_lsn, boolean); + +-- +-- pg_get_wal_block_info() +-- +CREATE FUNCTION pg_get_wal_block_info(IN start_lsn pg_lsn, + IN end_lsn pg_lsn, + IN show_data boolean DEFAULT true, + OUT start_lsn pg_lsn, + OUT end_lsn pg_lsn, + OUT prev_lsn pg_lsn, + OUT block_id int2, + OUT reltablespace oid, + OUT reldatabase oid, + OUT relfilenode oid, + OUT relforknumber int2, + OUT relblocknumber int8, + OUT xid xid, + OUT resource_manager text, + OUT record_type text, + OUT record_length int4, + OUT main_data_length int4, + OUT block_data_length int4, + OUT block_fpi_length int4, + OUT block_fpi_info text[], + OUT description text, + OUT block_data bytea, + OUT block_fpi_data bytea +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_get_wal_block_info' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) TO pg_read_server_files; diff --git a/contrib/pg_walinspect/pg_walinspect--1.0.sql b/contrib/pg_walinspect/pg_walinspect--1.0.sql new file mode 100644 index 0000000..08b3dd5 --- /dev/null +++ b/contrib/pg_walinspect/pg_walinspect--1.0.sql @@ -0,0 +1,118 @@ +/* contrib/pg_walinspect/pg_walinspect--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pg_walinspect" to load this file. \quit + +-- +-- pg_get_wal_record_info() +-- +CREATE FUNCTION pg_get_wal_record_info(IN in_lsn pg_lsn, + OUT start_lsn pg_lsn, + OUT end_lsn pg_lsn, + OUT prev_lsn pg_lsn, + OUT xid xid, + OUT resource_manager text, + OUT record_type text, + OUT record_length int4, + OUT main_data_length int4, + OUT fpi_length int4, + OUT description text, + OUT block_ref text +) +AS 'MODULE_PATHNAME', 'pg_get_wal_record_info' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pg_get_wal_record_info(pg_lsn) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_get_wal_record_info(pg_lsn) TO pg_read_server_files; + +-- +-- pg_get_wal_records_info() +-- +CREATE FUNCTION pg_get_wal_records_info(IN start_lsn pg_lsn, + IN end_lsn pg_lsn, + OUT start_lsn pg_lsn, + OUT end_lsn pg_lsn, + OUT prev_lsn pg_lsn, + OUT xid xid, + OUT resource_manager text, + OUT record_type text, + OUT record_length int4, + OUT main_data_length int4, + OUT fpi_length int4, + OUT description text, + OUT block_ref text +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_get_wal_records_info' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn) TO pg_read_server_files; + +-- +-- pg_get_wal_records_info_till_end_of_wal() +-- +CREATE FUNCTION pg_get_wal_records_info_till_end_of_wal(IN start_lsn pg_lsn, + OUT start_lsn pg_lsn, + OUT end_lsn pg_lsn, + OUT prev_lsn pg_lsn, + OUT xid xid, + OUT resource_manager text, + OUT record_type text, + OUT record_length int4, + OUT main_data_length int4, + OUT fpi_length int4, + OUT description text, + OUT block_ref text +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_get_wal_records_info_till_end_of_wal' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pg_get_wal_records_info_till_end_of_wal(pg_lsn) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_get_wal_records_info_till_end_of_wal(pg_lsn) TO pg_read_server_files; + +-- +-- pg_get_wal_stats() +-- +CREATE FUNCTION pg_get_wal_stats(IN start_lsn pg_lsn, + IN end_lsn pg_lsn, + IN per_record boolean DEFAULT false, + OUT "resource_manager/record_type" text, + OUT count int8, + OUT count_percentage float8, + OUT record_size int8, + OUT record_size_percentage float8, + OUT fpi_size int8, + OUT fpi_size_percentage float8, + OUT combined_size int8, + OUT combined_size_percentage float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_get_wal_stats' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean) TO pg_read_server_files; + +-- +-- pg_get_wal_stats_till_end_of_wal() +-- +CREATE FUNCTION pg_get_wal_stats_till_end_of_wal(IN start_lsn pg_lsn, + IN per_record boolean DEFAULT false, + OUT "resource_manager/record_type" text, + OUT count int8, + OUT count_percentage float8, + OUT record_size int8, + OUT record_size_percentage float8, + OUT fpi_size int8, + OUT fpi_size_percentage float8, + OUT combined_size int8, + OUT combined_size_percentage float8 +) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pg_get_wal_stats_till_end_of_wal' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pg_get_wal_stats_till_end_of_wal(pg_lsn, boolean) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_get_wal_stats_till_end_of_wal(pg_lsn, boolean) TO pg_read_server_files; diff --git a/contrib/pg_walinspect/pg_walinspect.c b/contrib/pg_walinspect/pg_walinspect.c new file mode 100644 index 0000000..796a74f --- /dev/null +++ b/contrib/pg_walinspect/pg_walinspect.c @@ -0,0 +1,851 @@ +/*------------------------------------------------------------------------- + * + * pg_walinspect.c + * Functions to inspect contents of PostgreSQL Write-Ahead Log + * + * Copyright (c) 2022-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pg_walinspect/pg_walinspect.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/xlog.h" +#include "access/xlog_internal.h" +#include "access/xlogreader.h" +#include "access/xlogrecovery.h" +#include "access/xlogstats.h" +#include "access/xlogutils.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/pg_lsn.h" + +/* + * NOTE: For any code change or issue fix here, it is highly recommended to + * give a thought about doing the same in pg_waldump tool as well. + */ + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(pg_get_wal_block_info); +PG_FUNCTION_INFO_V1(pg_get_wal_record_info); +PG_FUNCTION_INFO_V1(pg_get_wal_records_info); +PG_FUNCTION_INFO_V1(pg_get_wal_records_info_till_end_of_wal); +PG_FUNCTION_INFO_V1(pg_get_wal_stats); +PG_FUNCTION_INFO_V1(pg_get_wal_stats_till_end_of_wal); + +static void ValidateInputLSNs(XLogRecPtr start_lsn, XLogRecPtr *end_lsn); +static XLogRecPtr GetCurrentLSN(void); +static XLogReaderState *InitXLogReaderState(XLogRecPtr lsn); +static XLogRecord *ReadNextXLogRecord(XLogReaderState *xlogreader); +static void GetWALRecordInfo(XLogReaderState *record, Datum *values, + bool *nulls, uint32 ncols); +static void GetWALRecordsInfo(FunctionCallInfo fcinfo, + XLogRecPtr start_lsn, + XLogRecPtr end_lsn); +static void GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo, + Datum *values, bool *nulls, uint32 ncols, + bool stats_per_record); +static void FillXLogStatsRow(const char *name, uint64 n, uint64 total_count, + uint64 rec_len, uint64 total_rec_len, + uint64 fpi_len, uint64 total_fpi_len, + uint64 tot_len, uint64 total_len, + Datum *values, bool *nulls, uint32 ncols); +static void GetWalStats(FunctionCallInfo fcinfo, + XLogRecPtr start_lsn, + XLogRecPtr end_lsn, + bool stats_per_record); +static void GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record, + bool show_data); + +/* + * Return the LSN up to which the server has WAL. + */ +static XLogRecPtr +GetCurrentLSN(void) +{ + XLogRecPtr curr_lsn; + + /* + * We determine the current LSN of the server similar to how page_read + * callback read_local_xlog_page_no_wait does. + */ + if (!RecoveryInProgress()) + curr_lsn = GetFlushRecPtr(NULL); + else + curr_lsn = GetXLogReplayRecPtr(NULL); + + Assert(!XLogRecPtrIsInvalid(curr_lsn)); + + return curr_lsn; +} + +/* + * Initialize WAL reader and identify first valid LSN. + */ +static XLogReaderState * +InitXLogReaderState(XLogRecPtr lsn) +{ + XLogReaderState *xlogreader; + ReadLocalXLogPageNoWaitPrivate *private_data; + XLogRecPtr first_valid_record; + + /* + * Reading WAL below the first page of the first segments isn't allowed. + * This is a bootstrap WAL page and the page_read callback fails to read + * it. + */ + if (lsn < XLOG_BLCKSZ) + ereport(ERROR, + (errmsg("could not read WAL at LSN %X/%X", + LSN_FORMAT_ARGS(lsn)))); + + private_data = (ReadLocalXLogPageNoWaitPrivate *) + palloc0(sizeof(ReadLocalXLogPageNoWaitPrivate)); + + xlogreader = XLogReaderAllocate(wal_segment_size, NULL, + XL_ROUTINE(.page_read = &read_local_xlog_page_no_wait, + .segment_open = &wal_segment_open, + .segment_close = &wal_segment_close), + private_data); + + if (xlogreader == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Failed while allocating a WAL reading processor."))); + + /* first find a valid recptr to start from */ + first_valid_record = XLogFindNextRecord(xlogreader, lsn); + + if (XLogRecPtrIsInvalid(first_valid_record)) + ereport(ERROR, + (errmsg("could not find a valid record after %X/%X", + LSN_FORMAT_ARGS(lsn)))); + + return xlogreader; +} + +/* + * Read next WAL record. + * + * By design, to be less intrusive in a running system, no slot is allocated + * to reserve the WAL we're about to read. Therefore this function can + * encounter read errors for historical WAL. + * + * We guard against ordinary errors trying to read WAL that hasn't been + * written yet by limiting end_lsn to the flushed WAL, but that can also + * encounter errors if the flush pointer falls in the middle of a record. In + * that case we'll return NULL. + */ +static XLogRecord * +ReadNextXLogRecord(XLogReaderState *xlogreader) +{ + XLogRecord *record; + char *errormsg; + + record = XLogReadRecord(xlogreader, &errormsg); + + if (record == NULL) + { + ReadLocalXLogPageNoWaitPrivate *private_data; + + /* return NULL, if end of WAL is reached */ + private_data = (ReadLocalXLogPageNoWaitPrivate *) + xlogreader->private_data; + + if (private_data->end_of_wal) + return NULL; + + if (errormsg) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read WAL at %X/%X: %s", + LSN_FORMAT_ARGS(xlogreader->EndRecPtr), errormsg))); + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read WAL at %X/%X", + LSN_FORMAT_ARGS(xlogreader->EndRecPtr)))); + } + + return record; +} + +/* + * Output values that make up a row describing caller's WAL record. + * + * This function leaks memory. Caller may need to use its own custom memory + * context. + * + * Keep this in sync with GetWALBlockInfo. + */ +static void +GetWALRecordInfo(XLogReaderState *record, Datum *values, + bool *nulls, uint32 ncols) +{ + const char *record_type; + RmgrData desc; + uint32 fpi_len = 0; + StringInfoData rec_desc; + StringInfoData rec_blk_ref; + int i = 0; + + desc = GetRmgr(XLogRecGetRmid(record)); + record_type = desc.rm_identify(XLogRecGetInfo(record)); + + if (record_type == NULL) + record_type = psprintf("UNKNOWN (%x)", XLogRecGetInfo(record) & ~XLR_INFO_MASK); + + initStringInfo(&rec_desc); + desc.rm_desc(&rec_desc, record); + + if (XLogRecHasAnyBlockRefs(record)) + { + initStringInfo(&rec_blk_ref); + XLogRecGetBlockRefInfo(record, false, true, &rec_blk_ref, &fpi_len); + } + + values[i++] = LSNGetDatum(record->ReadRecPtr); + values[i++] = LSNGetDatum(record->EndRecPtr); + values[i++] = LSNGetDatum(XLogRecGetPrev(record)); + values[i++] = TransactionIdGetDatum(XLogRecGetXid(record)); + values[i++] = CStringGetTextDatum(desc.rm_name); + values[i++] = CStringGetTextDatum(record_type); + values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record)); + values[i++] = UInt32GetDatum(XLogRecGetDataLen(record)); + values[i++] = UInt32GetDatum(fpi_len); + + if (rec_desc.len > 0) + values[i++] = CStringGetTextDatum(rec_desc.data); + else + nulls[i++] = true; + + if (XLogRecHasAnyBlockRefs(record)) + values[i++] = CStringGetTextDatum(rec_blk_ref.data); + else + nulls[i++] = true; + + Assert(i == ncols); +} + + +/* + * Output one or more rows in rsinfo tuple store, each describing a single + * block reference from caller's WAL record. (Should only be called with + * records that have block references.) + * + * This function leaks memory. Caller may need to use its own custom memory + * context. + * + * Keep this in sync with GetWALRecordInfo. + */ +static void +GetWALBlockInfo(FunctionCallInfo fcinfo, XLogReaderState *record, + bool show_data) +{ +#define PG_GET_WAL_BLOCK_INFO_COLS 20 + int block_id; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + RmgrData desc; + const char *record_type; + StringInfoData rec_desc; + + Assert(XLogRecHasAnyBlockRefs(record)); + + desc = GetRmgr(XLogRecGetRmid(record)); + record_type = desc.rm_identify(XLogRecGetInfo(record)); + + if (record_type == NULL) + record_type = psprintf("UNKNOWN (%x)", + XLogRecGetInfo(record) & ~XLR_INFO_MASK); + + initStringInfo(&rec_desc); + desc.rm_desc(&rec_desc, record); + + for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++) + { + DecodedBkpBlock *blk; + BlockNumber blkno; + RelFileLocator rnode; + ForkNumber forknum; + Datum values[PG_GET_WAL_BLOCK_INFO_COLS] = {0}; + bool nulls[PG_GET_WAL_BLOCK_INFO_COLS] = {0}; + uint32 block_data_len = 0, + block_fpi_len = 0; + ArrayType *block_fpi_info = NULL; + int i = 0; + + if (!XLogRecHasBlockRef(record, block_id)) + continue; + + blk = XLogRecGetBlock(record, block_id); + + (void) XLogRecGetBlockTagExtended(record, block_id, + &rnode, &forknum, &blkno, NULL); + + /* Save block_data_len */ + if (blk->has_data) + block_data_len = blk->data_len; + + if (blk->has_image) + { + /* Block reference has an FPI, so prepare relevant output */ + int bitcnt; + int cnt = 0; + Datum *flags; + + /* Save block_fpi_len */ + block_fpi_len = blk->bimg_len; + + /* Construct and save block_fpi_info */ + bitcnt = pg_popcount((const char *) &blk->bimg_info, + sizeof(uint8)); + flags = (Datum *) palloc0(sizeof(Datum) * bitcnt); + if ((blk->bimg_info & BKPIMAGE_HAS_HOLE) != 0) + flags[cnt++] = CStringGetTextDatum("HAS_HOLE"); + if (blk->apply_image) + flags[cnt++] = CStringGetTextDatum("APPLY"); + if ((blk->bimg_info & BKPIMAGE_COMPRESS_PGLZ) != 0) + flags[cnt++] = CStringGetTextDatum("COMPRESS_PGLZ"); + if ((blk->bimg_info & BKPIMAGE_COMPRESS_LZ4) != 0) + flags[cnt++] = CStringGetTextDatum("COMPRESS_LZ4"); + if ((blk->bimg_info & BKPIMAGE_COMPRESS_ZSTD) != 0) + flags[cnt++] = CStringGetTextDatum("COMPRESS_ZSTD"); + + Assert(cnt <= bitcnt); + block_fpi_info = construct_array_builtin(flags, cnt, TEXTOID); + } + + /* start_lsn, end_lsn, prev_lsn, and blockid outputs */ + values[i++] = LSNGetDatum(record->ReadRecPtr); + values[i++] = LSNGetDatum(record->EndRecPtr); + values[i++] = LSNGetDatum(XLogRecGetPrev(record)); + values[i++] = Int16GetDatum(block_id); + + /* relfile and block related outputs */ + values[i++] = ObjectIdGetDatum(blk->rlocator.spcOid); + values[i++] = ObjectIdGetDatum(blk->rlocator.dbOid); + values[i++] = ObjectIdGetDatum(blk->rlocator.relNumber); + values[i++] = Int16GetDatum(forknum); + values[i++] = Int64GetDatum((int64) blkno); + + /* xid, resource_manager, and record_type outputs */ + values[i++] = TransactionIdGetDatum(XLogRecGetXid(record)); + values[i++] = CStringGetTextDatum(desc.rm_name); + values[i++] = CStringGetTextDatum(record_type); + + /* + * record_length, main_data_length, block_data_len, and + * block_fpi_length outputs + */ + values[i++] = UInt32GetDatum(XLogRecGetTotalLen(record)); + values[i++] = UInt32GetDatum(XLogRecGetDataLen(record)); + values[i++] = UInt32GetDatum(block_data_len); + values[i++] = UInt32GetDatum(block_fpi_len); + + /* block_fpi_info (text array) output */ + if (block_fpi_info) + values[i++] = PointerGetDatum(block_fpi_info); + else + nulls[i++] = true; + + /* description output (describes WAL record) */ + if (rec_desc.len > 0) + values[i++] = CStringGetTextDatum(rec_desc.data); + else + nulls[i++] = true; + + /* block_data output */ + if (blk->has_data && show_data) + { + bytea *block_data; + + block_data = (bytea *) palloc(block_data_len + VARHDRSZ); + SET_VARSIZE(block_data, block_data_len + VARHDRSZ); + memcpy(VARDATA(block_data), blk->data, block_data_len); + values[i++] = PointerGetDatum(block_data); + } + else + nulls[i++] = true; + + /* block_fpi_data output */ + if (blk->has_image && show_data) + { + PGAlignedBlock buf; + Page page; + bytea *block_fpi_data; + + page = (Page) buf.data; + if (!RestoreBlockImage(record, block_id, page)) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg_internal("%s", record->errormsg_buf))); + + block_fpi_data = (bytea *) palloc(BLCKSZ + VARHDRSZ); + SET_VARSIZE(block_fpi_data, BLCKSZ + VARHDRSZ); + memcpy(VARDATA(block_fpi_data), page, BLCKSZ); + values[i++] = PointerGetDatum(block_fpi_data); + } + else + nulls[i++] = true; + + Assert(i == PG_GET_WAL_BLOCK_INFO_COLS); + + /* Store a tuple for this block reference */ + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, + values, nulls); + } + +#undef PG_GET_WAL_BLOCK_INFO_COLS +} + +/* + * Get WAL record info, unnested by block reference + */ +Datum +pg_get_wal_block_info(PG_FUNCTION_ARGS) +{ + XLogRecPtr start_lsn = PG_GETARG_LSN(0); + XLogRecPtr end_lsn = PG_GETARG_LSN(1); + bool show_data = PG_GETARG_BOOL(2); + XLogReaderState *xlogreader; + MemoryContext old_cxt; + MemoryContext tmp_cxt; + + ValidateInputLSNs(start_lsn, &end_lsn); + + InitMaterializedSRF(fcinfo, 0); + + xlogreader = InitXLogReaderState(start_lsn); + + tmp_cxt = AllocSetContextCreate(CurrentMemoryContext, + "pg_get_wal_block_info temporary cxt", + ALLOCSET_DEFAULT_SIZES); + + while (ReadNextXLogRecord(xlogreader) && + xlogreader->EndRecPtr <= end_lsn) + { + CHECK_FOR_INTERRUPTS(); + + if (!XLogRecHasAnyBlockRefs(xlogreader)) + continue; + + /* Use the tmp context so we can clean up after each tuple is done */ + old_cxt = MemoryContextSwitchTo(tmp_cxt); + + GetWALBlockInfo(fcinfo, xlogreader, show_data); + + /* clean up and switch back */ + MemoryContextSwitchTo(old_cxt); + MemoryContextReset(tmp_cxt); + } + + MemoryContextDelete(tmp_cxt); + pfree(xlogreader->private_data); + XLogReaderFree(xlogreader); + + PG_RETURN_VOID(); +} + +/* + * Get WAL record info. + */ +Datum +pg_get_wal_record_info(PG_FUNCTION_ARGS) +{ +#define PG_GET_WAL_RECORD_INFO_COLS 11 + Datum result; + Datum values[PG_GET_WAL_RECORD_INFO_COLS] = {0}; + bool nulls[PG_GET_WAL_RECORD_INFO_COLS] = {0}; + XLogRecPtr lsn; + XLogRecPtr curr_lsn; + XLogReaderState *xlogreader; + TupleDesc tupdesc; + HeapTuple tuple; + + lsn = PG_GETARG_LSN(0); + curr_lsn = GetCurrentLSN(); + + if (lsn > curr_lsn) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("WAL input LSN must be less than current LSN"), + errdetail("Current WAL LSN on the database system is at %X/%X.", + LSN_FORMAT_ARGS(curr_lsn)))); + + /* 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"); + + xlogreader = InitXLogReaderState(lsn); + + if (!ReadNextXLogRecord(xlogreader)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not read WAL at %X/%X", + LSN_FORMAT_ARGS(xlogreader->EndRecPtr)))); + + GetWALRecordInfo(xlogreader, values, nulls, PG_GET_WAL_RECORD_INFO_COLS); + + pfree(xlogreader->private_data); + XLogReaderFree(xlogreader); + + tuple = heap_form_tuple(tupdesc, values, nulls); + result = HeapTupleGetDatum(tuple); + + PG_RETURN_DATUM(result); +#undef PG_GET_WAL_RECORD_INFO_COLS +} + +/* + * Validate start and end LSNs coming from the function inputs. + * + * If end_lsn is found to be higher than the current LSN reported by the + * cluster, use the current LSN as the upper bound. + */ +static void +ValidateInputLSNs(XLogRecPtr start_lsn, XLogRecPtr *end_lsn) +{ + XLogRecPtr curr_lsn = GetCurrentLSN(); + + if (start_lsn > curr_lsn) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("WAL start LSN must be less than current LSN"), + errdetail("Current WAL LSN on the database system is at %X/%X.", + LSN_FORMAT_ARGS(curr_lsn)))); + + if (start_lsn > *end_lsn) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("WAL start LSN must be less than end LSN"))); + + if (*end_lsn > curr_lsn) + *end_lsn = curr_lsn; +} + +/* + * Get info of all WAL records between start LSN and end LSN. + */ +static void +GetWALRecordsInfo(FunctionCallInfo fcinfo, XLogRecPtr start_lsn, + XLogRecPtr end_lsn) +{ +#define PG_GET_WAL_RECORDS_INFO_COLS 11 + XLogReaderState *xlogreader; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + MemoryContext old_cxt; + MemoryContext tmp_cxt; + + Assert(start_lsn <= end_lsn); + + InitMaterializedSRF(fcinfo, 0); + + xlogreader = InitXLogReaderState(start_lsn); + + tmp_cxt = AllocSetContextCreate(CurrentMemoryContext, + "GetWALRecordsInfo temporary cxt", + ALLOCSET_DEFAULT_SIZES); + + while (ReadNextXLogRecord(xlogreader) && + xlogreader->EndRecPtr <= end_lsn) + { + Datum values[PG_GET_WAL_RECORDS_INFO_COLS] = {0}; + bool nulls[PG_GET_WAL_RECORDS_INFO_COLS] = {0}; + + /* Use the tmp context so we can clean up after each tuple is done */ + old_cxt = MemoryContextSwitchTo(tmp_cxt); + + GetWALRecordInfo(xlogreader, values, nulls, + PG_GET_WAL_RECORDS_INFO_COLS); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, + values, nulls); + + /* clean up and switch back */ + MemoryContextSwitchTo(old_cxt); + MemoryContextReset(tmp_cxt); + + CHECK_FOR_INTERRUPTS(); + } + + MemoryContextDelete(tmp_cxt); + pfree(xlogreader->private_data); + XLogReaderFree(xlogreader); + +#undef PG_GET_WAL_RECORDS_INFO_COLS +} + +/* + * Get info of all WAL records between start LSN and end LSN. + */ +Datum +pg_get_wal_records_info(PG_FUNCTION_ARGS) +{ + XLogRecPtr start_lsn = PG_GETARG_LSN(0); + XLogRecPtr end_lsn = PG_GETARG_LSN(1); + + ValidateInputLSNs(start_lsn, &end_lsn); + GetWALRecordsInfo(fcinfo, start_lsn, end_lsn); + + PG_RETURN_VOID(); +} + +/* + * Fill single row of record counts and sizes for an rmgr or record. + */ +static void +FillXLogStatsRow(const char *name, + uint64 n, uint64 total_count, + uint64 rec_len, uint64 total_rec_len, + uint64 fpi_len, uint64 total_fpi_len, + uint64 tot_len, uint64 total_len, + Datum *values, bool *nulls, uint32 ncols) +{ + double n_pct, + rec_len_pct, + fpi_len_pct, + tot_len_pct; + int i = 0; + + n_pct = 0; + if (total_count != 0) + n_pct = 100 * (double) n / total_count; + + rec_len_pct = 0; + if (total_rec_len != 0) + rec_len_pct = 100 * (double) rec_len / total_rec_len; + + fpi_len_pct = 0; + if (total_fpi_len != 0) + fpi_len_pct = 100 * (double) fpi_len / total_fpi_len; + + tot_len_pct = 0; + if (total_len != 0) + tot_len_pct = 100 * (double) tot_len / total_len; + + values[i++] = CStringGetTextDatum(name); + values[i++] = Int64GetDatum(n); + values[i++] = Float8GetDatum(n_pct); + values[i++] = Int64GetDatum(rec_len); + values[i++] = Float8GetDatum(rec_len_pct); + values[i++] = Int64GetDatum(fpi_len); + values[i++] = Float8GetDatum(fpi_len_pct); + values[i++] = Int64GetDatum(tot_len); + values[i++] = Float8GetDatum(tot_len_pct); + + Assert(i == ncols); +} + +/* + * Get summary statistics about the records seen so far. + */ +static void +GetXLogSummaryStats(XLogStats *stats, ReturnSetInfo *rsinfo, + Datum *values, bool *nulls, uint32 ncols, + bool stats_per_record) +{ + MemoryContext old_cxt; + MemoryContext tmp_cxt; + uint64 total_count = 0; + uint64 total_rec_len = 0; + uint64 total_fpi_len = 0; + uint64 total_len = 0; + int ri; + + /* + * Each row shows its percentages of the total, so make a first pass to + * calculate column totals. + */ + for (ri = 0; ri <= RM_MAX_ID; ri++) + { + if (!RmgrIdIsValid(ri)) + continue; + + total_count += stats->rmgr_stats[ri].count; + total_rec_len += stats->rmgr_stats[ri].rec_len; + total_fpi_len += stats->rmgr_stats[ri].fpi_len; + } + total_len = total_rec_len + total_fpi_len; + + tmp_cxt = AllocSetContextCreate(CurrentMemoryContext, + "GetXLogSummaryStats temporary cxt", + ALLOCSET_DEFAULT_SIZES); + + for (ri = 0; ri <= RM_MAX_ID; ri++) + { + uint64 count; + uint64 rec_len; + uint64 fpi_len; + uint64 tot_len; + RmgrData desc; + + if (!RmgrIdIsValid(ri)) + continue; + + if (!RmgrIdExists(ri)) + continue; + + desc = GetRmgr(ri); + + if (stats_per_record) + { + int rj; + + for (rj = 0; rj < MAX_XLINFO_TYPES; rj++) + { + const char *id; + + count = stats->record_stats[ri][rj].count; + rec_len = stats->record_stats[ri][rj].rec_len; + fpi_len = stats->record_stats[ri][rj].fpi_len; + tot_len = rec_len + fpi_len; + + /* Skip undefined combinations and ones that didn't occur */ + if (count == 0) + continue; + + old_cxt = MemoryContextSwitchTo(tmp_cxt); + + /* the upper four bits in xl_info are the rmgr's */ + id = desc.rm_identify(rj << 4); + if (id == NULL) + id = psprintf("UNKNOWN (%x)", rj << 4); + + FillXLogStatsRow(psprintf("%s/%s", desc.rm_name, id), count, + total_count, rec_len, total_rec_len, fpi_len, + total_fpi_len, tot_len, total_len, + values, nulls, ncols); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, + values, nulls); + + /* clean up and switch back */ + MemoryContextSwitchTo(old_cxt); + MemoryContextReset(tmp_cxt); + } + } + else + { + count = stats->rmgr_stats[ri].count; + rec_len = stats->rmgr_stats[ri].rec_len; + fpi_len = stats->rmgr_stats[ri].fpi_len; + tot_len = rec_len + fpi_len; + + old_cxt = MemoryContextSwitchTo(tmp_cxt); + + FillXLogStatsRow(desc.rm_name, count, total_count, rec_len, + total_rec_len, fpi_len, total_fpi_len, tot_len, + total_len, values, nulls, ncols); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, + values, nulls); + + /* clean up and switch back */ + MemoryContextSwitchTo(old_cxt); + MemoryContextReset(tmp_cxt); + } + } + + MemoryContextDelete(tmp_cxt); +} + +/* + * Get WAL stats between start LSN and end LSN. + */ +static void +GetWalStats(FunctionCallInfo fcinfo, XLogRecPtr start_lsn, XLogRecPtr end_lsn, + bool stats_per_record) +{ +#define PG_GET_WAL_STATS_COLS 9 + XLogReaderState *xlogreader; + XLogStats stats = {0}; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Datum values[PG_GET_WAL_STATS_COLS] = {0}; + bool nulls[PG_GET_WAL_STATS_COLS] = {0}; + + Assert(start_lsn <= end_lsn); + + InitMaterializedSRF(fcinfo, 0); + + xlogreader = InitXLogReaderState(start_lsn); + + while (ReadNextXLogRecord(xlogreader) && + xlogreader->EndRecPtr <= end_lsn) + { + XLogRecStoreStats(&stats, xlogreader); + + CHECK_FOR_INTERRUPTS(); + } + + pfree(xlogreader->private_data); + XLogReaderFree(xlogreader); + + GetXLogSummaryStats(&stats, rsinfo, values, nulls, + PG_GET_WAL_STATS_COLS, + stats_per_record); + +#undef PG_GET_WAL_STATS_COLS +} + +/* + * Get stats of all WAL records between start LSN and end LSN. + */ +Datum +pg_get_wal_stats(PG_FUNCTION_ARGS) +{ + XLogRecPtr start_lsn = PG_GETARG_LSN(0); + XLogRecPtr end_lsn = PG_GETARG_LSN(1); + bool stats_per_record = PG_GETARG_BOOL(2); + + ValidateInputLSNs(start_lsn, &end_lsn); + GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record); + + PG_RETURN_VOID(); +} + +/* + * The following functions have been removed in newer versions in 1.1, but + * they are kept around for compatibility. + */ +Datum +pg_get_wal_records_info_till_end_of_wal(PG_FUNCTION_ARGS) +{ + XLogRecPtr start_lsn = PG_GETARG_LSN(0); + XLogRecPtr end_lsn = GetCurrentLSN(); + + if (start_lsn > end_lsn) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("WAL start LSN must be less than current LSN"), + errdetail("Current WAL LSN on the database system is at %X/%X.", + LSN_FORMAT_ARGS(end_lsn)))); + + GetWALRecordsInfo(fcinfo, start_lsn, end_lsn); + + PG_RETURN_VOID(); +} + +Datum +pg_get_wal_stats_till_end_of_wal(PG_FUNCTION_ARGS) +{ + XLogRecPtr start_lsn = PG_GETARG_LSN(0); + XLogRecPtr end_lsn = GetCurrentLSN(); + bool stats_per_record = PG_GETARG_BOOL(1); + + if (start_lsn > end_lsn) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("WAL start LSN must be less than current LSN"), + errdetail("Current WAL LSN on the database system is at %X/%X.", + LSN_FORMAT_ARGS(end_lsn)))); + + GetWalStats(fcinfo, start_lsn, end_lsn, stats_per_record); + + PG_RETURN_VOID(); +} diff --git a/contrib/pg_walinspect/pg_walinspect.control b/contrib/pg_walinspect/pg_walinspect.control new file mode 100644 index 0000000..efa3cb2 --- /dev/null +++ b/contrib/pg_walinspect/pg_walinspect.control @@ -0,0 +1,5 @@ +# pg_walinspect extension +comment = 'functions to inspect contents of PostgreSQL Write-Ahead Log' +default_version = '1.1' +module_pathname = '$libdir/pg_walinspect' +relocatable = true diff --git a/contrib/pg_walinspect/sql/oldextversions.sql b/contrib/pg_walinspect/sql/oldextversions.sql new file mode 100644 index 0000000..e35c4f3 --- /dev/null +++ b/contrib/pg_walinspect/sql/oldextversions.sql @@ -0,0 +1,39 @@ +-- Test old extension version entry points. + +CREATE EXTENSION pg_walinspect WITH VERSION '1.0'; + +-- Mask DETAIL messages as these could refer to current LSN positions. +\set VERBOSITY terse + +-- List what version 1.0 contains, using a locale-independent sorting. +SELECT pg_describe_object(classid, objid, 0) AS obj + FROM pg_depend + WHERE refclassid = 'pg_extension'::regclass AND + refobjid = (SELECT oid FROM pg_extension + WHERE extname = 'pg_walinspect') AND deptype = 'e' + ORDER BY pg_describe_object(classid, objid, 0) COLLATE "C"; + +-- Make sure checkpoints don't interfere with the test. +SELECT 'init' FROM pg_create_physical_replication_slot('regress_pg_walinspect_slot', true, false); + +CREATE TABLE sample_tbl(col1 int, col2 int); +SELECT pg_current_wal_lsn() AS wal_lsn1 \gset +INSERT INTO sample_tbl SELECT * FROM generate_series(1, 2); + +-- Tests for the past functions. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info_till_end_of_wal(:'wal_lsn1'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats_till_end_of_wal(:'wal_lsn1'); +-- Failures with start LSNs. +SELECT * FROM pg_get_wal_records_info_till_end_of_wal('FFFFFFFF/FFFFFFFF'); +SELECT * FROM pg_get_wal_stats_till_end_of_wal('FFFFFFFF/FFFFFFFF'); + +-- Move to new version 1.1. +ALTER EXTENSION pg_walinspect UPDATE TO '1.1'; + +-- List what version 1.1 contains. +\dx+ pg_walinspect + +SELECT pg_drop_replication_slot('regress_pg_walinspect_slot'); + +DROP TABLE sample_tbl; +DROP EXTENSION pg_walinspect; diff --git a/contrib/pg_walinspect/sql/pg_walinspect.sql b/contrib/pg_walinspect/sql/pg_walinspect.sql new file mode 100644 index 0000000..f987ca3 --- /dev/null +++ b/contrib/pg_walinspect/sql/pg_walinspect.sql @@ -0,0 +1,157 @@ +CREATE EXTENSION pg_walinspect; + +-- Mask DETAIL messages as these could refer to current LSN positions. +\set VERBOSITY terse + +-- Make sure checkpoints don't interfere with the test. +SELECT 'init' FROM pg_create_physical_replication_slot('regress_pg_walinspect_slot', true, false); + +CREATE TABLE sample_tbl(col1 int, col2 int); + +-- Save some LSNs for comparisons. +SELECT pg_current_wal_lsn() AS wal_lsn1 \gset +INSERT INTO sample_tbl SELECT * FROM generate_series(1, 2); +SELECT pg_current_wal_lsn() AS wal_lsn2 \gset +INSERT INTO sample_tbl SELECT * FROM generate_series(3, 4); + +-- =================================================================== +-- Tests for input validation +-- =================================================================== + +-- Invalid input LSN. +SELECT * FROM pg_get_wal_record_info('0/0'); + +-- Invalid start LSN. +SELECT * FROM pg_get_wal_records_info('0/0', :'wal_lsn1'); +SELECT * FROM pg_get_wal_stats('0/0', :'wal_lsn1'); +SELECT * FROM pg_get_wal_block_info('0/0', :'wal_lsn1'); + +-- Start LSN > End LSN. +SELECT * FROM pg_get_wal_records_info(:'wal_lsn2', :'wal_lsn1'); +SELECT * FROM pg_get_wal_stats(:'wal_lsn2', :'wal_lsn1'); +SELECT * FROM pg_get_wal_block_info(:'wal_lsn2', :'wal_lsn1'); + +-- LSNs with the highest value possible. +SELECT * FROM pg_get_wal_record_info('FFFFFFFF/FFFFFFFF'); +-- Success with end LSNs. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', 'FFFFFFFF/FFFFFFFF'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats(:'wal_lsn1', 'FFFFFFFF/FFFFFFFF'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn1', 'FFFFFFFF/FFFFFFFF'); +-- Failures with start LSNs. +SELECT * FROM pg_get_wal_records_info('FFFFFFFF/FFFFFFFE', 'FFFFFFFF/FFFFFFFF'); +SELECT * FROM pg_get_wal_stats('FFFFFFFF/FFFFFFFE', 'FFFFFFFF/FFFFFFFF'); +SELECT * FROM pg_get_wal_block_info('FFFFFFFF/FFFFFFFE', 'FFFFFFFF/FFFFFFFF'); + +-- =================================================================== +-- Tests for all function executions +-- =================================================================== + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_record_info(:'wal_lsn1'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_stats(:'wal_lsn1', :'wal_lsn2'); +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn1', :'wal_lsn2'); + +-- =================================================================== +-- Test for filtering out WAL records of a particular table +-- =================================================================== + +SELECT oid AS sample_tbl_oid FROM pg_class WHERE relname = 'sample_tbl' \gset + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2') + WHERE block_ref LIKE concat('%', :'sample_tbl_oid', '%') AND resource_manager = 'Heap'; + +-- =================================================================== +-- Test for filtering out WAL records based on resource_manager and +-- record_type +-- =================================================================== + +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_records_info(:'wal_lsn1', :'wal_lsn2') + WHERE resource_manager = 'Heap' AND record_type = 'INSERT'; + +-- =================================================================== +-- Tests to get block information from WAL record +-- =================================================================== + +-- Update table to generate some block data. +SELECT pg_current_wal_lsn() AS wal_lsn3 \gset +UPDATE sample_tbl SET col1 = col1 + 1 WHERE col1 = 1; +SELECT pg_current_wal_lsn() AS wal_lsn4 \gset +-- Check if we get block data from WAL record. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn3', :'wal_lsn4') + WHERE relfilenode = :'sample_tbl_oid' AND block_data IS NOT NULL; + +-- Force full-page image on the next update. +SELECT pg_current_wal_lsn() AS wal_lsn5 \gset +CHECKPOINT; +UPDATE sample_tbl SET col1 = col1 + 1 WHERE col1 = 2; +SELECT pg_current_wal_lsn() AS wal_lsn6 \gset +-- Check if we get FPI from WAL record. +SELECT COUNT(*) >= 1 AS ok FROM pg_get_wal_block_info(:'wal_lsn5', :'wal_lsn6') + WHERE relfilenode = :'sample_tbl_oid' AND block_fpi_data IS NOT NULL; + +-- =================================================================== +-- Tests for permissions +-- =================================================================== +CREATE ROLE regress_pg_walinspect; + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_record_info(pg_lsn)', 'EXECUTE'); -- no +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_records_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- no +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_stats(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- no +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- no + +-- Functions accessible by users with role pg_read_server_files. +GRANT pg_read_server_files TO regress_pg_walinspect; + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_record_info(pg_lsn)', 'EXECUTE'); -- yes +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_records_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- yes +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_stats(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- yes +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- yes + +REVOKE pg_read_server_files FROM regress_pg_walinspect; + +-- Superuser can grant execute to other users. +GRANT EXECUTE ON FUNCTION pg_get_wal_record_info(pg_lsn) + TO regress_pg_walinspect; +GRANT EXECUTE ON FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn) + TO regress_pg_walinspect; +GRANT EXECUTE ON FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean) + TO regress_pg_walinspect; +GRANT EXECUTE ON FUNCTION pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) + TO regress_pg_walinspect; + +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_record_info(pg_lsn)', 'EXECUTE'); -- yes +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_records_info(pg_lsn, pg_lsn) ', 'EXECUTE'); -- yes +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_stats(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- yes +SELECT has_function_privilege('regress_pg_walinspect', + 'pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) ', 'EXECUTE'); -- yes + +REVOKE EXECUTE ON FUNCTION pg_get_wal_record_info(pg_lsn) + FROM regress_pg_walinspect; +REVOKE EXECUTE ON FUNCTION pg_get_wal_records_info(pg_lsn, pg_lsn) + FROM regress_pg_walinspect; +REVOKE EXECUTE ON FUNCTION pg_get_wal_stats(pg_lsn, pg_lsn, boolean) + FROM regress_pg_walinspect; +REVOKE EXECUTE ON FUNCTION pg_get_wal_block_info(pg_lsn, pg_lsn, boolean) + FROM regress_pg_walinspect; + +-- =================================================================== +-- Clean up +-- =================================================================== + +DROP ROLE regress_pg_walinspect; + +SELECT pg_drop_replication_slot('regress_pg_walinspect_slot'); + +DROP TABLE sample_tbl; +DROP EXTENSION pg_walinspect; diff --git a/contrib/pg_walinspect/walinspect.conf b/contrib/pg_walinspect/walinspect.conf new file mode 100644 index 0000000..67ceb2b --- /dev/null +++ b/contrib/pg_walinspect/walinspect.conf @@ -0,0 +1,2 @@ +wal_level = replica +max_replication_slots = 4 diff --git a/contrib/pgcrypto/.gitignore b/contrib/pgcrypto/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pgcrypto/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile new file mode 100644 index 0000000..7fb59f5 --- /dev/null +++ b/contrib/pgcrypto/Makefile @@ -0,0 +1,69 @@ +# contrib/pgcrypto/Makefile + +ZLIB_TST = pgp-compression +ZLIB_OFF_TST = pgp-zlib-DISABLED + +CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST)) + +OBJS = \ + $(WIN32RES) \ + crypt-blowfish.o \ + crypt-des.o \ + crypt-gensalt.o \ + crypt-md5.o \ + mbuf.o \ + openssl.o \ + pgcrypto.o \ + pgp-armor.o \ + pgp-cfb.o \ + pgp-compress.o \ + pgp-decrypt.o \ + pgp-encrypt.o \ + pgp-info.o \ + pgp-mpi.o \ + pgp-mpi-openssl.o \ + pgp-pgsql.o \ + pgp-pubdec.o \ + pgp-pubenc.o \ + pgp-pubkey.o \ + pgp-s2k.o \ + pgp.o \ + px-crypt.o \ + px-hmac.o \ + px.o + +MODULE_big = pgcrypto + +EXTENSION = pgcrypto +DATA = pgcrypto--1.3.sql pgcrypto--1.2--1.3.sql pgcrypto--1.1--1.2.sql \ + pgcrypto--1.0--1.1.sql +PGFILEDESC = "pgcrypto - cryptographic functions" + +REGRESS = init md5 sha1 hmac-md5 hmac-sha1 blowfish rijndael \ + sha2 des 3des cast5 \ + crypt-des crypt-md5 crypt-blowfish crypt-xdes \ + pgp-armor pgp-decrypt pgp-encrypt $(CF_PGP_TESTS) \ + pgp-pubkey-decrypt pgp-pubkey-encrypt pgp-info + +EXTRA_CLEAN = gen-rtab + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pgcrypto +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +# Add libraries that pgcrypto depends (or might depend) on into the +# shared library link. (The order in which you list them here doesn't +# matter.) +SHLIB_LINK += $(filter -lcrypto -lz, $(LIBS)) +ifeq ($(PORTNAME), win32) +SHLIB_LINK += $(filter -leay32, $(LIBS)) +# those must be at the end +SHLIB_LINK += -lws2_32 +endif diff --git a/contrib/pgcrypto/crypt-blowfish.c b/contrib/pgcrypto/crypt-blowfish.c new file mode 100644 index 0000000..1264ecc --- /dev/null +++ b/contrib/pgcrypto/crypt-blowfish.c @@ -0,0 +1,756 @@ +/* + * contrib/pgcrypto/crypt-blowfish.c + * + * This code comes from John the Ripper password cracker, with reentrant + * and crypt(3) interfaces added, but optimizations specific to password + * cracking removed. + * + * Written by Solar Designer in 1998-2002 and + * placed in the public domain. + * + * There's absolutely no warranty. + * + * It is my intent that you should be able to use this on your system, + * as a part of a software package, or anywhere else to improve security, + * ensure compatibility, or for any other purpose. I would appreciate + * it if you give credit where it is due and keep your modifications in + * the public domain as well, but I don't require that in order to let + * you place this code and any modifications you make under a license + * of your choice. + * + * This implementation is compatible with OpenBSD bcrypt.c (version 2a) + * by Niels Provos , and uses some of his + * ideas. The password hashing algorithm was designed by David Mazieres + * . + * + * There's a paper on the algorithm that explains its design decisions: + * + * http://www.usenix.org/events/usenix99/provos.html + * + * Some of the tricks in BF_ROUND might be inspired by Eric Young's + * Blowfish library (I can't be sure if I would think of something if I + * hadn't seen his code). + */ + +#include "postgres.h" +#include "miscadmin.h" + +#include "px-crypt.h" +#include "px.h" + +#ifdef __i386__ +#define BF_ASM 0 /* 1 */ +#define BF_SCALE 1 +#elif defined(__x86_64__) || defined(__hppa__) +#define BF_ASM 0 +#define BF_SCALE 1 +#else +#define BF_ASM 0 +#define BF_SCALE 0 +#endif + +typedef unsigned int BF_word; +typedef signed int BF_word_signed; + +/* Number of Blowfish rounds, this is also hardcoded into a few places */ +#define BF_N 16 + +typedef BF_word BF_key[BF_N + 2]; + +typedef struct +{ + BF_word S[4][0x100]; + BF_key P; +} BF_ctx; + +/* + * Magic IV for 64 Blowfish encryptions that we do at the end. + * The string is "OrpheanBeholderScryDoubt" on big-endian. + */ +static BF_word BF_magic_w[6] = { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 +}; + +/* + * P-box and S-box tables initialized with digits of Pi. + */ +static BF_ctx BF_init_state = { + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }, { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } +}; + +static unsigned char BF_itoa64[64 + 1] = +"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static unsigned char BF_atoi64[0x60] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, + 64, 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, 64, 64, 64, 64, 64, + 64, 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, 64, 64, 64, 64, 64 +}; + +#define BF_safe_atoi64(dst, src) \ +do { \ + tmp = (unsigned char)(src); \ + if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ + tmp = BF_atoi64[tmp]; \ + if (tmp > 63) return -1; \ + (dst) = tmp; \ +} while (0) + +static int +BF_decode(BF_word *dst, const char *src, int size) +{ + unsigned char *dptr = (unsigned char *) dst; + unsigned char *end = dptr + size; + const unsigned char *sptr = (const unsigned char *) src; + unsigned int tmp, + c1, + c2, + c3, + c4; + + do + { + BF_safe_atoi64(c1, *sptr++); + BF_safe_atoi64(c2, *sptr++); + *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (dptr >= end) + break; + + BF_safe_atoi64(c3, *sptr++); + *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); + if (dptr >= end) + break; + + BF_safe_atoi64(c4, *sptr++); + *dptr++ = ((c3 & 0x03) << 6) | c4; + } while (dptr < end); + + return 0; +} + +static void +BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *) src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *) dst; + unsigned int c1, + c2; + + do + { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) + { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) + { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +static void +BF_swap(BF_word *x, int count) +{ + /* Swap on little-endian hardware, else do nothing */ +#ifndef WORDS_BIGENDIAN + BF_word tmp; + + do + { + tmp = *x; + tmp = (tmp << 16) | (tmp >> 16); + *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); + } while (--count); +#endif +} + +#if BF_SCALE +/* Architectures which can shift addresses left by 2 bits with no extra cost */ +#define BF_ROUND(L, R, N) \ + tmp1 = (L) & 0xFF; \ + tmp2 = (L) >> 8; \ + tmp2 &= 0xFF; \ + tmp3 = (L) >> 16; \ + tmp3 &= 0xFF; \ + tmp4 = (L) >> 24; \ + tmp1 = data.ctx.S[3][tmp1]; \ + tmp2 = data.ctx.S[2][tmp2]; \ + tmp3 = data.ctx.S[1][tmp3]; \ + tmp3 += data.ctx.S[0][tmp4]; \ + tmp3 ^= tmp2; \ + (R) ^= data.ctx.P[(N) + 1]; \ + tmp3 += tmp1; \ + (R) ^= tmp3 +#else +/* Architectures with no complicated addressing modes supported */ +#define BF_INDEX(S, i) \ + (*((BF_word *)(((unsigned char *)(S)) + (i)))) +#define BF_ROUND(L, R, N) \ + tmp1 = (L) & 0xFF; \ + tmp1 <<= 2; \ + tmp2 = (L) >> 6; \ + tmp2 &= 0x3FC; \ + tmp3 = (L) >> 14; \ + tmp3 &= 0x3FC; \ + tmp4 = (L) >> 22; \ + tmp4 &= 0x3FC; \ + tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ + tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ + tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ + tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ + tmp3 ^= tmp2; \ + (R) ^= data.ctx.P[(N) + 1]; \ + tmp3 += tmp1; \ + (R) ^= tmp3 +#endif + +/* + * Encrypt one block, BF_N is hardcoded here. + */ +#define BF_ENCRYPT \ + L ^= data.ctx.P[0]; \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + tmp4 = R; \ + R = L; \ + L = tmp4 ^ data.ctx.P[BF_N + 1] + +#if BF_ASM + +extern void _BF_body_r(BF_ctx *ctx); + +#define BF_body() \ + _BF_body_r(&data.ctx) +#else + +#define BF_body() \ +do { \ + L = R = 0; \ + ptr = data.ctx.P; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.P[BF_N + 2]); \ +\ + ptr = data.ctx.S[0]; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.S[3][0xFF]); \ +} while (0) +#endif + +static void +BF_set_key(const char *key, BF_key expanded, BF_key initial, + int sign_extension_bug) +{ + const char *ptr = key; + int i, + j; + BF_word tmp; + + for (i = 0; i < BF_N + 2; i++) + { + tmp = 0; + for (j = 0; j < 4; j++) + { + tmp <<= 8; + if (sign_extension_bug) + tmp |= (BF_word_signed) (signed char) *ptr; + else + tmp |= (unsigned char) *ptr; + + if (!*ptr) + ptr = key; + else + ptr++; + } + + expanded[i] = tmp; + initial[i] = BF_init_state.P[i] ^ tmp; + } +} + +char * +_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) +{ + struct + { + BF_ctx ctx; + BF_key expanded_key; + union + { + BF_word salt[4]; + BF_word output[6]; + } binary; + } data; + BF_word L, + R; + BF_word tmp1, + tmp2, + tmp3, + tmp4; + BF_word *ptr; + BF_word count; + int i; + + if (size < 7 + 22 + 31 + 1) + return NULL; + + /* + * Blowfish salt value must be formatted as follows: "$2a$" or "$2x$", a + * two digit cost parameter, "$", and 22 digits from the alphabet + * "./0-9A-Za-z". -- from the PHP crypt docs. Apparently we enforce a few + * more restrictions on the count in the salt as well. + */ + if (strlen(setting) < 29) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid salt"))); + + if (setting[0] != '$' || + setting[1] != '2' || + (setting[2] != 'a' && setting[2] != 'x') || + setting[3] != '$' || + setting[4] < '0' || setting[4] > '3' || + setting[5] < '0' || setting[5] > '9' || + (setting[4] == '3' && setting[5] > '1') || + setting[6] != '$') + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid salt"))); + } + + count = (BF_word) 1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); + if (count < 16 || BF_decode(data.binary.salt, &setting[7], 16)) + { + px_memset(data.binary.salt, 0, sizeof(data.binary.salt)); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid salt"))); + } + BF_swap(data.binary.salt, 4); + + BF_set_key(key, data.expanded_key, data.ctx.P, setting[2] == 'x'); + + memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); + + L = R = 0; + for (i = 0; i < BF_N + 2; i += 2) + { + L ^= data.binary.salt[i & 2]; + R ^= data.binary.salt[(i & 2) + 1]; + BF_ENCRYPT; + data.ctx.P[i] = L; + data.ctx.P[i + 1] = R; + } + + ptr = data.ctx.S[0]; + do + { + ptr += 4; + L ^= data.binary.salt[(BF_N + 2) & 3]; + R ^= data.binary.salt[(BF_N + 3) & 3]; + BF_ENCRYPT; + *(ptr - 4) = L; + *(ptr - 3) = R; + + L ^= data.binary.salt[(BF_N + 4) & 3]; + R ^= data.binary.salt[(BF_N + 5) & 3]; + BF_ENCRYPT; + *(ptr - 2) = L; + *(ptr - 1) = R; + } while (ptr < &data.ctx.S[3][0xFF]); + + do + { + CHECK_FOR_INTERRUPTS(); + + data.ctx.P[0] ^= data.expanded_key[0]; + data.ctx.P[1] ^= data.expanded_key[1]; + data.ctx.P[2] ^= data.expanded_key[2]; + data.ctx.P[3] ^= data.expanded_key[3]; + data.ctx.P[4] ^= data.expanded_key[4]; + data.ctx.P[5] ^= data.expanded_key[5]; + data.ctx.P[6] ^= data.expanded_key[6]; + data.ctx.P[7] ^= data.expanded_key[7]; + data.ctx.P[8] ^= data.expanded_key[8]; + data.ctx.P[9] ^= data.expanded_key[9]; + data.ctx.P[10] ^= data.expanded_key[10]; + data.ctx.P[11] ^= data.expanded_key[11]; + data.ctx.P[12] ^= data.expanded_key[12]; + data.ctx.P[13] ^= data.expanded_key[13]; + data.ctx.P[14] ^= data.expanded_key[14]; + data.ctx.P[15] ^= data.expanded_key[15]; + data.ctx.P[16] ^= data.expanded_key[16]; + data.ctx.P[17] ^= data.expanded_key[17]; + + BF_body(); + + tmp1 = data.binary.salt[0]; + tmp2 = data.binary.salt[1]; + tmp3 = data.binary.salt[2]; + tmp4 = data.binary.salt[3]; + data.ctx.P[0] ^= tmp1; + data.ctx.P[1] ^= tmp2; + data.ctx.P[2] ^= tmp3; + data.ctx.P[3] ^= tmp4; + data.ctx.P[4] ^= tmp1; + data.ctx.P[5] ^= tmp2; + data.ctx.P[6] ^= tmp3; + data.ctx.P[7] ^= tmp4; + data.ctx.P[8] ^= tmp1; + data.ctx.P[9] ^= tmp2; + data.ctx.P[10] ^= tmp3; + data.ctx.P[11] ^= tmp4; + data.ctx.P[12] ^= tmp1; + data.ctx.P[13] ^= tmp2; + data.ctx.P[14] ^= tmp3; + data.ctx.P[15] ^= tmp4; + data.ctx.P[16] ^= tmp1; + data.ctx.P[17] ^= tmp2; + + BF_body(); + } while (--count); + + for (i = 0; i < 6; i += 2) + { + L = BF_magic_w[i]; + R = BF_magic_w[i + 1]; + + count = 64; + do + { + BF_ENCRYPT; + } while (--count); + + data.binary.output[i] = L; + data.binary.output[i + 1] = R; + } + + memcpy(output, setting, 7 + 22 - 1); + output[7 + 22 - 1] = BF_itoa64[(int) + BF_atoi64[(int) setting[7 + 22 - 1] - 0x20] & 0x30]; + +/* This has to be bug-compatible with the original implementation, so + * only encode 23 of the 24 bytes. :-) */ + BF_swap(data.binary.output, 6); + BF_encode(&output[7 + 22], data.binary.output, 23); + output[7 + 22 + 31] = '\0'; + +/* Overwrite the most obvious sensitive data we have on the stack. Note + * that this does not guarantee there's no sensitive data left on the + * stack and/or in registers; I'm not aware of portable code that does. */ + px_memset(&data, 0, sizeof(data)); + + return output; +} diff --git a/contrib/pgcrypto/crypt-des.c b/contrib/pgcrypto/crypt-des.c new file mode 100644 index 0000000..98c30ea --- /dev/null +++ b/contrib/pgcrypto/crypt-des.c @@ -0,0 +1,791 @@ +/* + * FreeSec: libcrypt for NetBSD + * + * contrib/pgcrypto/crypt-des.c + * + * Copyright (c) 1994 David Burren + * All rights reserved. + * + * Adapted for FreeBSD-2.0 by Geoffrey M. Rehmet + * this file should now *only* export crypt(), in order to make + * binaries of libcrypt exportable from the USA + * + * Adapted for FreeBSD-4.0 by Mark R V Murray + * this file should now *only* export px_crypt_des(), in order to make + * a module that can be optionally included in libcrypt. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: src/secure/lib/libcrypt/crypt-des.c,v 1.12 1999/09/20 12:39:20 markm Exp $ + * + * This is an original implementation of the DES and the crypt(3) interfaces + * by David Burren . + * + * An excellent reference on the underlying algorithm (and related + * algorithms) is: + * + * B. Schneier, Applied Cryptography: protocols, algorithms, + * and source code in C, John Wiley & Sons, 1994. + * + * Note that in that book's description of DES the lookups for the initial, + * pbox, and final permutations are inverted (this has been brought to the + * attention of the author). A list of errata for this book has been + * posted to the sci.crypt newsgroup by the author and is available for FTP. + * + * ARCHITECTURE ASSUMPTIONS: + * It is assumed that the 8-byte arrays passed by reference can be + * addressed as arrays of uint32's (ie. the CPU is not picky about + * alignment). + */ + +#include "postgres.h" +#include "miscadmin.h" +#include "port/pg_bswap.h" + +#include "px-crypt.h" + +#define _PASSWORD_EFMT1 '_' + +static const char _crypt_a64[] = +"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static uint8 IP[64] = { + 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 +}; + +static uint8 inv_key_perm[64]; +static uint8 u_key_perm[56]; +static uint8 key_perm[56] = { + 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, + 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, + 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, + 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 +}; + +static uint8 key_shifts[16] = { + 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 +}; + +static uint8 inv_comp_perm[56]; +static uint8 comp_perm[48] = { + 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32 +}; + +/* + * No E box is used, as it's replaced by some ANDs, shifts, and ORs. + */ + +static uint8 u_sbox[8][64]; +static uint8 sbox[8][64] = { + { + 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, + 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, + 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, + 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 + }, + { + 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, + 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, + 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, + 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 + }, + { + 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, + 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, + 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, + 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 + }, + { + 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, + 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, + 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, + 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 + }, + { + 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, + 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, + 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, + 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 + }, + { + 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, + 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, + 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, + 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 + }, + { + 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, + 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, + 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, + 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 + }, + { + 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, + 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, + 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, + 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 + } +}; + +static uint8 un_pbox[32]; +static uint8 pbox[32] = { + 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, + 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 +}; + +static uint32 _crypt_bits32[32] = +{ + 0x80000000, 0x40000000, 0x20000000, 0x10000000, + 0x08000000, 0x04000000, 0x02000000, 0x01000000, + 0x00800000, 0x00400000, 0x00200000, 0x00100000, + 0x00080000, 0x00040000, 0x00020000, 0x00010000, + 0x00008000, 0x00004000, 0x00002000, 0x00001000, + 0x00000800, 0x00000400, 0x00000200, 0x00000100, + 0x00000080, 0x00000040, 0x00000020, 0x00000010, + 0x00000008, 0x00000004, 0x00000002, 0x00000001 +}; + +static uint8 _crypt_bits8[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; + +static uint32 saltbits; +static long old_salt; +static uint32 *bits28, + *bits24; +static uint8 init_perm[64], + final_perm[64]; +static uint32 en_keysl[16], + en_keysr[16]; +static uint32 de_keysl[16], + de_keysr[16]; +static int des_initialised = 0; +static uint8 m_sbox[4][4096]; +static uint32 psbox[4][256]; +static uint32 ip_maskl[8][256], + ip_maskr[8][256]; +static uint32 fp_maskl[8][256], + fp_maskr[8][256]; +static uint32 key_perm_maskl[8][128], + key_perm_maskr[8][128]; +static uint32 comp_maskl[8][128], + comp_maskr[8][128]; +static uint32 old_rawkey0, + old_rawkey1; + +static inline int +ascii_to_bin(char ch) +{ + if (ch > 'z') + return 0; + if (ch >= 'a') + return (ch - 'a' + 38); + if (ch > 'Z') + return 0; + if (ch >= 'A') + return (ch - 'A' + 12); + if (ch > '9') + return 0; + if (ch >= '.') + return (ch - '.'); + return 0; +} + +static void +des_init(void) +{ + int i, + j, + b, + k, + inbit, + obit; + uint32 *p, + *il, + *ir, + *fl, + *fr; + + old_rawkey0 = old_rawkey1 = 0L; + saltbits = 0L; + old_salt = 0L; + bits24 = (bits28 = _crypt_bits32 + 4) + 4; + + /* + * Invert the S-boxes, reordering the input bits. + */ + for (i = 0; i < 8; i++) + for (j = 0; j < 64; j++) + { + b = (j & 0x20) | ((j & 1) << 4) | ((j >> 1) & 0xf); + u_sbox[i][j] = sbox[i][b]; + } + + /* + * Convert the inverted S-boxes into 4 arrays of 8 bits. Each will handle + * 12 bits of the S-box input. + */ + for (b = 0; b < 4; b++) + for (i = 0; i < 64; i++) + for (j = 0; j < 64; j++) + m_sbox[b][(i << 6) | j] = + (u_sbox[(b << 1)][i] << 4) | + u_sbox[(b << 1) + 1][j]; + + /* + * Set up the initial & final permutations into a useful form, and + * initialise the inverted key permutation. + */ + for (i = 0; i < 64; i++) + { + init_perm[final_perm[i] = IP[i] - 1] = i; + inv_key_perm[i] = 255; + } + + /* + * Invert the key permutation and initialise the inverted key compression + * permutation. + */ + for (i = 0; i < 56; i++) + { + u_key_perm[i] = key_perm[i] - 1; + inv_key_perm[key_perm[i] - 1] = i; + inv_comp_perm[i] = 255; + } + + /* + * Invert the key compression permutation. + */ + for (i = 0; i < 48; i++) + inv_comp_perm[comp_perm[i] - 1] = i; + + /* + * Set up the OR-mask arrays for the initial and final permutations, and + * for the key initial and compression permutations. + */ + for (k = 0; k < 8; k++) + { + for (i = 0; i < 256; i++) + { + *(il = &ip_maskl[k][i]) = 0L; + *(ir = &ip_maskr[k][i]) = 0L; + *(fl = &fp_maskl[k][i]) = 0L; + *(fr = &fp_maskr[k][i]) = 0L; + for (j = 0; j < 8; j++) + { + inbit = 8 * k + j; + if (i & _crypt_bits8[j]) + { + if ((obit = init_perm[inbit]) < 32) + *il |= _crypt_bits32[obit]; + else + *ir |= _crypt_bits32[obit - 32]; + if ((obit = final_perm[inbit]) < 32) + *fl |= _crypt_bits32[obit]; + else + *fr |= _crypt_bits32[obit - 32]; + } + } + } + for (i = 0; i < 128; i++) + { + *(il = &key_perm_maskl[k][i]) = 0L; + *(ir = &key_perm_maskr[k][i]) = 0L; + for (j = 0; j < 7; j++) + { + inbit = 8 * k + j; + if (i & _crypt_bits8[j + 1]) + { + if ((obit = inv_key_perm[inbit]) == 255) + continue; + if (obit < 28) + *il |= bits28[obit]; + else + *ir |= bits28[obit - 28]; + } + } + *(il = &comp_maskl[k][i]) = 0L; + *(ir = &comp_maskr[k][i]) = 0L; + for (j = 0; j < 7; j++) + { + inbit = 7 * k + j; + if (i & _crypt_bits8[j + 1]) + { + if ((obit = inv_comp_perm[inbit]) == 255) + continue; + if (obit < 24) + *il |= bits24[obit]; + else + *ir |= bits24[obit - 24]; + } + } + } + } + + /* + * Invert the P-box permutation, and convert into OR-masks for handling + * the output of the S-box arrays setup above. + */ + for (i = 0; i < 32; i++) + un_pbox[pbox[i] - 1] = i; + + for (b = 0; b < 4; b++) + for (i = 0; i < 256; i++) + { + *(p = &psbox[b][i]) = 0L; + for (j = 0; j < 8; j++) + { + if (i & _crypt_bits8[j]) + *p |= _crypt_bits32[un_pbox[8 * b + j]]; + } + } + + des_initialised = 1; +} + +static void +setup_salt(long salt) +{ + uint32 obit, + saltbit; + int i; + + if (salt == old_salt) + return; + old_salt = salt; + + saltbits = 0L; + saltbit = 1; + obit = 0x800000; + for (i = 0; i < 24; i++) + { + if (salt & saltbit) + saltbits |= obit; + saltbit <<= 1; + obit >>= 1; + } +} + +static int +des_setkey(const char *key) +{ + uint32 k0, + k1, + rawkey0, + rawkey1; + int shifts, + round; + + if (!des_initialised) + des_init(); + + rawkey0 = pg_ntoh32(*(const uint32 *) key); + rawkey1 = pg_ntoh32(*(const uint32 *) (key + 4)); + + if ((rawkey0 | rawkey1) + && rawkey0 == old_rawkey0 + && rawkey1 == old_rawkey1) + { + /* + * Already setup for this key. This optimization fails on a zero key + * (which is weak and has bad parity anyway) in order to simplify the + * starting conditions. + */ + return 0; + } + old_rawkey0 = rawkey0; + old_rawkey1 = rawkey1; + + /* + * Do key permutation and split into two 28-bit subkeys. + */ + k0 = key_perm_maskl[0][rawkey0 >> 25] + | key_perm_maskl[1][(rawkey0 >> 17) & 0x7f] + | key_perm_maskl[2][(rawkey0 >> 9) & 0x7f] + | key_perm_maskl[3][(rawkey0 >> 1) & 0x7f] + | key_perm_maskl[4][rawkey1 >> 25] + | key_perm_maskl[5][(rawkey1 >> 17) & 0x7f] + | key_perm_maskl[6][(rawkey1 >> 9) & 0x7f] + | key_perm_maskl[7][(rawkey1 >> 1) & 0x7f]; + k1 = key_perm_maskr[0][rawkey0 >> 25] + | key_perm_maskr[1][(rawkey0 >> 17) & 0x7f] + | key_perm_maskr[2][(rawkey0 >> 9) & 0x7f] + | key_perm_maskr[3][(rawkey0 >> 1) & 0x7f] + | key_perm_maskr[4][rawkey1 >> 25] + | key_perm_maskr[5][(rawkey1 >> 17) & 0x7f] + | key_perm_maskr[6][(rawkey1 >> 9) & 0x7f] + | key_perm_maskr[7][(rawkey1 >> 1) & 0x7f]; + + /* + * Rotate subkeys and do compression permutation. + */ + shifts = 0; + for (round = 0; round < 16; round++) + { + uint32 t0, + t1; + + shifts += key_shifts[round]; + + t0 = (k0 << shifts) | (k0 >> (28 - shifts)); + t1 = (k1 << shifts) | (k1 >> (28 - shifts)); + + de_keysl[15 - round] = + en_keysl[round] = comp_maskl[0][(t0 >> 21) & 0x7f] + | comp_maskl[1][(t0 >> 14) & 0x7f] + | comp_maskl[2][(t0 >> 7) & 0x7f] + | comp_maskl[3][t0 & 0x7f] + | comp_maskl[4][(t1 >> 21) & 0x7f] + | comp_maskl[5][(t1 >> 14) & 0x7f] + | comp_maskl[6][(t1 >> 7) & 0x7f] + | comp_maskl[7][t1 & 0x7f]; + + de_keysr[15 - round] = + en_keysr[round] = comp_maskr[0][(t0 >> 21) & 0x7f] + | comp_maskr[1][(t0 >> 14) & 0x7f] + | comp_maskr[2][(t0 >> 7) & 0x7f] + | comp_maskr[3][t0 & 0x7f] + | comp_maskr[4][(t1 >> 21) & 0x7f] + | comp_maskr[5][(t1 >> 14) & 0x7f] + | comp_maskr[6][(t1 >> 7) & 0x7f] + | comp_maskr[7][t1 & 0x7f]; + } + return 0; +} + +static int +do_des(uint32 l_in, uint32 r_in, uint32 *l_out, uint32 *r_out, int count) +{ + /* + * l_in, r_in, l_out, and r_out are in pseudo-"big-endian" format. + */ + uint32 l, + r, + *kl, + *kr, + *kl1, + *kr1; + uint32 f, + r48l, + r48r; + int round; + + if (count == 0) + return 1; + else if (count > 0) + { + /* + * Encrypting + */ + kl1 = en_keysl; + kr1 = en_keysr; + } + else + { + /* + * Decrypting + */ + count = -count; + kl1 = de_keysl; + kr1 = de_keysr; + } + + /* + * Do initial permutation (IP). + */ + l = ip_maskl[0][l_in >> 24] + | ip_maskl[1][(l_in >> 16) & 0xff] + | ip_maskl[2][(l_in >> 8) & 0xff] + | ip_maskl[3][l_in & 0xff] + | ip_maskl[4][r_in >> 24] + | ip_maskl[5][(r_in >> 16) & 0xff] + | ip_maskl[6][(r_in >> 8) & 0xff] + | ip_maskl[7][r_in & 0xff]; + r = ip_maskr[0][l_in >> 24] + | ip_maskr[1][(l_in >> 16) & 0xff] + | ip_maskr[2][(l_in >> 8) & 0xff] + | ip_maskr[3][l_in & 0xff] + | ip_maskr[4][r_in >> 24] + | ip_maskr[5][(r_in >> 16) & 0xff] + | ip_maskr[6][(r_in >> 8) & 0xff] + | ip_maskr[7][r_in & 0xff]; + + while (count--) + { + CHECK_FOR_INTERRUPTS(); + + /* + * Do each round. + */ + kl = kl1; + kr = kr1; + round = 16; + while (round--) + { + /* + * Expand R to 48 bits (simulate the E-box). + */ + r48l = ((r & 0x00000001) << 23) + | ((r & 0xf8000000) >> 9) + | ((r & 0x1f800000) >> 11) + | ((r & 0x01f80000) >> 13) + | ((r & 0x001f8000) >> 15); + + r48r = ((r & 0x0001f800) << 7) + | ((r & 0x00001f80) << 5) + | ((r & 0x000001f8) << 3) + | ((r & 0x0000001f) << 1) + | ((r & 0x80000000) >> 31); + + /* + * Do salting for crypt() and friends, and XOR with the permuted + * key. + */ + f = (r48l ^ r48r) & saltbits; + r48l ^= f ^ *kl++; + r48r ^= f ^ *kr++; + + /* + * Do sbox lookups (which shrink it back to 32 bits) and do the + * pbox permutation at the same time. + */ + f = psbox[0][m_sbox[0][r48l >> 12]] + | psbox[1][m_sbox[1][r48l & 0xfff]] + | psbox[2][m_sbox[2][r48r >> 12]] + | psbox[3][m_sbox[3][r48r & 0xfff]]; + + /* + * Now that we've permuted things, complete f(). + */ + f ^= l; + l = r; + r = f; + } + r = l; + l = f; + } + + /* + * Do final permutation (inverse of IP). + */ + *l_out = fp_maskl[0][l >> 24] + | fp_maskl[1][(l >> 16) & 0xff] + | fp_maskl[2][(l >> 8) & 0xff] + | fp_maskl[3][l & 0xff] + | fp_maskl[4][r >> 24] + | fp_maskl[5][(r >> 16) & 0xff] + | fp_maskl[6][(r >> 8) & 0xff] + | fp_maskl[7][r & 0xff]; + *r_out = fp_maskr[0][l >> 24] + | fp_maskr[1][(l >> 16) & 0xff] + | fp_maskr[2][(l >> 8) & 0xff] + | fp_maskr[3][l & 0xff] + | fp_maskr[4][r >> 24] + | fp_maskr[5][(r >> 16) & 0xff] + | fp_maskr[6][(r >> 8) & 0xff] + | fp_maskr[7][r & 0xff]; + return 0; +} + +static int +des_cipher(const char *in, char *out, long salt, int count) +{ + uint32 buffer[2]; + uint32 l_out, + r_out, + rawl, + rawr; + int retval; + + if (!des_initialised) + des_init(); + + setup_salt(salt); + + /* copy data to avoid assuming input is word-aligned */ + memcpy(buffer, in, sizeof(buffer)); + + rawl = pg_ntoh32(buffer[0]); + rawr = pg_ntoh32(buffer[1]); + + retval = do_des(rawl, rawr, &l_out, &r_out, count); + if (retval) + return retval; + + buffer[0] = pg_hton32(l_out); + buffer[1] = pg_hton32(r_out); + + /* copy data to avoid assuming output is word-aligned */ + memcpy(out, buffer, sizeof(buffer)); + + return retval; +} + +char * +px_crypt_des(const char *key, const char *setting) +{ + int i; + uint32 count, + salt, + l, + r0, + r1, + keybuf[2]; + char *p; + uint8 *q; + static char output[21]; + + if (!des_initialised) + des_init(); + + + /* + * Copy the key, shifting each character up by one bit and padding with + * zeros. + */ + q = (uint8 *) keybuf; + while (q - (uint8 *) keybuf - 8) + { + *q++ = *key << 1; + if (*key != '\0') + key++; + } + if (des_setkey((char *) keybuf)) + return NULL; + +#ifndef DISABLE_XDES + if (*setting == _PASSWORD_EFMT1) + { + /* + * "new"-style: setting must be a 9-character (underscore, then 4 + * bytes of count, then 4 bytes of salt) string. See CRYPT(3) under + * the "Extended crypt" heading for further details. + * + * Unlimited characters of the input key are used. This is known as + * the "Extended crypt" DES method. + * + */ + if (strlen(setting) < 9) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid salt"))); + + for (i = 1, count = 0L; i < 5; i++) + count |= ascii_to_bin(setting[i]) << (i - 1) * 6; + + for (i = 5, salt = 0L; i < 9; i++) + salt |= ascii_to_bin(setting[i]) << (i - 5) * 6; + + while (*key) + { + /* + * Encrypt the key with itself. + */ + if (des_cipher((char *) keybuf, (char *) keybuf, 0L, 1)) + return NULL; + + /* + * And XOR with the next 8 characters of the key. + */ + q = (uint8 *) keybuf; + while (q - (uint8 *) keybuf - 8 && *key) + *q++ ^= *key++ << 1; + + if (des_setkey((char *) keybuf)) + return NULL; + } + strlcpy(output, setting, 10); + + /* + * Double check that we weren't given a short setting. If we were, the + * above code will probably have created weird values for count and + * salt, but we don't really care. Just make sure the output string + * doesn't have an extra NUL in it. + */ + p = output + strlen(output); + } + else +#endif /* !DISABLE_XDES */ + { + /* + * "old"-style: setting - 2 bytes of salt key - only up to the first 8 + * characters of the input key are used. + */ + count = 25; + + if (strlen(setting) < 2) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid salt"))); + + salt = (ascii_to_bin(setting[1]) << 6) + | ascii_to_bin(setting[0]); + + output[0] = setting[0]; + + /* + * If the encrypted password that the salt was extracted from is only + * 1 character long, the salt will be corrupted. We need to ensure + * that the output string doesn't have an extra NUL in it! + */ + output[1] = setting[1] ? setting[1] : output[0]; + + p = output + 2; + } + setup_salt(salt); + + /* + * Do it. + */ + if (do_des(0L, 0L, &r0, &r1, count)) + return NULL; + + /* + * Now encode the result... + */ + l = (r0 >> 8); + *p++ = _crypt_a64[(l >> 18) & 0x3f]; + *p++ = _crypt_a64[(l >> 12) & 0x3f]; + *p++ = _crypt_a64[(l >> 6) & 0x3f]; + *p++ = _crypt_a64[l & 0x3f]; + + l = (r0 << 16) | ((r1 >> 16) & 0xffff); + *p++ = _crypt_a64[(l >> 18) & 0x3f]; + *p++ = _crypt_a64[(l >> 12) & 0x3f]; + *p++ = _crypt_a64[(l >> 6) & 0x3f]; + *p++ = _crypt_a64[l & 0x3f]; + + l = r1 << 2; + *p++ = _crypt_a64[(l >> 12) & 0x3f]; + *p++ = _crypt_a64[(l >> 6) & 0x3f]; + *p++ = _crypt_a64[l & 0x3f]; + *p = 0; + + return output; +} diff --git a/contrib/pgcrypto/crypt-gensalt.c b/contrib/pgcrypto/crypt-gensalt.c new file mode 100644 index 0000000..740f361 --- /dev/null +++ b/contrib/pgcrypto/crypt-gensalt.c @@ -0,0 +1,187 @@ +/* + * Written by Solar Designer and placed in the public domain. + * See crypt_blowfish.c for more information. + * + * contrib/pgcrypto/crypt-gensalt.c + * + * This file contains salt generation functions for the traditional and + * other common crypt(3) algorithms, except for bcrypt which is defined + * entirely in crypt_blowfish.c. + * + * Put bcrypt generator also here as crypt-blowfish.c + * may not be compiled always. -- marko + */ + +#include "postgres.h" + +#include "px-crypt.h" + +typedef unsigned int BF_word; + +static unsigned char _crypt_itoa64[64 + 1] = +"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +char * +_crypt_gensalt_traditional_rn(unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 2 || output_size < 2 + 1 || (count && count != 25)) + { + if (output_size > 0) + output[0] = '\0'; + return NULL; + } + + output[0] = _crypt_itoa64[(unsigned int) input[0] & 0x3f]; + output[1] = _crypt_itoa64[(unsigned int) input[1] & 0x3f]; + output[2] = '\0'; + + return output; +} + +char * +_crypt_gensalt_extended_rn(unsigned long count, + const char *input, int size, char *output, int output_size) +{ + unsigned long value; + +/* Even iteration counts make it easier to detect weak DES keys from a look + * at the hash, so they should be avoided */ + if (size < 3 || output_size < 1 + 4 + 4 + 1 || + (count && (count > 0xffffff || !(count & 1)))) + { + if (output_size > 0) + output[0] = '\0'; + return NULL; + } + + if (!count) + count = 725; + + output[0] = '_'; + output[1] = _crypt_itoa64[count & 0x3f]; + output[2] = _crypt_itoa64[(count >> 6) & 0x3f]; + output[3] = _crypt_itoa64[(count >> 12) & 0x3f]; + output[4] = _crypt_itoa64[(count >> 18) & 0x3f]; + value = (unsigned long) (unsigned char) input[0] | + ((unsigned long) (unsigned char) input[1] << 8) | + ((unsigned long) (unsigned char) input[2] << 16); + output[5] = _crypt_itoa64[value & 0x3f]; + output[6] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[7] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[8] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[9] = '\0'; + + return output; +} + +char * +_crypt_gensalt_md5_rn(unsigned long count, + const char *input, int size, char *output, int output_size) +{ + unsigned long value; + + if (size < 3 || output_size < 3 + 4 + 1 || (count && count != 1000)) + { + if (output_size > 0) + output[0] = '\0'; + return NULL; + } + + output[0] = '$'; + output[1] = '1'; + output[2] = '$'; + value = (unsigned long) (unsigned char) input[0] | + ((unsigned long) (unsigned char) input[1] << 8) | + ((unsigned long) (unsigned char) input[2] << 16); + output[3] = _crypt_itoa64[value & 0x3f]; + output[4] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[5] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[6] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[7] = '\0'; + + if (size >= 6 && output_size >= 3 + 4 + 4 + 1) + { + value = (unsigned long) (unsigned char) input[3] | + ((unsigned long) (unsigned char) input[4] << 8) | + ((unsigned long) (unsigned char) input[5] << 16); + output[7] = _crypt_itoa64[value & 0x3f]; + output[8] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[9] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[10] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[11] = '\0'; + } + + return output; +} + + + +static unsigned char BF_itoa64[64 + 1] = +"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static void +BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *) src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *) dst; + unsigned int c1, + c2; + + do + { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) + { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) + { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +char * +_crypt_gensalt_blowfish_rn(unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 16 || output_size < 7 + 22 + 1 || + (count && (count < 4 || count > 31))) + { + if (output_size > 0) + output[0] = '\0'; + return NULL; + } + + if (!count) + count = 5; + + output[0] = '$'; + output[1] = '2'; + output[2] = 'a'; + output[3] = '$'; + output[4] = '0' + count / 10; + output[5] = '0' + count % 10; + output[6] = '$'; + + BF_encode(&output[7], (const BF_word *) input, 16); + output[7 + 22] = '\0'; + + return output; +} diff --git a/contrib/pgcrypto/crypt-md5.c b/contrib/pgcrypto/crypt-md5.c new file mode 100644 index 0000000..d38721a --- /dev/null +++ b/contrib/pgcrypto/crypt-md5.c @@ -0,0 +1,169 @@ +/* + * File imported from FreeBSD, original by Poul-Henning Kamp. + * + * $FreeBSD: src/lib/libcrypt/crypt-md5.c,v 1.5 1999/12/17 20:21:45 peter Exp $ + * + * contrib/pgcrypto/crypt-md5.c + */ + +#include "postgres.h" + +#include "px-crypt.h" +#include "px.h" + +#define MD5_SIZE 16 + +static const char _crypt_a64[] = +"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void +_crypt_to64(char *s, unsigned long v, int n) +{ + while (--n >= 0) + { + *s++ = _crypt_a64[v & 0x3f]; + v >>= 6; + } +} + +/* + * UNIX password + */ + +char * +px_crypt_md5(const char *pw, const char *salt, char *passwd, unsigned dstlen) +{ + static char *magic = "$1$"; /* This string is magic for this algorithm. + * Having it this way, we can get better later + * on */ + static char *p; + static const char *sp, + *ep; + unsigned char final[MD5_SIZE]; + int sl, + pl, + i; + PX_MD *ctx, + *ctx1; + int err; + unsigned long l; + + if (!passwd || dstlen < 120) + return NULL; + + /* Refine the Salt first */ + sp = salt; + + /* If it starts with the magic string, then skip that */ + if (strncmp(sp, magic, strlen(magic)) == 0) + sp += strlen(magic); + + /* It stops at the first '$', max 8 chars */ + for (ep = sp; *ep && *ep != '$' && ep < (sp + 8); ep++) + continue; + + /* get the length of the true salt */ + sl = ep - sp; + + /* we need two PX_MD objects */ + err = px_find_digest("md5", &ctx); + if (err) + return NULL; + err = px_find_digest("md5", &ctx1); + if (err) + { + /* this path is possible under low-memory circumstances */ + px_md_free(ctx); + return NULL; + } + + /* The password first, since that is what is most unknown */ + px_md_update(ctx, (const uint8 *) pw, strlen(pw)); + + /* Then our magic string */ + px_md_update(ctx, (uint8 *) magic, strlen(magic)); + + /* Then the raw salt */ + px_md_update(ctx, (const uint8 *) sp, sl); + + /* Then just as many characters of the MD5(pw,salt,pw) */ + px_md_update(ctx1, (const uint8 *) pw, strlen(pw)); + px_md_update(ctx1, (const uint8 *) sp, sl); + px_md_update(ctx1, (const uint8 *) pw, strlen(pw)); + px_md_finish(ctx1, final); + for (pl = strlen(pw); pl > 0; pl -= MD5_SIZE) + px_md_update(ctx, final, pl > MD5_SIZE ? MD5_SIZE : pl); + + /* Don't leave anything around in vm they could use. */ + px_memset(final, 0, sizeof final); + + /* Then something really weird... */ + for (i = strlen(pw); i; i >>= 1) + if (i & 1) + px_md_update(ctx, final, 1); + else + px_md_update(ctx, (const uint8 *) pw, 1); + + /* Now make the output string */ + strcpy(passwd, magic); + strncat(passwd, sp, sl); + strcat(passwd, "$"); + + px_md_finish(ctx, final); + + /* + * and now, just to make sure things don't run too fast On a 60 Mhz + * Pentium this takes 34 msec, so you would need 30 seconds to build a + * 1000 entry dictionary... + */ + for (i = 0; i < 1000; i++) + { + px_md_reset(ctx1); + if (i & 1) + px_md_update(ctx1, (const uint8 *) pw, strlen(pw)); + else + px_md_update(ctx1, final, MD5_SIZE); + + if (i % 3) + px_md_update(ctx1, (const uint8 *) sp, sl); + + if (i % 7) + px_md_update(ctx1, (const uint8 *) pw, strlen(pw)); + + if (i & 1) + px_md_update(ctx1, final, MD5_SIZE); + else + px_md_update(ctx1, (const uint8 *) pw, strlen(pw)); + px_md_finish(ctx1, final); + } + + p = passwd + strlen(passwd); + + l = (final[0] << 16) | (final[6] << 8) | final[12]; + _crypt_to64(p, l, 4); + p += 4; + l = (final[1] << 16) | (final[7] << 8) | final[13]; + _crypt_to64(p, l, 4); + p += 4; + l = (final[2] << 16) | (final[8] << 8) | final[14]; + _crypt_to64(p, l, 4); + p += 4; + l = (final[3] << 16) | (final[9] << 8) | final[15]; + _crypt_to64(p, l, 4); + p += 4; + l = (final[4] << 16) | (final[10] << 8) | final[5]; + _crypt_to64(p, l, 4); + p += 4; + l = final[11]; + _crypt_to64(p, l, 2); + p += 2; + *p = '\0'; + + /* Don't leave anything around in vm they could use. */ + px_memset(final, 0, sizeof final); + + px_md_free(ctx1); + px_md_free(ctx); + + return passwd; +} diff --git a/contrib/pgcrypto/expected/3des.out b/contrib/pgcrypto/expected/3des.out new file mode 100644 index 0000000..ee15489 --- /dev/null +++ b/contrib/pgcrypto/expected/3des.out @@ -0,0 +1,65 @@ +-- +-- 3DES cipher +-- +-- test vector from somewhere +SELECT encrypt('\x8000000000000000', + '\x010101010101010101010101010101010101010101010101', + '3des-ecb/pad:none'); + encrypt +-------------------- + \x95f8a5e5dd31d900 +(1 row) + +select encrypt('', 'foo', '3des'); + encrypt +-------------------- + \x752111e37a2d7ac3 +(1 row) + +-- 10 bytes key +select encrypt('foo', '0123456789', '3des'); + encrypt +-------------------- + \xd2fb8baa1717cb02 +(1 row) + +-- 22 bytes key +select encrypt('foo', '0123456789012345678901', '3des'); + encrypt +-------------------- + \xa44360e699269817 +(1 row) + +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', '3des'), '0123456', '3des'), 'escape'); + encode +-------- + foo +(1 row) + +-- iv +select encrypt_iv('foo', '0123456', 'abcd', '3des'); + encrypt_iv +-------------------- + \x50735067b073bb93 +(1 row) + +select encode(decrypt_iv('\x50735067b073bb93', '0123456', 'abcd', '3des'), 'escape'); + encode +-------- + foo +(1 row) + +-- long message +select encrypt('Lets try a longer message.', '0123456789012345678901', '3des'); + encrypt +-------------------------------------------------------------------- + \xb71e3422269d0ded19468f33d65cd663c28e0871984792a7b3ba0ddcecec8d2c +(1 row) + +select encode(decrypt(encrypt('Lets try a longer message.', '0123456789012345678901', '3des'), '0123456789012345678901', '3des'), 'escape'); + encode +---------------------------- + Lets try a longer message. +(1 row) + diff --git a/contrib/pgcrypto/expected/blowfish.out b/contrib/pgcrypto/expected/blowfish.out new file mode 100644 index 0000000..f0346a7 --- /dev/null +++ b/contrib/pgcrypto/expected/blowfish.out @@ -0,0 +1,142 @@ +-- +-- Blowfish cipher +-- +-- some standard Blowfish testvalues +SELECT encrypt('\x0000000000000000', '\x0000000000000000', 'bf-ecb/pad:none'); + encrypt +-------------------- + \x4ef997456198dd78 +(1 row) + +SELECT encrypt('\xffffffffffffffff', '\xffffffffffffffff', 'bf-ecb/pad:none'); + encrypt +-------------------- + \x51866fd5b85ecb8a +(1 row) + +SELECT encrypt('\x1000000000000001', '\x3000000000000000', 'bf-ecb/pad:none'); + encrypt +-------------------- + \x7d856f9a613063f2 +(1 row) + +SELECT encrypt('\x1111111111111111', '\x1111111111111111', 'bf-ecb/pad:none'); + encrypt +-------------------- + \x2466dd878b963c9d +(1 row) + +SELECT encrypt('\x0123456789abcdef', '\xfedcba9876543210', 'bf-ecb/pad:none'); + encrypt +-------------------- + \x0aceab0fc6a0a28d +(1 row) + +SELECT encrypt('\x01a1d6d039776742', '\xfedcba9876543210', 'bf-ecb/pad:none'); + encrypt +-------------------- + \x3273b8badc9e9e15 +(1 row) + +SELECT encrypt('\xffffffffffffffff', '\x0000000000000000', 'bf-ecb/pad:none'); + encrypt +-------------------- + \x014933e0cdaff6e4 +(1 row) + +-- setkey +SELECT encrypt('\xfedcba9876543210', '\xf0e1d2c3b4a5968778695a4b3c2d1e0f', 'bf-ecb/pad:none'); + encrypt +-------------------- + \x93142887ee3be15c +(1 row) + +-- with padding +SELECT encrypt('\x01234567890123456789', '\x33443344334433443344334433443344', 'bf-ecb'); + encrypt +------------------------------------ + \x0d04a43a20456dee5ede6ed9e4dcaaa6 +(1 row) + +-- cbc +-- 28 bytes key +SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5', + '\x37363534333231204e6f77206973207468652074696d6520666f7220', + 'bf-cbc'); + encrypt +-------------------------------------------------------------------- + \x4f2beb748c4f689ec755edb9dc252a41b93a3786850b4c75d6a702b6a8e48825 +(1 row) + +-- 29 bytes key +SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5ff92cc', + '\x37363534333231204e6f77206973207468652074696d6520666f722000', + 'bf-cbc'); + encrypt +------------------------------------------------------------------------------------ + \x3ea6357a0ee7fad6d0c4b63464f2aafa40c2e91b4b7e1bba8114932fd92b5c8f111e7e50e7b2e541 +(1 row) + +-- blowfish-448 +SELECT encrypt('\xfedcba9876543210', + '\xf0e1d2c3b4a5968778695a4b3c2d1e0f001122334455667704689104c2fd3b2f584023641aba61761f1f1f1f0e0e0e0effffffffffffffff', + 'bf-ecb/pad:none'); + encrypt +-------------------- + \xc04504012e4e1f53 +(1 row) + +-- empty data +select encrypt('', 'foo', 'bf'); + encrypt +-------------------- + \x1871949bb2311c8e +(1 row) + +-- 10 bytes key +select encrypt('foo', '0123456789', 'bf'); + encrypt +-------------------- + \x42f58af3b2c03f46 +(1 row) + +-- 22 bytes key +select encrypt('foo', '0123456789012345678901', 'bf'); + encrypt +-------------------- + \x86ab6f0bc72b5f22 +(1 row) + +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'bf'), '0123456', 'bf'), 'escape'); + encode +-------- + foo +(1 row) + +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'bf'); + encrypt_iv +-------------------- + \x95c7e89322525d59 +(1 row) + +select encode(decrypt_iv('\x95c7e89322525d59', '0123456', 'abcd', 'bf'), 'escape'); + encode +-------- + foo +(1 row) + +-- long message +select encrypt('Lets try a longer message.', '0123456789', 'bf'); + encrypt +-------------------------------------------------------------------- + \xa76059f7a1b627b5b84080d9beb337714c7a7f8b70300023e5feb6dfa6813536 +(1 row) + +select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'bf'), '0123456789', 'bf'), 'escape'); + encode +---------------------------- + Lets try a longer message. +(1 row) + diff --git a/contrib/pgcrypto/expected/blowfish_1.out b/contrib/pgcrypto/expected/blowfish_1.out new file mode 100644 index 0000000..e52abf1 --- /dev/null +++ b/contrib/pgcrypto/expected/blowfish_1.out @@ -0,0 +1,62 @@ +-- +-- Blowfish cipher +-- +-- some standard Blowfish testvalues +SELECT encrypt('\x0000000000000000', '\x0000000000000000', 'bf-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +SELECT encrypt('\xffffffffffffffff', '\xffffffffffffffff', 'bf-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +SELECT encrypt('\x1000000000000001', '\x3000000000000000', 'bf-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +SELECT encrypt('\x1111111111111111', '\x1111111111111111', 'bf-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +SELECT encrypt('\x0123456789abcdef', '\xfedcba9876543210', 'bf-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +SELECT encrypt('\x01a1d6d039776742', '\xfedcba9876543210', 'bf-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +SELECT encrypt('\xffffffffffffffff', '\x0000000000000000', 'bf-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +-- setkey +SELECT encrypt('\xfedcba9876543210', '\xf0e1d2c3b4a5968778695a4b3c2d1e0f', 'bf-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +-- with padding +SELECT encrypt('\x01234567890123456789', '\x33443344334433443344334433443344', 'bf-ecb'); +ERROR: encrypt error: Cipher cannot be initialized +-- cbc +-- 28 bytes key +SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5', + '\x37363534333231204e6f77206973207468652074696d6520666f7220', + 'bf-cbc'); +ERROR: encrypt error: Key was too big +-- 29 bytes key +SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5ff92cc', + '\x37363534333231204e6f77206973207468652074696d6520666f722000', + 'bf-cbc'); +ERROR: encrypt error: Key was too big +-- blowfish-448 +SELECT encrypt('\xfedcba9876543210', + '\xf0e1d2c3b4a5968778695a4b3c2d1e0f001122334455667704689104c2fd3b2f584023641aba61761f1f1f1f0e0e0e0effffffffffffffff', + 'bf-ecb/pad:none'); +ERROR: encrypt error: Key was too big +-- empty data +select encrypt('', 'foo', 'bf'); +ERROR: encrypt error: Cipher cannot be initialized +-- 10 bytes key +select encrypt('foo', '0123456789', 'bf'); +ERROR: encrypt error: Cipher cannot be initialized +-- 22 bytes key +select encrypt('foo', '0123456789012345678901', 'bf'); +ERROR: encrypt error: Key was too big +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'bf'), '0123456', 'bf'), 'escape'); +ERROR: encrypt error: Cipher cannot be initialized +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'bf'); +ERROR: encrypt_iv error: Cipher cannot be initialized +select encode(decrypt_iv('\x95c7e89322525d59', '0123456', 'abcd', 'bf'), 'escape'); +ERROR: decrypt_iv error: Cipher cannot be initialized +-- long message +select encrypt('Lets try a longer message.', '0123456789', 'bf'); +ERROR: encrypt error: Cipher cannot be initialized +select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'bf'), '0123456789', 'bf'), 'escape'); +ERROR: encrypt error: Cipher cannot be initialized diff --git a/contrib/pgcrypto/expected/cast5.out b/contrib/pgcrypto/expected/cast5.out new file mode 100644 index 0000000..8a4da98 --- /dev/null +++ b/contrib/pgcrypto/expected/cast5.out @@ -0,0 +1,73 @@ +-- +-- Cast5 cipher +-- +-- test vectors from RFC2144 +-- 128 bit key +SELECT encrypt('\x0123456789ABCDEF', '\x0123456712345678234567893456789A', 'cast5-ecb/pad:none'); + encrypt +-------------------- + \x238b4fe5847e44b2 +(1 row) + +-- 80 bit key +SELECT encrypt('\x0123456789ABCDEF', '\x01234567123456782345', 'cast5-ecb/pad:none'); + encrypt +-------------------- + \xeb6a711a2c02271b +(1 row) + +-- 40 bit key +SELECT encrypt('\x0123456789ABCDEF', '\x0123456712', 'cast5-ecb/pad:none'); + encrypt +-------------------- + \x7ac816d16e9b302e +(1 row) + +-- cbc +-- empty data +select encrypt('', 'foo', 'cast5'); + encrypt +-------------------- + \xa48bd1aabde4de10 +(1 row) + +-- 10 bytes key +select encrypt('foo', '0123456789', 'cast5'); + encrypt +-------------------- + \xb07f19255e60cb6d +(1 row) + +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'cast5'), '0123456', 'cast5'), 'escape'); + encode +-------- + foo +(1 row) + +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'cast5'); + encrypt_iv +-------------------- + \x384a970695ce016a +(1 row) + +select encode(decrypt_iv('\x384a970695ce016a', '0123456', 'abcd', 'cast5'), 'escape'); + encode +-------- + foo +(1 row) + +-- long message +select encrypt('Lets try a longer message.', '0123456789', 'cast5'); + encrypt +-------------------------------------------------------------------- + \x04fcffc91533e1505dadcb10766d9fed0937818e663e402384e049942ba60fff +(1 row) + +select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'cast5'), '0123456789', 'cast5'), 'escape'); + encode +---------------------------- + Lets try a longer message. +(1 row) + diff --git a/contrib/pgcrypto/expected/cast5_1.out b/contrib/pgcrypto/expected/cast5_1.out new file mode 100644 index 0000000..175e0b0 --- /dev/null +++ b/contrib/pgcrypto/expected/cast5_1.out @@ -0,0 +1,33 @@ +-- +-- Cast5 cipher +-- +-- test vectors from RFC2144 +-- 128 bit key +SELECT encrypt('\x0123456789ABCDEF', '\x0123456712345678234567893456789A', 'cast5-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +-- 80 bit key +SELECT encrypt('\x0123456789ABCDEF', '\x01234567123456782345', 'cast5-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +-- 40 bit key +SELECT encrypt('\x0123456789ABCDEF', '\x0123456712', 'cast5-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +-- cbc +-- empty data +select encrypt('', 'foo', 'cast5'); +ERROR: encrypt error: Cipher cannot be initialized +-- 10 bytes key +select encrypt('foo', '0123456789', 'cast5'); +ERROR: encrypt error: Cipher cannot be initialized +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'cast5'), '0123456', 'cast5'), 'escape'); +ERROR: encrypt error: Cipher cannot be initialized +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'cast5'); +ERROR: encrypt_iv error: Cipher cannot be initialized +select encode(decrypt_iv('\x384a970695ce016a', '0123456', 'abcd', 'cast5'), 'escape'); +ERROR: decrypt_iv error: Cipher cannot be initialized +-- long message +select encrypt('Lets try a longer message.', '0123456789', 'cast5'); +ERROR: encrypt error: Cipher cannot be initialized +select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'cast5'), '0123456789', 'cast5'), 'escape'); +ERROR: encrypt error: Cipher cannot be initialized diff --git a/contrib/pgcrypto/expected/crypt-blowfish.out b/contrib/pgcrypto/expected/crypt-blowfish.out new file mode 100644 index 0000000..d79b0c0 --- /dev/null +++ b/contrib/pgcrypto/expected/crypt-blowfish.out @@ -0,0 +1,36 @@ +-- +-- crypt() and gen_salt(): bcrypt +-- +SELECT crypt('', '$2a$06$RQiOJ.3ELirrXwxIZY8q0O'); + crypt +-------------------------------------------------------------- + $2a$06$RQiOJ.3ELirrXwxIZY8q0OlGbBEpDmx7IRZlNYvGJ1SHXwNi2cEKK +(1 row) + +SELECT crypt('foox', '$2a$06$RQiOJ.3ELirrXwxIZY8q0O'); + crypt +-------------------------------------------------------------- + $2a$06$RQiOJ.3ELirrXwxIZY8q0OR3CVJrAfda1z26CCHPnB6mmVZD8p0/C +(1 row) + +-- error, salt too short: +SELECT crypt('foox', '$2a$'); +ERROR: invalid salt +-- error, first digit of count in salt invalid +SELECT crypt('foox', '$2a$40$RQiOJ.3ELirrXwxIZY8q0O'); +ERROR: invalid salt +-- error, count in salt too small +SELECT crypt('foox', '$2a$00$RQiOJ.3ELirrXwxIZY8q0O'); +ERROR: invalid salt +CREATE TABLE ctest (data text, res text, salt text); +INSERT INTO ctest VALUES ('password', '', ''); +UPDATE ctest SET salt = gen_salt('bf', 8); +UPDATE ctest SET res = crypt(data, salt); +SELECT res = crypt(data, res) AS "worked" +FROM ctest; + worked +-------- + t +(1 row) + +DROP TABLE ctest; diff --git a/contrib/pgcrypto/expected/crypt-des.out b/contrib/pgcrypto/expected/crypt-des.out new file mode 100644 index 0000000..a462dcd --- /dev/null +++ b/contrib/pgcrypto/expected/crypt-des.out @@ -0,0 +1,31 @@ +-- +-- crypt() and gen_salt(): crypt-des +-- +SELECT crypt('', 'NB'); + crypt +--------------- + NBPx/38Y48kHg +(1 row) + +SELECT crypt('foox', 'NB'); + crypt +--------------- + NB53EGGqrrb5E +(1 row) + +-- We are supposed to pass in a 2-character salt. +-- error since salt is too short: +SELECT crypt('password', 'a'); +ERROR: invalid salt +CREATE TABLE ctest (data text, res text, salt text); +INSERT INTO ctest VALUES ('password', '', ''); +UPDATE ctest SET salt = gen_salt('des'); +UPDATE ctest SET res = crypt(data, salt); +SELECT res = crypt(data, res) AS "worked" +FROM ctest; + worked +-------- + t +(1 row) + +DROP TABLE ctest; diff --git a/contrib/pgcrypto/expected/crypt-md5.out b/contrib/pgcrypto/expected/crypt-md5.out new file mode 100644 index 0000000..a1c8304 --- /dev/null +++ b/contrib/pgcrypto/expected/crypt-md5.out @@ -0,0 +1,27 @@ +-- +-- crypt() and gen_salt(): md5 +-- +SELECT crypt('', '$1$Szzz0yzz'); + crypt +------------------------------------ + $1$Szzz0yzz$To38XrR3BsbXQW2ZpfKjF1 +(1 row) + +SELECT crypt('foox', '$1$Szzz0yzz'); + crypt +------------------------------------ + $1$Szzz0yzz$IYL49cd3t9bllsA7Jmz1M1 +(1 row) + +CREATE TABLE ctest (data text, res text, salt text); +INSERT INTO ctest VALUES ('password', '', ''); +UPDATE ctest SET salt = gen_salt('md5'); +UPDATE ctest SET res = crypt(data, salt); +SELECT res = crypt(data, res) AS "worked" +FROM ctest; + worked +-------- + t +(1 row) + +DROP TABLE ctest; diff --git a/contrib/pgcrypto/expected/crypt-xdes.out b/contrib/pgcrypto/expected/crypt-xdes.out new file mode 100644 index 0000000..8cf9075 --- /dev/null +++ b/contrib/pgcrypto/expected/crypt-xdes.out @@ -0,0 +1,51 @@ +-- +-- crypt() and gen_salt(): extended des +-- +SELECT crypt('', '_J9..j2zz'); + crypt +---------------------- + _J9..j2zzR/nIRDK3pPc +(1 row) + +SELECT crypt('foox', '_J9..j2zz'); + crypt +---------------------- + _J9..j2zzAYKMvO2BYRY +(1 row) + +-- check XDES handling of keys longer than 8 chars +SELECT crypt('longlongpassword', '_J9..j2zz'); + crypt +---------------------- + _J9..j2zz4BeseiQNwUg +(1 row) + +-- error, salt too short +SELECT crypt('foox', '_J9..BWH'); +ERROR: invalid salt +-- error, count specified in the second argument is 0 +SELECT crypt('password', '_........'); +ERROR: crypt(3) returned NULL +-- error, count will wind up still being 0 due to invalid encoding +-- of the count: only chars ``./0-9A-Za-z' are valid +SELECT crypt('password', '_..!!!!!!'); +ERROR: crypt(3) returned NULL +-- count should be non-zero here, will work +SELECT crypt('password', '_/!!!!!!!'); + crypt +---------------------- + _/!!!!!!!zqM49hRzxko +(1 row) + +CREATE TABLE ctest (data text, res text, salt text); +INSERT INTO ctest VALUES ('password', '', ''); +UPDATE ctest SET salt = gen_salt('xdes', 1001); +UPDATE ctest SET res = crypt(data, salt); +SELECT res = crypt(data, res) AS "worked" +FROM ctest; + worked +-------- + t +(1 row) + +DROP TABLE ctest; diff --git a/contrib/pgcrypto/expected/des.out b/contrib/pgcrypto/expected/des.out new file mode 100644 index 0000000..fdbaea2 --- /dev/null +++ b/contrib/pgcrypto/expected/des.out @@ -0,0 +1,58 @@ +-- +-- DES cipher +-- +-- no official test vectors atm +-- from blowfish.sql +SELECT encrypt('\x0123456789abcdef', '\xfedcba9876543210', 'des-ecb/pad:none'); + encrypt +-------------------- + \xed39d950fa74bcc4 +(1 row) + +-- empty data +select encrypt('', 'foo', 'des'); + encrypt +-------------------- + \x752111e37a2d7ac3 +(1 row) + +-- 8 bytes key +select encrypt('foo', '01234589', 'des'); + encrypt +-------------------- + \xdec0f9c602b647a8 +(1 row) + +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'des'), '0123456', 'des'), 'escape'); + encode +-------- + foo +(1 row) + +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'des'); + encrypt_iv +-------------------- + \x50735067b073bb93 +(1 row) + +select encode(decrypt_iv('\x50735067b073bb93', '0123456', 'abcd', 'des'), 'escape'); + encode +-------- + foo +(1 row) + +-- long message +select encrypt('Lets try a longer message.', '01234567', 'des'); + encrypt +-------------------------------------------------------------------- + \x5ad146043e5f30967e06a0fcbae602daf4ff2a5fd0ed12d6c5913cf85f1e36ca +(1 row) + +select encode(decrypt(encrypt('Lets try a longer message.', '01234567', 'des'), '01234567', 'des'), 'escape'); + encode +---------------------------- + Lets try a longer message. +(1 row) + diff --git a/contrib/pgcrypto/expected/des_1.out b/contrib/pgcrypto/expected/des_1.out new file mode 100644 index 0000000..5a76154 --- /dev/null +++ b/contrib/pgcrypto/expected/des_1.out @@ -0,0 +1,26 @@ +-- +-- DES cipher +-- +-- no official test vectors atm +-- from blowfish.sql +SELECT encrypt('\x0123456789abcdef', '\xfedcba9876543210', 'des-ecb/pad:none'); +ERROR: encrypt error: Cipher cannot be initialized +-- empty data +select encrypt('', 'foo', 'des'); +ERROR: encrypt error: Cipher cannot be initialized +-- 8 bytes key +select encrypt('foo', '01234589', 'des'); +ERROR: encrypt error: Cipher cannot be initialized +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'des'), '0123456', 'des'), 'escape'); +ERROR: encrypt error: Cipher cannot be initialized +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'des'); +ERROR: encrypt_iv error: Cipher cannot be initialized +select encode(decrypt_iv('\x50735067b073bb93', '0123456', 'abcd', 'des'), 'escape'); +ERROR: decrypt_iv error: Cipher cannot be initialized +-- long message +select encrypt('Lets try a longer message.', '01234567', 'des'); +ERROR: encrypt error: Cipher cannot be initialized +select encode(decrypt(encrypt('Lets try a longer message.', '01234567', 'des'), '01234567', 'des'), 'escape'); +ERROR: encrypt error: Cipher cannot be initialized diff --git a/contrib/pgcrypto/expected/hmac-md5.out b/contrib/pgcrypto/expected/hmac-md5.out new file mode 100644 index 0000000..0d8d761 --- /dev/null +++ b/contrib/pgcrypto/expected/hmac-md5.out @@ -0,0 +1,72 @@ +-- +-- HMAC-MD5 +-- +SELECT hmac( +'Hi There', +'\x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'::bytea, +'md5'); + hmac +------------------------------------ + \x9294727a3638bb1c13f48ef8158bfc9d +(1 row) + +-- 2 +SELECT hmac( +'Jefe', +'what do ya want for nothing?', +'md5'); + hmac +------------------------------------ + \x813aead7c4a34bff01a16d61368e7c13 +(1 row) + +-- 3 +SELECT hmac( +'\xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'::bytea, +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'md5'); + hmac +------------------------------------ + \x56be34521d144c88dbb8c733f0e8b3f6 +(1 row) + +-- 4 +SELECT hmac( +'\xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'::bytea, +'\x0102030405060708090a0b0c0d0e0f10111213141516171819'::bytea, +'md5'); + hmac +------------------------------------ + \x697eaf0aca3a3aea3a75164746ffaa79 +(1 row) + +-- 5 +SELECT hmac( +'Test With Truncation', +'\x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c'::bytea, +'md5'); + hmac +------------------------------------ + \x56461ef2342edc00f9bab995690efd4c +(1 row) + +-- 6 +SELECT hmac( +'Test Using Larger Than Block-Size Key - Hash Key First', +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'md5'); + hmac +------------------------------------ + \x6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd +(1 row) + +-- 7 +SELECT hmac( +'Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data', +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'md5'); + hmac +------------------------------------ + \x6f630fad67cda0ee1fb1f562db3aa53e +(1 row) + diff --git a/contrib/pgcrypto/expected/hmac-sha1.out b/contrib/pgcrypto/expected/hmac-sha1.out new file mode 100644 index 0000000..a7d4c38 --- /dev/null +++ b/contrib/pgcrypto/expected/hmac-sha1.out @@ -0,0 +1,72 @@ +-- +-- HMAC-SHA1 +-- +SELECT hmac( +'Hi There', +'\x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'::bytea, +'sha1'); + hmac +-------------------------------------------- + \x675b0b3a1b4ddf4e124872da6c2f632bfed957e9 +(1 row) + +-- 2 +SELECT hmac( +'Jefe', +'what do ya want for nothing?', +'sha1'); + hmac +-------------------------------------------- + \x156d4c35468a0339f3fa57a067bf47f814eb7a57 +(1 row) + +-- 3 +SELECT hmac( +'\xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'::bytea, +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'sha1'); + hmac +-------------------------------------------- + \xd730594d167e35d5956fd8003d0db3d3f46dc7bb +(1 row) + +-- 4 +SELECT hmac( +'\xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'::bytea, +'\x0102030405060708090a0b0c0d0e0f10111213141516171819'::bytea, +'sha1'); + hmac +-------------------------------------------- + \x4c9007f4026250c6bc8414f9bf50c86c2d7235da +(1 row) + +-- 5 +SELECT hmac( +'Test With Truncation', +'\x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c'::bytea, +'sha1'); + hmac +-------------------------------------------- + \x37268b7e21e84da5720c53c4ba03ad1104039fa7 +(1 row) + +-- 6 +SELECT hmac( +'Test Using Larger Than Block-Size Key - Hash Key First', +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'sha1'); + hmac +-------------------------------------------- + \xaa4ae5e15272d00e95705637ce8a3b55ed402112 +(1 row) + +-- 7 +SELECT hmac( +'Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data', +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'sha1'); + hmac +-------------------------------------------- + \xe8e99d0f45237d786d6bbaa7965c7808bbff1a91 +(1 row) + diff --git a/contrib/pgcrypto/expected/init.out b/contrib/pgcrypto/expected/init.out new file mode 100644 index 0000000..d1341a6 --- /dev/null +++ b/contrib/pgcrypto/expected/init.out @@ -0,0 +1,13 @@ +-- +-- init pgcrypto +-- +CREATE EXTENSION pgcrypto; +-- check error handling +select gen_salt('foo'); +ERROR: gen_salt: Unknown salt algorithm +select digest('foo', 'foo'); +ERROR: Cannot use "foo": No such hash algorithm +select hmac('foo', 'foo', 'foo'); +ERROR: Cannot use "foo": No such hash algorithm +select encrypt('foo', 'foo', 'foo'); +ERROR: Cannot use "foo": No such cipher algorithm diff --git a/contrib/pgcrypto/expected/md5.out b/contrib/pgcrypto/expected/md5.out new file mode 100644 index 0000000..1790594 --- /dev/null +++ b/contrib/pgcrypto/expected/md5.out @@ -0,0 +1,45 @@ +-- +-- MD5 message digest +-- +SELECT digest('', 'md5'); + digest +------------------------------------ + \xd41d8cd98f00b204e9800998ecf8427e +(1 row) + +SELECT digest('a', 'md5'); + digest +------------------------------------ + \x0cc175b9c0f1b6a831c399e269772661 +(1 row) + +SELECT digest('abc', 'md5'); + digest +------------------------------------ + \x900150983cd24fb0d6963f7d28e17f72 +(1 row) + +SELECT digest('message digest', 'md5'); + digest +------------------------------------ + \xf96b697d7cb7938d525a2f31aaf161d0 +(1 row) + +SELECT digest('abcdefghijklmnopqrstuvwxyz', 'md5'); + digest +------------------------------------ + \xc3fcd3d76192e4007dfb496cca67e13b +(1 row) + +SELECT digest('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 'md5'); + digest +------------------------------------ + \xd174ab98d277d9f5a5611c2c9f419d9f +(1 row) + +SELECT digest('12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'md5'); + digest +------------------------------------ + \x57edf4a22be3c955ac49da2e2107b67a +(1 row) + diff --git a/contrib/pgcrypto/expected/pgp-armor.out b/contrib/pgcrypto/expected/pgp-armor.out new file mode 100644 index 0000000..0f5ff46 --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-armor.out @@ -0,0 +1,370 @@ +-- +-- PGP Armor +-- +select armor(''); + armor +----------------------------- + -----BEGIN PGP MESSAGE-----+ + + + =twTO + + -----END PGP MESSAGE----- + + +(1 row) + +select armor('test'); + armor +----------------------------- + -----BEGIN PGP MESSAGE-----+ + + + dGVzdA== + + =+G7Q + + -----END PGP MESSAGE----- + + +(1 row) + +select encode(dearmor(armor('')), 'escape'); + encode +-------- + +(1 row) + +select encode(dearmor(armor('zooka')), 'escape'); + encode +-------- + zooka +(1 row) + +select armor('0123456789abcdef0123456789abcdef0123456789abcdef +0123456789abcdef0123456789abcdef0123456789abcdef'); + armor +------------------------------------------------------------------------------ + -----BEGIN PGP MESSAGE----- + + + + MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmCjAxMjM0NTY3+ + ODlhYmNkZWYwMTIzNDU2Nzg5YWJjZGVmMDEyMzQ1Njc4OWFiY2RlZg== + + =JFw5 + + -----END PGP MESSAGE----- + + +(1 row) + +-- lots formatting +select encode(dearmor(' a pgp msg: + +-----BEGIN PGP MESSAGE----- +Comment: Some junk + +em9va2E= + + =D5cR + +-----END PGP MESSAGE-----'), 'escape'); + encode +-------- + zooka +(1 row) + +-- lots messages +select encode(dearmor(' +wrong packet: + -----BEGIN PGP MESSAGE----- + + d3Jvbmc= + =vCYP + -----END PGP MESSAGE----- + +right packet: +-----BEGIN PGP MESSAGE----- + +cmlnaHQ= +=nbpj +-----END PGP MESSAGE----- + +use only first packet +-----BEGIN PGP MESSAGE----- + +d3Jvbmc= +=vCYP +-----END PGP MESSAGE----- +'), 'escape'); + encode +-------- + right +(1 row) + +-- bad crc +select dearmor(' +-----BEGIN PGP MESSAGE----- + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); +ERROR: Corrupt ascii-armor +-- corrupt (no space after the colon) +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +foo: + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); +ERROR: Corrupt ascii-armor +-- corrupt (no empty line) +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); +ERROR: Corrupt ascii-armor +-- no headers +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + key | value +-----+------- +(0 rows) + +-- header with empty value +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +foo: + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + key | value +-----+------- + foo | +(1 row) + +-- simple +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +fookey: foovalue +barkey: barvalue + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + key | value +--------+---------- + fookey | foovalue + barkey | barvalue +(2 rows) + +-- insane keys, part 1 +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +insane:key : + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + key | value +-------------+------- + insane:key | +(1 row) + +-- insane keys, part 2 +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +insane:key : text value here + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + key | value +-------------+----------------- + insane:key | text value here +(1 row) + +-- long value +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +long: this value is more than 76 characters long, but it should still parse correctly as that''s permitted by RFC 4880 + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + key | value +------+----------------------------------------------------------------------------------------------------------------- + long | this value is more than 76 characters long, but it should still parse correctly as that's permitted by RFC 4880 +(1 row) + +-- long value, split up +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +long: this value is more than 76 characters long, but it should still +long: parse correctly as that''s permitted by RFC 4880 + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + key | value +------+------------------------------------------------------------------ + long | this value is more than 76 characters long, but it should still + long | parse correctly as that's permitted by RFC 4880 +(2 rows) + +-- long value, split up, part 2 +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +long: this value is more than +long: 76 characters long, but it should still +long: parse correctly as that''s permitted by RFC 4880 + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + key | value +------+------------------------------------------------- + long | this value is more than + long | 76 characters long, but it should still + long | parse correctly as that's permitted by RFC 4880 +(3 rows) + +-- long value, split up, part 3 +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +emptykey: +long: this value is more than +emptykey: +long: 76 characters long, but it should still +emptykey: +long: parse correctly as that''s permitted by RFC 4880 +emptykey: + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + key | value +----------+------------------------------------------------- + emptykey | + long | this value is more than + emptykey | + long | 76 characters long, but it should still + emptykey | + long | parse correctly as that's permitted by RFC 4880 + emptykey | +(7 rows) + +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.blowfish.sha1.mdc.s2k3.z0 + +jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS +yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE= +=JcP+ +-----END PGP MESSAGE----- +'); + key | value +---------+-------------------------------- + Comment | dat1.blowfish.sha1.mdc.s2k3.z0 +(1 row) + +-- test CR+LF line endings +select * from pgp_armor_headers(replace(' +-----BEGIN PGP MESSAGE----- +fookey: foovalue +barkey: barvalue + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +', E'\n', E'\r\n')); + key | value +--------+---------- + fookey | foovalue + barkey | barvalue +(2 rows) + +-- test header generation +select armor('zooka', array['foo'], array['bar']); + armor +----------------------------- + -----BEGIN PGP MESSAGE-----+ + foo: bar + + + + em9va2E= + + =D5cR + + -----END PGP MESSAGE----- + + +(1 row) + +select armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']); + armor +--------------------------------------------------------------------- + -----BEGIN PGP MESSAGE----- + + Version: Created by pgcrypto + + Comment: PostgreSQL, the world's most advanced open source database+ + + + em9va2E= + + =D5cR + + -----END PGP MESSAGE----- + + +(1 row) + +select * from pgp_armor_headers( + armor('zooka', array['Version', 'Comment'], + array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database'])); + key | value +---------+------------------------------------------------------------ + Version | Created by pgcrypto + Comment | PostgreSQL, the world's most advanced open source database +(2 rows) + +-- error/corner cases +select armor('', array['foo'], array['too', 'many']); +ERROR: mismatched array dimensions +select armor('', array['too', 'many'], array['foo']); +ERROR: mismatched array dimensions +select armor('', array[['']], array['foo']); +ERROR: wrong number of array subscripts +select armor('', array['foo'], array[['']]); +ERROR: wrong number of array subscripts +select armor('', array[null], array['foo']); +ERROR: null value not allowed for header key +select armor('', array['foo'], array[null]); +ERROR: null value not allowed for header value +select armor('', '[0:0]={"foo"}', array['foo']); + armor +----------------------------- + -----BEGIN PGP MESSAGE-----+ + foo: foo + + + + =twTO + + -----END PGP MESSAGE----- + + +(1 row) + +select armor('', array['foo'], '[0:0]={"foo"}'); + armor +----------------------------- + -----BEGIN PGP MESSAGE-----+ + foo: foo + + + + =twTO + + -----END PGP MESSAGE----- + + +(1 row) + +select armor('', array[E'embedded\nnewline'], array['foo']); +ERROR: header key must not contain newlines +select armor('', array['foo'], array[E'embedded\nnewline']); +ERROR: header value must not contain newlines +select armor('', array['embedded: colon+space'], array['foo']); +ERROR: header key must not contain ": " diff --git a/contrib/pgcrypto/expected/pgp-compression.out b/contrib/pgcrypto/expected/pgp-compression.out new file mode 100644 index 0000000..d4c57fe --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-compression.out @@ -0,0 +1,80 @@ +-- +-- PGP compression support +-- +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0ECQMCsci6AdHnELlh0kQB4jFcVwHMJg0Bulop7m3Mi36s15TAhBo0AnzIrRFrdLVCkKohsS6+ +DMcmR53SXfLoDJOv/M8uKj3QSq7oWNIp95pxfA== +=tbSn +-----END PGP MESSAGE----- +'), 'key', 'expect-compress-algo=1'); + pgp_sym_decrypt +----------------- + Secret message +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret message', 'key', 'compress-algo=0'), + 'key', 'expect-compress-algo=0'); + pgp_sym_decrypt +----------------- + Secret message +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret message', 'key', 'compress-algo=1'), + 'key', 'expect-compress-algo=1'); + pgp_sym_decrypt +----------------- + Secret message +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret message', 'key', 'compress-algo=2'), + 'key', 'expect-compress-algo=2'); + pgp_sym_decrypt +----------------- + Secret message +(1 row) + +-- level=0 should turn compression off +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret message', 'key', + 'compress-algo=2, compress-level=0'), + 'key', 'expect-compress-algo=0'); + pgp_sym_decrypt +----------------- + Secret message +(1 row) + +-- check corner case involving an input string of 16kB, as per bug #16476. +SELECT setseed(0); + setseed +--------- + +(1 row) + +WITH random_string AS +( + -- This generates a random string of 16366 bytes. This is chosen + -- as random so that it does not get compressed, and the decompression + -- would work on a string with the same length as the origin, making the + -- test behavior more predictible. lpad() ensures that the generated + -- hexadecimal value is completed by extra zero characters if random() + -- has generated a value strictly lower than 16. + SELECT string_agg(decode(lpad(to_hex((random()*256)::int), 2, '0'), 'hex'), '') as bytes + FROM generate_series(0, 16365) +) +SELECT bytes = + pgp_sym_decrypt_bytea( + pgp_sym_encrypt_bytea(bytes, 'key', + 'compress-algo=1,compress-level=1'), + 'key', 'expect-compress-algo=1') + AS is_same + FROM random_string; + is_same +--------- + t +(1 row) + diff --git a/contrib/pgcrypto/expected/pgp-decrypt.out b/contrib/pgcrypto/expected/pgp-decrypt.out new file mode 100644 index 0000000..eb049ba --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-decrypt.out @@ -0,0 +1,419 @@ +-- +-- pgp decrypt tests +-- +-- Checking ciphers +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.blowfish.sha1.mdc.s2k3.z0 + +jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS +yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE= +=JcP+ +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest +UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA== +=XtrP +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI +5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g== +=rCZt +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/ +lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog== +=fB6S +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking MDC modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.nomdc.s2k3.z0 + +jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+ +u9YkgfJfsuRJmgQ9tmo= +=60ui +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE +8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q== +=moGf +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking hashes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.md5.mdc.s2k3.z0 + +jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO +KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA== +=NyLk +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m +/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw== +=FxbQ +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking S2K modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k0.z0 + +jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw +Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw== +=YvkV +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k1.z0 + +jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK +4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz +=dbXm +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl +z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg== +=VJKg +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k0.z0 + +jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E +Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw== +=cg+i +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k1.z0 + +jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9 +7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt +=aHmC +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv +q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw== +=K0LS +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k0.z0 + +jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu +rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w== +=RGts +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k1.z0 + +jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO ++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR +=SUrU +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+ +4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA== +=XZrG +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking longer passwords +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi +tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw== +=XKKG +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6 +CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg== +=gWDh +-----END PGP MESSAGE----- +'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt +FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q== +=OxOF +-----END PGP MESSAGE----- +'), 'x'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking various data +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8 +Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg== +=W/ik +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \x0225e3ede6f2587b076d021a189ff60aad67e066 +(1 row) + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat2.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB +SaV9L04ky1qECNDx3XjnoKLC+H7IOQ== +=Fxen +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \xda39a3ee5e6b4b0d3255bfef95601890afd80709 +(1 row) + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat3.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8 +gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk +73Hb8m1yRhQK +=ivrD +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c +(1 row) + +-- Checking CRLF +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=0'), 'sha1'); + digest +-------------------------------------------- + \x9353062be7720f1446d30b9e75573a4833886784 +(1 row) + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=1'), 'sha1'); + digest +-------------------------------------------- + \x7efefcab38467f7484d6fa43dc86cf5281bd78e2 +(1 row) + +-- check BUG #11905, problem with messages 6 less than a power of 2. +select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530); + ?column? +---------- + t +(1 row) + +-- Negative tests +-- Decryption with a certain incorrect key yields an apparent Literal Data +-- packet reporting its content to be binary data. Ciphertext source: +-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave +-- rise to that property. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV +VsxxqLSPzNLAeIspJk5G +=mSd/ +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); +NOTICE: dbg: prefix_init: corrupt prefix +NOTICE: dbg: parse_literal_data: data type=b +NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr +ERROR: Wrong key or corrupt data +-- Routine text/binary mismatch. +select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1'); +NOTICE: dbg: parse_literal_data: data type=b +ERROR: Not text data +-- Decryption with a certain incorrect key yields an apparent BZip2-compressed +-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key') +-- until the random prefix gave rise to that property. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP +GXsd65oYJZp3Khz0qfyn +=Nmpq +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); +NOTICE: dbg: prefix_init: corrupt prefix +NOTICE: dbg: parse_compressed_data: bzip2 unsupported +NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr +ERROR: Wrong key or corrupt data +-- Routine use of BZip2 compression. Ciphertext source: +-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \ +-- --personal-cipher-preferences aes --no-emit-version --batch \ +-- --symmetric --passphrase key --armor +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe +QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R +UCAAw2JRIISttRHMfDpDuZJpvYo= +=AZ9M +-----END PGP MESSAGE----- +'), 'key', 'debug=1'); +NOTICE: dbg: parse_compressed_data: bzip2 unsupported +ERROR: Unsupported compression algorithm diff --git a/contrib/pgcrypto/expected/pgp-decrypt_1.out b/contrib/pgcrypto/expected/pgp-decrypt_1.out new file mode 100644 index 0000000..80a4c48 --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-decrypt_1.out @@ -0,0 +1,415 @@ +-- +-- pgp decrypt tests +-- +-- Checking ciphers +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.blowfish.sha1.mdc.s2k3.z0 + +jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS +yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE= +=JcP+ +-----END PGP MESSAGE----- +'), 'foobar'); +ERROR: Wrong key or corrupt data +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest +UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA== +=XtrP +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI +5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g== +=rCZt +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/ +lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog== +=fB6S +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking MDC modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.nomdc.s2k3.z0 + +jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+ +u9YkgfJfsuRJmgQ9tmo= +=60ui +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE +8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q== +=moGf +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking hashes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.md5.mdc.s2k3.z0 + +jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO +KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA== +=NyLk +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m +/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw== +=FxbQ +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking S2K modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k0.z0 + +jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw +Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw== +=YvkV +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k1.z0 + +jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK +4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz +=dbXm +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl +z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg== +=VJKg +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k0.z0 + +jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E +Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw== +=cg+i +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k1.z0 + +jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9 +7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt +=aHmC +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv +q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw== +=K0LS +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k0.z0 + +jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu +rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w== +=RGts +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k1.z0 + +jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO ++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR +=SUrU +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+ +4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA== +=XZrG +-----END PGP MESSAGE----- +'), 'foobar'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking longer passwords +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi +tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw== +=XKKG +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6 +CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg== +=gWDh +-----END PGP MESSAGE----- +'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt +FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q== +=OxOF +-----END PGP MESSAGE----- +'), 'x'); + pgp_sym_decrypt +----------------- + Secret message. +(1 row) + +-- Checking various data +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8 +Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg== +=W/ik +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \x0225e3ede6f2587b076d021a189ff60aad67e066 +(1 row) + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat2.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB +SaV9L04ky1qECNDx3XjnoKLC+H7IOQ== +=Fxen +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \xda39a3ee5e6b4b0d3255bfef95601890afd80709 +(1 row) + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat3.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8 +gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk +73Hb8m1yRhQK +=ivrD +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + digest +-------------------------------------------- + \x5e5c135efc0dd00633efc6dfd6e731ea408a5b4c +(1 row) + +-- Checking CRLF +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=0'), 'sha1'); + digest +-------------------------------------------- + \x9353062be7720f1446d30b9e75573a4833886784 +(1 row) + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=1'), 'sha1'); + digest +-------------------------------------------- + \x7efefcab38467f7484d6fa43dc86cf5281bd78e2 +(1 row) + +-- check BUG #11905, problem with messages 6 less than a power of 2. +select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530); + ?column? +---------- + t +(1 row) + +-- Negative tests +-- Decryption with a certain incorrect key yields an apparent Literal Data +-- packet reporting its content to be binary data. Ciphertext source: +-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave +-- rise to that property. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV +VsxxqLSPzNLAeIspJk5G +=mSd/ +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); +NOTICE: dbg: prefix_init: corrupt prefix +NOTICE: dbg: parse_literal_data: data type=b +NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr +ERROR: Wrong key or corrupt data +-- Routine text/binary mismatch. +select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1'); +NOTICE: dbg: parse_literal_data: data type=b +ERROR: Not text data +-- Decryption with a certain incorrect key yields an apparent BZip2-compressed +-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key') +-- until the random prefix gave rise to that property. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP +GXsd65oYJZp3Khz0qfyn +=Nmpq +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); +NOTICE: dbg: prefix_init: corrupt prefix +NOTICE: dbg: parse_compressed_data: bzip2 unsupported +NOTICE: dbg: mdcbuf_finish: bad MDC pkt hdr +ERROR: Wrong key or corrupt data +-- Routine use of BZip2 compression. Ciphertext source: +-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \ +-- --personal-cipher-preferences aes --no-emit-version --batch \ +-- --symmetric --passphrase key --armor +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe +QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R +UCAAw2JRIISttRHMfDpDuZJpvYo= +=AZ9M +-----END PGP MESSAGE----- +'), 'key', 'debug=1'); +NOTICE: dbg: parse_compressed_data: bzip2 unsupported +ERROR: Unsupported compression algorithm diff --git a/contrib/pgcrypto/expected/pgp-encrypt.out b/contrib/pgcrypto/expected/pgp-encrypt.out new file mode 100644 index 0000000..77e45ab --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-encrypt.out @@ -0,0 +1,208 @@ +-- +-- PGP encrypt +-- +select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- check whether the defaults are ok +select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), + 'key', 'expect-cipher-algo=aes128, + expect-disable-mdc=0, + expect-sess-key=0, + expect-s2k-mode=3, + expect-s2k-digest-algo=sha1, + expect-compress-algo=0 + '); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- maybe the expect- stuff simply does not work +select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), + 'key', 'expect-cipher-algo=bf, + expect-disable-mdc=1, + expect-sess-key=1, + expect-s2k-mode=0, + expect-s2k-digest-algo=md5, + expect-compress-algo=1 + '); +NOTICE: pgp_decrypt: unexpected cipher_algo: expected 4 got 7 +NOTICE: pgp_decrypt: unexpected s2k_mode: expected 0 got 3 +NOTICE: pgp_decrypt: unexpected s2k_digest_algo: expected 1 got 2 +NOTICE: pgp_decrypt: unexpected use_sess_key: expected 1 got 0 +NOTICE: pgp_decrypt: unexpected disable_mdc: expected 1 got 0 +NOTICE: pgp_decrypt: unexpected compress_algo: expected 1 got 0 + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- bytea as text +select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz'); +ERROR: Not text data +-- text as bytea +select encode(pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz'), 'escape'); + encode +-------- + Text +(1 row) + +-- algorithm change +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'), + 'key', 'expect-cipher-algo=bf'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'), + 'key', 'expect-cipher-algo=aes128'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'), + 'key', 'expect-cipher-algo=aes192'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- s2k change +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'), + 'key', 'expect-s2k-mode=0'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'), + 'key', 'expect-s2k-mode=1'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'), + 'key', 'expect-s2k-mode=3'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- s2k count change +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'), + 'key', 'expect-s2k-count=1024'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- s2k_count rounds up +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'), + 'key', 'expect-s2k-count=65000000'); +NOTICE: pgp_decrypt: unexpected s2k_count: expected 65000000 got 65011712 + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- s2k digest change +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'), + 'key', 'expect-s2k-digest-algo=md5'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'), + 'key', 'expect-s2k-digest-algo=sha1'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- sess key +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'), + 'key', 'expect-sess-key=0'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'), + 'key', 'expect-sess-key=1'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'), + 'key', 'expect-sess-key=1, expect-cipher-algo=bf'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'), + 'key', 'expect-sess-key=1, expect-cipher-algo=aes192'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'), + 'key', 'expect-sess-key=1, expect-cipher-algo=aes256'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- no mdc +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'), + 'key', 'expect-disable-mdc=1'); + pgp_sym_decrypt +----------------- + Secret. +(1 row) + +-- crlf +select pgp_sym_decrypt_bytea( + pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'), + 'key'); + pgp_sym_decrypt_bytea +------------------------ + \x310d0a320d0a330d0d0a +(1 row) + +-- conversion should be lossless +select digest(pgp_sym_decrypt( + pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'), + 'key', 'convert-crlf=1'), 'sha1') as result, + digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1') as expect; + result | expect +--------------------------------------------+-------------------------------------------- + \x47bde5d88d6ef8770572b9cbb4278b402aa69966 | \x47bde5d88d6ef8770572b9cbb4278b402aa69966 +(1 row) + diff --git a/contrib/pgcrypto/expected/pgp-info.out b/contrib/pgcrypto/expected/pgp-info.out new file mode 100644 index 0000000..9064838 --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-info.out @@ -0,0 +1,79 @@ +-- +-- PGP info functions +-- +-- pgp_key_id +select pgp_key_id(dearmor(pubkey)) from keytbl where id=1; + pgp_key_id +------------------ + D936CF64BB73F466 +(1 row) + +select pgp_key_id(dearmor(pubkey)) from keytbl where id=2; + pgp_key_id +------------------ + 2C226E1FFE5CC7D4 +(1 row) + +select pgp_key_id(dearmor(pubkey)) from keytbl where id=3; + pgp_key_id +------------------ + B68504FD128E1FF9 +(1 row) + +select pgp_key_id(dearmor(pubkey)) from keytbl where id=4; -- should fail +ERROR: No encryption key found +select pgp_key_id(dearmor(pubkey)) from keytbl where id=5; + pgp_key_id +------------------ + D936CF64BB73F466 +(1 row) + +select pgp_key_id(dearmor(pubkey)) from keytbl where id=6; + pgp_key_id +------------------ + FD0206C409B74875 +(1 row) + +select pgp_key_id(dearmor(seckey)) from keytbl where id=1; + pgp_key_id +------------------ + D936CF64BB73F466 +(1 row) + +select pgp_key_id(dearmor(seckey)) from keytbl where id=2; + pgp_key_id +------------------ + 2C226E1FFE5CC7D4 +(1 row) + +select pgp_key_id(dearmor(seckey)) from keytbl where id=3; + pgp_key_id +------------------ + B68504FD128E1FF9 +(1 row) + +select pgp_key_id(dearmor(seckey)) from keytbl where id=4; -- should fail +ERROR: No encryption key found +select pgp_key_id(dearmor(seckey)) from keytbl where id=5; + pgp_key_id +------------------ + D936CF64BB73F466 +(1 row) + +select pgp_key_id(dearmor(seckey)) from keytbl where id=6; + pgp_key_id +------------------ + FD0206C409B74875 +(1 row) + +select pgp_key_id(dearmor(data)) as data_key_id +from encdata order by id; + data_key_id +------------------ + D936CF64BB73F466 + 2C226E1FFE5CC7D4 + B68504FD128E1FF9 + FD0206C409B74875 + FD0206C409B74875 +(5 rows) + diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out new file mode 100644 index 0000000..b4b6810 --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt.out @@ -0,0 +1,656 @@ +-- +-- PGP Public Key Encryption +-- +-- As most of the low-level stuff is tested in symmetric key +-- tests, here's only public-key specific tests +create table keytbl ( + id int4, + name text, + pubkey text, + seckey text +); +create table encdata ( + id int4, + data text +); +insert into keytbl (id, name, pubkey, seckey) +values (1, 'elg1024', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx +MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV +AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc +AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd +ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P +sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI ++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9 +6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF +k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v +iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F +ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80 +=RWci +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQG7BELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQAAAnj4i4st+s+C6 +WKTIDcL1Iy0Saq8lCp60H0VsZ2FtYWwgMTAyNCA8dGVzdEBleGFtcGxlLm9yZz6I +XgQTEQIAHgUCQsghSAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRAcKbwNGBdz +ZDrbAJ9cp6AsjOhiLxwznsMJheGf4xkH8wCfUPjMCLm4tAEnyYn2hDNt7CB8B6Kd +ATEEQsghShAEAIeUjW2yTALCfrEG3FhM3ZLvAHWAec2O0Mn/RDr59IN/W8wDYcZp +m+oG0ZUDdIqMppQ8K2kylAH7gmYDXIP9D7MRRm/Zw3L4yFfKnVaZ6tT7szBbgW5h +iOsHoOz49NXZT4jtMLdZS1/krm5Lam2MSPod9XN0Q2asY/igIMUfGDRjAAMGA/sE +LNh3tWefqeDkoDBEYjcxdGnGVGJnNHvv/eoHy9H7dyD/kkhaOoRAa5ClYWSqD0kk +a+SqTWhKG4XcbJyo1GsP6sqGhXDTM2+LBZPMKuVJQpEfoe9ruob/BbpXglfEiVE9 +VNiY7ZVyUdj3svYn4fK2X7ue1G3cHR2tL4lnOA4pYQAA9030E4u2ZKOfJBpUM+EM +m9VmsGjaQZV4teB0R/q3W8sRIYhJBBgRAgAJBQJCyCFKAhsMAAoJEBwpvA0YF3Nk +7a8AniFFotw1x2X+oryu3Q3nNtmxoKHpAJ9HU7jw7ydg33dI9J8gVkrmsSZ2/w== +=nvqq +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (2, 'elg2048', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n +vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke +5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO +RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij +8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl +Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt +J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/ +T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5 +0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6a7QjRWxnYW1hbCAy +MDQ4IDx0ZXN0MjA0OEBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgiCgIbAwYLCQgH +AwIDFQIDAxYCAQIeAQIXgAAKCRBI6c1W/qZo29PDAKCG724enIxRog1j+aeCp/uq +or6mbwCePuKy2/1kD1FvnhkZ/R5fpm+pdm25Ag0EQsgiIhAIAJI3Gb2Ehtz1taQ9 +AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMX +MhoWyw8ZF5Zs1mLIjFGVorePrm94N3MNPWM7x9M36bHUjx0vCZKFIhcGY1g+htE/ +QweaJzNVeA5z4qZmik41FbQyQSyHa3bOkTZu++/U6ghP+iDp5UDBjMTkVyqITUVN +gC+MR+da/I60irBVhue7younh4ovF+CrVDQJC06HZl6CAJJyA81SmRfi+dmKbbjZ +LF6rhz0norPjISJvkIqvdtM4VPBKI5wpgwCzpEqjuiKrAVujRT68zvBvJ4aVqb11 +k5QdJscAAwUH/jVJh0HbWAoiFTe+NvohfrA8vPcD0rtU3Y+siiqrabotnxJd2NuC +bxghJYGfNtnx0KDjFbCRKJVeTFok4UnuVYhXdH/c6i0/rCTNdeW2D6pmR4GfBozR +Pw/ARf+jONawGLyUj7uq13iquwMSE7VyNuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0R +QsetMq/iNBWraayKZnWUd+eQqNzE+NUo7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiF +Z1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qunfGW00ZMMTCWabg0ZgxPzMfMeIcm6525A +Yn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/RdWaISQQYEQIACQUCQsgiIgIbDAAKCRBI +6c1W/qZo25ZSAJ98WTrtl2HiX8ZqZq95v1+9cHtZPQCfZDoWQPybkNescLmXC7q5 +1kNTmEU= +=8QM5 +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQG7BELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n +vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke +5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO +RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij +8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl +Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt +J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/ +T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5 +0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6awAAn2F+iNBElfJS +8azqO/kEiIfpqu6/DQG0I0VsZ2FtYWwgMjA0OCA8dGVzdDIwNDhAZXhhbXBsZS5v +cmc+iF0EExECAB4FAkLIIgoCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQSOnN +Vv6maNvTwwCYkpcJmpl3aHCQdGomz7dFohDgjgCgiThZt2xTEi6GhBB1vuhk+f55 +n3+dAj0EQsgiIhAIAJI3Gb2Ehtz1taQ9AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D +29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMXMhoWyw8ZF5Zs1mLIjFGVorePrm94N3MN +PWM7x9M36bHUjx0vCZKFIhcGY1g+htE/QweaJzNVeA5z4qZmik41FbQyQSyHa3bO +kTZu++/U6ghP+iDp5UDBjMTkVyqITUVNgC+MR+da/I60irBVhue7younh4ovF+Cr +VDQJC06HZl6CAJJyA81SmRfi+dmKbbjZLF6rhz0norPjISJvkIqvdtM4VPBKI5wp +gwCzpEqjuiKrAVujRT68zvBvJ4aVqb11k5QdJscAAwUH/jVJh0HbWAoiFTe+Nvoh +frA8vPcD0rtU3Y+siiqrabotnxJd2NuCbxghJYGfNtnx0KDjFbCRKJVeTFok4Unu +VYhXdH/c6i0/rCTNdeW2D6pmR4GfBozRPw/ARf+jONawGLyUj7uq13iquwMSE7Vy +NuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0RQsetMq/iNBWraayKZnWUd+eQqNzE+NUo +7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiFZ1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qun +fGW00ZMMTCWabg0ZgxPzMfMeIcm6525AYn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/R +dWYAAVQKFPXbRaxbdArwRVXMzSD3qj/+VwwhwEDt8zmBGnlBfwVdkjQQrDUMmV1S +EwyISQQYEQIACQUCQsgiIgIbDAAKCRBI6c1W/qZo25ZSAJ4sgUfHTVsG/x3p3fcM +3b5R86qKEACggYKSwPWCs0YVRHOWqZY0pnHtLH8= +=3Dgk +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (3, 'elg4096', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELII7wRBACFuaAvb11cIvjJK9LkZr4cYuYhLWh3DJdojNNnLNiym5OEksvY +05cw8OgqKtPzICU7o/mHXTWhzJYUt3i50/AeYygI8Q0uATS6RnDAKNlES1EMoHKz +2a5iFbYs4bm4IwlkvYd8uWjcu+U0YLbxir39u+anIc6eT+q3WiH/q3zDRwCgkT98 +cnIG8iO8PdwDSP8G4Lt6TYED/R45GvCzJ4onQALLE92KkLUz8aFWSl05r84kczEN +SxiP9Ss6m465RmwWHfwYAu4b+c4GeNyU8fIU2EM8cezchC+edEi3xu1s+pCV0Dk4 +18DGC8WKCICO30vBynuNmYg7W/7Zd4wtjss454fMW7+idVDNM701mmXBtI1nsBtG +7Z4tA/9FxjFbJK9jh24RewfjHpLYqcfCo2SsUjOwsnMZ5yg2yv9KyVVQhRqwmrqt +q8MRyjGmfoD9PPdCgvqgzy0hHvAHUtTm2zUczGTG+0g4hNIklxC/Mv6J4KE+NWTh +uB4acqofHyaw2WnKOuRUsoDi6rG5AyjNMyAK/vVcEGj7J1tk27QjRWxnYW1hbCA0 +MDk2IDx0ZXN0NDA5NkBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgjvAIbAwYLCQgH +AwIDFQIDAxYCAQIeAQIXgAAKCRBj+HX2P2d0oAEDAJ9lI+CNmb42z3+a6TnVusM6 +FI7oLwCfUwA1zEcRdsT3nIkoYh0iKxFSDFW5BA0EQsgkdhAQAJQbLXlgcJ/jq+Xh +Eujb77/eeftFJObNIRYD9fmJ7HFIXbUcknEpbs+cRH/nrj5dGSY3OT3jCXOUtvec +sCoX/CpZWL0oqDjAiZtNSFiulw5Gav4gHYkWKgKdSo+2rkavEPqKIVHvMeXaJtGT +d7v/AmL/P8T7gls93o5WFBOLtPbDvWqaKRy2U5TAhl1laiM0vGALRVjvSCgnGw9g +FpSnXbO3AfenUSjDzZujfGLHtU44ixHSS/D4DepiF3YaYLsN4CBqZRv6FbMZD5W3 +DnJY4kS1kH0MzdcF19TlcZ3itTCcGIt1tMKf84mccPoqdMzH7vumBGTeFEly5Afp +9berJcirqh2fzlunN0GS02z6SGWnjTbDlkNDxuxPSBbpcpNyD3jpYAUqSwRsZ/+5 +zkzcbGtDmvy9sJ5lAXkxGoIoQ1tEVX/LOHnh2NQHK8ourVOnr7MS0nozssITZJ5E +XqtHiREjiYEuPyZiVZKJHLWuYYaF+n40znnz3sJuXFRreHhHbbvRdlYUU5mJV+XZ +BLgKuS33NdpGeMIngnCc/9IQ6OZb6ixc94kbkd3w2PVr8CbKlu/IHTjWOO2mAo+D ++OydlYl23FiM3KOyMP1HcEOJMB/nwkMtrvd+522Lu9n77ktKfot9IPrQDIQTyXjR +3pCOFtCOBnk2tJHMPoG9jn9ah/LHAAMHEACDZ5I/MHGfmiKg2hrmqBu2J2j/deC8 +CpwcyDH1ovQ0gHvb9ESa+CVRU2Wdy2CD7Q9SmtMverB5eneL418iPVRcQdwRmQ2y +IH4udlBa6ce9HTUCaecAZ4/tYBnaC0Av/9l9tz14eYcwRMDpB+bnkhgF+PZ1KAfD +9wcY2aHbtsf3lZBc5h4owPJkxpe/BNzuJxW3q4VpSbLsZhwnCZ2wg7DRwP44wFIk +00ptmoBY59gsU6I40XtzrF8JDr0cA57xND5RY21Z8lnnYRE1Tc8h5REps9ZIxW3/ +yl91404bPLqxczpUHQAMSTAmBaStPYX1nS51uofOhLs5SKPCUmxfGKIOhsD0oLUn +78DnkONVGeXzBibSwwtbgfMzee4G8wSUfJ7w8WXz1TyanaGLnJ+DuKASSOrFoBCD +HEDuWZWgSL74NOQupFRk0gxOPmqU94Y8HziQWma/cETbmD83q8rxN+GM2oBxQkQG +xcbqMTHE7aVhV3tymbSWVaYhww3oIwsZS9oUIi1DnPEowS6CpVRrwdvLjLJnJzzV +O3AFPn9eZ1Q7R1tNx+zZ4OOfhvI/OlRJ3HBx2L53embkbdY9gFYCCdTjPyjKoDIx +kALgCajjCYMNUsAKNSd6mMCQ8TtvukSzkZS1RGKP27ohsdnzIVsiEAbxDMMcI4k1 +ul0LExUTCXSjeIhJBBgRAgAJBQJCyCR2AhsMAAoJEGP4dfY/Z3Sg19sAn0NDS8pb +qrMpQAxSb7zRTmcXEFd9AJ435H0ttP/NhLHXC9ezgbCMmpXMOQ== +=kRxT +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQG7BELII7wRBACFuaAvb11cIvjJK9LkZr4cYuYhLWh3DJdojNNnLNiym5OEksvY +05cw8OgqKtPzICU7o/mHXTWhzJYUt3i50/AeYygI8Q0uATS6RnDAKNlES1EMoHKz +2a5iFbYs4bm4IwlkvYd8uWjcu+U0YLbxir39u+anIc6eT+q3WiH/q3zDRwCgkT98 +cnIG8iO8PdwDSP8G4Lt6TYED/R45GvCzJ4onQALLE92KkLUz8aFWSl05r84kczEN +SxiP9Ss6m465RmwWHfwYAu4b+c4GeNyU8fIU2EM8cezchC+edEi3xu1s+pCV0Dk4 +18DGC8WKCICO30vBynuNmYg7W/7Zd4wtjss454fMW7+idVDNM701mmXBtI1nsBtG +7Z4tA/9FxjFbJK9jh24RewfjHpLYqcfCo2SsUjOwsnMZ5yg2yv9KyVVQhRqwmrqt +q8MRyjGmfoD9PPdCgvqgzy0hHvAHUtTm2zUczGTG+0g4hNIklxC/Mv6J4KE+NWTh +uB4acqofHyaw2WnKOuRUsoDi6rG5AyjNMyAK/vVcEGj7J1tk2wAAoJCUNy6awTkw +XfbLbpqh0fvDst7jDLa0I0VsZ2FtYWwgNDA5NiA8dGVzdDQwOTZAZXhhbXBsZS5v +cmc+iF4EExECAB4FAkLII7wCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQY/h1 +9j9ndKABAwCeNEOVK87EzXYbtxYBsnjrUI948NIAn2+f3BXiBFDV5NvqPwIZ0m77 +Fwy4nQRMBELIJHYQEACUGy15YHCf46vl4RLo2++/3nn7RSTmzSEWA/X5iexxSF21 +HJJxKW7PnER/564+XRkmNzk94wlzlLb3nLAqF/wqWVi9KKg4wImbTUhYrpcORmr+ +IB2JFioCnUqPtq5GrxD6iiFR7zHl2ibRk3e7/wJi/z/E+4JbPd6OVhQTi7T2w71q +mikctlOUwIZdZWojNLxgC0VY70goJxsPYBaUp12ztwH3p1Eow82bo3xix7VOOIsR +0kvw+A3qYhd2GmC7DeAgamUb+hWzGQ+Vtw5yWOJEtZB9DM3XBdfU5XGd4rUwnBiL +dbTCn/OJnHD6KnTMx+77pgRk3hRJcuQH6fW3qyXIq6odn85bpzdBktNs+khlp402 +w5ZDQ8bsT0gW6XKTcg946WAFKksEbGf/uc5M3GxrQ5r8vbCeZQF5MRqCKENbRFV/ +yzh54djUByvKLq1Tp6+zEtJ6M7LCE2SeRF6rR4kRI4mBLj8mYlWSiRy1rmGGhfp+ +NM55897CblxUa3h4R2270XZWFFOZiVfl2QS4Crkt9zXaRnjCJ4JwnP/SEOjmW+os +XPeJG5Hd8Nj1a/AmypbvyB041jjtpgKPg/jsnZWJdtxYjNyjsjD9R3BDiTAf58JD +La73fudti7vZ++5LSn6LfSD60AyEE8l40d6QjhbQjgZ5NrSRzD6BvY5/WofyxwAD +BxAAg2eSPzBxn5oioNoa5qgbtido/3XgvAqcHMgx9aL0NIB72/REmvglUVNlnctg +g+0PUprTL3qweXp3i+NfIj1UXEHcEZkNsiB+LnZQWunHvR01AmnnAGeP7WAZ2gtA +L//Zfbc9eHmHMETA6Qfm55IYBfj2dSgHw/cHGNmh27bH95WQXOYeKMDyZMaXvwTc +7icVt6uFaUmy7GYcJwmdsIOw0cD+OMBSJNNKbZqAWOfYLFOiONF7c6xfCQ69HAOe +8TQ+UWNtWfJZ52ERNU3PIeURKbPWSMVt/8pfdeNOGzy6sXM6VB0ADEkwJgWkrT2F +9Z0udbqHzoS7OUijwlJsXxiiDobA9KC1J+/A55DjVRnl8wYm0sMLW4HzM3nuBvME +lHye8PFl89U8mp2hi5yfg7igEkjqxaAQgxxA7lmVoEi++DTkLqRUZNIMTj5qlPeG +PB84kFpmv3BE25g/N6vK8TfhjNqAcUJEBsXG6jExxO2lYVd7cpm0llWmIcMN6CML +GUvaFCItQ5zxKMEugqVUa8Hby4yyZyc81TtwBT5/XmdUO0dbTcfs2eDjn4byPzpU +Sdxwcdi+d3pm5G3WPYBWAgnU4z8oyqAyMZAC4Amo4wmDDVLACjUnepjAkPE7b7pE +s5GUtURij9u6IbHZ8yFbIhAG8QzDHCOJNbpdCxMVEwl0o3gAAckBdfKuasiNUn5G +L5XRnSvaOFzftr8zteOlZChCSNvzH5k+i1j7RJbWq06OeKRywPzjfjgM2MvRzI43 +ICeISQQYEQIACQUCQsgkdgIbDAAKCRBj+HX2P2d0oNfbAJ9+G3SeXrk+dWwo9EGi +hqMi2GVTsgCfeoQJPsc8FLYUgfymc/3xqAVLUtg= +=Gjq6 +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (4, 'rsa2048', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQELBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP +ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2 +55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx +5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K +MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz +R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYptB5SU0EgMjA0OCA8cnNhMjA0 +OEBleGFtcGxlLm9yZz6JATQEEwECAB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgEC +HgECF4AACgkQnc+OnJvTHyQqHwf8DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liR +nrLuVlLBpdO6CWmMUzfKRvyZlx54GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzw +bLZyM5Gb3lsE/FEmE7Dxw/0Utf59uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDP +D3dnU4uzKPhMcjnSN00pzjusP7C9NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv +9bgGWopumlOkt8Zu5YG6+CtTbJXprPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhv +S3NZKoJ/1DrGgoDAu1mGkM4KvLAxfDs/qQ9dZhtEmDbKPLTVEA== +=lR4n +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQOWBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP +ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2 +55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx +5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K +MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz +R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYpAAf/QZsrrz0c7dgWwGqMIpw6 +fP+/lLa74+fa2CFRWtYowEiKsfDg/wN7Ua07036dNhPa8aZPsU6SRzm5PybKOURe +D9pNt0FxJkX0j5pCWfjSJgTbc1rCdqZ/oyBk/U6pQtf//zfw3PbDl7I8TC6GOt2w +5NgcXdsWHP7LAmPctOVUyzFsenevR0MFTHkMbmKI1HpFm8XN/e1Fl+qIAD+OagTF +5B32VvpoJtkh5nxnIuToNJsa9Iy7F9MM2CeFOyTMihMcjXKBBUaAYoF115irBvqu +7N/qWmzqLg8yxBZ56mh6meCF3+67VA2y7fL8rhw2QuqgLg1JFlKAVL+9crCSrn// +GQQA1kT7FytW6BNOffblFYZkrJer3icoRDqa/ljgH/yVaWoVT1igy0E9XzYO7MwP +2usj/resLy0NC1qCthk51cZ/wthooMl88e5Wb4l5FYwBEac7muSBTo4W8cAH1hFj +TWL6XAGvEzGX3Mt9pn8uYGlQLZAhJoNCAU2EOCbN1PchDvsEAOWNKYesuUVk8+sQ +St0NDNhd9BWtTWTHkCZb1dKC3JTfr9PqkTBLrWFbYjkOtvdPAW7FDaXXXZfdH1jH +WfwP3Q+I6sqgSaWpCS4dBAns3/RVtO7czVgyIwma04iIvJqderYrfvkUq95KfwP2 +V8wXkhrPPPxyrg5y3wQlpY2jb5RBBAC17SK1ms+DBtck4vpdjp3SJ32SbyC/DU30 +89Q12j74S7Zdu1qZlKnvy3kWPYX/hMuSzGZ+mLVJNFEqH2X01aFzppYz0hdI9PGB +9tTFEqZWQL9ZkXfjc79Cgnt12pNukRbtw0N/kyutOdIFHVT79wVAd+powqziXJsC +Kc+4xjwSCkZitB5SU0EgMjA0OCA8cnNhMjA0OEBleGFtcGxlLm9yZz6JATQEEwEC +AB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQnc+OnJvTHyQqHwf8 +DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liRnrLuVlLBpdO6CWmMUzfKRvyZlx54 +GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzwbLZyM5Gb3lsE/FEmE7Dxw/0Utf59 +uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDPD3dnU4uzKPhMcjnSN00pzjusP7C9 +NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv9bgGWopumlOkt8Zu5YG6+CtTbJXp +rPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhvS3NZKoJ/1DrGgoDAu1mGkM4KvLAx +fDs/qQ9dZhtEmDbKPLTVEA== +=WKAv +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (5, 'psw-elg1024', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx +MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV +AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc +AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd +ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P +sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI ++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9 +6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF +k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v +iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F +ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80 +=RWci +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQHpBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQP4HAwImKZ5q2QwT +D2DDAY/IQBjes7WgqZeacfLPDoB8ecD/KLoSCH6Z3etvbPHSOKiazxoJ962Ix74H +ZAE6ZbMTtl5dZW1ptB9FbGdhbWFsIDEwMjQgPHRlc3RAZXhhbXBsZS5vcmc+iF4E +ExECAB4FAkLIIUgCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQHCm8DRgXc2Q6 +2wCfXKegLIzoYi8cM57DCYXhn+MZB/MAn1D4zAi5uLQBJ8mJ9oQzbewgfAeinQFf +BELIIUoQBACHlI1tskwCwn6xBtxYTN2S7wB1gHnNjtDJ/0Q6+fSDf1vMA2HGaZvq +BtGVA3SKjKaUPCtpMpQB+4JmA1yD/Q+zEUZv2cNy+MhXyp1WmerU+7MwW4FuYYjr +B6Ds+PTV2U+I7TC3WUtf5K5uS2ptjEj6HfVzdENmrGP4oCDFHxg0YwADBgP7BCzY +d7Vnn6ng5KAwRGI3MXRpxlRiZzR77/3qB8vR+3cg/5JIWjqEQGuQpWFkqg9JJGvk +qk1oShuF3GycqNRrD+rKhoVw0zNviwWTzCrlSUKRH6Hva7qG/wW6V4JXxIlRPVTY +mO2VclHY97L2J+Hytl+7ntRt3B0drS+JZzgOKWH+BwMCJimeatkMEw9gRkFjt4Xa +9rX8awMBE5+vVcGKv/DNiCvJnlYvSdCj8VfuHsYFliiJo6u17NJon+K43e3yvDNk +f631VOVanGEz7TyqOkWQiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCe +IUWi3DXHZf6ivK7dDec22bGgoekAn0dTuPDvJ2Dfd0j0nyBWSuaxJnb/ +=SNvr +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (6, 'rsaenc2048', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQELBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj +UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW +czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT +4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ +dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4 +NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYptCVSU0EgMjA0OCBFbmMgPHJz +YTIwNDhlbmNAZXhhbXBsZS5vcmc+iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMV +AgMDFgIBAh4BAheAAAoJEMiZ6pNEGVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8 +JCaxHa31wH3PKqGtq2M+cpb2rXf7gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw ++IkhihEb5bWxQBNj+3zAFs1YX6v2HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzku +UOhcgfpBgIt3Q+MpT6M2+OIF7lVfSb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQ +RJ6DWH61F8fFIDJg1z+A/Obx4fqX6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO8 +0g/oVYBbuvOYedffDBeQarhERZ5W2TnIE+nqY61YOLBqosliygdZTXULzNi5AQsE +QuvaugEIAOuCJZdkzORA6e1lr81Lnr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPX +z1YIrMTu+1rMIiy10IWbA6zgMTpzPhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfK +lAXIxJurCHXTbEa+YvPdn76vJ3HsXOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zE +FZcAoKbFqAAjDLEai64SoOFh0W3CsD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9U +rHlolqYNhYze/uRLyfnUx9PN4r/GhEzauyDMV0smo91uB3aewPft+eCpmeWnu0PF +JVK4xyRmhIq2rVCw16a1pBJirvGM+y0ABimJAR8EGAECAAkFAkLr2roCGwwACgkQ +yJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+1iqYE0B0 +WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwarANhHxkWg +w/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx2eVM1k+X +dmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOyla+wJdro +PYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CChbt6LhKh +CLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug== +=pwU2 +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQOWBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj +UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW +czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT +4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ +dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4 +NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYpAAf9GuKpxrXp267eSPw9ZeSw +Ik6ob1I0MHbhhHeaXQnF0SuOViJ1+Bs74hUB3/F5fqrnjVLIS/ysYzegYpbpXOIa +MZwYcp2e+dpmVb7tkGQgzXH0igGtBQBqoSUVq9mG2XKPVh2JmiYgOH6GrHSGmnCq +GCgEK4ezSomB/3OtPFSjAxOlSw6dXSkapSxW3pEGvCdaWd9p8yl4rSpGsZEErPPL +uSbZZrHtWfgq5UXdPeE1UnMlBcvSruvpN4qgWMgSMs4d2lXvzXJLcht/nryP+atT +H1gwnRmlDCVv5BeJepKo3ORJDvcPlXkJPhqS9If3BhTqt6QgQEFI4aIYYZOZpZoi +2QQA2Zckzktmsc1MS04zS9gm1CbxM9d2KK8EOlh7fycRQhYYqqavhTBH2MgEp+Dd +ZtuEN5saNDe9x/fwi2ok1Bq6luGMWPZU/nZe7fxadzwfliy/qPzStWFW3vY9mMLu +6uEqgjin/lf4YrAswXDZaEc5e4GuNgGfwr27hpjxE1jg3PsEAPMqXEOMT2yh+yRu +DlLRbFhYOI4aUHY2CGoQQONnwv2O5gFvmOcPlg3J5lvnwlOYCx0c3bDxAtHyjPJq +FAZqcJBaB9RDhKHwlWDrbx/6FPH2SuKE+u4msIhPFin4V3FAP+yTem/TKrdnaWy6 +EUrhCWTXVRTijBaCudfjFd/ipHZbA/0dv7UAcoWK6kiVLzyE+jOvtN+ZxTzxq7CW +mlFPgAC966hgJmz9IXqadtMgPAoL3PK9q1DbPM3JhsQcJrNzTJqZrdN1/kPU0HHa ++aof1BVy3wSvp2mXgaRUULStyhUIyBRM6hAYp3/MoWEYn/bwr+zQkIU8Zsk6OsZ6 +q1xE3cowrUWFtCVSU0EgMjA0OCBFbmMgPHJzYTIwNDhlbmNAZXhhbXBsZS5vcmc+ +iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJEMiZ6pNE +GVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8JCaxHa31wH3PKqGtq2M+cpb2rXf7 +gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw+IkhihEb5bWxQBNj+3zAFs1YX6v2 +HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzkuUOhcgfpBgIt3Q+MpT6M2+OIF7lVf +Sb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQRJ6DWH61F8fFIDJg1z+A/Obx4fqX +6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO80g/oVYBbuvOYedffDBeQarhERZ5W +2TnIE+nqY61YOLBqosliygdZTXULzNidA5YEQuvaugEIAOuCJZdkzORA6e1lr81L +nr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPXz1YIrMTu+1rMIiy10IWbA6zgMTpz +PhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfKlAXIxJurCHXTbEa+YvPdn76vJ3Hs +XOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zEFZcAoKbFqAAjDLEai64SoOFh0W3C +sD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9UrHlolqYNhYze/uRLyfnUx9PN4r/G +hEzauyDMV0smo91uB3aewPft+eCpmeWnu0PFJVK4xyRmhIq2rVCw16a1pBJirvGM ++y0ABikAB/oC3z7lv6sVg+ngjbpWy9lZu2/ECZ9FqViVz7bUkjfvSuowgpncryLW +4EpVV4U6mMSgU6kAi5VGT/BvYGSAtnqDWGiPs7Kk+h4Adz74bEAXzU280pNBtSfX +tGvzlS4a376KzYFSCJDRBdMebEhJMbY0wQmR8lTZu5JSUI4YYEuN0c7ckdsw8w42 +QWTLonG8HC6h8UPKS0EAcaCo7tFubMIesU6cWuTYucsHE+wjbADjuSNX968qczNe +NoL2BUznXOQoPu6HQO4/8cr7ib+VQkB2bHQcMoZazPUStIID1e4CL4XcxfuAmT8o +3XDvMLgVqNp5W2f8Mzmk3/DbtsLXLOv5BADsCzQpseC8ikSYJC72hcon1wlUmGeH +3qgGiiHhYXFa18xgI5juoO8DaWno0rPPlgr36Y8mSB5qjYHMXwjKnKyUmt11H+hU ++6uk4hq3Rjd8l+vfuOSr1xoTrtBUg9Rwfw6JVo0DC+8CWg4oBWsLXVM6KQXPFdJs +8kyFQplR/iP1XQQA/2tbDANjAYGNNDjJO9/0kEnSAUyYMasFJDrA2q17J5CroVQw +QpMmWwdDkRANUVPKnWHS5sS65BRc7UytKe2f3A3ZInGXJIK2Hl+TzapWYcYxql+4 +ol5mEDDMDbhEE8Wmj9KyB6iifdLI0K+yxNb9T4Jpj3J18+St+G8+9AcFcBEEAM1b +M9C+/05cnV8gjcByqH9M9ypo8fzPvMKVXWwCLQXpaL50QIkzLURkiMoEWrCdELaA +sVPotRzePTIQ1ooLeDxd1gRnDqjZiIR0kwmv6vq8tfzY96O2ZbGWFI5eth89aWEJ +WB8AR3zYcXpwJLwPuhXW2/NlZF0bclJ3jNzAfTIeQmeJAR8EGAECAAkFAkLr2roC +GwwACgkQyJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+ +1iqYE0B0WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwar +ANhHxkWgw/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx +2eVM1k+XdmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOy +la+wJdroPYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CC +hbt6LhKhCLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug== +=UKh3 +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (7, 'rsaenc2048-psw', ' +same key with password +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.11 (GNU/Linux) + +lQPEBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj +UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW +czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT +4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ +dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4 +NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYp/gcDCNnoEKwFo86JYCE1J92R +HRQ7DoyAZpW1O0dTXL8Epk0sKsKDrCJOrIkDymsjfyBexADIeqOkioy/50wD2Mku +CVHKWO2duAiJN5t/FoRgpR1/Q11K6QdfqOG0HxwfIXLcPv7eSIso8kWorj+I01BP +Fn/atGEbIjdWaz/q2XHbu0Q3x6Et2gIsbLRVMhiYz1UG9uzGJ0TYCdBa2SFhs184 +52akMpD+XVdM0Sq9/Cx40Seo8hzERB96+GXnQ48q2OhlvcEXiFyD6M6wYCWbEV+6 +XQVMymbl22FPP/bD9ReQX2kjrkQlFAtmhr+0y8reMCbcxwLuQfA3173lSPo7jrbH +oLrGhkRpqd2bYCelqdy/XMmRFso0+7uytHfTFrUNfDWfmHVrygoVrNnarCbxMMI0 +I8Q+tKHMThWgf0rIOSh0+w38kOXFCEqEWF8YkAqCrMZIlJIed78rOCFgG4aHajZR +D8rpXdUOIr/WeUddK25Tu8IuNJb0kFf12IMgNh0nS+mzlqWiofS5kA0TeB8wBV6t +RotaeyDNSsMoowfN8cf1yHMTxli+K1Tasg003WVUoWgUc+EsJ5+KTNwaX5uGv0Cs +j6dg6/FVeVRL9UsyF+2kt7euX3mABuUtcVGx/ZKTq/MNGEh6/r3B5U37qt+FDRbw +ppKPc2AP+yBUWsQskyrxFgv4eSpcLEg+lgdz/zLyG4qW4lrFUoO790Cm/J6C7/WQ +Z+E8kcS8aINJkg1skahH31d59ZkbW9PVeJMFGzNb0Z2LowngNP/BMrJ0LT2CQyLs +UxbT16S/gwAyUpJnbhWYr3nDdlwtC0rVopVTPD7khPRppcsq1f8D70rdIxI4Ouuw +vbjNZ1EWRJ9f2Ywb++k/xgSXwJkGodUlrUr+3i8cv8mPx+fWvif9q7Y5Ex1wCRa8 +8FAj/o+hEbQlUlNBIDIwNDggRW5jIDxyc2EyMDQ4ZW5jQGV4YW1wbGUub3JnPokB +NAQTAQIAHgUCQuvabQIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRDImeqTRBlV +WRzJCACbRhx2fYjPGKta69M5dS+kr5UD/CQmsR2t9cB9zyqhratjPnKW9q13+4AG +P3aByT14IH1c5Mha8rJkNYD2wxmC8jrrcPiJIYoRG+W1sUATY/t8wBbNWF+r9h11 +m0lEpsmNVff/jU7SpNN6JQ3P7MHd5V85LlDoXIH6QYCLd0PjKU+jNvjiBe5VX0m9 +a1nacE3xoWc1vbM0DnqEuID78Qgkcrmm0ESeg1h+tRfHxSAyYNc/gPzm8eH6l+hj +gOvUc4Gd6LpBQSF8TcFfT2TZwJh7WVWDvNIP6FWAW7rzmHnX3wwXkGq4REWeVtk5 +yBPp6mOtWDiwaqLJYsoHWU11C8zYnQPEBELr2roBCADrgiWXZMzkQOntZa/NS56+ +CczLFQRQPl/8iJAW1eql/wOJ1UiwGSjT189WCKzE7vtazCIstdCFmwOs4DE6cz4S +UX4HjzjYHZwmMiuSrIefwuZ7cysMBsMXypQFyMSbqwh102xGvmLz3Z++rydx7Fzl +1RC/ny2+FN5dzYPO2DNtNi4dR2tjHktsxBWXAKCmxagAIwyxGouuEqDhYdFtwrA9 +Qy+M5n6fmGa1Dx07WWnbIud4uCilv8LPVKx5aJamDYWM3v7kS8n51MfTzeK/xoRM +2rsgzFdLJqPdbgd2nsD37fngqZnlp7tDxSVSuMckZoSKtq1QsNemtaQSYq7xjPst +AAYp/gcDCNnoEKwFo86JYAsxoD+wQ0zBi5RBM5EphXTpM1qKxmigsKOvBSaMmr0y +VjHtGY3poyV3t6VboOGCsFcaKm0tIdDL7vrxxwyYESETpF29b7QrYcoaLKMG7fsy +t9SUI3UV2H9uUquHgqHtsqz0jYOgm9tYnpesgQ/kOAWI/tej1ZJXUIWEmZMH/W6d +ATNvZ3ivwApfC0qF5G3oPgBSoIuQ/8I+pN/kmuyNAnJWNgagFhA/2VFBvh5XgztV +NW7G//KpR1scsn140SO/wpGBM3Kr4m8ztl9w9U6a7NlQZ2ub3/pIUTpSzyLBxJZ/ +RfuZI7ROdgDMKmEgCYrN2kfp0LIxnYL6ZJu3FDcS4V098lyf5rHvB3PAEdL6Zyhd +qYp3Sx68r0F4vzk5iAIWf6pG2YdfoP2Z48Pmq9xW8qD9iwFcoz9oAzDEMENn6dfq +6MzfoaXEoYp8cR/o+aeEaGUtYBHiaxQcJYx35B9IhsXXA49yRORK8qdwhSHxB3NQ +H3pUWkfw368f/A207hQVs9yYXlEvMZikxl58gldCd3BAPqHm/XzgknRRNQZBPPKJ +BMZebZ22Dm0qDuIqW4GXLB4sLf0+UXydVINIUOlzg+S4jrwx7eZqb6UkRXTIWVo5 +psTsD14wzWBRdUQHZOZD33+M8ugmewvLY/0Uix+2RorkmB7/jqoZvx/MehDwmCZd +VH8sb2wpZ55sj7gCXxvrfieQD/VeH54OwjjbtK56iYq56RVD0h1az8xDY2GZXeT7 +J0c3BGpuoca5xOFWr1SylAr/miEPxOBfnfk8oZQJvZrjSBGjsTbALep2vDJk8ROD +sdQCJuU1RHDrwKHlbUL0NbGRO2juJGsatdWnuVKsFbaFW2pHHkezKuwOcaAJv7Xt +8LRF17czAJ1uaLKwV8Paqx6UIv+089GbWZi7HIkBHwQYAQIACQUCQuvaugIbDAAK +CRDImeqTRBlVWS7XCACDVstKM+SHD6V0bkfO6ampHzj4krKjN0lonN5+7b7WKpgT +QHRYvPY8lUiIrjXGISQqEG9M5Bi5ea1aoBZem0P3U/lKheg0lYtA7dM3BqsA2EfG +RaDD9M5TFCqhy2VFR6Pk0MP7h5bkb2VxLUUQa4oNa1fT3q7zS875NvImO/HZ5UzW +T5d2Z5iwY6I2AOKYKt4kZhzXgbt5j2O3biDDXSfWwwAojWqbqVygepn047KVr7Al +2ug9hkY7tHz7U71HbZasroFgNPmP/UnAxmps4RKM28MRVPTI4cKUIdE3gIKFu3ou +EqEItQ13P+50i3QkALpz8d08tJbceeYzf6I2P4q6 +=QFm5 +-----END PGP PRIVATE KEY BLOCK----- +'); +-- elg1024 / aes128 +insert into encdata (id, data) values (1, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQEOA9k2z2S7c/RmEAQAgVWW0DeLrZ+1thWJGBPp2WRFL9HeNqqWHbKJCXJbz1Uy +faUY7yxVvG5Eutmo+JMiY3mg23/DgVVXHQZsTWpGvGM6djgUNGKUjZDbW6Nog7Mr +e78IywattCOmgUP9vIwwg3OVjuDCN/nVirGQFnXpJBc8DzWqDMWRWDy1M0ZsK7AD +/2JTosSFxUdpON0DKtIY3GLzmh6Nk3iV0g8VgJKUBT1rhCXuMDj3snm//EMm7hTY +PlnObq4mIhgz8NqprmhooxnU0Kapofb3P3wCHPpU14zxhXY8iKO/3JhBq2uFcx4X +uBMwkW4AdNxY/mzJZELteTL8Tr0s7PISk+owb4URpG3n0jsBc0CVULxrjh5Ejkdw +wCM195J6+KbQxOOFQ0b3uOVvv4dEgd/hRERCOq5EPaFhlHegyYJ7YO842vnSDA== +=PABx +-----END PGP MESSAGE----- +'); +-- elg2048 / blowfish +insert into encdata (id, data) values (2, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQIOAywibh/+XMfUEAf+OINhBngEsw4a/IJIeJvUgv1gTQzBwOdQEuc/runr4Oa8 +Skw/Bj0X/zgABVZLem1a35NHaNwaQaCFwMQ41YyWCu+jTdsiyX/Nw0w8LKKz0rNC +vVpG6YuV7Turtsf8a5lXy1K0SHkLlgxQ6c76GS4gtSl5+bsL2+5R1gSRJ9NXqCQP +OHRipEiYwBPqr5R21ZG0FXXNKGOGkj6jt/M/wh3WVtAhYuBI+HPKRfAEjd/Pu/eD +e1zYtkH1dKKFmp44+nF0tTI274xpuso7ShfKYrOK3saFWrl0DWiWteUinjSA1YBY +m7dG7NZ8PW+g1SZWhEoPjEEEHz3kWMvlKheMRDudnQf/dDyX6kZVIAQF/5B012hq +QyVewgTGysowFIDn01uIewoEA9cASw699jw9IoJp+k5WZXnU+INllBLzQxniQCSu +iEcr0x3fYqNtj9QBfbIqyRcY6HTWcmzyOUeGaSyX76j+tRAvtVtXpraFFFnaHB70 +YpXTjLkp8EBafzMghFaKDeXlr2TG/T7rbwcwWrFIwPqEAUKWN5m97Q3eyo8/ioMd +YoFD64J9ovSsgbuU5IpIGAsjxK+NKzg/2STH7zZFEVCtgcIXsTHTZfiwS98/+1H9 +p1DIDaXIcUFV2ztmcKxh9gt2sXRz1W+x6D8O0k3nanU5yGG4miLKaq18fbcA0BD1 ++NIzAfelq6nvvxYKcGcamBMgLo5JkZOBHvyr6RsAKIT5QYc0QTjysTk9l0Am3gYc +G2pAE+3k +=TBHV +-----END PGP MESSAGE----- +'); +-- elg4096 / aes256 +insert into encdata (id, data) values (3, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQQOA7aFBP0Sjh/5EA/+JCgncc8IZmmRjPStWnGf9tVJhgHTn+smIclibGzs0deS +SPSCitzpblwbUDvu964+/5e5Q1l7rRuNN+AgETlEd4eppv7Swn2ChdgOXxRwukcT +Nh3G+PTFvD4ayi7w1db3qvXIt0MwN4Alt436wJmK1oz2Ka9IcyO+wHWrDy1nSGSx +z5x7YEj+EZPgWc/YAvudqE8Jpzd/OT5zSHN09UFkIAk6NxisKaIstbEGFgpqtoDZ +1SJM84XAdL2IcaJ3YY7k/yzwlawhsakKd4GSd5vWmAwvyzzbSiBMfKsDE16ePLNU +ZBF7CzmlCBPZ7YrFAHLpXBXXkCQvzD2BEYOjse50ZEfJ036T7950Ozcdy1EQbGon +nyQ4Gh0PBpnMcBuiXOceWuYzhlzFOzDtlVKdNTxFRDcbEyW2jo9xQYvCCLnYy8EH +2M7S8jCtVYJBbn63a82ELv+3+kWYcsvBJv2ZVBh4ncrBu9o0P+OYS7ApoOU+j6p2 ++t0RXHksqXS1YiUwYF5KSw09EbYMgNZ9G04Px/PxLU6fSC9iDrGX7Xt3kOUP0mku +C518fPckT0zzRXqfFruJNRzDytW50KxkOQZzU1/Az1YlYN9QzWeU4EtLPb2fftZo +D0qH/ln+f9Op5t6sD2fcxZVECU1b/bFtZsxvwH406YL+UQ7hU/XnZrzVVzODal8P +/j1hg7v7BdJqu1DTp9nFWUuwMFcYAczuXn29IG183NZ7Ts4whDeYEhS8eNoLPX4j +txY12ILD/w/3Q4LoW/hPa6OdfEzsn0U5GLf1WiGmJE1H6ft2U/xUnerc/u0kt+FU +WAisArd4MuKtf7B5Vu/VF3kUdrR0hTniUKUivmC4o1jSId31Dufxj4aadVyldXAr +6TNBcdyragZjxEZ6hsBCYzA0Rd1a8atd6OaQoIEEfAzCu5Ks29pydHErStYGjWJ1 +KA5KPLVvjbHpDmRhlCcm8vgpYQsBYEB5gE9fx5yCTlsVhCB6y23h7hfdMqerDqkO +ZOPsO5h+tiHCdIrQ36sMjuINy1/K2rYcXd+Crh2iHcfidpU9fvDz2ihTRNQlhjuT +0cQZM5JhctEx4VXF4LDctRhit7Hn0iqsk604woQfJVvP8O673xSXT/kBY0A/v9C0 +3C4YoFNeSaKwbfZQ/4u1ZFPJxK2IIJa8UGpyAUewLMlzGVVagljybv/f4Z9ERAhy +huq5sMmw8UPsrJF2TUGHz5WSIwoh0J/qovoQI09I9sdEnFczDvRavMO2Mldy3E5i +exz9oewtel6GOmsZQSYWT/vJzbYMmvHNmNpVwwoKrLV6oI3kyQ80GHBwI1WlwHoK +2iRB0w8q4VVvJeYAz8ZIp380cqC3pfO0uZsrOx4g3k4X0jsB5y7rF5xXcZfnVbvG +DYKcOy60/OHMWVvpw6trAoA+iP+cVWPtrbRvLglTVTfYmi1ToZDDipkALBhndQ== +=L/M/ +-----END PGP MESSAGE----- +'); +-- rsaenc2048 / aes128 +insert into encdata (id, data) values (4, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQEMA/0CBsQJt0h1AQf+JyYnCiortj26P11zk28MKOGfWpWyAhuIgwbJXsdQ+e6r +pEyyqs9GC6gI7SNF6+J8B/gsMwvkAL4FHAQCvA4ZZ6eeXR1Of4YG22JQGmpWVWZg +DTyfhA2vkczuqfAD2tgUpMT6sdyGkQ/fnQ0lknlfHgC5GRx7aavOoAKtMqiZW5PR +yae/qR48mjX7Mb+mLvbagv9mHEgQSmHwFpaq2k456BbcZ23bvCmBnCvqV/90Ggfb +VP6gkSoFVsJ19RHsOhW1dk9ehbl51WB3zUOO5FZWwUTY9DJvKblRK/frF0+CXjE4 +HfcZXHSpSjx4haGGTsMvEJ85qFjZpr0eTGOdY5cFhNJAAVP8MZfji7OhPRAoOOIK +eRGOCkao12pvPyFTFnPd5vqmyBbdNpK4Q0hS82ljugMJvM0p3vJZVzW402Kz6iBL +GQ== +=XHkF +-----END PGP MESSAGE----- +'); +-- rsaenc2048 / aes128 (not from gnupg) +insert into encdata (id, data) values (5, ' +-----BEGIN PGP MESSAGE----- + +wcBMA/0CBsQJt0h1AQgAzxZ8j+OTeZ8IlLxfZ/mVd28/gUsCY+xigWBk/anZlK3T +p2tNU2idHzKdAttH2Hu/PWbZp4kwjl9spezYxMqCeBZqtfGED88Y+rqK0n/ul30A +7jjFHaw0XUOqFNlST1v6H2i7UXndnp+kcLfHPhnO5BIYWxB2CYBehItqtrn75eqr +C7trGzU/cr74efcWagbCDSNjiAV7GlEptlzmgVMmNikyI6w0ojEUx8lCLc/OsFz9 +pJUAX8xuwjxDVv+W7xk6c96grQiQlm+FLDYGiGNXoAzx3Wi/howu3uV40dXfY+jx +3WBrhEew5Pkpt1SsWoFnJWOfJ8GLd0ec8vfRCqAIVdLgAeS7NyawQYtd6wuVrEAj +5SMg4Thb4d+g45RksuGLHUUr4qO9tiXglODa4InhmJfgNuLk+RGz4LXjq8wepEmW +vRbgFOG54+Cf4C/gC+HkreDm5JKSKjvvw4B/jC6CDxq+JoziEe2Z1uEjCuEcr+Es +/eGzeOi36BejXPMHeKxXejj5qBBHKV0pHVhZSgffR0TtlXdB967Yl/5agV0R89hI +7Gw52emfnH4Z0Y4V0au2H0k1dR/2IxXdJEWSTG7Be1JHT59p9ei2gSEOrdBMIOjP +tbYYUlmmbvD49bHfThkDiC+oc9947LgQsk3kOOLbNHcjkbrjH8R5kjII4m/SEZA1 +g09T+338SzevBcVXh/cFrQ6/Et+lyyO2LJRUMs69g/HyzJOVWT2Iu8E0eS9MWevY +Qtrkrhrpkl3Y02qEp/j6M03Yu2t6ZF7dp51aJ5VhO2mmmtHaTnCyCc8Fcf72LmD8 +blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA= +=PHJ1 +-----END PGP MESSAGE----- +'); +-- successful decrypt +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=1 and encdata.id=1; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=2 and encdata.id=2; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=3 and encdata.id=3; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=6 and encdata.id=4; + pgp_pub_decrypt +----------------- + Secret message. +(1 row) + +-- wrong key +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=2 and encdata.id=1; +ERROR: Wrong key +-- sign-only key +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=4 and encdata.id=1; +ERROR: No encryption key found +-- rsa: password-protected secret key, wrong password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123') +from keytbl, encdata where keytbl.id=7 and encdata.id=4; +ERROR: Wrong key or corrupt data +-- rsa: password-protected secret key, right password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool') +from keytbl, encdata where keytbl.id=7 and encdata.id=4; + pgp_pub_decrypt +----------------- + Secret message. +(1 row) + +-- password-protected secret key, no password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=5 and encdata.id=1; +ERROR: Need password for secret key +-- password-protected secret key, wrong password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo') +from keytbl, encdata where keytbl.id=5 and encdata.id=1; +ERROR: Wrong key or corrupt data +-- password-protected secret key, right password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool') +from keytbl, encdata where keytbl.id=5 and encdata.id=1; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +-- test for a short read from prefix_init +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=6 and encdata.id=5; +ERROR: Wrong key or corrupt data diff --git a/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out new file mode 100644 index 0000000..f41c6c9 --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-pubkey-decrypt_1.out @@ -0,0 +1,652 @@ +-- +-- PGP Public Key Encryption +-- +-- As most of the low-level stuff is tested in symmetric key +-- tests, here's only public-key specific tests +create table keytbl ( + id int4, + name text, + pubkey text, + seckey text +); +create table encdata ( + id int4, + data text +); +insert into keytbl (id, name, pubkey, seckey) +values (1, 'elg1024', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx +MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV +AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc +AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd +ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P +sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI ++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9 +6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF +k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v +iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F +ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80 +=RWci +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQG7BELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQAAAnj4i4st+s+C6 +WKTIDcL1Iy0Saq8lCp60H0VsZ2FtYWwgMTAyNCA8dGVzdEBleGFtcGxlLm9yZz6I +XgQTEQIAHgUCQsghSAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRAcKbwNGBdz +ZDrbAJ9cp6AsjOhiLxwznsMJheGf4xkH8wCfUPjMCLm4tAEnyYn2hDNt7CB8B6Kd +ATEEQsghShAEAIeUjW2yTALCfrEG3FhM3ZLvAHWAec2O0Mn/RDr59IN/W8wDYcZp +m+oG0ZUDdIqMppQ8K2kylAH7gmYDXIP9D7MRRm/Zw3L4yFfKnVaZ6tT7szBbgW5h +iOsHoOz49NXZT4jtMLdZS1/krm5Lam2MSPod9XN0Q2asY/igIMUfGDRjAAMGA/sE +LNh3tWefqeDkoDBEYjcxdGnGVGJnNHvv/eoHy9H7dyD/kkhaOoRAa5ClYWSqD0kk +a+SqTWhKG4XcbJyo1GsP6sqGhXDTM2+LBZPMKuVJQpEfoe9ruob/BbpXglfEiVE9 +VNiY7ZVyUdj3svYn4fK2X7ue1G3cHR2tL4lnOA4pYQAA9030E4u2ZKOfJBpUM+EM +m9VmsGjaQZV4teB0R/q3W8sRIYhJBBgRAgAJBQJCyCFKAhsMAAoJEBwpvA0YF3Nk +7a8AniFFotw1x2X+oryu3Q3nNtmxoKHpAJ9HU7jw7ydg33dI9J8gVkrmsSZ2/w== +=nvqq +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (2, 'elg2048', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n +vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke +5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO +RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij +8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl +Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt +J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/ +T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5 +0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6a7QjRWxnYW1hbCAy +MDQ4IDx0ZXN0MjA0OEBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgiCgIbAwYLCQgH +AwIDFQIDAxYCAQIeAQIXgAAKCRBI6c1W/qZo29PDAKCG724enIxRog1j+aeCp/uq +or6mbwCePuKy2/1kD1FvnhkZ/R5fpm+pdm25Ag0EQsgiIhAIAJI3Gb2Ehtz1taQ9 +AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMX +MhoWyw8ZF5Zs1mLIjFGVorePrm94N3MNPWM7x9M36bHUjx0vCZKFIhcGY1g+htE/ +QweaJzNVeA5z4qZmik41FbQyQSyHa3bOkTZu++/U6ghP+iDp5UDBjMTkVyqITUVN +gC+MR+da/I60irBVhue7younh4ovF+CrVDQJC06HZl6CAJJyA81SmRfi+dmKbbjZ +LF6rhz0norPjISJvkIqvdtM4VPBKI5wpgwCzpEqjuiKrAVujRT68zvBvJ4aVqb11 +k5QdJscAAwUH/jVJh0HbWAoiFTe+NvohfrA8vPcD0rtU3Y+siiqrabotnxJd2NuC +bxghJYGfNtnx0KDjFbCRKJVeTFok4UnuVYhXdH/c6i0/rCTNdeW2D6pmR4GfBozR +Pw/ARf+jONawGLyUj7uq13iquwMSE7VyNuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0R +QsetMq/iNBWraayKZnWUd+eQqNzE+NUo7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiF +Z1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qunfGW00ZMMTCWabg0ZgxPzMfMeIcm6525A +Yn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/RdWaISQQYEQIACQUCQsgiIgIbDAAKCRBI +6c1W/qZo25ZSAJ98WTrtl2HiX8ZqZq95v1+9cHtZPQCfZDoWQPybkNescLmXC7q5 +1kNTmEU= +=8QM5 +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQG7BELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n +vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke +5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO +RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij +8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl +Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt +J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/ +T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5 +0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6awAAn2F+iNBElfJS +8azqO/kEiIfpqu6/DQG0I0VsZ2FtYWwgMjA0OCA8dGVzdDIwNDhAZXhhbXBsZS5v +cmc+iF0EExECAB4FAkLIIgoCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQSOnN +Vv6maNvTwwCYkpcJmpl3aHCQdGomz7dFohDgjgCgiThZt2xTEi6GhBB1vuhk+f55 +n3+dAj0EQsgiIhAIAJI3Gb2Ehtz1taQ9AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D +29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMXMhoWyw8ZF5Zs1mLIjFGVorePrm94N3MN +PWM7x9M36bHUjx0vCZKFIhcGY1g+htE/QweaJzNVeA5z4qZmik41FbQyQSyHa3bO +kTZu++/U6ghP+iDp5UDBjMTkVyqITUVNgC+MR+da/I60irBVhue7younh4ovF+Cr +VDQJC06HZl6CAJJyA81SmRfi+dmKbbjZLF6rhz0norPjISJvkIqvdtM4VPBKI5wp +gwCzpEqjuiKrAVujRT68zvBvJ4aVqb11k5QdJscAAwUH/jVJh0HbWAoiFTe+Nvoh +frA8vPcD0rtU3Y+siiqrabotnxJd2NuCbxghJYGfNtnx0KDjFbCRKJVeTFok4Unu +VYhXdH/c6i0/rCTNdeW2D6pmR4GfBozRPw/ARf+jONawGLyUj7uq13iquwMSE7Vy +NuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0RQsetMq/iNBWraayKZnWUd+eQqNzE+NUo +7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiFZ1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qun +fGW00ZMMTCWabg0ZgxPzMfMeIcm6525AYn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/R +dWYAAVQKFPXbRaxbdArwRVXMzSD3qj/+VwwhwEDt8zmBGnlBfwVdkjQQrDUMmV1S +EwyISQQYEQIACQUCQsgiIgIbDAAKCRBI6c1W/qZo25ZSAJ4sgUfHTVsG/x3p3fcM +3b5R86qKEACggYKSwPWCs0YVRHOWqZY0pnHtLH8= +=3Dgk +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (3, 'elg4096', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELII7wRBACFuaAvb11cIvjJK9LkZr4cYuYhLWh3DJdojNNnLNiym5OEksvY +05cw8OgqKtPzICU7o/mHXTWhzJYUt3i50/AeYygI8Q0uATS6RnDAKNlES1EMoHKz +2a5iFbYs4bm4IwlkvYd8uWjcu+U0YLbxir39u+anIc6eT+q3WiH/q3zDRwCgkT98 +cnIG8iO8PdwDSP8G4Lt6TYED/R45GvCzJ4onQALLE92KkLUz8aFWSl05r84kczEN +SxiP9Ss6m465RmwWHfwYAu4b+c4GeNyU8fIU2EM8cezchC+edEi3xu1s+pCV0Dk4 +18DGC8WKCICO30vBynuNmYg7W/7Zd4wtjss454fMW7+idVDNM701mmXBtI1nsBtG +7Z4tA/9FxjFbJK9jh24RewfjHpLYqcfCo2SsUjOwsnMZ5yg2yv9KyVVQhRqwmrqt +q8MRyjGmfoD9PPdCgvqgzy0hHvAHUtTm2zUczGTG+0g4hNIklxC/Mv6J4KE+NWTh +uB4acqofHyaw2WnKOuRUsoDi6rG5AyjNMyAK/vVcEGj7J1tk27QjRWxnYW1hbCA0 +MDk2IDx0ZXN0NDA5NkBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgjvAIbAwYLCQgH +AwIDFQIDAxYCAQIeAQIXgAAKCRBj+HX2P2d0oAEDAJ9lI+CNmb42z3+a6TnVusM6 +FI7oLwCfUwA1zEcRdsT3nIkoYh0iKxFSDFW5BA0EQsgkdhAQAJQbLXlgcJ/jq+Xh +Eujb77/eeftFJObNIRYD9fmJ7HFIXbUcknEpbs+cRH/nrj5dGSY3OT3jCXOUtvec +sCoX/CpZWL0oqDjAiZtNSFiulw5Gav4gHYkWKgKdSo+2rkavEPqKIVHvMeXaJtGT +d7v/AmL/P8T7gls93o5WFBOLtPbDvWqaKRy2U5TAhl1laiM0vGALRVjvSCgnGw9g +FpSnXbO3AfenUSjDzZujfGLHtU44ixHSS/D4DepiF3YaYLsN4CBqZRv6FbMZD5W3 +DnJY4kS1kH0MzdcF19TlcZ3itTCcGIt1tMKf84mccPoqdMzH7vumBGTeFEly5Afp +9berJcirqh2fzlunN0GS02z6SGWnjTbDlkNDxuxPSBbpcpNyD3jpYAUqSwRsZ/+5 +zkzcbGtDmvy9sJ5lAXkxGoIoQ1tEVX/LOHnh2NQHK8ourVOnr7MS0nozssITZJ5E +XqtHiREjiYEuPyZiVZKJHLWuYYaF+n40znnz3sJuXFRreHhHbbvRdlYUU5mJV+XZ +BLgKuS33NdpGeMIngnCc/9IQ6OZb6ixc94kbkd3w2PVr8CbKlu/IHTjWOO2mAo+D ++OydlYl23FiM3KOyMP1HcEOJMB/nwkMtrvd+522Lu9n77ktKfot9IPrQDIQTyXjR +3pCOFtCOBnk2tJHMPoG9jn9ah/LHAAMHEACDZ5I/MHGfmiKg2hrmqBu2J2j/deC8 +CpwcyDH1ovQ0gHvb9ESa+CVRU2Wdy2CD7Q9SmtMverB5eneL418iPVRcQdwRmQ2y +IH4udlBa6ce9HTUCaecAZ4/tYBnaC0Av/9l9tz14eYcwRMDpB+bnkhgF+PZ1KAfD +9wcY2aHbtsf3lZBc5h4owPJkxpe/BNzuJxW3q4VpSbLsZhwnCZ2wg7DRwP44wFIk +00ptmoBY59gsU6I40XtzrF8JDr0cA57xND5RY21Z8lnnYRE1Tc8h5REps9ZIxW3/ +yl91404bPLqxczpUHQAMSTAmBaStPYX1nS51uofOhLs5SKPCUmxfGKIOhsD0oLUn +78DnkONVGeXzBibSwwtbgfMzee4G8wSUfJ7w8WXz1TyanaGLnJ+DuKASSOrFoBCD +HEDuWZWgSL74NOQupFRk0gxOPmqU94Y8HziQWma/cETbmD83q8rxN+GM2oBxQkQG +xcbqMTHE7aVhV3tymbSWVaYhww3oIwsZS9oUIi1DnPEowS6CpVRrwdvLjLJnJzzV +O3AFPn9eZ1Q7R1tNx+zZ4OOfhvI/OlRJ3HBx2L53embkbdY9gFYCCdTjPyjKoDIx +kALgCajjCYMNUsAKNSd6mMCQ8TtvukSzkZS1RGKP27ohsdnzIVsiEAbxDMMcI4k1 +ul0LExUTCXSjeIhJBBgRAgAJBQJCyCR2AhsMAAoJEGP4dfY/Z3Sg19sAn0NDS8pb +qrMpQAxSb7zRTmcXEFd9AJ435H0ttP/NhLHXC9ezgbCMmpXMOQ== +=kRxT +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQG7BELII7wRBACFuaAvb11cIvjJK9LkZr4cYuYhLWh3DJdojNNnLNiym5OEksvY +05cw8OgqKtPzICU7o/mHXTWhzJYUt3i50/AeYygI8Q0uATS6RnDAKNlES1EMoHKz +2a5iFbYs4bm4IwlkvYd8uWjcu+U0YLbxir39u+anIc6eT+q3WiH/q3zDRwCgkT98 +cnIG8iO8PdwDSP8G4Lt6TYED/R45GvCzJ4onQALLE92KkLUz8aFWSl05r84kczEN +SxiP9Ss6m465RmwWHfwYAu4b+c4GeNyU8fIU2EM8cezchC+edEi3xu1s+pCV0Dk4 +18DGC8WKCICO30vBynuNmYg7W/7Zd4wtjss454fMW7+idVDNM701mmXBtI1nsBtG +7Z4tA/9FxjFbJK9jh24RewfjHpLYqcfCo2SsUjOwsnMZ5yg2yv9KyVVQhRqwmrqt +q8MRyjGmfoD9PPdCgvqgzy0hHvAHUtTm2zUczGTG+0g4hNIklxC/Mv6J4KE+NWTh +uB4acqofHyaw2WnKOuRUsoDi6rG5AyjNMyAK/vVcEGj7J1tk2wAAoJCUNy6awTkw +XfbLbpqh0fvDst7jDLa0I0VsZ2FtYWwgNDA5NiA8dGVzdDQwOTZAZXhhbXBsZS5v +cmc+iF4EExECAB4FAkLII7wCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQY/h1 +9j9ndKABAwCeNEOVK87EzXYbtxYBsnjrUI948NIAn2+f3BXiBFDV5NvqPwIZ0m77 +Fwy4nQRMBELIJHYQEACUGy15YHCf46vl4RLo2++/3nn7RSTmzSEWA/X5iexxSF21 +HJJxKW7PnER/564+XRkmNzk94wlzlLb3nLAqF/wqWVi9KKg4wImbTUhYrpcORmr+ +IB2JFioCnUqPtq5GrxD6iiFR7zHl2ibRk3e7/wJi/z/E+4JbPd6OVhQTi7T2w71q +mikctlOUwIZdZWojNLxgC0VY70goJxsPYBaUp12ztwH3p1Eow82bo3xix7VOOIsR +0kvw+A3qYhd2GmC7DeAgamUb+hWzGQ+Vtw5yWOJEtZB9DM3XBdfU5XGd4rUwnBiL +dbTCn/OJnHD6KnTMx+77pgRk3hRJcuQH6fW3qyXIq6odn85bpzdBktNs+khlp402 +w5ZDQ8bsT0gW6XKTcg946WAFKksEbGf/uc5M3GxrQ5r8vbCeZQF5MRqCKENbRFV/ +yzh54djUByvKLq1Tp6+zEtJ6M7LCE2SeRF6rR4kRI4mBLj8mYlWSiRy1rmGGhfp+ +NM55897CblxUa3h4R2270XZWFFOZiVfl2QS4Crkt9zXaRnjCJ4JwnP/SEOjmW+os +XPeJG5Hd8Nj1a/AmypbvyB041jjtpgKPg/jsnZWJdtxYjNyjsjD9R3BDiTAf58JD +La73fudti7vZ++5LSn6LfSD60AyEE8l40d6QjhbQjgZ5NrSRzD6BvY5/WofyxwAD +BxAAg2eSPzBxn5oioNoa5qgbtido/3XgvAqcHMgx9aL0NIB72/REmvglUVNlnctg +g+0PUprTL3qweXp3i+NfIj1UXEHcEZkNsiB+LnZQWunHvR01AmnnAGeP7WAZ2gtA +L//Zfbc9eHmHMETA6Qfm55IYBfj2dSgHw/cHGNmh27bH95WQXOYeKMDyZMaXvwTc +7icVt6uFaUmy7GYcJwmdsIOw0cD+OMBSJNNKbZqAWOfYLFOiONF7c6xfCQ69HAOe +8TQ+UWNtWfJZ52ERNU3PIeURKbPWSMVt/8pfdeNOGzy6sXM6VB0ADEkwJgWkrT2F +9Z0udbqHzoS7OUijwlJsXxiiDobA9KC1J+/A55DjVRnl8wYm0sMLW4HzM3nuBvME +lHye8PFl89U8mp2hi5yfg7igEkjqxaAQgxxA7lmVoEi++DTkLqRUZNIMTj5qlPeG +PB84kFpmv3BE25g/N6vK8TfhjNqAcUJEBsXG6jExxO2lYVd7cpm0llWmIcMN6CML +GUvaFCItQ5zxKMEugqVUa8Hby4yyZyc81TtwBT5/XmdUO0dbTcfs2eDjn4byPzpU +Sdxwcdi+d3pm5G3WPYBWAgnU4z8oyqAyMZAC4Amo4wmDDVLACjUnepjAkPE7b7pE +s5GUtURij9u6IbHZ8yFbIhAG8QzDHCOJNbpdCxMVEwl0o3gAAckBdfKuasiNUn5G +L5XRnSvaOFzftr8zteOlZChCSNvzH5k+i1j7RJbWq06OeKRywPzjfjgM2MvRzI43 +ICeISQQYEQIACQUCQsgkdgIbDAAKCRBj+HX2P2d0oNfbAJ9+G3SeXrk+dWwo9EGi +hqMi2GVTsgCfeoQJPsc8FLYUgfymc/3xqAVLUtg= +=Gjq6 +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (4, 'rsa2048', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQELBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP +ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2 +55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx +5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K +MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz +R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYptB5SU0EgMjA0OCA8cnNhMjA0 +OEBleGFtcGxlLm9yZz6JATQEEwECAB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgEC +HgECF4AACgkQnc+OnJvTHyQqHwf8DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liR +nrLuVlLBpdO6CWmMUzfKRvyZlx54GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzw +bLZyM5Gb3lsE/FEmE7Dxw/0Utf59uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDP +D3dnU4uzKPhMcjnSN00pzjusP7C9NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv +9bgGWopumlOkt8Zu5YG6+CtTbJXprPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhv +S3NZKoJ/1DrGgoDAu1mGkM4KvLAxfDs/qQ9dZhtEmDbKPLTVEA== +=lR4n +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQOWBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP +ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2 +55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx +5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K +MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz +R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYpAAf/QZsrrz0c7dgWwGqMIpw6 +fP+/lLa74+fa2CFRWtYowEiKsfDg/wN7Ua07036dNhPa8aZPsU6SRzm5PybKOURe +D9pNt0FxJkX0j5pCWfjSJgTbc1rCdqZ/oyBk/U6pQtf//zfw3PbDl7I8TC6GOt2w +5NgcXdsWHP7LAmPctOVUyzFsenevR0MFTHkMbmKI1HpFm8XN/e1Fl+qIAD+OagTF +5B32VvpoJtkh5nxnIuToNJsa9Iy7F9MM2CeFOyTMihMcjXKBBUaAYoF115irBvqu +7N/qWmzqLg8yxBZ56mh6meCF3+67VA2y7fL8rhw2QuqgLg1JFlKAVL+9crCSrn// +GQQA1kT7FytW6BNOffblFYZkrJer3icoRDqa/ljgH/yVaWoVT1igy0E9XzYO7MwP +2usj/resLy0NC1qCthk51cZ/wthooMl88e5Wb4l5FYwBEac7muSBTo4W8cAH1hFj +TWL6XAGvEzGX3Mt9pn8uYGlQLZAhJoNCAU2EOCbN1PchDvsEAOWNKYesuUVk8+sQ +St0NDNhd9BWtTWTHkCZb1dKC3JTfr9PqkTBLrWFbYjkOtvdPAW7FDaXXXZfdH1jH +WfwP3Q+I6sqgSaWpCS4dBAns3/RVtO7czVgyIwma04iIvJqderYrfvkUq95KfwP2 +V8wXkhrPPPxyrg5y3wQlpY2jb5RBBAC17SK1ms+DBtck4vpdjp3SJ32SbyC/DU30 +89Q12j74S7Zdu1qZlKnvy3kWPYX/hMuSzGZ+mLVJNFEqH2X01aFzppYz0hdI9PGB +9tTFEqZWQL9ZkXfjc79Cgnt12pNukRbtw0N/kyutOdIFHVT79wVAd+powqziXJsC +Kc+4xjwSCkZitB5SU0EgMjA0OCA8cnNhMjA0OEBleGFtcGxlLm9yZz6JATQEEwEC +AB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQnc+OnJvTHyQqHwf8 +DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liRnrLuVlLBpdO6CWmMUzfKRvyZlx54 +GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzwbLZyM5Gb3lsE/FEmE7Dxw/0Utf59 +uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDPD3dnU4uzKPhMcjnSN00pzjusP7C9 +NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv9bgGWopumlOkt8Zu5YG6+CtTbJXp +rPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhvS3NZKoJ/1DrGgoDAu1mGkM4KvLAx +fDs/qQ9dZhtEmDbKPLTVEA== +=WKAv +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (5, 'psw-elg1024', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx +MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV +AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc +AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd +ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P +sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI ++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9 +6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF +k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v +iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F +ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80 +=RWci +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQHpBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQP4HAwImKZ5q2QwT +D2DDAY/IQBjes7WgqZeacfLPDoB8ecD/KLoSCH6Z3etvbPHSOKiazxoJ962Ix74H +ZAE6ZbMTtl5dZW1ptB9FbGdhbWFsIDEwMjQgPHRlc3RAZXhhbXBsZS5vcmc+iF4E +ExECAB4FAkLIIUgCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQHCm8DRgXc2Q6 +2wCfXKegLIzoYi8cM57DCYXhn+MZB/MAn1D4zAi5uLQBJ8mJ9oQzbewgfAeinQFf +BELIIUoQBACHlI1tskwCwn6xBtxYTN2S7wB1gHnNjtDJ/0Q6+fSDf1vMA2HGaZvq +BtGVA3SKjKaUPCtpMpQB+4JmA1yD/Q+zEUZv2cNy+MhXyp1WmerU+7MwW4FuYYjr +B6Ds+PTV2U+I7TC3WUtf5K5uS2ptjEj6HfVzdENmrGP4oCDFHxg0YwADBgP7BCzY +d7Vnn6ng5KAwRGI3MXRpxlRiZzR77/3qB8vR+3cg/5JIWjqEQGuQpWFkqg9JJGvk +qk1oShuF3GycqNRrD+rKhoVw0zNviwWTzCrlSUKRH6Hva7qG/wW6V4JXxIlRPVTY +mO2VclHY97L2J+Hytl+7ntRt3B0drS+JZzgOKWH+BwMCJimeatkMEw9gRkFjt4Xa +9rX8awMBE5+vVcGKv/DNiCvJnlYvSdCj8VfuHsYFliiJo6u17NJon+K43e3yvDNk +f631VOVanGEz7TyqOkWQiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCe +IUWi3DXHZf6ivK7dDec22bGgoekAn0dTuPDvJ2Dfd0j0nyBWSuaxJnb/ +=SNvr +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (6, 'rsaenc2048', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQELBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj +UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW +czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT +4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ +dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4 +NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYptCVSU0EgMjA0OCBFbmMgPHJz +YTIwNDhlbmNAZXhhbXBsZS5vcmc+iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMV +AgMDFgIBAh4BAheAAAoJEMiZ6pNEGVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8 +JCaxHa31wH3PKqGtq2M+cpb2rXf7gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw ++IkhihEb5bWxQBNj+3zAFs1YX6v2HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzku +UOhcgfpBgIt3Q+MpT6M2+OIF7lVfSb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQ +RJ6DWH61F8fFIDJg1z+A/Obx4fqX6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO8 +0g/oVYBbuvOYedffDBeQarhERZ5W2TnIE+nqY61YOLBqosliygdZTXULzNi5AQsE +QuvaugEIAOuCJZdkzORA6e1lr81Lnr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPX +z1YIrMTu+1rMIiy10IWbA6zgMTpzPhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfK +lAXIxJurCHXTbEa+YvPdn76vJ3HsXOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zE +FZcAoKbFqAAjDLEai64SoOFh0W3CsD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9U +rHlolqYNhYze/uRLyfnUx9PN4r/GhEzauyDMV0smo91uB3aewPft+eCpmeWnu0PF +JVK4xyRmhIq2rVCw16a1pBJirvGM+y0ABimJAR8EGAECAAkFAkLr2roCGwwACgkQ +yJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+1iqYE0B0 +WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwarANhHxkWg +w/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx2eVM1k+X +dmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOyla+wJdro +PYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CChbt6LhKh +CLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug== +=pwU2 +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQOWBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj +UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW +czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT +4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ +dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4 +NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYpAAf9GuKpxrXp267eSPw9ZeSw +Ik6ob1I0MHbhhHeaXQnF0SuOViJ1+Bs74hUB3/F5fqrnjVLIS/ysYzegYpbpXOIa +MZwYcp2e+dpmVb7tkGQgzXH0igGtBQBqoSUVq9mG2XKPVh2JmiYgOH6GrHSGmnCq +GCgEK4ezSomB/3OtPFSjAxOlSw6dXSkapSxW3pEGvCdaWd9p8yl4rSpGsZEErPPL +uSbZZrHtWfgq5UXdPeE1UnMlBcvSruvpN4qgWMgSMs4d2lXvzXJLcht/nryP+atT +H1gwnRmlDCVv5BeJepKo3ORJDvcPlXkJPhqS9If3BhTqt6QgQEFI4aIYYZOZpZoi +2QQA2Zckzktmsc1MS04zS9gm1CbxM9d2KK8EOlh7fycRQhYYqqavhTBH2MgEp+Dd +ZtuEN5saNDe9x/fwi2ok1Bq6luGMWPZU/nZe7fxadzwfliy/qPzStWFW3vY9mMLu +6uEqgjin/lf4YrAswXDZaEc5e4GuNgGfwr27hpjxE1jg3PsEAPMqXEOMT2yh+yRu +DlLRbFhYOI4aUHY2CGoQQONnwv2O5gFvmOcPlg3J5lvnwlOYCx0c3bDxAtHyjPJq +FAZqcJBaB9RDhKHwlWDrbx/6FPH2SuKE+u4msIhPFin4V3FAP+yTem/TKrdnaWy6 +EUrhCWTXVRTijBaCudfjFd/ipHZbA/0dv7UAcoWK6kiVLzyE+jOvtN+ZxTzxq7CW +mlFPgAC966hgJmz9IXqadtMgPAoL3PK9q1DbPM3JhsQcJrNzTJqZrdN1/kPU0HHa ++aof1BVy3wSvp2mXgaRUULStyhUIyBRM6hAYp3/MoWEYn/bwr+zQkIU8Zsk6OsZ6 +q1xE3cowrUWFtCVSU0EgMjA0OCBFbmMgPHJzYTIwNDhlbmNAZXhhbXBsZS5vcmc+ +iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJEMiZ6pNE +GVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8JCaxHa31wH3PKqGtq2M+cpb2rXf7 +gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw+IkhihEb5bWxQBNj+3zAFs1YX6v2 +HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzkuUOhcgfpBgIt3Q+MpT6M2+OIF7lVf +Sb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQRJ6DWH61F8fFIDJg1z+A/Obx4fqX +6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO80g/oVYBbuvOYedffDBeQarhERZ5W +2TnIE+nqY61YOLBqosliygdZTXULzNidA5YEQuvaugEIAOuCJZdkzORA6e1lr81L +nr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPXz1YIrMTu+1rMIiy10IWbA6zgMTpz +PhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfKlAXIxJurCHXTbEa+YvPdn76vJ3Hs +XOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zEFZcAoKbFqAAjDLEai64SoOFh0W3C +sD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9UrHlolqYNhYze/uRLyfnUx9PN4r/G +hEzauyDMV0smo91uB3aewPft+eCpmeWnu0PFJVK4xyRmhIq2rVCw16a1pBJirvGM ++y0ABikAB/oC3z7lv6sVg+ngjbpWy9lZu2/ECZ9FqViVz7bUkjfvSuowgpncryLW +4EpVV4U6mMSgU6kAi5VGT/BvYGSAtnqDWGiPs7Kk+h4Adz74bEAXzU280pNBtSfX +tGvzlS4a376KzYFSCJDRBdMebEhJMbY0wQmR8lTZu5JSUI4YYEuN0c7ckdsw8w42 +QWTLonG8HC6h8UPKS0EAcaCo7tFubMIesU6cWuTYucsHE+wjbADjuSNX968qczNe +NoL2BUznXOQoPu6HQO4/8cr7ib+VQkB2bHQcMoZazPUStIID1e4CL4XcxfuAmT8o +3XDvMLgVqNp5W2f8Mzmk3/DbtsLXLOv5BADsCzQpseC8ikSYJC72hcon1wlUmGeH +3qgGiiHhYXFa18xgI5juoO8DaWno0rPPlgr36Y8mSB5qjYHMXwjKnKyUmt11H+hU ++6uk4hq3Rjd8l+vfuOSr1xoTrtBUg9Rwfw6JVo0DC+8CWg4oBWsLXVM6KQXPFdJs +8kyFQplR/iP1XQQA/2tbDANjAYGNNDjJO9/0kEnSAUyYMasFJDrA2q17J5CroVQw +QpMmWwdDkRANUVPKnWHS5sS65BRc7UytKe2f3A3ZInGXJIK2Hl+TzapWYcYxql+4 +ol5mEDDMDbhEE8Wmj9KyB6iifdLI0K+yxNb9T4Jpj3J18+St+G8+9AcFcBEEAM1b +M9C+/05cnV8gjcByqH9M9ypo8fzPvMKVXWwCLQXpaL50QIkzLURkiMoEWrCdELaA +sVPotRzePTIQ1ooLeDxd1gRnDqjZiIR0kwmv6vq8tfzY96O2ZbGWFI5eth89aWEJ +WB8AR3zYcXpwJLwPuhXW2/NlZF0bclJ3jNzAfTIeQmeJAR8EGAECAAkFAkLr2roC +GwwACgkQyJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+ +1iqYE0B0WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwar +ANhHxkWgw/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx +2eVM1k+XdmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOy +la+wJdroPYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CC +hbt6LhKhCLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug== +=UKh3 +-----END PGP PRIVATE KEY BLOCK----- +'); +insert into keytbl (id, name, pubkey, seckey) +values (7, 'rsaenc2048-psw', ' +same key with password +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.11 (GNU/Linux) + +lQPEBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj +UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW +czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT +4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ +dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4 +NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYp/gcDCNnoEKwFo86JYCE1J92R +HRQ7DoyAZpW1O0dTXL8Epk0sKsKDrCJOrIkDymsjfyBexADIeqOkioy/50wD2Mku +CVHKWO2duAiJN5t/FoRgpR1/Q11K6QdfqOG0HxwfIXLcPv7eSIso8kWorj+I01BP +Fn/atGEbIjdWaz/q2XHbu0Q3x6Et2gIsbLRVMhiYz1UG9uzGJ0TYCdBa2SFhs184 +52akMpD+XVdM0Sq9/Cx40Seo8hzERB96+GXnQ48q2OhlvcEXiFyD6M6wYCWbEV+6 +XQVMymbl22FPP/bD9ReQX2kjrkQlFAtmhr+0y8reMCbcxwLuQfA3173lSPo7jrbH +oLrGhkRpqd2bYCelqdy/XMmRFso0+7uytHfTFrUNfDWfmHVrygoVrNnarCbxMMI0 +I8Q+tKHMThWgf0rIOSh0+w38kOXFCEqEWF8YkAqCrMZIlJIed78rOCFgG4aHajZR +D8rpXdUOIr/WeUddK25Tu8IuNJb0kFf12IMgNh0nS+mzlqWiofS5kA0TeB8wBV6t +RotaeyDNSsMoowfN8cf1yHMTxli+K1Tasg003WVUoWgUc+EsJ5+KTNwaX5uGv0Cs +j6dg6/FVeVRL9UsyF+2kt7euX3mABuUtcVGx/ZKTq/MNGEh6/r3B5U37qt+FDRbw +ppKPc2AP+yBUWsQskyrxFgv4eSpcLEg+lgdz/zLyG4qW4lrFUoO790Cm/J6C7/WQ +Z+E8kcS8aINJkg1skahH31d59ZkbW9PVeJMFGzNb0Z2LowngNP/BMrJ0LT2CQyLs +UxbT16S/gwAyUpJnbhWYr3nDdlwtC0rVopVTPD7khPRppcsq1f8D70rdIxI4Ouuw +vbjNZ1EWRJ9f2Ywb++k/xgSXwJkGodUlrUr+3i8cv8mPx+fWvif9q7Y5Ex1wCRa8 +8FAj/o+hEbQlUlNBIDIwNDggRW5jIDxyc2EyMDQ4ZW5jQGV4YW1wbGUub3JnPokB +NAQTAQIAHgUCQuvabQIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRDImeqTRBlV +WRzJCACbRhx2fYjPGKta69M5dS+kr5UD/CQmsR2t9cB9zyqhratjPnKW9q13+4AG +P3aByT14IH1c5Mha8rJkNYD2wxmC8jrrcPiJIYoRG+W1sUATY/t8wBbNWF+r9h11 +m0lEpsmNVff/jU7SpNN6JQ3P7MHd5V85LlDoXIH6QYCLd0PjKU+jNvjiBe5VX0m9 +a1nacE3xoWc1vbM0DnqEuID78Qgkcrmm0ESeg1h+tRfHxSAyYNc/gPzm8eH6l+hj +gOvUc4Gd6LpBQSF8TcFfT2TZwJh7WVWDvNIP6FWAW7rzmHnX3wwXkGq4REWeVtk5 +yBPp6mOtWDiwaqLJYsoHWU11C8zYnQPEBELr2roBCADrgiWXZMzkQOntZa/NS56+ +CczLFQRQPl/8iJAW1eql/wOJ1UiwGSjT189WCKzE7vtazCIstdCFmwOs4DE6cz4S +UX4HjzjYHZwmMiuSrIefwuZ7cysMBsMXypQFyMSbqwh102xGvmLz3Z++rydx7Fzl +1RC/ny2+FN5dzYPO2DNtNi4dR2tjHktsxBWXAKCmxagAIwyxGouuEqDhYdFtwrA9 +Qy+M5n6fmGa1Dx07WWnbIud4uCilv8LPVKx5aJamDYWM3v7kS8n51MfTzeK/xoRM +2rsgzFdLJqPdbgd2nsD37fngqZnlp7tDxSVSuMckZoSKtq1QsNemtaQSYq7xjPst +AAYp/gcDCNnoEKwFo86JYAsxoD+wQ0zBi5RBM5EphXTpM1qKxmigsKOvBSaMmr0y +VjHtGY3poyV3t6VboOGCsFcaKm0tIdDL7vrxxwyYESETpF29b7QrYcoaLKMG7fsy +t9SUI3UV2H9uUquHgqHtsqz0jYOgm9tYnpesgQ/kOAWI/tej1ZJXUIWEmZMH/W6d +ATNvZ3ivwApfC0qF5G3oPgBSoIuQ/8I+pN/kmuyNAnJWNgagFhA/2VFBvh5XgztV +NW7G//KpR1scsn140SO/wpGBM3Kr4m8ztl9w9U6a7NlQZ2ub3/pIUTpSzyLBxJZ/ +RfuZI7ROdgDMKmEgCYrN2kfp0LIxnYL6ZJu3FDcS4V098lyf5rHvB3PAEdL6Zyhd +qYp3Sx68r0F4vzk5iAIWf6pG2YdfoP2Z48Pmq9xW8qD9iwFcoz9oAzDEMENn6dfq +6MzfoaXEoYp8cR/o+aeEaGUtYBHiaxQcJYx35B9IhsXXA49yRORK8qdwhSHxB3NQ +H3pUWkfw368f/A207hQVs9yYXlEvMZikxl58gldCd3BAPqHm/XzgknRRNQZBPPKJ +BMZebZ22Dm0qDuIqW4GXLB4sLf0+UXydVINIUOlzg+S4jrwx7eZqb6UkRXTIWVo5 +psTsD14wzWBRdUQHZOZD33+M8ugmewvLY/0Uix+2RorkmB7/jqoZvx/MehDwmCZd +VH8sb2wpZ55sj7gCXxvrfieQD/VeH54OwjjbtK56iYq56RVD0h1az8xDY2GZXeT7 +J0c3BGpuoca5xOFWr1SylAr/miEPxOBfnfk8oZQJvZrjSBGjsTbALep2vDJk8ROD +sdQCJuU1RHDrwKHlbUL0NbGRO2juJGsatdWnuVKsFbaFW2pHHkezKuwOcaAJv7Xt +8LRF17czAJ1uaLKwV8Paqx6UIv+089GbWZi7HIkBHwQYAQIACQUCQuvaugIbDAAK +CRDImeqTRBlVWS7XCACDVstKM+SHD6V0bkfO6ampHzj4krKjN0lonN5+7b7WKpgT +QHRYvPY8lUiIrjXGISQqEG9M5Bi5ea1aoBZem0P3U/lKheg0lYtA7dM3BqsA2EfG +RaDD9M5TFCqhy2VFR6Pk0MP7h5bkb2VxLUUQa4oNa1fT3q7zS875NvImO/HZ5UzW +T5d2Z5iwY6I2AOKYKt4kZhzXgbt5j2O3biDDXSfWwwAojWqbqVygepn047KVr7Al +2ug9hkY7tHz7U71HbZasroFgNPmP/UnAxmps4RKM28MRVPTI4cKUIdE3gIKFu3ou +EqEItQ13P+50i3QkALpz8d08tJbceeYzf6I2P4q6 +=QFm5 +-----END PGP PRIVATE KEY BLOCK----- +'); +-- elg1024 / aes128 +insert into encdata (id, data) values (1, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQEOA9k2z2S7c/RmEAQAgVWW0DeLrZ+1thWJGBPp2WRFL9HeNqqWHbKJCXJbz1Uy +faUY7yxVvG5Eutmo+JMiY3mg23/DgVVXHQZsTWpGvGM6djgUNGKUjZDbW6Nog7Mr +e78IywattCOmgUP9vIwwg3OVjuDCN/nVirGQFnXpJBc8DzWqDMWRWDy1M0ZsK7AD +/2JTosSFxUdpON0DKtIY3GLzmh6Nk3iV0g8VgJKUBT1rhCXuMDj3snm//EMm7hTY +PlnObq4mIhgz8NqprmhooxnU0Kapofb3P3wCHPpU14zxhXY8iKO/3JhBq2uFcx4X +uBMwkW4AdNxY/mzJZELteTL8Tr0s7PISk+owb4URpG3n0jsBc0CVULxrjh5Ejkdw +wCM195J6+KbQxOOFQ0b3uOVvv4dEgd/hRERCOq5EPaFhlHegyYJ7YO842vnSDA== +=PABx +-----END PGP MESSAGE----- +'); +-- elg2048 / blowfish +insert into encdata (id, data) values (2, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQIOAywibh/+XMfUEAf+OINhBngEsw4a/IJIeJvUgv1gTQzBwOdQEuc/runr4Oa8 +Skw/Bj0X/zgABVZLem1a35NHaNwaQaCFwMQ41YyWCu+jTdsiyX/Nw0w8LKKz0rNC +vVpG6YuV7Turtsf8a5lXy1K0SHkLlgxQ6c76GS4gtSl5+bsL2+5R1gSRJ9NXqCQP +OHRipEiYwBPqr5R21ZG0FXXNKGOGkj6jt/M/wh3WVtAhYuBI+HPKRfAEjd/Pu/eD +e1zYtkH1dKKFmp44+nF0tTI274xpuso7ShfKYrOK3saFWrl0DWiWteUinjSA1YBY +m7dG7NZ8PW+g1SZWhEoPjEEEHz3kWMvlKheMRDudnQf/dDyX6kZVIAQF/5B012hq +QyVewgTGysowFIDn01uIewoEA9cASw699jw9IoJp+k5WZXnU+INllBLzQxniQCSu +iEcr0x3fYqNtj9QBfbIqyRcY6HTWcmzyOUeGaSyX76j+tRAvtVtXpraFFFnaHB70 +YpXTjLkp8EBafzMghFaKDeXlr2TG/T7rbwcwWrFIwPqEAUKWN5m97Q3eyo8/ioMd +YoFD64J9ovSsgbuU5IpIGAsjxK+NKzg/2STH7zZFEVCtgcIXsTHTZfiwS98/+1H9 +p1DIDaXIcUFV2ztmcKxh9gt2sXRz1W+x6D8O0k3nanU5yGG4miLKaq18fbcA0BD1 ++NIzAfelq6nvvxYKcGcamBMgLo5JkZOBHvyr6RsAKIT5QYc0QTjysTk9l0Am3gYc +G2pAE+3k +=TBHV +-----END PGP MESSAGE----- +'); +-- elg4096 / aes256 +insert into encdata (id, data) values (3, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQQOA7aFBP0Sjh/5EA/+JCgncc8IZmmRjPStWnGf9tVJhgHTn+smIclibGzs0deS +SPSCitzpblwbUDvu964+/5e5Q1l7rRuNN+AgETlEd4eppv7Swn2ChdgOXxRwukcT +Nh3G+PTFvD4ayi7w1db3qvXIt0MwN4Alt436wJmK1oz2Ka9IcyO+wHWrDy1nSGSx +z5x7YEj+EZPgWc/YAvudqE8Jpzd/OT5zSHN09UFkIAk6NxisKaIstbEGFgpqtoDZ +1SJM84XAdL2IcaJ3YY7k/yzwlawhsakKd4GSd5vWmAwvyzzbSiBMfKsDE16ePLNU +ZBF7CzmlCBPZ7YrFAHLpXBXXkCQvzD2BEYOjse50ZEfJ036T7950Ozcdy1EQbGon +nyQ4Gh0PBpnMcBuiXOceWuYzhlzFOzDtlVKdNTxFRDcbEyW2jo9xQYvCCLnYy8EH +2M7S8jCtVYJBbn63a82ELv+3+kWYcsvBJv2ZVBh4ncrBu9o0P+OYS7ApoOU+j6p2 ++t0RXHksqXS1YiUwYF5KSw09EbYMgNZ9G04Px/PxLU6fSC9iDrGX7Xt3kOUP0mku +C518fPckT0zzRXqfFruJNRzDytW50KxkOQZzU1/Az1YlYN9QzWeU4EtLPb2fftZo +D0qH/ln+f9Op5t6sD2fcxZVECU1b/bFtZsxvwH406YL+UQ7hU/XnZrzVVzODal8P +/j1hg7v7BdJqu1DTp9nFWUuwMFcYAczuXn29IG183NZ7Ts4whDeYEhS8eNoLPX4j +txY12ILD/w/3Q4LoW/hPa6OdfEzsn0U5GLf1WiGmJE1H6ft2U/xUnerc/u0kt+FU +WAisArd4MuKtf7B5Vu/VF3kUdrR0hTniUKUivmC4o1jSId31Dufxj4aadVyldXAr +6TNBcdyragZjxEZ6hsBCYzA0Rd1a8atd6OaQoIEEfAzCu5Ks29pydHErStYGjWJ1 +KA5KPLVvjbHpDmRhlCcm8vgpYQsBYEB5gE9fx5yCTlsVhCB6y23h7hfdMqerDqkO +ZOPsO5h+tiHCdIrQ36sMjuINy1/K2rYcXd+Crh2iHcfidpU9fvDz2ihTRNQlhjuT +0cQZM5JhctEx4VXF4LDctRhit7Hn0iqsk604woQfJVvP8O673xSXT/kBY0A/v9C0 +3C4YoFNeSaKwbfZQ/4u1ZFPJxK2IIJa8UGpyAUewLMlzGVVagljybv/f4Z9ERAhy +huq5sMmw8UPsrJF2TUGHz5WSIwoh0J/qovoQI09I9sdEnFczDvRavMO2Mldy3E5i +exz9oewtel6GOmsZQSYWT/vJzbYMmvHNmNpVwwoKrLV6oI3kyQ80GHBwI1WlwHoK +2iRB0w8q4VVvJeYAz8ZIp380cqC3pfO0uZsrOx4g3k4X0jsB5y7rF5xXcZfnVbvG +DYKcOy60/OHMWVvpw6trAoA+iP+cVWPtrbRvLglTVTfYmi1ToZDDipkALBhndQ== +=L/M/ +-----END PGP MESSAGE----- +'); +-- rsaenc2048 / aes128 +insert into encdata (id, data) values (4, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQEMA/0CBsQJt0h1AQf+JyYnCiortj26P11zk28MKOGfWpWyAhuIgwbJXsdQ+e6r +pEyyqs9GC6gI7SNF6+J8B/gsMwvkAL4FHAQCvA4ZZ6eeXR1Of4YG22JQGmpWVWZg +DTyfhA2vkczuqfAD2tgUpMT6sdyGkQ/fnQ0lknlfHgC5GRx7aavOoAKtMqiZW5PR +yae/qR48mjX7Mb+mLvbagv9mHEgQSmHwFpaq2k456BbcZ23bvCmBnCvqV/90Ggfb +VP6gkSoFVsJ19RHsOhW1dk9ehbl51WB3zUOO5FZWwUTY9DJvKblRK/frF0+CXjE4 +HfcZXHSpSjx4haGGTsMvEJ85qFjZpr0eTGOdY5cFhNJAAVP8MZfji7OhPRAoOOIK +eRGOCkao12pvPyFTFnPd5vqmyBbdNpK4Q0hS82ljugMJvM0p3vJZVzW402Kz6iBL +GQ== +=XHkF +-----END PGP MESSAGE----- +'); +-- rsaenc2048 / aes128 (not from gnupg) +insert into encdata (id, data) values (5, ' +-----BEGIN PGP MESSAGE----- + +wcBMA/0CBsQJt0h1AQgAzxZ8j+OTeZ8IlLxfZ/mVd28/gUsCY+xigWBk/anZlK3T +p2tNU2idHzKdAttH2Hu/PWbZp4kwjl9spezYxMqCeBZqtfGED88Y+rqK0n/ul30A +7jjFHaw0XUOqFNlST1v6H2i7UXndnp+kcLfHPhnO5BIYWxB2CYBehItqtrn75eqr +C7trGzU/cr74efcWagbCDSNjiAV7GlEptlzmgVMmNikyI6w0ojEUx8lCLc/OsFz9 +pJUAX8xuwjxDVv+W7xk6c96grQiQlm+FLDYGiGNXoAzx3Wi/howu3uV40dXfY+jx +3WBrhEew5Pkpt1SsWoFnJWOfJ8GLd0ec8vfRCqAIVdLgAeS7NyawQYtd6wuVrEAj +5SMg4Thb4d+g45RksuGLHUUr4qO9tiXglODa4InhmJfgNuLk+RGz4LXjq8wepEmW +vRbgFOG54+Cf4C/gC+HkreDm5JKSKjvvw4B/jC6CDxq+JoziEe2Z1uEjCuEcr+Es +/eGzeOi36BejXPMHeKxXejj5qBBHKV0pHVhZSgffR0TtlXdB967Yl/5agV0R89hI +7Gw52emfnH4Z0Y4V0au2H0k1dR/2IxXdJEWSTG7Be1JHT59p9ei2gSEOrdBMIOjP +tbYYUlmmbvD49bHfThkDiC+oc9947LgQsk3kOOLbNHcjkbrjH8R5kjII4m/SEZA1 +g09T+338SzevBcVXh/cFrQ6/Et+lyyO2LJRUMs69g/HyzJOVWT2Iu8E0eS9MWevY +Qtrkrhrpkl3Y02qEp/j6M03Yu2t6ZF7dp51aJ5VhO2mmmtHaTnCyCc8Fcf72LmD8 +blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA= +=PHJ1 +-----END PGP MESSAGE----- +'); +-- successful decrypt +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=1 and encdata.id=1; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=2 and encdata.id=2; +ERROR: Wrong key or corrupt data +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=3 and encdata.id=3; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=6 and encdata.id=4; + pgp_pub_decrypt +----------------- + Secret message. +(1 row) + +-- wrong key +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=2 and encdata.id=1; +ERROR: Wrong key +-- sign-only key +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=4 and encdata.id=1; +ERROR: No encryption key found +-- rsa: password-protected secret key, wrong password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123') +from keytbl, encdata where keytbl.id=7 and encdata.id=4; +ERROR: Wrong key or corrupt data +-- rsa: password-protected secret key, right password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool') +from keytbl, encdata where keytbl.id=7 and encdata.id=4; + pgp_pub_decrypt +----------------- + Secret message. +(1 row) + +-- password-protected secret key, no password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=5 and encdata.id=1; +ERROR: Need password for secret key +-- password-protected secret key, wrong password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo') +from keytbl, encdata where keytbl.id=5 and encdata.id=1; +ERROR: Wrong key or corrupt data +-- password-protected secret key, right password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool') +from keytbl, encdata where keytbl.id=5 and encdata.id=1; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +-- test for a short read from prefix_init +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=6 and encdata.id=5; +ERROR: Wrong key or corrupt data diff --git a/contrib/pgcrypto/expected/pgp-pubkey-encrypt.out b/contrib/pgcrypto/expected/pgp-pubkey-encrypt.out new file mode 100644 index 0000000..7f90554 --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-pubkey-encrypt.out @@ -0,0 +1,68 @@ +-- +-- PGP Public Key Encryption +-- +-- successful encrypt/decrypt +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=1; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=2; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=3; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=6; + pgp_pub_decrypt +----------------- + Secret msg +(1 row) + +-- try with rsa-sign only +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=4; +ERROR: No encryption key found +-- try with secret key +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(seckey)), + dearmor(seckey)) +from keytbl where keytbl.id=1; +ERROR: Refusing to encrypt with secret key +-- does text-to-bytea works +select encode(pgp_pub_decrypt_bytea( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)), 'escape') +from keytbl where keytbl.id=1; + encode +------------ + Secret msg +(1 row) + +-- and bytea-to-text? +select pgp_pub_decrypt( + pgp_pub_encrypt_bytea('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=1; +ERROR: Not text data diff --git a/contrib/pgcrypto/expected/pgp-zlib-DISABLED.out b/contrib/pgcrypto/expected/pgp-zlib-DISABLED.out new file mode 100644 index 0000000..6f4eccd --- /dev/null +++ b/contrib/pgcrypto/expected/pgp-zlib-DISABLED.out @@ -0,0 +1 @@ +-- zlib is disabled diff --git a/contrib/pgcrypto/expected/rijndael.out b/contrib/pgcrypto/expected/rijndael.out new file mode 100644 index 0000000..015ba44 --- /dev/null +++ b/contrib/pgcrypto/expected/rijndael.out @@ -0,0 +1,137 @@ +-- +-- AES cipher (aka Rijndael-128, -192, or -256) +-- +-- some standard Rijndael testvalues +SELECT encrypt( +'\x00112233445566778899aabbccddeeff', +'\x000102030405060708090a0b0c0d0e0f', +'aes-ecb/pad:none'); + encrypt +------------------------------------ + \x69c4e0d86a7b0430d8cdb78070b4c55a +(1 row) + +SELECT encrypt( +'\x00112233445566778899aabbccddeeff', +'\x000102030405060708090a0b0c0d0e0f1011121314151617', +'aes-ecb/pad:none'); + encrypt +------------------------------------ + \xdda97ca4864cdfe06eaf70a0ec0d7191 +(1 row) + +SELECT encrypt( +'\x00112233445566778899aabbccddeeff', +'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', +'aes-ecb/pad:none'); + encrypt +------------------------------------ + \x8ea2b7ca516745bfeafc49904b496089 +(1 row) + +-- cbc +SELECT encrypt( +'\x00112233445566778899aabbccddeeff', +'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', +'aes-cbc/pad:none'); + encrypt +------------------------------------ + \x8ea2b7ca516745bfeafc49904b496089 +(1 row) + +-- without padding, input not multiple of block size +SELECT encrypt( +'\x00112233445566778899aabbccddeeff00', +'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', +'aes-cbc/pad:none'); +ERROR: encrypt error: Encryption failed +-- key padding +SELECT encrypt( +'\x0011223344', +'\x000102030405', +'aes-cbc'); + encrypt +------------------------------------ + \x189a28932213f017b246678dbc28655f +(1 row) + +SELECT encrypt( +'\x0011223344', +'\x000102030405060708090a0b0c0d0e0f10111213', +'aes-cbc'); + encrypt +------------------------------------ + \x3b02279162d15580e069d3a71407a556 +(1 row) + +SELECT encrypt( +'\x0011223344', +'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b', +'aes-cbc'); + encrypt +------------------------------------ + \x4facb6a041d53e0a5a73289170901fe7 +(1 row) + +-- empty data +select encrypt('', 'foo', 'aes'); + encrypt +------------------------------------ + \xb48cc3338a2eb293b6007ef72c360d48 +(1 row) + +-- 10 bytes key +select encrypt('foo', '0123456789', 'aes'); + encrypt +------------------------------------ + \xf397f03d2819b7172b68d0706fda4693 +(1 row) + +-- 22 bytes key +select encrypt('foo', '0123456789012345678901', 'aes'); + encrypt +------------------------------------ + \x5c9db77af02b4678117bcd8a71ae7f53 +(1 row) + +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'aes'), '0123456', 'aes'), 'escape'); + encode +-------- + foo +(1 row) + +-- data not multiple of block size +select encode(decrypt(encrypt('foo', '0123456', 'aes') || '\x00'::bytea, '0123456', 'aes'), 'escape'); +ERROR: decrypt error: Decryption failed +-- bad padding +-- (The input value is the result of encrypt_iv('abcdefghijklmnopqrstuvwxyz', '0123456', 'abcd', 'aes') +-- with the 16th byte changed (s/db/eb/) to corrupt the padding of the last block.) +select encode(decrypt_iv('\xa21a9c15231465964e3396d32095e67eb52bab05f556a581621dee1b85385789', '0123456', 'abcd', 'aes'), 'escape'); +ERROR: decrypt_iv error: Decryption failed +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'aes'); + encrypt_iv +------------------------------------ + \x2c24cb7da91d6d5699801268b0f5adad +(1 row) + +select encode(decrypt_iv('\x2c24cb7da91d6d5699801268b0f5adad', '0123456', 'abcd', 'aes'), 'escape'); + encode +-------- + foo +(1 row) + +-- long message +select encrypt('Lets try a longer message.', '0123456789', 'aes'); + encrypt +-------------------------------------------------------------------- + \xd9beb785dd5403ed02f66b755bb191b93ed93ca54930153f2c3b9ec7785056ad +(1 row) + +select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'aes'), '0123456789', 'aes'), 'escape'); + encode +---------------------------- + Lets try a longer message. +(1 row) + diff --git a/contrib/pgcrypto/expected/sha1.out b/contrib/pgcrypto/expected/sha1.out new file mode 100644 index 0000000..b8eaed1 --- /dev/null +++ b/contrib/pgcrypto/expected/sha1.out @@ -0,0 +1,45 @@ +-- +-- SHA1 message digest +-- +SELECT digest('', 'sha1'); + digest +-------------------------------------------- + \xda39a3ee5e6b4b0d3255bfef95601890afd80709 +(1 row) + +SELECT digest('a', 'sha1'); + digest +-------------------------------------------- + \x86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 +(1 row) + +SELECT digest('abc', 'sha1'); + digest +-------------------------------------------- + \xa9993e364706816aba3e25717850c26c9cd0d89d +(1 row) + +SELECT digest('message digest', 'sha1'); + digest +-------------------------------------------- + \xc12252ceda8be8994d5fa0290a47231c1d16aae3 +(1 row) + +SELECT digest('abcdefghijklmnopqrstuvwxyz', 'sha1'); + digest +-------------------------------------------- + \x32d10c7b8cf96570ca04ce37f2a19d84240d3a89 +(1 row) + +SELECT digest('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 'sha1'); + digest +-------------------------------------------- + \x761c457bf73b14d27e9e9265c46f4b4dda11f940 +(1 row) + +SELECT digest('12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'sha1'); + digest +-------------------------------------------- + \x50abf5706a150990a08b2c5ea40fa0e585554732 +(1 row) + diff --git a/contrib/pgcrypto/expected/sha2.out b/contrib/pgcrypto/expected/sha2.out new file mode 100644 index 0000000..6f67fe6 --- /dev/null +++ b/contrib/pgcrypto/expected/sha2.out @@ -0,0 +1,139 @@ +-- +-- SHA2 family +-- +-- SHA224 +SELECT digest('', 'sha224'); + digest +------------------------------------------------------------ + \xd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f +(1 row) + +SELECT digest('a', 'sha224'); + digest +------------------------------------------------------------ + \xabd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5 +(1 row) + +SELECT digest('abc', 'sha224'); + digest +------------------------------------------------------------ + \x23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7 +(1 row) + +SELECT digest('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', 'sha224'); + digest +------------------------------------------------------------ + \x75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525 +(1 row) + +SELECT digest('12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'sha224'); + digest +------------------------------------------------------------ + \xb50aecbe4e9bb0b57bc5f3ae760a8e01db24f203fb3cdcd13148046e +(1 row) + +-- SHA256 +SELECT digest('', 'sha256'); + digest +-------------------------------------------------------------------- + \xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +(1 row) + +SELECT digest('a', 'sha256'); + digest +-------------------------------------------------------------------- + \xca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb +(1 row) + +SELECT digest('abc', 'sha256'); + digest +-------------------------------------------------------------------- + \xba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad +(1 row) + +SELECT digest('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', 'sha256'); + digest +-------------------------------------------------------------------- + \x248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1 +(1 row) + +SELECT digest('12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'sha256'); + digest +-------------------------------------------------------------------- + \xf371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e +(1 row) + +-- SHA384 +SELECT digest('', 'sha384'); + digest +---------------------------------------------------------------------------------------------------- + \x38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b +(1 row) + +SELECT digest('a', 'sha384'); + digest +---------------------------------------------------------------------------------------------------- + \x54a59b9f22b0b80880d8427e548b7c23abd873486e1f035dce9cd697e85175033caa88e6d57bc35efae0b5afd3145f31 +(1 row) + +SELECT digest('abc', 'sha384'); + digest +---------------------------------------------------------------------------------------------------- + \xcb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7 +(1 row) + +SELECT digest('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', 'sha384'); + digest +---------------------------------------------------------------------------------------------------- + \x3391fdddfc8dc7393707a65b1b4709397cf8b1d162af05abfe8f450de5f36bc6b0455a8520bc4e6f5fe95b1fe3c8452b +(1 row) + +SELECT digest('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu', 'sha384'); + digest +---------------------------------------------------------------------------------------------------- + \x09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039 +(1 row) + +SELECT digest('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz', 'sha384'); + digest +---------------------------------------------------------------------------------------------------- + \x3d208973ab3508dbbd7e2c2862ba290ad3010e4978c198dc4d8fd014e582823a89e16f9b2a7bbc1ac938e2d199e8bea4 +(1 row) + +-- SHA512 +SELECT digest('', 'sha512'); + digest +------------------------------------------------------------------------------------------------------------------------------------ + \xcf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +(1 row) + +SELECT digest('a', 'sha512'); + digest +------------------------------------------------------------------------------------------------------------------------------------ + \x1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75 +(1 row) + +SELECT digest('abc', 'sha512'); + digest +------------------------------------------------------------------------------------------------------------------------------------ + \xddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f +(1 row) + +SELECT digest('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', 'sha512'); + digest +------------------------------------------------------------------------------------------------------------------------------------ + \x204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445 +(1 row) + +SELECT digest('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu', 'sha512'); + digest +------------------------------------------------------------------------------------------------------------------------------------ + \x8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909 +(1 row) + +SELECT digest('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz', 'sha512'); + digest +------------------------------------------------------------------------------------------------------------------------------------ + \x930d0cefcb30ff1133b6898121f1cf3d27578afcafe8677c5257cf069911f75d8f5831b56ebfda67b278e66dff8b84fe2b2870f742a580d8edb41987232850c9 +(1 row) + diff --git a/contrib/pgcrypto/mbuf.c b/contrib/pgcrypto/mbuf.c new file mode 100644 index 0000000..99f8957 --- /dev/null +++ b/contrib/pgcrypto/mbuf.c @@ -0,0 +1,547 @@ +/* + * mbuf.c + * Memory buffer operations. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/mbuf.c + */ + +#include "postgres.h" + +#include "mbuf.h" +#include "px.h" + +#define STEP (16*1024) + +struct MBuf +{ + uint8 *data; + uint8 *data_end; + uint8 *read_pos; + uint8 *buf_end; + bool no_write; + bool own_data; +}; + +int +mbuf_avail(MBuf *mbuf) +{ + return mbuf->data_end - mbuf->read_pos; +} + +int +mbuf_size(MBuf *mbuf) +{ + return mbuf->data_end - mbuf->data; +} + +int +mbuf_free(MBuf *mbuf) +{ + if (mbuf->own_data) + { + px_memset(mbuf->data, 0, mbuf->buf_end - mbuf->data); + pfree(mbuf->data); + } + pfree(mbuf); + return 0; +} + +static void +prepare_room(MBuf *mbuf, int block_len) +{ + uint8 *newbuf; + unsigned newlen; + + if (mbuf->data_end + block_len <= mbuf->buf_end) + return; + + newlen = (mbuf->buf_end - mbuf->data) + + ((block_len + STEP + STEP - 1) & -STEP); + + newbuf = repalloc(mbuf->data, newlen); + + mbuf->buf_end = newbuf + newlen; + mbuf->data_end = newbuf + (mbuf->data_end - mbuf->data); + mbuf->read_pos = newbuf + (mbuf->read_pos - mbuf->data); + mbuf->data = newbuf; +} + +int +mbuf_append(MBuf *dst, const uint8 *buf, int len) +{ + if (dst->no_write) + { + px_debug("mbuf_append: no_write"); + return PXE_BUG; + } + + prepare_room(dst, len); + + memcpy(dst->data_end, buf, len); + dst->data_end += len; + + return 0; +} + +MBuf * +mbuf_create(int len) +{ + MBuf *mbuf; + + if (!len) + len = 8192; + + mbuf = palloc(sizeof *mbuf); + mbuf->data = palloc(len); + mbuf->buf_end = mbuf->data + len; + mbuf->data_end = mbuf->data; + mbuf->read_pos = mbuf->data; + + mbuf->no_write = false; + mbuf->own_data = true; + + return mbuf; +} + +MBuf * +mbuf_create_from_data(uint8 *data, int len) +{ + MBuf *mbuf; + + mbuf = palloc(sizeof *mbuf); + mbuf->data = (uint8 *) data; + mbuf->buf_end = mbuf->data + len; + mbuf->data_end = mbuf->data + len; + mbuf->read_pos = mbuf->data; + + mbuf->no_write = true; + mbuf->own_data = false; + + return mbuf; +} + + +int +mbuf_grab(MBuf *mbuf, int len, uint8 **data_p) +{ + if (len > mbuf_avail(mbuf)) + len = mbuf_avail(mbuf); + + mbuf->no_write = true; + + *data_p = mbuf->read_pos; + mbuf->read_pos += len; + return len; +} + +int +mbuf_steal_data(MBuf *mbuf, uint8 **data_p) +{ + int len = mbuf_size(mbuf); + + mbuf->no_write = true; + mbuf->own_data = false; + + *data_p = mbuf->data; + + mbuf->data = mbuf->data_end = mbuf->read_pos = mbuf->buf_end = NULL; + + return len; +} + +/* + * PullFilter + */ + +struct PullFilter +{ + PullFilter *src; + const PullFilterOps *op; + int buflen; + uint8 *buf; + int pos; + void *priv; +}; + +int +pullf_create(PullFilter **pf_p, const PullFilterOps *op, void *init_arg, PullFilter *src) +{ + PullFilter *pf; + void *priv; + int res; + + if (op->init != NULL) + { + res = op->init(&priv, init_arg, src); + if (res < 0) + return res; + } + else + { + priv = init_arg; + res = 0; + } + + pf = palloc0(sizeof(*pf)); + pf->buflen = res; + pf->op = op; + pf->priv = priv; + pf->src = src; + if (pf->buflen > 0) + { + pf->buf = palloc(pf->buflen); + pf->pos = 0; + } + else + { + pf->buf = NULL; + pf->pos = 0; + } + *pf_p = pf; + return 0; +} + +void +pullf_free(PullFilter *pf) +{ + if (pf->op->free) + pf->op->free(pf->priv); + + if (pf->buf) + { + px_memset(pf->buf, 0, pf->buflen); + pfree(pf->buf); + } + + px_memset(pf, 0, sizeof(*pf)); + pfree(pf); +} + +/* may return less data than asked, 0 means eof */ +int +pullf_read(PullFilter *pf, int len, uint8 **data_p) +{ + int res; + + if (pf->op->pull) + { + if (pf->buflen && len > pf->buflen) + len = pf->buflen; + res = pf->op->pull(pf->priv, pf->src, len, data_p, + pf->buf, pf->buflen); + } + else + res = pullf_read(pf->src, len, data_p); + return res; +} + +int +pullf_read_max(PullFilter *pf, int len, uint8 **data_p, uint8 *tmpbuf) +{ + int res, + total; + uint8 *tmp; + + res = pullf_read(pf, len, data_p); + if (res <= 0 || res == len) + return res; + + /* read was shorter, use tmpbuf */ + memcpy(tmpbuf, *data_p, res); + *data_p = tmpbuf; + len -= res; + total = res; + + while (len > 0) + { + res = pullf_read(pf, len, &tmp); + if (res < 0) + { + /* so the caller must clear only on success */ + px_memset(tmpbuf, 0, total); + return res; + } + if (res == 0) + break; + memcpy(tmpbuf + total, tmp, res); + total += res; + len -= res; + } + return total; +} + +/* + * caller wants exactly len bytes and don't bother with references + */ +int +pullf_read_fixed(PullFilter *src, int len, uint8 *dst) +{ + int res; + uint8 *p; + + res = pullf_read_max(src, len, &p, dst); + if (res < 0) + return res; + if (res != len) + { + px_debug("pullf_read_fixed: need=%d got=%d", len, res); + return PXE_PGP_CORRUPT_DATA; + } + if (p != dst) + memcpy(dst, p, len); + return 0; +} + +/* + * read from MBuf + */ +static int +pull_from_mbuf(void *arg, PullFilter *src, int len, + uint8 **data_p, uint8 *buf, int buflen) +{ + MBuf *mbuf = arg; + + return mbuf_grab(mbuf, len, data_p); +} + +static const struct PullFilterOps mbuf_reader = { + NULL, pull_from_mbuf, NULL +}; + +int +pullf_create_mbuf_reader(PullFilter **mp_p, MBuf *src) +{ + return pullf_create(mp_p, &mbuf_reader, src, NULL); +} + + +/* + * PushFilter + */ + +struct PushFilter +{ + PushFilter *next; + const PushFilterOps *op; + int block_size; + uint8 *buf; + int pos; + void *priv; +}; + +int +pushf_create(PushFilter **mp_p, const PushFilterOps *op, void *init_arg, PushFilter *next) +{ + PushFilter *mp; + void *priv; + int res; + + if (op->init != NULL) + { + res = op->init(next, init_arg, &priv); + if (res < 0) + return res; + } + else + { + priv = init_arg; + res = 0; + } + + mp = palloc0(sizeof(*mp)); + mp->block_size = res; + mp->op = op; + mp->priv = priv; + mp->next = next; + if (mp->block_size > 0) + { + mp->buf = palloc(mp->block_size); + mp->pos = 0; + } + else + { + mp->buf = NULL; + mp->pos = 0; + } + *mp_p = mp; + return 0; +} + +void +pushf_free(PushFilter *mp) +{ + if (mp->op->free) + mp->op->free(mp->priv); + + if (mp->buf) + { + px_memset(mp->buf, 0, mp->block_size); + pfree(mp->buf); + } + + px_memset(mp, 0, sizeof(*mp)); + pfree(mp); +} + +void +pushf_free_all(PushFilter *mp) +{ + PushFilter *tmp; + + while (mp) + { + tmp = mp->next; + pushf_free(mp); + mp = tmp; + } +} + +static int +wrap_process(PushFilter *mp, const uint8 *data, int len) +{ + int res; + + if (mp->op->push != NULL) + res = mp->op->push(mp->next, mp->priv, data, len); + else + res = pushf_write(mp->next, data, len); + if (res > 0) + return PXE_BUG; + return res; +} + +/* consumes all data, returns len on success */ +int +pushf_write(PushFilter *mp, const uint8 *data, int len) +{ + int need, + res; + + /* + * no buffering + */ + if (mp->block_size <= 0) + return wrap_process(mp, data, len); + + /* + * try to empty buffer + */ + need = mp->block_size - mp->pos; + if (need > 0) + { + if (len < need) + { + memcpy(mp->buf + mp->pos, data, len); + mp->pos += len; + return 0; + } + memcpy(mp->buf + mp->pos, data, need); + len -= need; + data += need; + } + + /* + * buffer full, process + */ + res = wrap_process(mp, mp->buf, mp->block_size); + if (res < 0) + return res; + mp->pos = 0; + + /* + * now process directly from data + */ + while (len > 0) + { + if (len > mp->block_size) + { + res = wrap_process(mp, data, mp->block_size); + if (res < 0) + return res; + data += mp->block_size; + len -= mp->block_size; + } + else + { + memcpy(mp->buf, data, len); + mp->pos += len; + break; + } + } + return 0; +} + +int +pushf_flush(PushFilter *mp) +{ + int res; + + while (mp) + { + if (mp->block_size > 0) + { + res = wrap_process(mp, mp->buf, mp->pos); + if (res < 0) + return res; + } + + if (mp->op->flush) + { + res = mp->op->flush(mp->next, mp->priv); + if (res < 0) + return res; + } + + mp = mp->next; + } + return 0; +} + + +/* + * write to MBuf + */ +static int +push_into_mbuf(PushFilter *next, void *arg, const uint8 *data, int len) +{ + int res = 0; + MBuf *mbuf = arg; + + if (len > 0) + res = mbuf_append(mbuf, data, len); + return res < 0 ? res : 0; +} + +static const struct PushFilterOps mbuf_filter = { + NULL, push_into_mbuf, NULL, NULL +}; + +int +pushf_create_mbuf_writer(PushFilter **res, MBuf *dst) +{ + return pushf_create(res, &mbuf_filter, dst, NULL); +} diff --git a/contrib/pgcrypto/mbuf.h b/contrib/pgcrypto/mbuf.h new file mode 100644 index 0000000..97873c9 --- /dev/null +++ b/contrib/pgcrypto/mbuf.h @@ -0,0 +1,122 @@ +/* + * mbuf.h + * Memory buffer operations. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/mbuf.h + */ + +#ifndef __PX_MBUF_H +#define __PX_MBUF_H + +typedef struct MBuf MBuf; +typedef struct PushFilter PushFilter; +typedef struct PullFilter PullFilter; +typedef struct PushFilterOps PushFilterOps; +typedef struct PullFilterOps PullFilterOps; + +struct PushFilterOps +{ + /* + * should return needed buffer size, 0- no buffering, <0 on error if NULL, + * no buffering, and priv=init_arg + */ + int (*init) (PushFilter *next, void *init_arg, void **priv_p); + + /* + * send data to next. should consume all? if null, it will be simply + * copied (in-place) returns 0 on error + */ + int (*push) (PushFilter *next, void *priv, + const uint8 *src, int len); + int (*flush) (PushFilter *next, void *priv); + void (*free) (void *priv); +}; + +struct PullFilterOps +{ + /* + * should return needed buffer size, 0- no buffering, <0 on error if NULL, + * no buffering, and priv=init_arg + */ + int (*init) (void **priv_p, void *init_arg, PullFilter *src); + + /* + * request data from src, put result ptr to data_p can use ptr from src or + * use buf as work area if NULL in-place copy + */ + int (*pull) (void *priv, PullFilter *src, int len, + uint8 **data_p, uint8 *buf, int buflen); + void (*free) (void *priv); +}; + +/* + * Memory buffer + */ +MBuf *mbuf_create(int len); +MBuf *mbuf_create_from_data(uint8 *data, int len); +int mbuf_avail(MBuf *mbuf); +int mbuf_size(MBuf *mbuf); +int mbuf_grab(MBuf *mbuf, int len, uint8 **data_p); +int mbuf_steal_data(MBuf *mbuf, uint8 **data_p); +int mbuf_append(MBuf *dst, const uint8 *buf, int len); +int mbuf_free(MBuf *mbuf); + +/* + * Push filter + */ +int pushf_create(PushFilter **mp_p, const PushFilterOps *op, + void *init_arg, PushFilter *next); +int pushf_write(PushFilter *mp, const uint8 *data, int len); +void pushf_free_all(PushFilter *mp); +void pushf_free(PushFilter *mp); +int pushf_flush(PushFilter *mp); + +int pushf_create_mbuf_writer(PushFilter **res, MBuf *dst); + +/* + * Pull filter + */ +int pullf_create(PullFilter **pf_p, const PullFilterOps *op, + void *init_arg, PullFilter *src); +int pullf_read(PullFilter *pf, int len, uint8 **data_p); +int pullf_read_max(PullFilter *pf, int len, + uint8 **data_p, uint8 *tmpbuf); +void pullf_free(PullFilter *pf); +int pullf_read_fixed(PullFilter *src, int len, uint8 *dst); + +int pullf_create_mbuf_reader(PullFilter **mp_p, MBuf *src); + +#define GETBYTE(pf, dst) \ + do { \ + uint8 __b; \ + int __res = pullf_read_fixed(pf, 1, &__b); \ + if (__res < 0) \ + return __res; \ + (dst) = __b; \ + } while (0) + +#endif /* __PX_MBUF_H */ diff --git a/contrib/pgcrypto/meson.build b/contrib/pgcrypto/meson.build new file mode 100644 index 0000000..df7dd50 --- /dev/null +++ b/contrib/pgcrypto/meson.build @@ -0,0 +1,109 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not ssl.found() + subdir_done() +endif + +pgcrypto_sources = files( + 'crypt-blowfish.c', + 'crypt-des.c', + 'crypt-gensalt.c', + 'crypt-md5.c', + 'mbuf.c', + 'pgcrypto.c', + 'pgp-armor.c', + 'pgp-cfb.c', + 'pgp-compress.c', + 'pgp-decrypt.c', + 'pgp-encrypt.c', + 'pgp-info.c', + 'pgp-mpi.c', + 'pgp-pgsql.c', + 'pgp-pubdec.c', + 'pgp-pubenc.c', + 'pgp-pubkey.c', + 'pgp-s2k.c', + 'pgp.c', + 'px-crypt.c', + 'px-hmac.c', + 'px.c', +) + +pgcrypto_regress = [ + 'init', + 'md5', + 'sha1', + 'hmac-md5', + 'hmac-sha1', + 'blowfish', + 'rijndael', + 'sha2', + 'des', + '3des', + 'cast5', + 'crypt-des', + 'crypt-md5', + 'crypt-blowfish', + 'crypt-xdes', + 'pgp-armor', + 'pgp-decrypt', + 'pgp-encrypt', + 'pgp-pubkey-decrypt', + 'pgp-pubkey-encrypt', + 'pgp-info', +] + +pgcrypto_openssl_sources = files( + 'openssl.c', + 'pgp-mpi-openssl.c', +) + +pgcrypto_deps = [] +pgcrypto_link_with = [] + +pgcrypto_deps += ssl +pgcrypto_sources += pgcrypto_openssl_sources + +if zlib.found() + pgcrypto_deps += zlib + pgcrypto_regress += 'pgp-compression' +else + pgcrypto_regress += 'pgp-zlib-DISABLED' +endif + +if host_system == 'windows' + pgcrypto_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pgcrypto', + '--FILEDESC', 'pgcrypto - cryptographic functions',]) +endif + +pgcrypto = shared_module('pgcrypto', + pgcrypto_sources, + link_with: pgcrypto_link_with, + c_pch: pch_postgres_h, + kwargs: contrib_mod_args + { + 'dependencies': [pgcrypto_deps, contrib_mod_args['dependencies']] + }, +) +contrib_targets += pgcrypto + +install_data( + 'pgcrypto--1.0--1.1.sql', + 'pgcrypto--1.1--1.2.sql', + 'pgcrypto--1.2--1.3.sql', + 'pgcrypto--1.3.sql', + 'pgcrypto.control', + kwargs: contrib_data_args, +) + + +tests += { + 'name': 'pgcrypto', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + pgcrypto_regress, + ], + }, +} diff --git a/contrib/pgcrypto/openssl.c b/contrib/pgcrypto/openssl.c new file mode 100644 index 0000000..cf31551 --- /dev/null +++ b/contrib/pgcrypto/openssl.c @@ -0,0 +1,829 @@ +/* + * openssl.c + * Wrapper for OpenSSL library. + * + * Copyright (c) 2001 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/openssl.c + */ + +#include "postgres.h" + +#include +#include +#include + +#include "px.h" +#include "utils/memutils.h" +#include "utils/resowner.h" + +/* + * Max lengths we might want to handle. + */ +#define MAX_KEY (512/8) +#define MAX_IV (128/8) + +/* + * Hashes + */ + +/* + * To make sure we don't leak OpenSSL handles on abort, we keep OSSLDigest + * objects in a linked list, allocated in TopMemoryContext. We use the + * ResourceOwner mechanism to free them on abort. + */ +typedef struct OSSLDigest +{ + const EVP_MD *algo; + EVP_MD_CTX *ctx; + + ResourceOwner owner; + struct OSSLDigest *next; + struct OSSLDigest *prev; +} OSSLDigest; + +static OSSLDigest *open_digests = NULL; +static bool digest_resowner_callback_registered = false; + +static void +free_openssl_digest(OSSLDigest *digest) +{ + EVP_MD_CTX_destroy(digest->ctx); + if (digest->prev) + digest->prev->next = digest->next; + else + open_digests = digest->next; + if (digest->next) + digest->next->prev = digest->prev; + pfree(digest); +} + +/* + * Close any open OpenSSL handles on abort. + */ +static void +digest_free_callback(ResourceReleasePhase phase, + bool isCommit, + bool isTopLevel, + void *arg) +{ + OSSLDigest *curr; + OSSLDigest *next; + + if (phase != RESOURCE_RELEASE_AFTER_LOCKS) + return; + + next = open_digests; + while (next) + { + curr = next; + next = curr->next; + + if (curr->owner == CurrentResourceOwner) + { + if (isCommit) + elog(WARNING, "pgcrypto digest reference leak: digest %p still referenced", curr); + free_openssl_digest(curr); + } + } +} + +static unsigned +digest_result_size(PX_MD *h) +{ + OSSLDigest *digest = (OSSLDigest *) h->p.ptr; + int result = EVP_MD_CTX_size(digest->ctx); + + if (result < 0) + elog(ERROR, "EVP_MD_CTX_size() failed"); + + return result; +} + +static unsigned +digest_block_size(PX_MD *h) +{ + OSSLDigest *digest = (OSSLDigest *) h->p.ptr; + int result = EVP_MD_CTX_block_size(digest->ctx); + + if (result < 0) + elog(ERROR, "EVP_MD_CTX_block_size() failed"); + + return result; +} + +static void +digest_reset(PX_MD *h) +{ + OSSLDigest *digest = (OSSLDigest *) h->p.ptr; + + if (!EVP_DigestInit_ex(digest->ctx, digest->algo, NULL)) + elog(ERROR, "EVP_DigestInit_ex() failed"); +} + +static void +digest_update(PX_MD *h, const uint8 *data, unsigned dlen) +{ + OSSLDigest *digest = (OSSLDigest *) h->p.ptr; + + if (!EVP_DigestUpdate(digest->ctx, data, dlen)) + elog(ERROR, "EVP_DigestUpdate() failed"); +} + +static void +digest_finish(PX_MD *h, uint8 *dst) +{ + OSSLDigest *digest = (OSSLDigest *) h->p.ptr; + + if (!EVP_DigestFinal_ex(digest->ctx, dst, NULL)) + elog(ERROR, "EVP_DigestFinal_ex() failed"); +} + +static void +digest_free(PX_MD *h) +{ + OSSLDigest *digest = (OSSLDigest *) h->p.ptr; + + free_openssl_digest(digest); + pfree(h); +} + +static int px_openssl_initialized = 0; + +/* PUBLIC functions */ + +int +px_find_digest(const char *name, PX_MD **res) +{ + const EVP_MD *md; + EVP_MD_CTX *ctx; + PX_MD *h; + OSSLDigest *digest; + + if (!px_openssl_initialized) + { + px_openssl_initialized = 1; + OpenSSL_add_all_algorithms(); + } + + if (!digest_resowner_callback_registered) + { + RegisterResourceReleaseCallback(digest_free_callback, NULL); + digest_resowner_callback_registered = true; + } + + md = EVP_get_digestbyname(name); + if (md == NULL) + return PXE_NO_HASH; + + /* + * Create an OSSLDigest object, an OpenSSL MD object, and a PX_MD object. + * The order is crucial, to make sure we don't leak anything on + * out-of-memory or other error. + */ + digest = MemoryContextAlloc(TopMemoryContext, sizeof(*digest)); + + ctx = EVP_MD_CTX_create(); + if (!ctx) + { + pfree(digest); + return PXE_CIPHER_INIT; + } + if (EVP_DigestInit_ex(ctx, md, NULL) == 0) + { + EVP_MD_CTX_destroy(ctx); + pfree(digest); + return PXE_CIPHER_INIT; + } + + digest->algo = md; + digest->ctx = ctx; + digest->owner = CurrentResourceOwner; + digest->next = open_digests; + digest->prev = NULL; + open_digests = digest; + + /* The PX_MD object is allocated in the current memory context. */ + h = palloc(sizeof(*h)); + h->result_size = digest_result_size; + h->block_size = digest_block_size; + h->reset = digest_reset; + h->update = digest_update; + h->finish = digest_finish; + h->free = digest_free; + h->p.ptr = (void *) digest; + + *res = h; + return 0; +} + +/* + * Ciphers + * + * We use OpenSSL's EVP* family of functions for these. + */ + +/* + * prototype for the EVP functions that return an algorithm, e.g. + * EVP_aes_128_cbc(). + */ +typedef const EVP_CIPHER *(*ossl_EVP_cipher_func) (void); + +/* + * ossl_cipher contains the static information about each cipher. + */ +struct ossl_cipher +{ + int (*init) (PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv); + ossl_EVP_cipher_func cipher_func; + int block_size; + int max_key_size; +}; + +/* + * OSSLCipher contains the state for using a cipher. A separate OSSLCipher + * object is allocated in each px_find_cipher() call. + * + * To make sure we don't leak OpenSSL handles on abort, we keep OSSLCipher + * objects in a linked list, allocated in TopMemoryContext. We use the + * ResourceOwner mechanism to free them on abort. + */ +typedef struct OSSLCipher +{ + EVP_CIPHER_CTX *evp_ctx; + const EVP_CIPHER *evp_ciph; + uint8 key[MAX_KEY]; + uint8 iv[MAX_IV]; + unsigned klen; + unsigned init; + const struct ossl_cipher *ciph; + + ResourceOwner owner; + struct OSSLCipher *next; + struct OSSLCipher *prev; +} OSSLCipher; + +static OSSLCipher *open_ciphers = NULL; +static bool cipher_resowner_callback_registered = false; + +static void +free_openssl_cipher(OSSLCipher *od) +{ + EVP_CIPHER_CTX_free(od->evp_ctx); + if (od->prev) + od->prev->next = od->next; + else + open_ciphers = od->next; + if (od->next) + od->next->prev = od->prev; + pfree(od); +} + +/* + * Close any open OpenSSL cipher handles on abort. + */ +static void +cipher_free_callback(ResourceReleasePhase phase, + bool isCommit, + bool isTopLevel, + void *arg) +{ + OSSLCipher *curr; + OSSLCipher *next; + + if (phase != RESOURCE_RELEASE_AFTER_LOCKS) + return; + + next = open_ciphers; + while (next) + { + curr = next; + next = curr->next; + + if (curr->owner == CurrentResourceOwner) + { + if (isCommit) + elog(WARNING, "pgcrypto cipher reference leak: cipher %p still referenced", curr); + free_openssl_cipher(curr); + } + } +} + +/* Common routines for all algorithms */ + +static unsigned +gen_ossl_block_size(PX_Cipher *c) +{ + OSSLCipher *od = (OSSLCipher *) c->ptr; + + return od->ciph->block_size; +} + +static unsigned +gen_ossl_key_size(PX_Cipher *c) +{ + OSSLCipher *od = (OSSLCipher *) c->ptr; + + return od->ciph->max_key_size; +} + +static unsigned +gen_ossl_iv_size(PX_Cipher *c) +{ + unsigned ivlen; + OSSLCipher *od = (OSSLCipher *) c->ptr; + + ivlen = od->ciph->block_size; + return ivlen; +} + +static void +gen_ossl_free(PX_Cipher *c) +{ + OSSLCipher *od = (OSSLCipher *) c->ptr; + + free_openssl_cipher(od); + pfree(c); +} + +static int +gen_ossl_decrypt(PX_Cipher *c, int padding, const uint8 *data, unsigned dlen, + uint8 *res, unsigned *rlen) +{ + OSSLCipher *od = c->ptr; + int outlen, + outlen2; + + if (!od->init) + { + if (!EVP_DecryptInit_ex(od->evp_ctx, od->evp_ciph, NULL, NULL, NULL)) + return PXE_CIPHER_INIT; + if (!EVP_CIPHER_CTX_set_padding(od->evp_ctx, padding)) + return PXE_CIPHER_INIT; + if (!EVP_CIPHER_CTX_set_key_length(od->evp_ctx, od->klen)) + return PXE_CIPHER_INIT; + if (!EVP_DecryptInit_ex(od->evp_ctx, NULL, NULL, od->key, od->iv)) + return PXE_CIPHER_INIT; + od->init = true; + } + + if (!EVP_DecryptUpdate(od->evp_ctx, res, &outlen, data, dlen)) + return PXE_DECRYPT_FAILED; + if (!EVP_DecryptFinal_ex(od->evp_ctx, res + outlen, &outlen2)) + return PXE_DECRYPT_FAILED; + *rlen = outlen + outlen2; + + return 0; +} + +static int +gen_ossl_encrypt(PX_Cipher *c, int padding, const uint8 *data, unsigned dlen, + uint8 *res, unsigned *rlen) +{ + OSSLCipher *od = c->ptr; + int outlen, + outlen2; + + if (!od->init) + { + if (!EVP_EncryptInit_ex(od->evp_ctx, od->evp_ciph, NULL, NULL, NULL)) + return PXE_CIPHER_INIT; + if (!EVP_CIPHER_CTX_set_padding(od->evp_ctx, padding)) + return PXE_CIPHER_INIT; + if (!EVP_CIPHER_CTX_set_key_length(od->evp_ctx, od->klen)) + return PXE_CIPHER_INIT; + if (!EVP_EncryptInit_ex(od->evp_ctx, NULL, NULL, od->key, od->iv)) + return PXE_CIPHER_INIT; + od->init = true; + } + + if (!EVP_EncryptUpdate(od->evp_ctx, res, &outlen, data, dlen)) + return PXE_ENCRYPT_FAILED; + if (!EVP_EncryptFinal_ex(od->evp_ctx, res + outlen, &outlen2)) + return PXE_ENCRYPT_FAILED; + *rlen = outlen + outlen2; + + return 0; +} + +/* Blowfish */ + +/* + * Check if strong crypto is supported. Some OpenSSL installations + * support only short keys and unfortunately BF_set_key does not return any + * error value. This function tests if is possible to use strong key. + */ +static int +bf_check_supported_key_len(void) +{ + static const uint8 key[56] = { + 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, + 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f, 0x00, 0x11, 0x22, 0x33, + 0x44, 0x55, 0x66, 0x77, 0x04, 0x68, 0x91, 0x04, 0xc2, 0xfd, + 0x3b, 0x2f, 0x58, 0x40, 0x23, 0x64, 0x1a, 0xba, 0x61, 0x76, + 0x1f, 0x1f, 0x1f, 0x1f, 0x0e, 0x0e, 0x0e, 0x0e, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + static const uint8 data[8] = {0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10}; + static const uint8 res[8] = {0xc0, 0x45, 0x04, 0x01, 0x2e, 0x4e, 0x1f, 0x53}; + uint8 out[8]; + EVP_CIPHER_CTX *evp_ctx; + int outlen; + int status = 0; + + /* encrypt with 448bits key and verify output */ + evp_ctx = EVP_CIPHER_CTX_new(); + if (!evp_ctx) + return 0; + if (!EVP_EncryptInit_ex(evp_ctx, EVP_bf_ecb(), NULL, NULL, NULL)) + goto leave; + if (!EVP_CIPHER_CTX_set_key_length(evp_ctx, 56)) + goto leave; + if (!EVP_EncryptInit_ex(evp_ctx, NULL, NULL, key, NULL)) + goto leave; + + if (!EVP_EncryptUpdate(evp_ctx, out, &outlen, data, 8)) + goto leave; + + if (memcmp(out, res, 8) != 0) + goto leave; /* Output does not match -> strong cipher is + * not supported */ + status = 1; + +leave: + EVP_CIPHER_CTX_free(evp_ctx); + return status; +} + +static int +bf_init(PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv) +{ + OSSLCipher *od = c->ptr; + unsigned bs = gen_ossl_block_size(c); + static int bf_is_strong = -1; + + /* + * Test if key len is supported. BF_set_key silently cut large keys and it + * could be a problem when user transfer crypted data from one server to + * another. + */ + + if (bf_is_strong == -1) + bf_is_strong = bf_check_supported_key_len(); + + if (!bf_is_strong && klen > 16) + return PXE_KEY_TOO_BIG; + + /* Key len is supported. We can use it. */ + od->klen = klen; + memcpy(od->key, key, klen); + + if (iv) + memcpy(od->iv, iv, bs); + else + memset(od->iv, 0, bs); + return 0; +} + +/* DES */ + +static int +ossl_des_init(PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv) +{ + OSSLCipher *od = c->ptr; + unsigned bs = gen_ossl_block_size(c); + + od->klen = 8; + memset(od->key, 0, 8); + memcpy(od->key, key, klen > 8 ? 8 : klen); + + if (iv) + memcpy(od->iv, iv, bs); + else + memset(od->iv, 0, bs); + return 0; +} + +/* DES3 */ + +static int +ossl_des3_init(PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv) +{ + OSSLCipher *od = c->ptr; + unsigned bs = gen_ossl_block_size(c); + + od->klen = 24; + memset(od->key, 0, 24); + memcpy(od->key, key, klen > 24 ? 24 : klen); + + if (iv) + memcpy(od->iv, iv, bs); + else + memset(od->iv, 0, bs); + return 0; +} + +/* CAST5 */ + +static int +ossl_cast_init(PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv) +{ + OSSLCipher *od = c->ptr; + unsigned bs = gen_ossl_block_size(c); + + od->klen = klen; + memcpy(od->key, key, klen); + + if (iv) + memcpy(od->iv, iv, bs); + else + memset(od->iv, 0, bs); + return 0; +} + +/* AES */ + +static int +ossl_aes_init(PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv) +{ + OSSLCipher *od = c->ptr; + unsigned bs = gen_ossl_block_size(c); + + if (klen <= 128 / 8) + od->klen = 128 / 8; + else if (klen <= 192 / 8) + od->klen = 192 / 8; + else if (klen <= 256 / 8) + od->klen = 256 / 8; + else + return PXE_KEY_TOO_BIG; + + memcpy(od->key, key, klen); + + if (iv) + memcpy(od->iv, iv, bs); + else + memset(od->iv, 0, bs); + + return 0; +} + +static int +ossl_aes_ecb_init(PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv) +{ + OSSLCipher *od = c->ptr; + int err; + + err = ossl_aes_init(c, key, klen, iv); + if (err) + return err; + + switch (od->klen) + { + case 128 / 8: + od->evp_ciph = EVP_aes_128_ecb(); + break; + case 192 / 8: + od->evp_ciph = EVP_aes_192_ecb(); + break; + case 256 / 8: + od->evp_ciph = EVP_aes_256_ecb(); + break; + default: + /* shouldn't happen */ + err = PXE_CIPHER_INIT; + break; + } + + return err; +} + +static int +ossl_aes_cbc_init(PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv) +{ + OSSLCipher *od = c->ptr; + int err; + + err = ossl_aes_init(c, key, klen, iv); + if (err) + return err; + + switch (od->klen) + { + case 128 / 8: + od->evp_ciph = EVP_aes_128_cbc(); + break; + case 192 / 8: + od->evp_ciph = EVP_aes_192_cbc(); + break; + case 256 / 8: + od->evp_ciph = EVP_aes_256_cbc(); + break; + default: + /* shouldn't happen */ + err = PXE_CIPHER_INIT; + break; + } + + return err; +} + +/* + * aliases + */ + +static PX_Alias ossl_aliases[] = { + {"bf", "bf-cbc"}, + {"blowfish", "bf-cbc"}, + {"blowfish-cbc", "bf-cbc"}, + {"blowfish-ecb", "bf-ecb"}, + {"blowfish-cfb", "bf-cfb"}, + {"des", "des-cbc"}, + {"3des", "des3-cbc"}, + {"3des-ecb", "des3-ecb"}, + {"3des-cbc", "des3-cbc"}, + {"cast5", "cast5-cbc"}, + {"aes", "aes-cbc"}, + {"rijndael", "aes-cbc"}, + {"rijndael-cbc", "aes-cbc"}, + {"rijndael-ecb", "aes-ecb"}, + {NULL} +}; + +static const struct ossl_cipher ossl_bf_cbc = { + bf_init, + EVP_bf_cbc, + 64 / 8, 448 / 8 +}; + +static const struct ossl_cipher ossl_bf_ecb = { + bf_init, + EVP_bf_ecb, + 64 / 8, 448 / 8 +}; + +static const struct ossl_cipher ossl_bf_cfb = { + bf_init, + EVP_bf_cfb, + 64 / 8, 448 / 8 +}; + +static const struct ossl_cipher ossl_des_ecb = { + ossl_des_init, + EVP_des_ecb, + 64 / 8, 64 / 8 +}; + +static const struct ossl_cipher ossl_des_cbc = { + ossl_des_init, + EVP_des_cbc, + 64 / 8, 64 / 8 +}; + +static const struct ossl_cipher ossl_des3_ecb = { + ossl_des3_init, + EVP_des_ede3_ecb, + 64 / 8, 192 / 8 +}; + +static const struct ossl_cipher ossl_des3_cbc = { + ossl_des3_init, + EVP_des_ede3_cbc, + 64 / 8, 192 / 8 +}; + +static const struct ossl_cipher ossl_cast_ecb = { + ossl_cast_init, + EVP_cast5_ecb, + 64 / 8, 128 / 8 +}; + +static const struct ossl_cipher ossl_cast_cbc = { + ossl_cast_init, + EVP_cast5_cbc, + 64 / 8, 128 / 8 +}; + +static const struct ossl_cipher ossl_aes_ecb = { + ossl_aes_ecb_init, + NULL, /* EVP_aes_XXX_ecb(), determined in init + * function */ + 128 / 8, 256 / 8 +}; + +static const struct ossl_cipher ossl_aes_cbc = { + ossl_aes_cbc_init, + NULL, /* EVP_aes_XXX_cbc(), determined in init + * function */ + 128 / 8, 256 / 8 +}; + +/* + * Special handlers + */ +struct ossl_cipher_lookup +{ + const char *name; + const struct ossl_cipher *ciph; +}; + +static const struct ossl_cipher_lookup ossl_cipher_types[] = { + {"bf-cbc", &ossl_bf_cbc}, + {"bf-ecb", &ossl_bf_ecb}, + {"bf-cfb", &ossl_bf_cfb}, + {"des-ecb", &ossl_des_ecb}, + {"des-cbc", &ossl_des_cbc}, + {"des3-ecb", &ossl_des3_ecb}, + {"des3-cbc", &ossl_des3_cbc}, + {"cast5-ecb", &ossl_cast_ecb}, + {"cast5-cbc", &ossl_cast_cbc}, + {"aes-ecb", &ossl_aes_ecb}, + {"aes-cbc", &ossl_aes_cbc}, + {NULL} +}; + +/* PUBLIC functions */ + +int +px_find_cipher(const char *name, PX_Cipher **res) +{ + const struct ossl_cipher_lookup *i; + PX_Cipher *c = NULL; + EVP_CIPHER_CTX *ctx; + OSSLCipher *od; + + name = px_resolve_alias(ossl_aliases, name); + for (i = ossl_cipher_types; i->name; i++) + if (strcmp(i->name, name) == 0) + break; + if (i->name == NULL) + return PXE_NO_CIPHER; + + if (!cipher_resowner_callback_registered) + { + RegisterResourceReleaseCallback(cipher_free_callback, NULL); + cipher_resowner_callback_registered = true; + } + + /* + * Create an OSSLCipher object, an EVP_CIPHER_CTX object and a PX_Cipher. + * The order is crucial, to make sure we don't leak anything on + * out-of-memory or other error. + */ + od = MemoryContextAllocZero(TopMemoryContext, sizeof(*od)); + od->ciph = i->ciph; + + /* Allocate an EVP_CIPHER_CTX object. */ + ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + { + pfree(od); + return PXE_CIPHER_INIT; + } + + od->evp_ctx = ctx; + od->owner = CurrentResourceOwner; + od->next = open_ciphers; + od->prev = NULL; + open_ciphers = od; + + if (i->ciph->cipher_func) + od->evp_ciph = i->ciph->cipher_func(); + + /* The PX_Cipher is allocated in current memory context */ + c = palloc(sizeof(*c)); + c->block_size = gen_ossl_block_size; + c->key_size = gen_ossl_key_size; + c->iv_size = gen_ossl_iv_size; + c->free = gen_ossl_free; + c->init = od->ciph->init; + c->encrypt = gen_ossl_encrypt; + c->decrypt = gen_ossl_decrypt; + c->ptr = od; + + *res = c; + return 0; +} diff --git a/contrib/pgcrypto/pgcrypto--1.0--1.1.sql b/contrib/pgcrypto/pgcrypto--1.0--1.1.sql new file mode 100644 index 0000000..42e0c7f --- /dev/null +++ b/contrib/pgcrypto/pgcrypto--1.0--1.1.sql @@ -0,0 +1,9 @@ +/* contrib/pgcrypto/pgcrypto--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgcrypto UPDATE TO '1.1'" to load this file. \quit + +CREATE FUNCTION gen_random_uuid() +RETURNS uuid +AS 'MODULE_PATHNAME', 'pg_random_uuid' +LANGUAGE C VOLATILE; diff --git a/contrib/pgcrypto/pgcrypto--1.1--1.2.sql b/contrib/pgcrypto/pgcrypto--1.1--1.2.sql new file mode 100644 index 0000000..753e169 --- /dev/null +++ b/contrib/pgcrypto/pgcrypto--1.1--1.2.sql @@ -0,0 +1,14 @@ +/* contrib/pgcrypto/pgcrypto--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgcrypto UPDATE TO '1.2'" to load this file. \quit + +CREATE FUNCTION armor(bytea, text[], text[]) +RETURNS text +AS 'MODULE_PATHNAME', 'pg_armor' +LANGUAGE C IMMUTABLE STRICT; + +CREATE FUNCTION pgp_armor_headers(text, key OUT text, value OUT text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pgp_armor_headers' +LANGUAGE C IMMUTABLE STRICT; diff --git a/contrib/pgcrypto/pgcrypto--1.2--1.3.sql b/contrib/pgcrypto/pgcrypto--1.2--1.3.sql new file mode 100644 index 0000000..525a037 --- /dev/null +++ b/contrib/pgcrypto/pgcrypto--1.2--1.3.sql @@ -0,0 +1,41 @@ +/* contrib/pgcrypto/pgcrypto--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgcrypto UPDATE TO '1.3'" to load this file. \quit + +ALTER FUNCTION digest(text, text) PARALLEL SAFE; +ALTER FUNCTION digest(bytea, text) PARALLEL SAFE; +ALTER FUNCTION hmac(text, text, text) PARALLEL SAFE; +ALTER FUNCTION hmac(bytea, bytea, text) PARALLEL SAFE; +ALTER FUNCTION crypt(text, text) PARALLEL SAFE; +ALTER FUNCTION gen_salt(text) PARALLEL SAFE; +ALTER FUNCTION gen_salt(text, int4) PARALLEL SAFE; +ALTER FUNCTION encrypt(bytea, bytea, text) PARALLEL SAFE; +ALTER FUNCTION decrypt(bytea, bytea, text) PARALLEL SAFE; +ALTER FUNCTION encrypt_iv(bytea, bytea, bytea, text) PARALLEL SAFE; +ALTER FUNCTION decrypt_iv(bytea, bytea, bytea, text) PARALLEL SAFE; +ALTER FUNCTION gen_random_bytes(int4) PARALLEL SAFE; +ALTER FUNCTION gen_random_uuid() PARALLEL SAFE; +ALTER FUNCTION pgp_sym_encrypt(text, text) PARALLEL SAFE; +ALTER FUNCTION pgp_sym_encrypt_bytea(bytea, text) PARALLEL SAFE; +ALTER FUNCTION pgp_sym_encrypt(text, text, text) PARALLEL SAFE; +ALTER FUNCTION pgp_sym_encrypt_bytea(bytea, text, text) PARALLEL SAFE; +ALTER FUNCTION pgp_sym_decrypt(bytea, text) PARALLEL SAFE; +ALTER FUNCTION pgp_sym_decrypt_bytea(bytea, text) PARALLEL SAFE; +ALTER FUNCTION pgp_sym_decrypt(bytea, text, text) PARALLEL SAFE; +ALTER FUNCTION pgp_sym_decrypt_bytea(bytea, text, text) PARALLEL SAFE; +ALTER FUNCTION pgp_pub_encrypt(text, bytea) PARALLEL SAFE; +ALTER FUNCTION pgp_pub_encrypt_bytea(bytea, bytea) PARALLEL SAFE; +ALTER FUNCTION pgp_pub_encrypt(text, bytea, text) PARALLEL SAFE; +ALTER FUNCTION pgp_pub_encrypt_bytea(bytea, bytea, text) PARALLEL SAFE; +ALTER FUNCTION pgp_pub_decrypt(bytea, bytea) PARALLEL SAFE; +ALTER FUNCTION pgp_pub_decrypt_bytea(bytea, bytea) PARALLEL SAFE; +ALTER FUNCTION pgp_pub_decrypt(bytea, bytea, text) PARALLEL SAFE; +ALTER FUNCTION pgp_pub_decrypt_bytea(bytea, bytea, text) PARALLEL SAFE; +ALTER FUNCTION pgp_pub_decrypt(bytea, bytea, text, text) PARALLEL SAFE; +ALTER FUNCTION pgp_pub_decrypt_bytea(bytea, bytea, text, text) PARALLEL SAFE; +ALTER FUNCTION pgp_key_id(bytea) PARALLEL SAFE; +ALTER FUNCTION armor(bytea) PARALLEL SAFE; +ALTER FUNCTION armor(bytea, text[], text[]) PARALLEL SAFE; +ALTER FUNCTION dearmor(text) PARALLEL SAFE; +ALTER FUNCTION pgp_armor_headers(text) PARALLEL SAFE; diff --git a/contrib/pgcrypto/pgcrypto--1.3.sql b/contrib/pgcrypto/pgcrypto--1.3.sql new file mode 100644 index 0000000..c2628ca --- /dev/null +++ b/contrib/pgcrypto/pgcrypto--1.3.sql @@ -0,0 +1,217 @@ +/* contrib/pgcrypto/pgcrypto--1.3.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pgcrypto" to load this file. \quit + +CREATE FUNCTION digest(text, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pg_digest' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION digest(bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pg_digest' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION hmac(text, text, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pg_hmac' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION hmac(bytea, bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pg_hmac' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION crypt(text, text) +RETURNS text +AS 'MODULE_PATHNAME', 'pg_crypt' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gen_salt(text) +RETURNS text +AS 'MODULE_PATHNAME', 'pg_gen_salt' +LANGUAGE C VOLATILE STRICT PARALLEL SAFE; + +CREATE FUNCTION gen_salt(text, int4) +RETURNS text +AS 'MODULE_PATHNAME', 'pg_gen_salt_rounds' +LANGUAGE C VOLATILE STRICT PARALLEL SAFE; + +CREATE FUNCTION encrypt(bytea, bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pg_encrypt' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION decrypt(bytea, bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pg_decrypt' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION encrypt_iv(bytea, bytea, bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pg_encrypt_iv' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION decrypt_iv(bytea, bytea, bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pg_decrypt_iv' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gen_random_bytes(int4) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pg_random_bytes' +LANGUAGE C VOLATILE STRICT PARALLEL SAFE; + +CREATE FUNCTION gen_random_uuid() +RETURNS uuid +AS 'MODULE_PATHNAME', 'pg_random_uuid' +LANGUAGE C VOLATILE PARALLEL SAFE; + +-- +-- pgp_sym_encrypt(data, key) +-- +CREATE FUNCTION pgp_sym_encrypt(text, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_sym_encrypt_text' +LANGUAGE C STRICT PARALLEL SAFE; + +CREATE FUNCTION pgp_sym_encrypt_bytea(bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_sym_encrypt_bytea' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- pgp_sym_encrypt(data, key, args) +-- +CREATE FUNCTION pgp_sym_encrypt(text, text, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_sym_encrypt_text' +LANGUAGE C STRICT PARALLEL SAFE; + +CREATE FUNCTION pgp_sym_encrypt_bytea(bytea, text, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_sym_encrypt_bytea' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- pgp_sym_decrypt(data, key) +-- +CREATE FUNCTION pgp_sym_decrypt(bytea, text) +RETURNS text +AS 'MODULE_PATHNAME', 'pgp_sym_decrypt_text' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION pgp_sym_decrypt_bytea(bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_sym_decrypt_bytea' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- +-- pgp_sym_decrypt(data, key, args) +-- +CREATE FUNCTION pgp_sym_decrypt(bytea, text, text) +RETURNS text +AS 'MODULE_PATHNAME', 'pgp_sym_decrypt_text' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION pgp_sym_decrypt_bytea(bytea, text, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_sym_decrypt_bytea' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- +-- pgp_pub_encrypt(data, key) +-- +CREATE FUNCTION pgp_pub_encrypt(text, bytea) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_pub_encrypt_text' +LANGUAGE C STRICT PARALLEL SAFE; + +CREATE FUNCTION pgp_pub_encrypt_bytea(bytea, bytea) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_pub_encrypt_bytea' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- pgp_pub_encrypt(data, key, args) +-- +CREATE FUNCTION pgp_pub_encrypt(text, bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_pub_encrypt_text' +LANGUAGE C STRICT PARALLEL SAFE; + +CREATE FUNCTION pgp_pub_encrypt_bytea(bytea, bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_pub_encrypt_bytea' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- pgp_pub_decrypt(data, key) +-- +CREATE FUNCTION pgp_pub_decrypt(bytea, bytea) +RETURNS text +AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_text' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION pgp_pub_decrypt_bytea(bytea, bytea) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_bytea' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- +-- pgp_pub_decrypt(data, key, psw) +-- +CREATE FUNCTION pgp_pub_decrypt(bytea, bytea, text) +RETURNS text +AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_text' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION pgp_pub_decrypt_bytea(bytea, bytea, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_bytea' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- +-- pgp_pub_decrypt(data, key, psw, arg) +-- +CREATE FUNCTION pgp_pub_decrypt(bytea, bytea, text, text) +RETURNS text +AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_text' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION pgp_pub_decrypt_bytea(bytea, bytea, text, text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pgp_pub_decrypt_bytea' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- +-- PGP key ID +-- +CREATE FUNCTION pgp_key_id(bytea) +RETURNS text +AS 'MODULE_PATHNAME', 'pgp_key_id_w' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +-- +-- pgp armor +-- +CREATE FUNCTION armor(bytea) +RETURNS text +AS 'MODULE_PATHNAME', 'pg_armor' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION armor(bytea, text[], text[]) +RETURNS text +AS 'MODULE_PATHNAME', 'pg_armor' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION dearmor(text) +RETURNS bytea +AS 'MODULE_PATHNAME', 'pg_dearmor' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION pgp_armor_headers(text, key OUT text, value OUT text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pgp_armor_headers' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; diff --git a/contrib/pgcrypto/pgcrypto.c b/contrib/pgcrypto/pgcrypto.c new file mode 100644 index 0000000..96447c5 --- /dev/null +++ b/contrib/pgcrypto/pgcrypto.c @@ -0,0 +1,477 @@ +/* + * pgcrypto.c + * Various cryptographic stuff for PostgreSQL. + * + * Copyright (c) 2001 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgcrypto.c + */ + +#include "postgres.h" + +#include + +#include "parser/scansup.h" +#include "pgcrypto.h" +#include "px-crypt.h" +#include "px.h" +#include "utils/builtins.h" +#include "utils/uuid.h" +#include "varatt.h" + +PG_MODULE_MAGIC; + +/* private stuff */ + +typedef int (*PFN) (const char *name, void **res); +static void *find_provider(text *name, PFN provider_lookup, const char *desc, + int silent); + +/* SQL function: hash(bytea, text) returns bytea */ +PG_FUNCTION_INFO_V1(pg_digest); + +Datum +pg_digest(PG_FUNCTION_ARGS) +{ + bytea *arg; + text *name; + unsigned len, + hlen; + PX_MD *md; + bytea *res; + + name = PG_GETARG_TEXT_PP(1); + + /* will give error if fails */ + md = find_provider(name, (PFN) px_find_digest, "Digest", 0); + + hlen = px_md_result_size(md); + + res = (text *) palloc(hlen + VARHDRSZ); + SET_VARSIZE(res, hlen + VARHDRSZ); + + arg = PG_GETARG_BYTEA_PP(0); + len = VARSIZE_ANY_EXHDR(arg); + + px_md_update(md, (uint8 *) VARDATA_ANY(arg), len); + px_md_finish(md, (uint8 *) VARDATA(res)); + px_md_free(md); + + PG_FREE_IF_COPY(arg, 0); + PG_FREE_IF_COPY(name, 1); + + PG_RETURN_BYTEA_P(res); +} + +/* SQL function: hmac(data:bytea, key:bytea, type:text) returns bytea */ +PG_FUNCTION_INFO_V1(pg_hmac); + +Datum +pg_hmac(PG_FUNCTION_ARGS) +{ + bytea *arg; + bytea *key; + text *name; + unsigned len, + hlen, + klen; + PX_HMAC *h; + bytea *res; + + name = PG_GETARG_TEXT_PP(2); + + /* will give error if fails */ + h = find_provider(name, (PFN) px_find_hmac, "HMAC", 0); + + hlen = px_hmac_result_size(h); + + res = (text *) palloc(hlen + VARHDRSZ); + SET_VARSIZE(res, hlen + VARHDRSZ); + + arg = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + len = VARSIZE_ANY_EXHDR(arg); + klen = VARSIZE_ANY_EXHDR(key); + + px_hmac_init(h, (uint8 *) VARDATA_ANY(key), klen); + px_hmac_update(h, (uint8 *) VARDATA_ANY(arg), len); + px_hmac_finish(h, (uint8 *) VARDATA(res)); + px_hmac_free(h); + + PG_FREE_IF_COPY(arg, 0); + PG_FREE_IF_COPY(key, 1); + PG_FREE_IF_COPY(name, 2); + + PG_RETURN_BYTEA_P(res); +} + + +/* SQL function: pg_gen_salt(text) returns text */ +PG_FUNCTION_INFO_V1(pg_gen_salt); + +Datum +pg_gen_salt(PG_FUNCTION_ARGS) +{ + text *arg0 = PG_GETARG_TEXT_PP(0); + int len; + char buf[PX_MAX_SALT_LEN + 1]; + + text_to_cstring_buffer(arg0, buf, sizeof(buf)); + len = px_gen_salt(buf, buf, 0); + if (len < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("gen_salt: %s", px_strerror(len)))); + + PG_FREE_IF_COPY(arg0, 0); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(buf, len)); +} + +/* SQL function: pg_gen_salt(text, int4) returns text */ +PG_FUNCTION_INFO_V1(pg_gen_salt_rounds); + +Datum +pg_gen_salt_rounds(PG_FUNCTION_ARGS) +{ + text *arg0 = PG_GETARG_TEXT_PP(0); + int rounds = PG_GETARG_INT32(1); + int len; + char buf[PX_MAX_SALT_LEN + 1]; + + text_to_cstring_buffer(arg0, buf, sizeof(buf)); + len = px_gen_salt(buf, buf, rounds); + if (len < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("gen_salt: %s", px_strerror(len)))); + + PG_FREE_IF_COPY(arg0, 0); + + PG_RETURN_TEXT_P(cstring_to_text_with_len(buf, len)); +} + +/* SQL function: pg_crypt(psw:text, salt:text) returns text */ +PG_FUNCTION_INFO_V1(pg_crypt); + +Datum +pg_crypt(PG_FUNCTION_ARGS) +{ + text *arg0 = PG_GETARG_TEXT_PP(0); + text *arg1 = PG_GETARG_TEXT_PP(1); + char *buf0, + *buf1, + *cres, + *resbuf; + text *res; + + buf0 = text_to_cstring(arg0); + buf1 = text_to_cstring(arg1); + + resbuf = palloc0(PX_MAX_CRYPT); + + cres = px_crypt(buf0, buf1, resbuf, PX_MAX_CRYPT); + + pfree(buf0); + pfree(buf1); + + if (cres == NULL) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), + errmsg("crypt(3) returned NULL"))); + + res = cstring_to_text(cres); + + pfree(resbuf); + + PG_FREE_IF_COPY(arg0, 0); + PG_FREE_IF_COPY(arg1, 1); + + PG_RETURN_TEXT_P(res); +} + +/* SQL function: pg_encrypt(bytea, bytea, text) returns bytea */ +PG_FUNCTION_INFO_V1(pg_encrypt); + +Datum +pg_encrypt(PG_FUNCTION_ARGS) +{ + int err; + bytea *data, + *key, + *res; + text *type; + PX_Combo *c; + unsigned dlen, + klen, + rlen; + + type = PG_GETARG_TEXT_PP(2); + c = find_provider(type, (PFN) px_find_combo, "Cipher", 0); + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + dlen = VARSIZE_ANY_EXHDR(data); + klen = VARSIZE_ANY_EXHDR(key); + + rlen = px_combo_encrypt_len(c, dlen); + res = palloc(VARHDRSZ + rlen); + + err = px_combo_init(c, (uint8 *) VARDATA_ANY(key), klen, NULL, 0); + if (!err) + err = px_combo_encrypt(c, (uint8 *) VARDATA_ANY(data), dlen, + (uint8 *) VARDATA(res), &rlen); + px_combo_free(c); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + PG_FREE_IF_COPY(type, 2); + + if (err) + { + pfree(res); + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), + errmsg("encrypt error: %s", px_strerror(err)))); + } + + SET_VARSIZE(res, VARHDRSZ + rlen); + PG_RETURN_BYTEA_P(res); +} + +/* SQL function: pg_decrypt(bytea, bytea, text) returns bytea */ +PG_FUNCTION_INFO_V1(pg_decrypt); + +Datum +pg_decrypt(PG_FUNCTION_ARGS) +{ + int err; + bytea *data, + *key, + *res; + text *type; + PX_Combo *c; + unsigned dlen, + klen, + rlen; + + type = PG_GETARG_TEXT_PP(2); + c = find_provider(type, (PFN) px_find_combo, "Cipher", 0); + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + dlen = VARSIZE_ANY_EXHDR(data); + klen = VARSIZE_ANY_EXHDR(key); + + rlen = px_combo_decrypt_len(c, dlen); + res = palloc(VARHDRSZ + rlen); + + err = px_combo_init(c, (uint8 *) VARDATA_ANY(key), klen, NULL, 0); + if (!err) + err = px_combo_decrypt(c, (uint8 *) VARDATA_ANY(data), dlen, + (uint8 *) VARDATA(res), &rlen); + + px_combo_free(c); + + if (err) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), + errmsg("decrypt error: %s", px_strerror(err)))); + + SET_VARSIZE(res, VARHDRSZ + rlen); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + PG_FREE_IF_COPY(type, 2); + + PG_RETURN_BYTEA_P(res); +} + +/* SQL function: pg_encrypt_iv(bytea, bytea, bytea, text) returns bytea */ +PG_FUNCTION_INFO_V1(pg_encrypt_iv); + +Datum +pg_encrypt_iv(PG_FUNCTION_ARGS) +{ + int err; + bytea *data, + *key, + *iv, + *res; + text *type; + PX_Combo *c; + unsigned dlen, + klen, + ivlen, + rlen; + + type = PG_GETARG_TEXT_PP(3); + c = find_provider(type, (PFN) px_find_combo, "Cipher", 0); + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + iv = PG_GETARG_BYTEA_PP(2); + dlen = VARSIZE_ANY_EXHDR(data); + klen = VARSIZE_ANY_EXHDR(key); + ivlen = VARSIZE_ANY_EXHDR(iv); + + rlen = px_combo_encrypt_len(c, dlen); + res = palloc(VARHDRSZ + rlen); + + err = px_combo_init(c, (uint8 *) VARDATA_ANY(key), klen, + (uint8 *) VARDATA_ANY(iv), ivlen); + if (!err) + err = px_combo_encrypt(c, (uint8 *) VARDATA_ANY(data), dlen, + (uint8 *) VARDATA(res), &rlen); + + px_combo_free(c); + + if (err) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), + errmsg("encrypt_iv error: %s", px_strerror(err)))); + + SET_VARSIZE(res, VARHDRSZ + rlen); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + PG_FREE_IF_COPY(iv, 2); + PG_FREE_IF_COPY(type, 3); + + PG_RETURN_BYTEA_P(res); +} + +/* SQL function: pg_decrypt_iv(bytea, bytea, bytea, text) returns bytea */ +PG_FUNCTION_INFO_V1(pg_decrypt_iv); + +Datum +pg_decrypt_iv(PG_FUNCTION_ARGS) +{ + int err; + bytea *data, + *key, + *iv, + *res; + text *type; + PX_Combo *c; + unsigned dlen, + klen, + rlen, + ivlen; + + type = PG_GETARG_TEXT_PP(3); + c = find_provider(type, (PFN) px_find_combo, "Cipher", 0); + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + iv = PG_GETARG_BYTEA_PP(2); + dlen = VARSIZE_ANY_EXHDR(data); + klen = VARSIZE_ANY_EXHDR(key); + ivlen = VARSIZE_ANY_EXHDR(iv); + + rlen = px_combo_decrypt_len(c, dlen); + res = palloc(VARHDRSZ + rlen); + + err = px_combo_init(c, (uint8 *) VARDATA_ANY(key), klen, + (uint8 *) VARDATA_ANY(iv), ivlen); + if (!err) + err = px_combo_decrypt(c, (uint8 *) VARDATA_ANY(data), dlen, + (uint8 *) VARDATA(res), &rlen); + + px_combo_free(c); + + if (err) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), + errmsg("decrypt_iv error: %s", px_strerror(err)))); + + SET_VARSIZE(res, VARHDRSZ + rlen); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + PG_FREE_IF_COPY(iv, 2); + PG_FREE_IF_COPY(type, 3); + + PG_RETURN_BYTEA_P(res); +} + +/* SQL function: pg_random_bytes(int4) returns bytea */ +PG_FUNCTION_INFO_V1(pg_random_bytes); + +Datum +pg_random_bytes(PG_FUNCTION_ARGS) +{ + int len = PG_GETARG_INT32(0); + bytea *res; + + if (len < 1 || len > 1024) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), + errmsg("Length not in range"))); + + res = palloc(VARHDRSZ + len); + SET_VARSIZE(res, VARHDRSZ + len); + + /* generate result */ + if (!pg_strong_random(VARDATA(res), len)) + px_THROW_ERROR(PXE_NO_RANDOM); + + PG_RETURN_BYTEA_P(res); +} + +/* SQL function: gen_random_uuid() returns uuid */ +PG_FUNCTION_INFO_V1(pg_random_uuid); + +Datum +pg_random_uuid(PG_FUNCTION_ARGS) +{ + /* redirect to built-in function */ + return gen_random_uuid(fcinfo); +} + +static void * +find_provider(text *name, + PFN provider_lookup, + const char *desc, int silent) +{ + void *res; + char *buf; + int err; + + buf = downcase_truncate_identifier(VARDATA_ANY(name), + VARSIZE_ANY_EXHDR(name), + false); + + err = provider_lookup(buf, &res); + + if (err && !silent) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Cannot use \"%s\": %s", buf, px_strerror(err)))); + + pfree(buf); + + return err ? NULL : res; +} diff --git a/contrib/pgcrypto/pgcrypto.control b/contrib/pgcrypto/pgcrypto.control new file mode 100644 index 0000000..d2151d3 --- /dev/null +++ b/contrib/pgcrypto/pgcrypto.control @@ -0,0 +1,6 @@ +# pgcrypto extension +comment = 'cryptographic functions' +default_version = '1.3' +module_pathname = '$libdir/pgcrypto' +relocatable = true +trusted = true diff --git a/contrib/pgcrypto/pgcrypto.h b/contrib/pgcrypto/pgcrypto.h new file mode 100644 index 0000000..65a1ed3 --- /dev/null +++ b/contrib/pgcrypto/pgcrypto.h @@ -0,0 +1,37 @@ +/* + * pgcrypto.h + * Header file for pgcrypto. + * + * Copyright (c) 2000 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgcrypto.h + */ + +#ifndef _PG_CRYPTO_H +#define _PG_CRYPTO_H + +#include "fmgr.h" + +#endif diff --git a/contrib/pgcrypto/pgp-armor.c b/contrib/pgcrypto/pgp-armor.c new file mode 100644 index 0000000..9128756 --- /dev/null +++ b/contrib/pgcrypto/pgp-armor.c @@ -0,0 +1,488 @@ +/* + * pgp-armor.c + * PGP ascii-armor. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-armor.c + */ + +#include "postgres.h" + +#include "pgp.h" +#include "px.h" + +/* + * BASE64 - duplicated :( + */ + +static const unsigned char _base64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static int +pg_base64_encode(const uint8 *src, unsigned len, uint8 *dst) +{ + uint8 *p, + *lend = dst + 76; + const uint8 *s, + *end = src + len; + int pos = 2; + unsigned long buf = 0; + + s = src; + p = dst; + + while (s < end) + { + buf |= *s << (pos << 3); + pos--; + s++; + + /* + * write it out + */ + if (pos < 0) + { + *p++ = _base64[(buf >> 18) & 0x3f]; + *p++ = _base64[(buf >> 12) & 0x3f]; + *p++ = _base64[(buf >> 6) & 0x3f]; + *p++ = _base64[buf & 0x3f]; + + pos = 2; + buf = 0; + } + if (p >= lend) + { + *p++ = '\n'; + lend = p + 76; + } + } + if (pos != 2) + { + *p++ = _base64[(buf >> 18) & 0x3f]; + *p++ = _base64[(buf >> 12) & 0x3f]; + *p++ = (pos == 0) ? _base64[(buf >> 6) & 0x3f] : '='; + *p++ = '='; + } + + return p - dst; +} + +/* probably should use lookup table */ +static int +pg_base64_decode(const uint8 *src, unsigned len, uint8 *dst) +{ + const uint8 *srcend = src + len, + *s = src; + uint8 *p = dst; + char c; + unsigned b = 0; + unsigned long buf = 0; + int pos = 0, + end = 0; + + while (s < srcend) + { + c = *s++; + if (c >= 'A' && c <= 'Z') + b = c - 'A'; + else if (c >= 'a' && c <= 'z') + b = c - 'a' + 26; + else if (c >= '0' && c <= '9') + b = c - '0' + 52; + else if (c == '+') + b = 62; + else if (c == '/') + b = 63; + else if (c == '=') + { + /* + * end sequence + */ + if (!end) + { + if (pos == 2) + end = 1; + else if (pos == 3) + end = 2; + else + return PXE_PGP_CORRUPT_ARMOR; + } + b = 0; + } + else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') + continue; + else + return PXE_PGP_CORRUPT_ARMOR; + + /* + * add it to buffer + */ + buf = (buf << 6) + b; + pos++; + if (pos == 4) + { + *p++ = (buf >> 16) & 255; + if (end == 0 || end > 1) + *p++ = (buf >> 8) & 255; + if (end == 0 || end > 2) + *p++ = buf & 255; + buf = 0; + pos = 0; + } + } + + if (pos != 0) + return PXE_PGP_CORRUPT_ARMOR; + return p - dst; +} + +static unsigned +pg_base64_enc_len(unsigned srclen) +{ + /* + * 3 bytes will be converted to 4, linefeed after 76 chars + */ + return (srclen + 2) / 3 * 4 + srclen / (76 * 3 / 4); +} + +static unsigned +pg_base64_dec_len(unsigned srclen) +{ + return (srclen * 3) >> 2; +} + +/* + * PGP armor + */ + +static const char *armor_header = "-----BEGIN PGP MESSAGE-----\n"; +static const char *armor_footer = "\n-----END PGP MESSAGE-----\n"; + +/* CRC24 implementation from rfc2440 */ +#define CRC24_INIT 0x00b704ceL +#define CRC24_POLY 0x01864cfbL +static long +crc24(const uint8 *data, unsigned len) +{ + unsigned crc = CRC24_INIT; + int i; + + while (len--) + { + crc ^= (*data++) << 16; + for (i = 0; i < 8; i++) + { + crc <<= 1; + if (crc & 0x1000000) + crc ^= CRC24_POLY; + } + } + return crc & 0xffffffL; +} + +void +pgp_armor_encode(const uint8 *src, unsigned len, StringInfo dst, + int num_headers, char **keys, char **values) +{ + int n; + int res; + unsigned b64len; + unsigned crc = crc24(src, len); + + appendStringInfoString(dst, armor_header); + + for (n = 0; n < num_headers; n++) + appendStringInfo(dst, "%s: %s\n", keys[n], values[n]); + appendStringInfoChar(dst, '\n'); + + /* make sure we have enough room to pg_base64_encode() */ + b64len = pg_base64_enc_len(len); + enlargeStringInfo(dst, (int) b64len); + + res = pg_base64_encode(src, len, (uint8 *) dst->data + dst->len); + if (res > b64len) + elog(FATAL, "overflow - encode estimate too small"); + dst->len += res; + + if (*(dst->data + dst->len - 1) != '\n') + appendStringInfoChar(dst, '\n'); + + appendStringInfoChar(dst, '='); + appendStringInfoChar(dst, _base64[(crc >> 18) & 0x3f]); + appendStringInfoChar(dst, _base64[(crc >> 12) & 0x3f]); + appendStringInfoChar(dst, _base64[(crc >> 6) & 0x3f]); + appendStringInfoChar(dst, _base64[crc & 0x3f]); + + appendStringInfoString(dst, armor_footer); +} + +static const uint8 * +find_str(const uint8 *data, const uint8 *data_end, const char *str, int strlen) +{ + const uint8 *p = data; + + if (!strlen) + return NULL; + if (data_end - data < strlen) + return NULL; + while (p < data_end) + { + p = memchr(p, str[0], data_end - p); + if (p == NULL) + return NULL; + if (p + strlen > data_end) + return NULL; + if (memcmp(p, str, strlen) == 0) + return p; + p++; + } + return NULL; +} + +static int +find_header(const uint8 *data, const uint8 *datend, + const uint8 **start_p, int is_end) +{ + const uint8 *p = data; + static const char *start_sep = "-----BEGIN"; + static const char *end_sep = "-----END"; + const char *sep = is_end ? end_sep : start_sep; + + /* find header line */ + while (1) + { + p = find_str(p, datend, sep, strlen(sep)); + if (p == NULL) + return PXE_PGP_CORRUPT_ARMOR; + /* it must start at beginning of line */ + if (p == data || *(p - 1) == '\n') + break; + p += strlen(sep); + } + *start_p = p; + p += strlen(sep); + + /* check if header text ok */ + for (; p < datend && *p != '-'; p++) + { + /* various junk can be there, but definitely not line-feed */ + if (*p >= ' ') + continue; + return PXE_PGP_CORRUPT_ARMOR; + } + if (datend - p < 5 || memcmp(p, sep, 5) != 0) + return PXE_PGP_CORRUPT_ARMOR; + p += 5; + + /* check if at end of line */ + if (p < datend) + { + if (*p != '\n' && *p != '\r') + return PXE_PGP_CORRUPT_ARMOR; + if (*p == '\r') + p++; + if (p < datend && *p == '\n') + p++; + } + return p - *start_p; +} + +int +pgp_armor_decode(const uint8 *src, int len, StringInfo dst) +{ + const uint8 *p = src; + const uint8 *data_end = src + len; + long crc; + const uint8 *base64_start, + *armor_end; + const uint8 *base64_end = NULL; + uint8 buf[4]; + int hlen; + int blen; + int res = PXE_PGP_CORRUPT_ARMOR; + + /* armor start */ + hlen = find_header(src, data_end, &p, 0); + if (hlen <= 0) + goto out; + p += hlen; + + /* armor end */ + hlen = find_header(p, data_end, &armor_end, 1); + if (hlen <= 0) + goto out; + + /* skip comments - find empty line */ + while (p < armor_end && *p != '\n' && *p != '\r') + { + p = memchr(p, '\n', armor_end - p); + if (!p) + goto out; + + /* step to start of next line */ + p++; + } + base64_start = p; + + /* find crc pos */ + for (p = armor_end; p >= base64_start; p--) + if (*p == '=') + { + base64_end = p - 1; + break; + } + if (base64_end == NULL) + goto out; + + /* decode crc */ + if (pg_base64_decode(p + 1, 4, buf) != 3) + goto out; + crc = (((long) buf[0]) << 16) + (((long) buf[1]) << 8) + (long) buf[2]; + + /* decode data */ + blen = (int) pg_base64_dec_len(len); + enlargeStringInfo(dst, blen); + res = pg_base64_decode(base64_start, base64_end - base64_start, (uint8 *) dst->data); + if (res > blen) + elog(FATAL, "overflow - decode estimate too small"); + if (res >= 0) + { + if (crc24((uint8 *) dst->data, res) == crc) + dst->len += res; + else + res = PXE_PGP_CORRUPT_ARMOR; + } +out: + return res; +} + +/* + * Extracts all armor headers from an ASCII-armored input. + * + * Returns 0 on success, or PXE_* error code on error. On success, the + * number of headers and their keys and values are returned in *nheaders, + * *nkeys and *nvalues. + */ +int +pgp_extract_armor_headers(const uint8 *src, unsigned len, + int *nheaders, char ***keys, char ***values) +{ + const uint8 *data_end = src + len; + const uint8 *p; + const uint8 *base64_start; + const uint8 *armor_start; + const uint8 *armor_end; + Size armor_len; + char *line; + char *nextline; + char *eol, + *colon; + int hlen; + char *buf; + int hdrlines; + int n; + + /* armor start */ + hlen = find_header(src, data_end, &armor_start, 0); + if (hlen <= 0) + return PXE_PGP_CORRUPT_ARMOR; + armor_start += hlen; + + /* armor end */ + hlen = find_header(armor_start, data_end, &armor_end, 1); + if (hlen <= 0) + return PXE_PGP_CORRUPT_ARMOR; + + /* Count the number of armor header lines. */ + hdrlines = 0; + p = armor_start; + while (p < armor_end && *p != '\n' && *p != '\r') + { + p = memchr(p, '\n', armor_end - p); + if (!p) + return PXE_PGP_CORRUPT_ARMOR; + + /* step to start of next line */ + p++; + hdrlines++; + } + base64_start = p; + + /* + * Make a modifiable copy of the part of the input that contains the + * headers. The returned key/value pointers will point inside the buffer. + */ + armor_len = base64_start - armor_start; + buf = palloc(armor_len + 1); + memcpy(buf, armor_start, armor_len); + buf[armor_len] = '\0'; + + /* Allocate return arrays */ + *keys = (char **) palloc(hdrlines * sizeof(char *)); + *values = (char **) palloc(hdrlines * sizeof(char *)); + + /* + * Split the header lines at newlines and ": " separators, and collect + * pointers to the keys and values in the return arrays. + */ + n = 0; + line = buf; + for (;;) + { + /* find end of line */ + eol = strchr(line, '\n'); + if (!eol) + break; + nextline = eol + 1; + /* if the line ends in CR + LF, strip the CR */ + if (eol > line && *(eol - 1) == '\r') + eol--; + *eol = '\0'; + + /* find colon+space separating the key and value */ + colon = strstr(line, ": "); + if (!colon) + return PXE_PGP_CORRUPT_ARMOR; + *colon = '\0'; + + /* shouldn't happen, we counted the number of lines beforehand */ + if (n >= hdrlines) + elog(ERROR, "unexpected number of armor header lines"); + + (*keys)[n] = line; + (*values)[n] = colon + 2; + n++; + + /* step to start of next line */ + line = nextline; + } + + if (n != hdrlines) + elog(ERROR, "unexpected number of armor header lines"); + + *nheaders = n; + return 0; +} diff --git a/contrib/pgcrypto/pgp-cfb.c b/contrib/pgcrypto/pgp-cfb.c new file mode 100644 index 0000000..de41e82 --- /dev/null +++ b/contrib/pgcrypto/pgp-cfb.c @@ -0,0 +1,265 @@ +/* + * pgp-cfb.c + * Implements both normal and PGP-specific CFB mode. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-cfb.c + */ + +#include "postgres.h" + +#include "pgp.h" +#include "px.h" + +typedef int (*mix_data_t) (PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst); + +struct PGP_CFB +{ + PX_Cipher *ciph; + int block_size; + int pos; + int block_no; + int resync; + uint8 fr[PGP_MAX_BLOCK]; + uint8 fre[PGP_MAX_BLOCK]; + uint8 encbuf[PGP_MAX_BLOCK]; +}; + +int +pgp_cfb_create(PGP_CFB **ctx_p, int algo, const uint8 *key, int key_len, + int resync, uint8 *iv) +{ + int res; + PX_Cipher *ciph; + PGP_CFB *ctx; + + res = pgp_load_cipher(algo, &ciph); + if (res < 0) + return res; + + res = px_cipher_init(ciph, key, key_len, NULL); + if (res < 0) + { + px_cipher_free(ciph); + return res; + } + + ctx = palloc0(sizeof(*ctx)); + ctx->ciph = ciph; + ctx->block_size = px_cipher_block_size(ciph); + ctx->resync = resync; + + if (iv) + memcpy(ctx->fr, iv, ctx->block_size); + + *ctx_p = ctx; + return 0; +} + +void +pgp_cfb_free(PGP_CFB *ctx) +{ + px_cipher_free(ctx->ciph); + px_memset(ctx, 0, sizeof(*ctx)); + pfree(ctx); +} + +/* + * Data processing for normal CFB. (PGP_PKT_SYMENCRYPTED_DATA_MDC) + */ +static int +mix_encrypt_normal(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst) +{ + int i; + + for (i = ctx->pos; i < ctx->pos + len; i++) + *dst++ = ctx->encbuf[i] = ctx->fre[i] ^ (*data++); + ctx->pos += len; + return len; +} + +static int +mix_decrypt_normal(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst) +{ + int i; + + for (i = ctx->pos; i < ctx->pos + len; i++) + { + ctx->encbuf[i] = *data++; + *dst++ = ctx->fre[i] ^ ctx->encbuf[i]; + } + ctx->pos += len; + return len; +} + +/* + * Data processing for old PGP CFB mode. (PGP_PKT_SYMENCRYPTED_DATA) + * + * The goal is to hide the horror from the rest of the code, + * thus its all concentrated here. + */ +static int +mix_encrypt_resync(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst) +{ + int i, + n; + + /* block #2 is 2 bytes long */ + if (ctx->block_no == 2) + { + n = 2 - ctx->pos; + if (len < n) + n = len; + for (i = ctx->pos; i < ctx->pos + n; i++) + *dst++ = ctx->encbuf[i] = ctx->fre[i] ^ (*data++); + + ctx->pos += n; + len -= n; + + if (ctx->pos == 2) + { + memcpy(ctx->fr, ctx->encbuf + 2, ctx->block_size - 2); + memcpy(ctx->fr + ctx->block_size - 2, ctx->encbuf, 2); + ctx->pos = 0; + return n; + } + } + for (i = ctx->pos; i < ctx->pos + len; i++) + *dst++ = ctx->encbuf[i] = ctx->fre[i] ^ (*data++); + ctx->pos += len; + return len; +} + +static int +mix_decrypt_resync(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst) +{ + int i, + n; + + /* block #2 is 2 bytes long */ + if (ctx->block_no == 2) + { + n = 2 - ctx->pos; + if (len < n) + n = len; + for (i = ctx->pos; i < ctx->pos + n; i++) + { + ctx->encbuf[i] = *data++; + *dst++ = ctx->fre[i] ^ ctx->encbuf[i]; + } + ctx->pos += n; + len -= n; + + if (ctx->pos == 2) + { + memcpy(ctx->fr, ctx->encbuf + 2, ctx->block_size - 2); + memcpy(ctx->fr + ctx->block_size - 2, ctx->encbuf, 2); + ctx->pos = 0; + return n; + } + } + for (i = ctx->pos; i < ctx->pos + len; i++) + { + ctx->encbuf[i] = *data++; + *dst++ = ctx->fre[i] ^ ctx->encbuf[i]; + } + ctx->pos += len; + return len; +} + +/* + * common code for both encrypt and decrypt. + */ +static int +cfb_process(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst, + mix_data_t mix_data) +{ + int n; + int res; + + while (len > 0 && ctx->pos > 0) + { + n = ctx->block_size - ctx->pos; + if (len < n) + n = len; + + n = mix_data(ctx, data, n, dst); + data += n; + dst += n; + len -= n; + + if (ctx->pos == ctx->block_size) + { + memcpy(ctx->fr, ctx->encbuf, ctx->block_size); + ctx->pos = 0; + } + } + + while (len > 0) + { + unsigned rlen; + + px_cipher_encrypt(ctx->ciph, 0, ctx->fr, ctx->block_size, ctx->fre, &rlen); + if (ctx->block_no < 5) + ctx->block_no++; + + n = ctx->block_size; + if (len < n) + n = len; + + res = mix_data(ctx, data, n, dst); + data += res; + dst += res; + len -= res; + + if (ctx->pos == ctx->block_size) + { + memcpy(ctx->fr, ctx->encbuf, ctx->block_size); + ctx->pos = 0; + } + } + return 0; +} + +/* + * public interface + */ + +int +pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst) +{ + mix_data_t mix = ctx->resync ? mix_encrypt_resync : mix_encrypt_normal; + + return cfb_process(ctx, data, len, dst, mix); +} + +int +pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst) +{ + mix_data_t mix = ctx->resync ? mix_decrypt_resync : mix_decrypt_normal; + + return cfb_process(ctx, data, len, dst, mix); +} diff --git a/contrib/pgcrypto/pgp-compress.c b/contrib/pgcrypto/pgp-compress.c new file mode 100644 index 0000000..086bec3 --- /dev/null +++ b/contrib/pgcrypto/pgp-compress.c @@ -0,0 +1,346 @@ +/* + * pgp-compress.c + * ZIP and ZLIB compression via zlib. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-compress.c + */ + +#include "postgres.h" + +#include "pgp.h" +#include "px.h" + +/* + * Compressed pkt writer + */ + +#ifdef HAVE_LIBZ + +#include + +#define ZIP_OUT_BUF 8192 +#define ZIP_IN_BLOCK 8192 + +struct ZipStat +{ + uint8 type; + int buf_len; + int hdr_done; + z_stream stream; + uint8 buf[ZIP_OUT_BUF]; +}; + +static void * +z_alloc(void *priv, unsigned n_items, unsigned item_len) +{ + return palloc(n_items * item_len); +} + +static void +z_free(void *priv, void *addr) +{ + pfree(addr); +} + +static int +compress_init(PushFilter *next, void *init_arg, void **priv_p) +{ + int res; + struct ZipStat *st; + PGP_Context *ctx = init_arg; + uint8 type = ctx->compress_algo; + + if (type != PGP_COMPR_ZLIB && type != PGP_COMPR_ZIP) + return PXE_PGP_UNSUPPORTED_COMPR; + + /* + * init + */ + st = palloc0(sizeof(*st)); + st->buf_len = ZIP_OUT_BUF; + st->stream.zalloc = z_alloc; + st->stream.zfree = z_free; + + if (type == PGP_COMPR_ZIP) + res = deflateInit2(&st->stream, ctx->compress_level, + Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + else + res = deflateInit(&st->stream, ctx->compress_level); + if (res != Z_OK) + { + pfree(st); + return PXE_PGP_COMPRESSION_ERROR; + } + *priv_p = st; + + return ZIP_IN_BLOCK; +} + +/* writes compressed data packet */ + +/* can handle zero-len incoming data, but shouldn't */ +static int +compress_process(PushFilter *next, void *priv, const uint8 *data, int len) +{ + int res, + n_out; + struct ZipStat *st = priv; + + /* + * process data + */ + st->stream.next_in = unconstify(uint8 *, data); + st->stream.avail_in = len; + while (st->stream.avail_in > 0) + { + st->stream.next_out = st->buf; + st->stream.avail_out = st->buf_len; + res = deflate(&st->stream, Z_NO_FLUSH); + if (res != Z_OK) + return PXE_PGP_COMPRESSION_ERROR; + + n_out = st->buf_len - st->stream.avail_out; + if (n_out > 0) + { + res = pushf_write(next, st->buf, n_out); + if (res < 0) + return res; + } + } + + return 0; +} + +static int +compress_flush(PushFilter *next, void *priv) +{ + int res, + zres, + n_out; + struct ZipStat *st = priv; + + st->stream.next_in = NULL; + st->stream.avail_in = 0; + while (1) + { + st->stream.next_out = st->buf; + st->stream.avail_out = st->buf_len; + zres = deflate(&st->stream, Z_FINISH); + if (zres != Z_STREAM_END && zres != Z_OK) + return PXE_PGP_COMPRESSION_ERROR; + + n_out = st->buf_len - st->stream.avail_out; + if (n_out > 0) + { + res = pushf_write(next, st->buf, n_out); + if (res < 0) + return res; + } + if (zres == Z_STREAM_END) + break; + } + return 0; +} + +static void +compress_free(void *priv) +{ + struct ZipStat *st = priv; + + deflateEnd(&st->stream); + px_memset(st, 0, sizeof(*st)); + pfree(st); +} + +static const PushFilterOps + compress_filter = { + compress_init, compress_process, compress_flush, compress_free +}; + +int +pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst) +{ + return pushf_create(res, &compress_filter, ctx, dst); +} + +/* + * Decompress + */ +struct DecomprData +{ + int buf_len; /* = ZIP_OUT_BUF */ + int buf_data; /* available data */ + uint8 *pos; + z_stream stream; + int eof; + uint8 buf[ZIP_OUT_BUF]; +}; + +static int +decompress_init(void **priv_p, void *arg, PullFilter *src) +{ + PGP_Context *ctx = arg; + struct DecomprData *dec; + int res; + + if (ctx->compress_algo != PGP_COMPR_ZLIB + && ctx->compress_algo != PGP_COMPR_ZIP) + return PXE_PGP_UNSUPPORTED_COMPR; + + dec = palloc0(sizeof(*dec)); + dec->buf_len = ZIP_OUT_BUF; + *priv_p = dec; + + dec->stream.zalloc = z_alloc; + dec->stream.zfree = z_free; + + if (ctx->compress_algo == PGP_COMPR_ZIP) + res = inflateInit2(&dec->stream, -15); + else + res = inflateInit(&dec->stream); + if (res != Z_OK) + { + pfree(dec); + px_debug("decompress_init: inflateInit error"); + return PXE_PGP_COMPRESSION_ERROR; + } + + return 0; +} + +static int +decompress_read(void *priv, PullFilter *src, int len, + uint8 **data_p, uint8 *buf, int buflen) +{ + int res; + int flush; + struct DecomprData *dec = priv; + +restart: + if (dec->buf_data > 0) + { + if (len > dec->buf_data) + len = dec->buf_data; + *data_p = dec->pos; + dec->pos += len; + dec->buf_data -= len; + return len; + } + + if (dec->eof) + return 0; + + if (dec->stream.avail_in == 0) + { + uint8 *tmp; + + res = pullf_read(src, 8192, &tmp); + if (res < 0) + return res; + dec->stream.next_in = tmp; + dec->stream.avail_in = res; + } + + dec->stream.next_out = dec->buf; + dec->stream.avail_out = dec->buf_len; + dec->pos = dec->buf; + + /* + * Z_SYNC_FLUSH is tell zlib to output as much as possible. It should do + * it anyway (Z_NO_FLUSH), but seems to reserve the right not to. So lets + * follow the API. + */ + flush = dec->stream.avail_in ? Z_SYNC_FLUSH : Z_FINISH; + res = inflate(&dec->stream, flush); + if (res != Z_OK && res != Z_STREAM_END) + { + px_debug("decompress_read: inflate error: %d", res); + return PXE_PGP_CORRUPT_DATA; + } + + dec->buf_data = dec->buf_len - dec->stream.avail_out; + if (res == Z_STREAM_END) + { + uint8 *tmp; + + /* + * A stream must be terminated by a normal packet. If the last stream + * packet in the source stream is a full packet, a normal empty packet + * must follow. Since the underlying packet reader doesn't know that + * the compressed stream has been ended, we need to consume the + * terminating packet here. This read does not harm even if the + * stream has already ended. + */ + res = pullf_read(src, 1, &tmp); + + if (res < 0) + return res; + else if (res > 0) + { + px_debug("decompress_read: extra bytes after end of stream"); + return PXE_PGP_CORRUPT_DATA; + } + dec->eof = 1; + } + goto restart; +} + +static void +decompress_free(void *priv) +{ + struct DecomprData *dec = priv; + + inflateEnd(&dec->stream); + px_memset(dec, 0, sizeof(*dec)); + pfree(dec); +} + +static const PullFilterOps + decompress_filter = { + decompress_init, decompress_read, decompress_free +}; + +int +pgp_decompress_filter(PullFilter **res, PGP_Context *ctx, PullFilter *src) +{ + return pullf_create(res, &decompress_filter, ctx, src); +} +#else /* !HAVE_LIBZ */ + +int +pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst) +{ + return PXE_PGP_UNSUPPORTED_COMPR; +} + +int +pgp_decompress_filter(PullFilter **res, PGP_Context *ctx, PullFilter *src) +{ + return PXE_PGP_UNSUPPORTED_COMPR; +} + +#endif diff --git a/contrib/pgcrypto/pgp-decrypt.c b/contrib/pgcrypto/pgp-decrypt.c new file mode 100644 index 0000000..e1ea5b3 --- /dev/null +++ b/contrib/pgcrypto/pgp-decrypt.c @@ -0,0 +1,1213 @@ +/* + * pgp-decrypt.c + * OpenPGP decrypt. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-decrypt.c + */ + +#include "postgres.h" + +#include "mbuf.h" +#include "pgp.h" +#include "px.h" + +#define NO_CTX_SIZE 0 +#define ALLOW_CTX_SIZE 1 +#define NO_COMPR 0 +#define ALLOW_COMPR 1 +#define NO_MDC 0 +#define NEED_MDC 1 + +#define PKT_NORMAL 1 +#define PKT_STREAM 2 +#define PKT_CONTEXT 3 + +#define MAX_CHUNK (16*1024*1024) + +static int +parse_new_len(PullFilter *src, int *len_p) +{ + uint8 b; + int len; + int pkttype = PKT_NORMAL; + + GETBYTE(src, b); + if (b <= 191) + len = b; + else if (b >= 192 && b <= 223) + { + len = ((unsigned) (b) - 192) << 8; + GETBYTE(src, b); + len += 192 + b; + } + else if (b == 255) + { + GETBYTE(src, b); + len = b; + GETBYTE(src, b); + len = (len << 8) | b; + GETBYTE(src, b); + len = (len << 8) | b; + GETBYTE(src, b); + len = (len << 8) | b; + } + else + { + len = 1 << (b & 0x1F); + pkttype = PKT_STREAM; + } + + if (len < 0 || len > MAX_CHUNK) + { + px_debug("parse_new_len: weird length"); + return PXE_PGP_CORRUPT_DATA; + } + + *len_p = len; + return pkttype; +} + +static int +parse_old_len(PullFilter *src, int *len_p, int lentype) +{ + uint8 b; + int len; + + GETBYTE(src, b); + len = b; + + if (lentype == 1) + { + GETBYTE(src, b); + len = (len << 8) | b; + } + else if (lentype == 2) + { + GETBYTE(src, b); + len = (len << 8) | b; + GETBYTE(src, b); + len = (len << 8) | b; + GETBYTE(src, b); + len = (len << 8) | b; + } + + if (len < 0 || len > MAX_CHUNK) + { + px_debug("parse_old_len: weird length"); + return PXE_PGP_CORRUPT_DATA; + } + *len_p = len; + return PKT_NORMAL; +} + +/* returns pkttype or 0 on eof */ +int +pgp_parse_pkt_hdr(PullFilter *src, uint8 *tag, int *len_p, int allow_ctx) +{ + int lentype; + int res; + uint8 *p; + + /* EOF is normal here, thus we don't use GETBYTE */ + res = pullf_read(src, 1, &p); + if (res < 0) + return res; + if (res == 0) + return 0; + + if ((*p & 0x80) == 0) + { + px_debug("pgp_parse_pkt_hdr: not pkt hdr"); + return PXE_PGP_CORRUPT_DATA; + } + + if (*p & 0x40) + { + *tag = *p & 0x3f; + res = parse_new_len(src, len_p); + } + else + { + lentype = *p & 3; + *tag = (*p >> 2) & 0x0F; + if (lentype == 3) + res = allow_ctx ? PKT_CONTEXT : PXE_PGP_CORRUPT_DATA; + else + res = parse_old_len(src, len_p, lentype); + } + return res; +} + +/* + * Packet reader + */ +struct PktData +{ + int type; + int len; +}; + +static int +pktreader_pull(void *priv, PullFilter *src, int len, + uint8 **data_p, uint8 *buf, int buflen) +{ + int res; + struct PktData *pkt = priv; + + /* PKT_CONTEXT means: whatever there is */ + if (pkt->type == PKT_CONTEXT) + return pullf_read(src, len, data_p); + + while (pkt->len == 0) + { + /* this was last chunk in stream */ + if (pkt->type == PKT_NORMAL) + return 0; + + /* next chunk in stream */ + res = parse_new_len(src, &pkt->len); + if (res < 0) + return res; + pkt->type = res; + } + + if (len > pkt->len) + len = pkt->len; + + res = pullf_read(src, len, data_p); + if (res > 0) + pkt->len -= res; + + return res; +} + +static void +pktreader_free(void *priv) +{ + struct PktData *pkt = priv; + + px_memset(pkt, 0, sizeof(*pkt)); + pfree(pkt); +} + +static struct PullFilterOps pktreader_filter = { + NULL, pktreader_pull, pktreader_free +}; + +/* needs helper function to pass several parameters */ +int +pgp_create_pkt_reader(PullFilter **pf_p, PullFilter *src, int len, + int pkttype, PGP_Context *ctx) +{ + int res; + struct PktData *pkt = palloc(sizeof(*pkt)); + + pkt->type = pkttype; + pkt->len = len; + res = pullf_create(pf_p, &pktreader_filter, pkt, src); + if (res < 0) + pfree(pkt); + return res; +} + +/* + * Prefix check filter + * https://tools.ietf.org/html/rfc4880#section-5.7 + * https://tools.ietf.org/html/rfc4880#section-5.13 + */ + +static int +prefix_init(void **priv_p, void *arg, PullFilter *src) +{ + PGP_Context *ctx = arg; + int len; + int res; + uint8 *buf; + uint8 tmpbuf[PGP_MAX_BLOCK + 2]; + + len = pgp_get_cipher_block_size(ctx->cipher_algo); + /* Make sure we have space for prefix */ + if (len > PGP_MAX_BLOCK) + return PXE_BUG; + + res = pullf_read_max(src, len + 2, &buf, tmpbuf); + if (res < 0) + return res; + if (res != len + 2) + { + px_debug("prefix_init: short read"); + px_memset(tmpbuf, 0, sizeof(tmpbuf)); + return PXE_PGP_CORRUPT_DATA; + } + + if (buf[len - 2] != buf[len] || buf[len - 1] != buf[len + 1]) + { + px_debug("prefix_init: corrupt prefix"); + /* report error in pgp_decrypt() */ + ctx->corrupt_prefix = 1; + } + px_memset(tmpbuf, 0, sizeof(tmpbuf)); + return 0; +} + +static struct PullFilterOps prefix_filter = { + prefix_init, NULL, NULL +}; + + +/* + * Decrypt filter + */ + +static int +decrypt_init(void **priv_p, void *arg, PullFilter *src) +{ + PGP_CFB *cfb = arg; + + *priv_p = cfb; + + /* we need to write somewhere, so ask for a buffer */ + return 4096; +} + +static int +decrypt_read(void *priv, PullFilter *src, int len, + uint8 **data_p, uint8 *buf, int buflen) +{ + PGP_CFB *cfb = priv; + uint8 *tmp; + int res; + + res = pullf_read(src, len, &tmp); + if (res > 0) + { + pgp_cfb_decrypt(cfb, tmp, res, buf); + *data_p = buf; + } + return res; +} + +struct PullFilterOps pgp_decrypt_filter = { + decrypt_init, decrypt_read, NULL +}; + + +/* + * MDC hasher filter + */ + +static int +mdc_init(void **priv_p, void *arg, PullFilter *src) +{ + PGP_Context *ctx = arg; + + *priv_p = ctx; + return pgp_load_digest(PGP_DIGEST_SHA1, &ctx->mdc_ctx); +} + +static void +mdc_free(void *priv) +{ + PGP_Context *ctx = priv; + + if (ctx->use_mdcbuf_filter) + return; + px_md_free(ctx->mdc_ctx); + ctx->mdc_ctx = NULL; +} + +static int +mdc_finish(PGP_Context *ctx, PullFilter *src, int len) +{ + int res; + uint8 hash[20]; + uint8 tmpbuf[20]; + uint8 *data; + + /* should not happen */ + if (ctx->use_mdcbuf_filter) + return PXE_BUG; + + /* It's SHA1 */ + if (len != 20) + return PXE_PGP_CORRUPT_DATA; + + /* mdc_read should not call px_md_update */ + ctx->in_mdc_pkt = 1; + + /* read data */ + res = pullf_read_max(src, len, &data, tmpbuf); + if (res < 0) + return res; + if (res == 0) + { + px_debug("no mdc"); + return PXE_PGP_CORRUPT_DATA; + } + + /* is the packet sane? */ + if (res != 20) + { + px_debug("mdc_finish: read failed, res=%d", res); + return PXE_PGP_CORRUPT_DATA; + } + + /* + * ok, we got the hash, now check + */ + px_md_finish(ctx->mdc_ctx, hash); + res = memcmp(hash, data, 20); + px_memset(hash, 0, 20); + px_memset(tmpbuf, 0, sizeof(tmpbuf)); + if (res != 0) + { + px_debug("mdc_finish: mdc failed"); + return PXE_PGP_CORRUPT_DATA; + } + ctx->mdc_checked = 1; + return 0; +} + +static int +mdc_read(void *priv, PullFilter *src, int len, + uint8 **data_p, uint8 *buf, int buflen) +{ + int res; + PGP_Context *ctx = priv; + + /* skip this filter? */ + if (ctx->use_mdcbuf_filter || ctx->in_mdc_pkt) + return pullf_read(src, len, data_p); + + res = pullf_read(src, len, data_p); + if (res < 0) + return res; + if (res == 0) + { + px_debug("mdc_read: unexpected eof"); + return PXE_PGP_CORRUPT_DATA; + } + px_md_update(ctx->mdc_ctx, *data_p, res); + + return res; +} + +static struct PullFilterOps mdc_filter = { + mdc_init, mdc_read, mdc_free +}; + + +/* + * Combined Pkt reader and MDC hasher. + * + * For the case of SYMENCRYPTED_DATA_MDC packet, where + * the data part has 'context length', which means + * that data packet ends 22 bytes before end of parent + * packet, which is silly. + */ +#define MDCBUF_LEN 8192 +struct MDCBufData +{ + PGP_Context *ctx; + int eof; + int buflen; + int avail; + uint8 *pos; + int mdc_avail; + uint8 mdc_buf[22]; + uint8 buf[MDCBUF_LEN]; +}; + +static int +mdcbuf_init(void **priv_p, void *arg, PullFilter *src) +{ + PGP_Context *ctx = arg; + struct MDCBufData *st; + + st = palloc0(sizeof(*st)); + st->buflen = sizeof(st->buf); + st->ctx = ctx; + *priv_p = st; + + /* take over the work of mdc_filter */ + ctx->use_mdcbuf_filter = 1; + + return 0; +} + +static int +mdcbuf_finish(struct MDCBufData *st) +{ + uint8 hash[20]; + int res; + + st->eof = 1; + + if (st->mdc_buf[0] != 0xD3 || st->mdc_buf[1] != 0x14) + { + px_debug("mdcbuf_finish: bad MDC pkt hdr"); + return PXE_PGP_CORRUPT_DATA; + } + px_md_update(st->ctx->mdc_ctx, st->mdc_buf, 2); + px_md_finish(st->ctx->mdc_ctx, hash); + res = memcmp(hash, st->mdc_buf + 2, 20); + px_memset(hash, 0, 20); + if (res) + { + px_debug("mdcbuf_finish: MDC does not match"); + res = PXE_PGP_CORRUPT_DATA; + } + return res; +} + +static void +mdcbuf_load_data(struct MDCBufData *st, uint8 *src, int len) +{ + uint8 *dst = st->pos + st->avail; + + memcpy(dst, src, len); + px_md_update(st->ctx->mdc_ctx, src, len); + st->avail += len; +} + +static void +mdcbuf_load_mdc(struct MDCBufData *st, uint8 *src, int len) +{ + memmove(st->mdc_buf + st->mdc_avail, src, len); + st->mdc_avail += len; +} + +static int +mdcbuf_refill(struct MDCBufData *st, PullFilter *src) +{ + uint8 *data; + int res; + int need; + + /* put avail data in start */ + if (st->avail > 0 && st->pos != st->buf) + memmove(st->buf, st->pos, st->avail); + st->pos = st->buf; + + /* read new data */ + need = st->buflen + 22 - st->avail - st->mdc_avail; + res = pullf_read(src, need, &data); + if (res < 0) + return res; + if (res == 0) + return mdcbuf_finish(st); + + /* add to buffer */ + if (res >= 22) + { + mdcbuf_load_data(st, st->mdc_buf, st->mdc_avail); + st->mdc_avail = 0; + + mdcbuf_load_data(st, data, res - 22); + mdcbuf_load_mdc(st, data + res - 22, 22); + } + else + { + int canmove = st->mdc_avail + res - 22; + + if (canmove > 0) + { + mdcbuf_load_data(st, st->mdc_buf, canmove); + st->mdc_avail -= canmove; + memmove(st->mdc_buf, st->mdc_buf + canmove, st->mdc_avail); + } + mdcbuf_load_mdc(st, data, res); + } + return 0; +} + +static int +mdcbuf_read(void *priv, PullFilter *src, int len, + uint8 **data_p, uint8 *buf, int buflen) +{ + struct MDCBufData *st = priv; + int res; + + if (!st->eof && len > st->avail) + { + res = mdcbuf_refill(st, src); + if (res < 0) + return res; + } + + if (len > st->avail) + len = st->avail; + + *data_p = st->pos; + st->pos += len; + st->avail -= len; + return len; +} + +static void +mdcbuf_free(void *priv) +{ + struct MDCBufData *st = priv; + + px_md_free(st->ctx->mdc_ctx); + st->ctx->mdc_ctx = NULL; + px_memset(st, 0, sizeof(*st)); + pfree(st); +} + +static struct PullFilterOps mdcbuf_filter = { + mdcbuf_init, mdcbuf_read, mdcbuf_free +}; + + +/* + * Decrypt separate session key + */ +static int +decrypt_key(PGP_Context *ctx, const uint8 *src, int len) +{ + int res; + uint8 algo; + PGP_CFB *cfb; + + res = pgp_cfb_create(&cfb, ctx->s2k_cipher_algo, + ctx->s2k.key, ctx->s2k.key_len, 0, NULL); + if (res < 0) + return res; + + pgp_cfb_decrypt(cfb, src, 1, &algo); + src++; + len--; + + pgp_cfb_decrypt(cfb, src, len, ctx->sess_key); + pgp_cfb_free(cfb); + ctx->sess_key_len = len; + ctx->cipher_algo = algo; + + if (pgp_get_cipher_key_size(algo) != len) + { + px_debug("sesskey bad len: algo=%d, expected=%d, got=%d", + algo, pgp_get_cipher_key_size(algo), len); + return PXE_PGP_CORRUPT_DATA; + } + return 0; +} + +/* + * Handle key packet + */ +static int +parse_symenc_sesskey(PGP_Context *ctx, PullFilter *src) +{ + uint8 *p; + int res; + uint8 tmpbuf[PGP_MAX_KEY + 2]; + uint8 ver; + + GETBYTE(src, ver); + GETBYTE(src, ctx->s2k_cipher_algo); + if (ver != 4) + { + px_debug("bad key pkt ver"); + return PXE_PGP_CORRUPT_DATA; + } + + /* + * read S2K info + */ + res = pgp_s2k_read(src, &ctx->s2k); + if (res < 0) + return res; + ctx->s2k_mode = ctx->s2k.mode; + ctx->s2k_count = s2k_decode_count(ctx->s2k.iter); + ctx->s2k_digest_algo = ctx->s2k.digest_algo; + + /* + * generate key from password + */ + res = pgp_s2k_process(&ctx->s2k, ctx->s2k_cipher_algo, + ctx->sym_key, ctx->sym_key_len); + if (res < 0) + return res; + + /* + * do we have separate session key? + */ + res = pullf_read_max(src, PGP_MAX_KEY + 2, &p, tmpbuf); + if (res < 0) + return res; + + if (res == 0) + { + /* + * no, s2k key is session key + */ + memcpy(ctx->sess_key, ctx->s2k.key, ctx->s2k.key_len); + ctx->sess_key_len = ctx->s2k.key_len; + ctx->cipher_algo = ctx->s2k_cipher_algo; + res = 0; + ctx->use_sess_key = 0; + } + else + { + /* + * yes, decrypt it + */ + if (res < 17 || res > PGP_MAX_KEY + 1) + { + px_debug("expect key, but bad data"); + return PXE_PGP_CORRUPT_DATA; + } + ctx->use_sess_key = 1; + res = decrypt_key(ctx, p, res); + } + + px_memset(tmpbuf, 0, sizeof(tmpbuf)); + return res; +} + +static int +copy_crlf(MBuf *dst, uint8 *data, int len, int *got_cr) +{ + uint8 *data_end = data + len; + uint8 tmpbuf[1024]; + uint8 *tmp_end = tmpbuf + sizeof(tmpbuf); + uint8 *p; + int res; + + p = tmpbuf; + if (*got_cr) + { + if (*data != '\n') + *p++ = '\r'; + *got_cr = 0; + } + while (data < data_end) + { + if (*data == '\r') + { + if (data + 1 < data_end) + { + if (*(data + 1) == '\n') + data++; + } + else + { + *got_cr = 1; + break; + } + } + *p++ = *data++; + if (p >= tmp_end) + { + res = mbuf_append(dst, tmpbuf, p - tmpbuf); + if (res < 0) + return res; + p = tmpbuf; + } + } + if (p - tmpbuf > 0) + { + res = mbuf_append(dst, tmpbuf, p - tmpbuf); + if (res < 0) + return res; + } + px_memset(tmpbuf, 0, sizeof(tmpbuf)); + return 0; +} + +static int +parse_literal_data(PGP_Context *ctx, MBuf *dst, PullFilter *pkt) +{ + int type; + int name_len; + int res; + uint8 *buf; + uint8 tmpbuf[4]; + int got_cr = 0; + + GETBYTE(pkt, type); + GETBYTE(pkt, name_len); + + /* skip name */ + while (name_len > 0) + { + res = pullf_read(pkt, name_len, &buf); + if (res < 0) + return res; + if (res == 0) + break; + name_len -= res; + } + if (name_len > 0) + { + px_debug("parse_literal_data: unexpected eof"); + return PXE_PGP_CORRUPT_DATA; + } + + /* skip date */ + res = pullf_read_max(pkt, 4, &buf, tmpbuf); + if (res != 4) + { + px_debug("parse_literal_data: unexpected eof"); + return PXE_PGP_CORRUPT_DATA; + } + px_memset(tmpbuf, 0, 4); + + /* + * If called from an SQL function that returns text, pgp_decrypt() rejects + * inputs not self-identifying as text. + */ + if (ctx->text_mode) + if (type != 't' && type != 'u') + { + px_debug("parse_literal_data: data type=%c", type); + ctx->unexpected_binary = true; + } + + ctx->unicode_mode = (type == 'u') ? 1 : 0; + + /* read data */ + while (1) + { + res = pullf_read(pkt, 32 * 1024, &buf); + if (res <= 0) + break; + + if (ctx->text_mode && ctx->convert_crlf) + res = copy_crlf(dst, buf, res, &got_cr); + else + res = mbuf_append(dst, buf, res); + if (res < 0) + break; + } + if (res >= 0 && got_cr) + res = mbuf_append(dst, (const uint8 *) "\r", 1); + return res; +} + +/* process_data_packets and parse_compressed_data call each other */ +static int process_data_packets(PGP_Context *ctx, MBuf *dst, + PullFilter *src, int allow_compr, int need_mdc); + +static int +parse_compressed_data(PGP_Context *ctx, MBuf *dst, PullFilter *pkt) +{ + int res; + uint8 type; + PullFilter *pf_decompr; + uint8 *discard_buf; + + GETBYTE(pkt, type); + + ctx->compress_algo = type; + switch (type) + { + case PGP_COMPR_NONE: + res = process_data_packets(ctx, dst, pkt, NO_COMPR, NO_MDC); + break; + + case PGP_COMPR_ZIP: + case PGP_COMPR_ZLIB: + res = pgp_decompress_filter(&pf_decompr, ctx, pkt); + if (res >= 0) + { + res = process_data_packets(ctx, dst, pf_decompr, + NO_COMPR, NO_MDC); + pullf_free(pf_decompr); + } + break; + + case PGP_COMPR_BZIP2: + px_debug("parse_compressed_data: bzip2 unsupported"); + /* report error in pgp_decrypt() */ + ctx->unsupported_compr = 1; + + /* + * Discard the compressed data, allowing it to first affect any + * MDC digest computation. + */ + while (1) + { + res = pullf_read(pkt, 32 * 1024, &discard_buf); + if (res <= 0) + break; + } + + break; + + default: + px_debug("parse_compressed_data: unknown compr type"); + res = PXE_PGP_CORRUPT_DATA; + } + + return res; +} + +static int +process_data_packets(PGP_Context *ctx, MBuf *dst, PullFilter *src, + int allow_compr, int need_mdc) +{ + uint8 tag; + int len, + res; + int got_data = 0; + int got_mdc = 0; + PullFilter *pkt = NULL; + + while (1) + { + res = pgp_parse_pkt_hdr(src, &tag, &len, ALLOW_CTX_SIZE); + if (res <= 0) + break; + + + /* mdc packet should be last */ + if (got_mdc) + { + px_debug("process_data_packets: data after mdc"); + res = PXE_PGP_CORRUPT_DATA; + break; + } + + /* + * Context length inside SYMENCRYPTED_DATA_MDC packet needs special + * handling. + */ + if (need_mdc && res == PKT_CONTEXT) + res = pullf_create(&pkt, &mdcbuf_filter, ctx, src); + else + res = pgp_create_pkt_reader(&pkt, src, len, res, ctx); + if (res < 0) + break; + + switch (tag) + { + case PGP_PKT_LITERAL_DATA: + got_data = 1; + res = parse_literal_data(ctx, dst, pkt); + break; + case PGP_PKT_COMPRESSED_DATA: + if (allow_compr == 0) + { + px_debug("process_data_packets: unexpected compression"); + res = PXE_PGP_CORRUPT_DATA; + } + else if (got_data) + { + /* + * compr data must be alone + */ + px_debug("process_data_packets: only one cmpr pkt allowed"); + res = PXE_PGP_CORRUPT_DATA; + } + else + { + got_data = 1; + res = parse_compressed_data(ctx, dst, pkt); + } + break; + case PGP_PKT_MDC: + if (need_mdc == NO_MDC) + { + px_debug("process_data_packets: unexpected MDC"); + res = PXE_PGP_CORRUPT_DATA; + break; + } + + res = mdc_finish(ctx, pkt, len); + if (res >= 0) + got_mdc = 1; + break; + default: + px_debug("process_data_packets: unexpected pkt tag=%d", tag); + res = PXE_PGP_CORRUPT_DATA; + } + + pullf_free(pkt); + pkt = NULL; + + if (res < 0) + break; + } + + if (pkt) + pullf_free(pkt); + + if (res < 0) + return res; + + if (!got_data) + { + px_debug("process_data_packets: no data"); + res = PXE_PGP_CORRUPT_DATA; + } + if (need_mdc && !got_mdc && !ctx->use_mdcbuf_filter) + { + px_debug("process_data_packets: got no mdc"); + res = PXE_PGP_CORRUPT_DATA; + } + return res; +} + +static int +parse_symenc_data(PGP_Context *ctx, PullFilter *pkt, MBuf *dst) +{ + int res; + PGP_CFB *cfb = NULL; + PullFilter *pf_decrypt = NULL; + PullFilter *pf_prefix = NULL; + + res = pgp_cfb_create(&cfb, ctx->cipher_algo, + ctx->sess_key, ctx->sess_key_len, 1, NULL); + if (res < 0) + goto out; + + res = pullf_create(&pf_decrypt, &pgp_decrypt_filter, cfb, pkt); + if (res < 0) + goto out; + + res = pullf_create(&pf_prefix, &prefix_filter, ctx, pf_decrypt); + if (res < 0) + goto out; + + res = process_data_packets(ctx, dst, pf_prefix, ALLOW_COMPR, NO_MDC); + +out: + if (pf_prefix) + pullf_free(pf_prefix); + if (pf_decrypt) + pullf_free(pf_decrypt); + if (cfb) + pgp_cfb_free(cfb); + + return res; +} + +static int +parse_symenc_mdc_data(PGP_Context *ctx, PullFilter *pkt, MBuf *dst) +{ + int res; + PGP_CFB *cfb = NULL; + PullFilter *pf_decrypt = NULL; + PullFilter *pf_prefix = NULL; + PullFilter *pf_mdc = NULL; + uint8 ver; + + GETBYTE(pkt, ver); + if (ver != 1) + { + px_debug("parse_symenc_mdc_data: pkt ver != 1"); + return PXE_PGP_CORRUPT_DATA; + } + + res = pgp_cfb_create(&cfb, ctx->cipher_algo, + ctx->sess_key, ctx->sess_key_len, 0, NULL); + if (res < 0) + goto out; + + res = pullf_create(&pf_decrypt, &pgp_decrypt_filter, cfb, pkt); + if (res < 0) + goto out; + + res = pullf_create(&pf_mdc, &mdc_filter, ctx, pf_decrypt); + if (res < 0) + goto out; + + res = pullf_create(&pf_prefix, &prefix_filter, ctx, pf_mdc); + if (res < 0) + goto out; + + res = process_data_packets(ctx, dst, pf_prefix, ALLOW_COMPR, NEED_MDC); + +out: + if (pf_prefix) + pullf_free(pf_prefix); + if (pf_mdc) + pullf_free(pf_mdc); + if (pf_decrypt) + pullf_free(pf_decrypt); + if (cfb) + pgp_cfb_free(cfb); + + return res; +} + +/* + * skip over packet contents + */ +int +pgp_skip_packet(PullFilter *pkt) +{ + int res = 1; + uint8 *tmp; + + while (res > 0) + res = pullf_read(pkt, 32 * 1024, &tmp); + return res; +} + +/* + * expect to be at packet end, any data is error + */ +int +pgp_expect_packet_end(PullFilter *pkt) +{ + int res; + uint8 *tmp; + + res = pullf_read(pkt, 32 * 1024, &tmp); + if (res > 0) + { + px_debug("pgp_expect_packet_end: got data"); + return PXE_PGP_CORRUPT_DATA; + } + return res; +} + +int +pgp_decrypt(PGP_Context *ctx, MBuf *msrc, MBuf *mdst) +{ + int res; + PullFilter *src = NULL; + PullFilter *pkt = NULL; + uint8 tag; + int len; + int got_key = 0; + int got_data = 0; + + res = pullf_create_mbuf_reader(&src, msrc); + + while (res >= 0) + { + res = pgp_parse_pkt_hdr(src, &tag, &len, NO_CTX_SIZE); + if (res <= 0) + break; + + res = pgp_create_pkt_reader(&pkt, src, len, res, ctx); + if (res < 0) + break; + + res = PXE_PGP_CORRUPT_DATA; + switch (tag) + { + case PGP_PKT_MARKER: + res = pgp_skip_packet(pkt); + break; + case PGP_PKT_PUBENCRYPTED_SESSKEY: + /* fixme: skip those */ + res = pgp_parse_pubenc_sesskey(ctx, pkt); + got_key = 1; + break; + case PGP_PKT_SYMENCRYPTED_SESSKEY: + if (got_key) + + /* + * Theoretically, there could be several keys, both public + * and symmetric, all of which encrypt same session key. + * Decrypt should try with each one, before failing. + */ + px_debug("pgp_decrypt: using first of several keys"); + else + { + got_key = 1; + res = parse_symenc_sesskey(ctx, pkt); + } + break; + case PGP_PKT_SYMENCRYPTED_DATA: + if (!got_key) + px_debug("pgp_decrypt: have data but no key"); + else if (got_data) + px_debug("pgp_decrypt: got second data packet"); + else + { + got_data = 1; + ctx->disable_mdc = 1; + res = parse_symenc_data(ctx, pkt, mdst); + } + break; + case PGP_PKT_SYMENCRYPTED_DATA_MDC: + if (!got_key) + px_debug("pgp_decrypt: have data but no key"); + else if (got_data) + px_debug("pgp_decrypt: several data pkts not supported"); + else + { + got_data = 1; + ctx->disable_mdc = 0; + res = parse_symenc_mdc_data(ctx, pkt, mdst); + } + break; + default: + px_debug("pgp_decrypt: unknown tag: 0x%02x", tag); + } + pullf_free(pkt); + pkt = NULL; + } + + if (pkt) + pullf_free(pkt); + + if (src) + pullf_free(src); + + if (res < 0) + return res; + + /* + * Report a failure of the prefix_init() "quick check" now, rather than + * upon detection, to hinder timing attacks. pgcrypto is not generally + * secure against timing attacks, but this helps. + */ + if (!got_data || ctx->corrupt_prefix) + return PXE_PGP_CORRUPT_DATA; + + /* + * Code interpreting purportedly-decrypted data prior to this stage shall + * report no error other than PXE_PGP_CORRUPT_DATA. (PXE_BUG is okay so + * long as it remains unreachable.) This ensures that an attacker able to + * choose a ciphertext and receive a corresponding decryption error + * message cannot use that oracle to gather clues about the decryption + * key. See "An Attack on CFB Mode Encryption As Used By OpenPGP" by + * Serge Mister and Robert Zuccherato. + * + * A problematic value in the first octet of a Literal Data or Compressed + * Data packet may indicate a simple user error, such as the need to call + * pgp_sym_decrypt_bytea instead of pgp_sym_decrypt. Occasionally, + * though, it is the first symptom of the encryption key not matching the + * decryption key. When this was the only problem encountered, report a + * specific error to guide the user; otherwise, we will have reported + * PXE_PGP_CORRUPT_DATA before now. A key mismatch makes the other errors + * into red herrings, and this avoids leaking clues to attackers. + */ + if (ctx->unsupported_compr) + return PXE_PGP_UNSUPPORTED_COMPR; + if (ctx->unexpected_binary) + return PXE_PGP_NOT_TEXT; + + return res; +} diff --git a/contrib/pgcrypto/pgp-encrypt.c b/contrib/pgcrypto/pgp-encrypt.c new file mode 100644 index 0000000..f7467c9 --- /dev/null +++ b/contrib/pgcrypto/pgp-encrypt.c @@ -0,0 +1,704 @@ +/* + * pgp-encrypt.c + * OpenPGP encrypt. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-encrypt.c + */ + +#include "postgres.h" + +#include + +#include "mbuf.h" +#include "pgp.h" +#include "px.h" + +#define MDC_DIGEST_LEN 20 +#define STREAM_ID 0xE0 +#define STREAM_BLOCK_SHIFT 14 + +static uint8 * +render_newlen(uint8 *h, int len) +{ + if (len <= 191) + { + *h++ = len & 255; + } + else if (len > 191 && len <= 8383) + { + *h++ = ((len - 192) >> 8) + 192; + *h++ = (len - 192) & 255; + } + else + { + *h++ = 255; + *h++ = (len >> 24) & 255; + *h++ = (len >> 16) & 255; + *h++ = (len >> 8) & 255; + *h++ = len & 255; + } + return h; +} + +static int +write_tag_only(PushFilter *dst, int tag) +{ + uint8 hdr = 0xC0 | tag; + + return pushf_write(dst, &hdr, 1); +} + +static int +write_normal_header(PushFilter *dst, int tag, int len) +{ + uint8 hdr[8]; + uint8 *h = hdr; + + *h++ = 0xC0 | tag; + h = render_newlen(h, len); + return pushf_write(dst, hdr, h - hdr); +} + + +/* + * MAC writer + */ + +static int +mdc_init(PushFilter *dst, void *init_arg, void **priv_p) +{ + int res; + PX_MD *md; + + res = pgp_load_digest(PGP_DIGEST_SHA1, &md); + if (res < 0) + return res; + + *priv_p = md; + return 0; +} + +static int +mdc_write(PushFilter *dst, void *priv, const uint8 *data, int len) +{ + PX_MD *md = priv; + + px_md_update(md, data, len); + return pushf_write(dst, data, len); +} + +static int +mdc_flush(PushFilter *dst, void *priv) +{ + int res; + uint8 pkt[2 + MDC_DIGEST_LEN]; + PX_MD *md = priv; + + /* + * create mdc pkt + */ + pkt[0] = 0xD3; + pkt[1] = 0x14; /* MDC_DIGEST_LEN */ + px_md_update(md, pkt, 2); + px_md_finish(md, pkt + 2); + + res = pushf_write(dst, pkt, 2 + MDC_DIGEST_LEN); + px_memset(pkt, 0, 2 + MDC_DIGEST_LEN); + return res; +} + +static void +mdc_free(void *priv) +{ + PX_MD *md = priv; + + px_md_free(md); +} + +static const PushFilterOps mdc_filter = { + mdc_init, mdc_write, mdc_flush, mdc_free +}; + + +/* + * Encrypted pkt writer + */ +#define ENCBUF 8192 +struct EncStat +{ + PGP_CFB *ciph; + uint8 buf[ENCBUF]; +}; + +static int +encrypt_init(PushFilter *next, void *init_arg, void **priv_p) +{ + struct EncStat *st; + PGP_Context *ctx = init_arg; + PGP_CFB *ciph; + int resync = 1; + int res; + + /* should we use newer packet format? */ + if (ctx->disable_mdc == 0) + { + uint8 ver = 1; + + resync = 0; + res = pushf_write(next, &ver, 1); + if (res < 0) + return res; + } + res = pgp_cfb_create(&ciph, ctx->cipher_algo, + ctx->sess_key, ctx->sess_key_len, resync, NULL); + if (res < 0) + return res; + + st = palloc0(sizeof(*st)); + st->ciph = ciph; + + *priv_p = st; + return ENCBUF; +} + +static int +encrypt_process(PushFilter *next, void *priv, const uint8 *data, int len) +{ + int res; + struct EncStat *st = priv; + int avail = len; + + while (avail > 0) + { + int tmplen = avail > ENCBUF ? ENCBUF : avail; + + res = pgp_cfb_encrypt(st->ciph, data, tmplen, st->buf); + if (res < 0) + return res; + + res = pushf_write(next, st->buf, tmplen); + if (res < 0) + return res; + + data += tmplen; + avail -= tmplen; + } + return 0; +} + +static void +encrypt_free(void *priv) +{ + struct EncStat *st = priv; + + if (st->ciph) + pgp_cfb_free(st->ciph); + px_memset(st, 0, sizeof(*st)); + pfree(st); +} + +static const PushFilterOps encrypt_filter = { + encrypt_init, encrypt_process, NULL, encrypt_free +}; + +/* + * Write Streamable pkts + */ + +struct PktStreamStat +{ + int final_done; + int pkt_block; +}; + +static int +pkt_stream_init(PushFilter *next, void *init_arg, void **priv_p) +{ + struct PktStreamStat *st; + + st = palloc(sizeof(*st)); + st->final_done = 0; + st->pkt_block = 1 << STREAM_BLOCK_SHIFT; + *priv_p = st; + + return st->pkt_block; +} + +static int +pkt_stream_process(PushFilter *next, void *priv, const uint8 *data, int len) +{ + int res; + uint8 hdr[8]; + uint8 *h = hdr; + struct PktStreamStat *st = priv; + + if (st->final_done) + return PXE_BUG; + + if (len == st->pkt_block) + *h++ = STREAM_ID | STREAM_BLOCK_SHIFT; + else + { + h = render_newlen(h, len); + st->final_done = 1; + } + + res = pushf_write(next, hdr, h - hdr); + if (res < 0) + return res; + + return pushf_write(next, data, len); +} + +static int +pkt_stream_flush(PushFilter *next, void *priv) +{ + int res; + uint8 hdr[8]; + uint8 *h = hdr; + struct PktStreamStat *st = priv; + + /* stream MUST end with normal packet. */ + if (!st->final_done) + { + h = render_newlen(h, 0); + res = pushf_write(next, hdr, h - hdr); + if (res < 0) + return res; + st->final_done = 1; + } + return 0; +} + +static void +pkt_stream_free(void *priv) +{ + struct PktStreamStat *st = priv; + + px_memset(st, 0, sizeof(*st)); + pfree(st); +} + +static const PushFilterOps pkt_stream_filter = { + pkt_stream_init, pkt_stream_process, pkt_stream_flush, pkt_stream_free +}; + +int +pgp_create_pkt_writer(PushFilter *dst, int tag, PushFilter **res_p) +{ + int res; + + res = write_tag_only(dst, tag); + if (res < 0) + return res; + + return pushf_create(res_p, &pkt_stream_filter, NULL, dst); +} + +/* + * Text conversion filter + */ + +static int +crlf_process(PushFilter *dst, void *priv, const uint8 *data, int len) +{ + const uint8 *data_end = data + len; + const uint8 *p2, + *p1 = data; + int line_len; + static const uint8 crlf[] = {'\r', '\n'}; + int res = 0; + + while (p1 < data_end) + { + p2 = memchr(p1, '\n', data_end - p1); + if (p2 == NULL) + p2 = data_end; + + line_len = p2 - p1; + + /* write data */ + res = 0; + if (line_len > 0) + { + res = pushf_write(dst, p1, line_len); + if (res < 0) + break; + p1 += line_len; + } + + /* write crlf */ + while (p1 < data_end && *p1 == '\n') + { + res = pushf_write(dst, crlf, 2); + if (res < 0) + break; + p1++; + } + } + return res; +} + +static const PushFilterOps crlf_filter = { + NULL, crlf_process, NULL, NULL +}; + +/* + * Initialize literal data packet + */ +static int +init_litdata_packet(PushFilter **pf_res, PGP_Context *ctx, PushFilter *dst) +{ + int res; + int hdrlen; + uint8 hdr[6]; + uint32 t; + PushFilter *pkt; + int type; + + /* + * Create header + */ + + if (ctx->text_mode) + type = ctx->unicode_mode ? 'u' : 't'; + else + type = 'b'; + + /* + * Store the creation time into packet. The goal is to have as few known + * bytes as possible. + */ + t = (uint32) time(NULL); + + hdr[0] = type; + hdr[1] = 0; + hdr[2] = (t >> 24) & 255; + hdr[3] = (t >> 16) & 255; + hdr[4] = (t >> 8) & 255; + hdr[5] = t & 255; + hdrlen = 6; + + res = write_tag_only(dst, PGP_PKT_LITERAL_DATA); + if (res < 0) + return res; + + res = pushf_create(&pkt, &pkt_stream_filter, ctx, dst); + if (res < 0) + return res; + + res = pushf_write(pkt, hdr, hdrlen); + if (res < 0) + { + pushf_free(pkt); + return res; + } + + *pf_res = pkt; + return 0; +} + +/* + * Initialize compression filter + */ +static int +init_compress(PushFilter **pf_res, PGP_Context *ctx, PushFilter *dst) +{ + int res; + uint8 type = ctx->compress_algo; + PushFilter *pkt; + + res = write_tag_only(dst, PGP_PKT_COMPRESSED_DATA); + if (res < 0) + return res; + + res = pushf_create(&pkt, &pkt_stream_filter, ctx, dst); + if (res < 0) + return res; + + res = pushf_write(pkt, &type, 1); + if (res >= 0) + res = pgp_compress_filter(pf_res, ctx, pkt); + + if (res < 0) + pushf_free(pkt); + + return res; +} + +/* + * Initialize encdata packet + */ +static int +init_encdata_packet(PushFilter **pf_res, PGP_Context *ctx, PushFilter *dst) +{ + int res; + int tag; + + if (ctx->disable_mdc) + tag = PGP_PKT_SYMENCRYPTED_DATA; + else + tag = PGP_PKT_SYMENCRYPTED_DATA_MDC; + + res = write_tag_only(dst, tag); + if (res < 0) + return res; + + return pushf_create(pf_res, &pkt_stream_filter, ctx, dst); +} + +/* + * write prefix + */ +static int +write_prefix(PGP_Context *ctx, PushFilter *dst) +{ + uint8 prefix[PGP_MAX_BLOCK + 2]; + int res, + bs; + + bs = pgp_get_cipher_block_size(ctx->cipher_algo); + if (!pg_strong_random(prefix, bs)) + return PXE_NO_RANDOM; + + prefix[bs + 0] = prefix[bs - 2]; + prefix[bs + 1] = prefix[bs - 1]; + + res = pushf_write(dst, prefix, bs + 2); + px_memset(prefix, 0, bs + 2); + return res < 0 ? res : 0; +} + +/* + * write symmetrically encrypted session key packet + */ + +static int +symencrypt_sesskey(PGP_Context *ctx, uint8 *dst) +{ + int res; + PGP_CFB *cfb; + uint8 algo = ctx->cipher_algo; + + res = pgp_cfb_create(&cfb, ctx->s2k_cipher_algo, + ctx->s2k.key, ctx->s2k.key_len, 0, NULL); + if (res < 0) + return res; + + pgp_cfb_encrypt(cfb, &algo, 1, dst); + pgp_cfb_encrypt(cfb, ctx->sess_key, ctx->sess_key_len, dst + 1); + + pgp_cfb_free(cfb); + return ctx->sess_key_len + 1; +} + +/* 5.3: Symmetric-Key Encrypted Session-Key */ +static int +write_symenc_sesskey(PGP_Context *ctx, PushFilter *dst) +{ + uint8 pkt[256]; + int pktlen; + int res; + uint8 *p = pkt; + + *p++ = 4; /* 5.3 - version number */ + *p++ = ctx->s2k_cipher_algo; + + *p++ = ctx->s2k.mode; + *p++ = ctx->s2k.digest_algo; + if (ctx->s2k.mode > 0) + { + memcpy(p, ctx->s2k.salt, 8); + p += 8; + } + if (ctx->s2k.mode == 3) + *p++ = ctx->s2k.iter; + + if (ctx->use_sess_key) + { + res = symencrypt_sesskey(ctx, p); + if (res < 0) + return res; + p += res; + } + + pktlen = p - pkt; + res = write_normal_header(dst, PGP_PKT_SYMENCRYPTED_SESSKEY, pktlen); + if (res >= 0) + res = pushf_write(dst, pkt, pktlen); + + px_memset(pkt, 0, pktlen); + return res; +} + +/* + * key setup + */ +static int +init_s2k_key(PGP_Context *ctx) +{ + int res; + + if (ctx->s2k_cipher_algo < 0) + ctx->s2k_cipher_algo = ctx->cipher_algo; + + res = pgp_s2k_fill(&ctx->s2k, ctx->s2k_mode, ctx->s2k_digest_algo, ctx->s2k_count); + if (res < 0) + return res; + + return pgp_s2k_process(&ctx->s2k, ctx->s2k_cipher_algo, + ctx->sym_key, ctx->sym_key_len); +} + +static int +init_sess_key(PGP_Context *ctx) +{ + if (ctx->use_sess_key || ctx->pub_key) + { + ctx->sess_key_len = pgp_get_cipher_key_size(ctx->cipher_algo); + if (!pg_strong_random(ctx->sess_key, ctx->sess_key_len)) + return PXE_NO_RANDOM; + } + else + { + ctx->sess_key_len = ctx->s2k.key_len; + memcpy(ctx->sess_key, ctx->s2k.key, ctx->s2k.key_len); + } + + return 0; +} + +/* + * combine + */ +int +pgp_encrypt(PGP_Context *ctx, MBuf *src, MBuf *dst) +{ + int res; + int len; + uint8 *buf; + PushFilter *pf, + *pf_tmp; + + /* + * do we have any key + */ + if (!ctx->sym_key && !ctx->pub_key) + return PXE_ARGUMENT_ERROR; + + /* MBuf writer */ + res = pushf_create_mbuf_writer(&pf, dst); + if (res < 0) + goto out; + + /* + * initialize sym_key + */ + if (ctx->sym_key) + { + res = init_s2k_key(ctx); + if (res < 0) + goto out; + } + + res = init_sess_key(ctx); + if (res < 0) + goto out; + + /* + * write keypkt + */ + if (ctx->pub_key) + res = pgp_write_pubenc_sesskey(ctx, pf); + else + res = write_symenc_sesskey(ctx, pf); + if (res < 0) + goto out; + + /* encrypted data pkt */ + res = init_encdata_packet(&pf_tmp, ctx, pf); + if (res < 0) + goto out; + pf = pf_tmp; + + /* encrypter */ + res = pushf_create(&pf_tmp, &encrypt_filter, ctx, pf); + if (res < 0) + goto out; + pf = pf_tmp; + + /* hasher */ + if (ctx->disable_mdc == 0) + { + res = pushf_create(&pf_tmp, &mdc_filter, ctx, pf); + if (res < 0) + goto out; + pf = pf_tmp; + } + + /* prefix */ + res = write_prefix(ctx, pf); + if (res < 0) + goto out; + + /* compressor */ + if (ctx->compress_algo > 0 && ctx->compress_level > 0) + { + res = init_compress(&pf_tmp, ctx, pf); + if (res < 0) + goto out; + pf = pf_tmp; + } + + /* data streamer */ + res = init_litdata_packet(&pf_tmp, ctx, pf); + if (res < 0) + goto out; + pf = pf_tmp; + + + /* text conversion? */ + if (ctx->text_mode && ctx->convert_crlf) + { + res = pushf_create(&pf_tmp, &crlf_filter, ctx, pf); + if (res < 0) + goto out; + pf = pf_tmp; + } + + /* + * chain complete + */ + + len = mbuf_grab(src, mbuf_avail(src), &buf); + res = pushf_write(pf, buf, len); + if (res >= 0) + res = pushf_flush(pf); +out: + pushf_free_all(pf); + return res; +} diff --git a/contrib/pgcrypto/pgp-info.c b/contrib/pgcrypto/pgp-info.c new file mode 100644 index 0000000..83dc604 --- /dev/null +++ b/contrib/pgcrypto/pgp-info.c @@ -0,0 +1,235 @@ +/* + * pgp-info.c + * Provide info about PGP data. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-info.c + */ +#include "postgres.h" + +#include "mbuf.h" +#include "pgp.h" +#include "px.h" + +static int +read_pubkey_keyid(PullFilter *pkt, uint8 *keyid_buf) +{ + int res; + PGP_PubKey *pk = NULL; + + res = _pgp_read_public_key(pkt, &pk); + if (res < 0) + goto err; + + /* skip secret key part, if it exists */ + res = pgp_skip_packet(pkt); + if (res < 0) + goto err; + + /* is it encryption key */ + switch (pk->algo) + { + case PGP_PUB_ELG_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT_SIGN: + memcpy(keyid_buf, pk->key_id, 8); + res = 1; + break; + default: + res = 0; + } + +err: + pgp_key_free(pk); + return res; +} + +static int +read_pubenc_keyid(PullFilter *pkt, uint8 *keyid_buf) +{ + uint8 ver; + int res; + + GETBYTE(pkt, ver); + if (ver != 3) + return -1; + + res = pullf_read_fixed(pkt, 8, keyid_buf); + if (res < 0) + return res; + + return pgp_skip_packet(pkt); +} + +static const char hextbl[] = "0123456789ABCDEF"; + +static int +print_key(uint8 *keyid, char *dst) +{ + int i; + unsigned c; + + for (i = 0; i < 8; i++) + { + c = keyid[i]; + *dst++ = hextbl[(c >> 4) & 0x0F]; + *dst++ = hextbl[c & 0x0F]; + } + *dst = 0; + return 8 * 2; +} + +static const uint8 any_key[] = +{0, 0, 0, 0, 0, 0, 0, 0}; + +/* + * dst should have room for 17 bytes + */ +int +pgp_get_keyid(MBuf *pgp_data, char *dst) +{ + int res; + PullFilter *src; + PullFilter *pkt = NULL; + int len; + uint8 tag; + int got_pub_key = 0, + got_symenc_key = 0, + got_pubenc_key = 0; + int got_data = 0; + uint8 keyid_buf[8]; + int got_main_key = 0; + + + res = pullf_create_mbuf_reader(&src, pgp_data); + if (res < 0) + return res; + + while (1) + { + res = pgp_parse_pkt_hdr(src, &tag, &len, 0); + if (res <= 0) + break; + res = pgp_create_pkt_reader(&pkt, src, len, res, NULL); + if (res < 0) + break; + + switch (tag) + { + case PGP_PKT_SECRET_KEY: + case PGP_PKT_PUBLIC_KEY: + /* main key is for signing, so ignore it */ + if (!got_main_key) + { + got_main_key = 1; + res = pgp_skip_packet(pkt); + } + else + res = PXE_PGP_MULTIPLE_KEYS; + break; + case PGP_PKT_SECRET_SUBKEY: + case PGP_PKT_PUBLIC_SUBKEY: + res = read_pubkey_keyid(pkt, keyid_buf); + if (res < 0) + break; + if (res > 0) + got_pub_key++; + break; + case PGP_PKT_PUBENCRYPTED_SESSKEY: + got_pubenc_key++; + res = read_pubenc_keyid(pkt, keyid_buf); + break; + case PGP_PKT_SYMENCRYPTED_DATA: + case PGP_PKT_SYMENCRYPTED_DATA_MDC: + /* don't skip it, just stop */ + got_data = 1; + break; + case PGP_PKT_SYMENCRYPTED_SESSKEY: + got_symenc_key++; + /* fall through */ + case PGP_PKT_SIGNATURE: + case PGP_PKT_MARKER: + case PGP_PKT_TRUST: + case PGP_PKT_USER_ID: + case PGP_PKT_USER_ATTR: + case PGP_PKT_PRIV_61: + res = pgp_skip_packet(pkt); + break; + default: + res = PXE_PGP_CORRUPT_DATA; + } + + if (pkt) + pullf_free(pkt); + pkt = NULL; + + if (res < 0 || got_data) + break; + } + + pullf_free(src); + if (pkt) + pullf_free(pkt); + + if (res < 0) + return res; + + /* now check sanity */ + if (got_pub_key && got_pubenc_key) + res = PXE_PGP_CORRUPT_DATA; + + if (got_pub_key > 1) + res = PXE_PGP_MULTIPLE_KEYS; + + if (got_pubenc_key > 1) + res = PXE_PGP_MULTIPLE_KEYS; + + /* + * if still ok, look what we got + */ + if (res >= 0) + { + if (got_pubenc_key || got_pub_key) + { + if (memcmp(keyid_buf, any_key, 8) == 0) + { + memcpy(dst, "ANYKEY", 7); + res = 6; + } + else + res = print_key(keyid_buf, dst); + } + else if (got_symenc_key) + { + memcpy(dst, "SYMKEY", 7); + res = 6; + } + else + res = PXE_PGP_NO_USABLE_KEY; + } + + return res; +} diff --git a/contrib/pgcrypto/pgp-mpi-openssl.c b/contrib/pgcrypto/pgp-mpi-openssl.c new file mode 100644 index 0000000..75e4c8b --- /dev/null +++ b/contrib/pgcrypto/pgp-mpi-openssl.c @@ -0,0 +1,284 @@ +/* + * pgp-mpi-openssl.c + * OpenPGP MPI functions using OpenSSL BIGNUM code. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-mpi-openssl.c + */ +#include "postgres.h" + +#include + +#include "pgp.h" +#include "px.h" + +static BIGNUM * +mpi_to_bn(PGP_MPI *n) +{ + BIGNUM *bn = BN_bin2bn(n->data, n->bytes, NULL); + + if (!bn) + return NULL; + if (BN_num_bits(bn) != n->bits) + { + px_debug("mpi_to_bn: bignum conversion failed: mpi=%d, bn=%d", + n->bits, BN_num_bits(bn)); + BN_clear_free(bn); + return NULL; + } + return bn; +} + +static PGP_MPI * +bn_to_mpi(BIGNUM *bn) +{ + int res; + PGP_MPI *n; + + res = pgp_mpi_alloc(BN_num_bits(bn), &n); + if (res < 0) + return NULL; + + if (BN_num_bytes(bn) != n->bytes) + { + px_debug("bn_to_mpi: bignum conversion failed: bn=%d, mpi=%d", + BN_num_bytes(bn), n->bytes); + pgp_mpi_free(n); + return NULL; + } + BN_bn2bin(bn, n->data); + return n; +} + +/* + * Decide the number of bits in the random component k + * + * It should be in the same range as p for signing (which + * is deprecated), but can be much smaller for encrypting. + * + * Until I research it further, I just mimic gpg behaviour. + * It has a special mapping table, for values <= 5120, + * above that it uses 'arbitrary high number'. Following + * algorithm hovers 10-70 bits above gpg values. And for + * larger p, it uses gpg's algorithm. + * + * The point is - if k gets large, encryption will be + * really slow. It does not matter for decryption. + */ +static int +decide_k_bits(int p_bits) +{ + if (p_bits <= 5120) + return p_bits / 10 + 160; + else + return (p_bits / 8 + 200) * 3 / 2; +} + +int +pgp_elgamal_encrypt(PGP_PubKey *pk, PGP_MPI *_m, + PGP_MPI **c1_p, PGP_MPI **c2_p) +{ + int res = PXE_PGP_MATH_FAILED; + int k_bits; + BIGNUM *m = mpi_to_bn(_m); + BIGNUM *p = mpi_to_bn(pk->pub.elg.p); + BIGNUM *g = mpi_to_bn(pk->pub.elg.g); + BIGNUM *y = mpi_to_bn(pk->pub.elg.y); + BIGNUM *k = BN_new(); + BIGNUM *yk = BN_new(); + BIGNUM *c1 = BN_new(); + BIGNUM *c2 = BN_new(); + BN_CTX *tmp = BN_CTX_new(); + + if (!m || !p || !g || !y || !k || !yk || !c1 || !c2 || !tmp) + goto err; + + /* + * generate k + */ + k_bits = decide_k_bits(BN_num_bits(p)); + if (!BN_rand(k, k_bits, 0, 0)) + goto err; + + /* + * c1 = g^k c2 = m * y^k + */ + if (!BN_mod_exp(c1, g, k, p, tmp)) + goto err; + if (!BN_mod_exp(yk, y, k, p, tmp)) + goto err; + if (!BN_mod_mul(c2, m, yk, p, tmp)) + goto err; + + /* result */ + *c1_p = bn_to_mpi(c1); + *c2_p = bn_to_mpi(c2); + if (*c1_p && *c2_p) + res = 0; +err: + if (tmp) + BN_CTX_free(tmp); + if (c2) + BN_clear_free(c2); + if (c1) + BN_clear_free(c1); + if (yk) + BN_clear_free(yk); + if (k) + BN_clear_free(k); + if (y) + BN_clear_free(y); + if (g) + BN_clear_free(g); + if (p) + BN_clear_free(p); + if (m) + BN_clear_free(m); + return res; +} + +int +pgp_elgamal_decrypt(PGP_PubKey *pk, PGP_MPI *_c1, PGP_MPI *_c2, + PGP_MPI **msg_p) +{ + int res = PXE_PGP_MATH_FAILED; + BIGNUM *c1 = mpi_to_bn(_c1); + BIGNUM *c2 = mpi_to_bn(_c2); + BIGNUM *p = mpi_to_bn(pk->pub.elg.p); + BIGNUM *x = mpi_to_bn(pk->sec.elg.x); + BIGNUM *c1x = BN_new(); + BIGNUM *div = BN_new(); + BIGNUM *m = BN_new(); + BN_CTX *tmp = BN_CTX_new(); + + if (!c1 || !c2 || !p || !x || !c1x || !div || !m || !tmp) + goto err; + + /* + * m = c2 / (c1^x) + */ + if (!BN_mod_exp(c1x, c1, x, p, tmp)) + goto err; + if (!BN_mod_inverse(div, c1x, p, tmp)) + goto err; + if (!BN_mod_mul(m, c2, div, p, tmp)) + goto err; + + /* result */ + *msg_p = bn_to_mpi(m); + if (*msg_p) + res = 0; +err: + if (tmp) + BN_CTX_free(tmp); + if (m) + BN_clear_free(m); + if (div) + BN_clear_free(div); + if (c1x) + BN_clear_free(c1x); + if (x) + BN_clear_free(x); + if (p) + BN_clear_free(p); + if (c2) + BN_clear_free(c2); + if (c1) + BN_clear_free(c1); + return res; +} + +int +pgp_rsa_encrypt(PGP_PubKey *pk, PGP_MPI *_m, PGP_MPI **c_p) +{ + int res = PXE_PGP_MATH_FAILED; + BIGNUM *m = mpi_to_bn(_m); + BIGNUM *e = mpi_to_bn(pk->pub.rsa.e); + BIGNUM *n = mpi_to_bn(pk->pub.rsa.n); + BIGNUM *c = BN_new(); + BN_CTX *tmp = BN_CTX_new(); + + if (!m || !e || !n || !c || !tmp) + goto err; + + /* + * c = m ^ e + */ + if (!BN_mod_exp(c, m, e, n, tmp)) + goto err; + + *c_p = bn_to_mpi(c); + if (*c_p) + res = 0; +err: + if (tmp) + BN_CTX_free(tmp); + if (c) + BN_clear_free(c); + if (n) + BN_clear_free(n); + if (e) + BN_clear_free(e); + if (m) + BN_clear_free(m); + return res; +} + +int +pgp_rsa_decrypt(PGP_PubKey *pk, PGP_MPI *_c, PGP_MPI **m_p) +{ + int res = PXE_PGP_MATH_FAILED; + BIGNUM *c = mpi_to_bn(_c); + BIGNUM *d = mpi_to_bn(pk->sec.rsa.d); + BIGNUM *n = mpi_to_bn(pk->pub.rsa.n); + BIGNUM *m = BN_new(); + BN_CTX *tmp = BN_CTX_new(); + + if (!m || !d || !n || !c || !tmp) + goto err; + + /* + * m = c ^ d + */ + if (!BN_mod_exp(m, c, d, n, tmp)) + goto err; + + *m_p = bn_to_mpi(m); + if (*m_p) + res = 0; +err: + if (tmp) + BN_CTX_free(tmp); + if (m) + BN_clear_free(m); + if (n) + BN_clear_free(n); + if (d) + BN_clear_free(d); + if (c) + BN_clear_free(c); + return res; +} diff --git a/contrib/pgcrypto/pgp-mpi.c b/contrib/pgcrypto/pgp-mpi.c new file mode 100644 index 0000000..03be279 --- /dev/null +++ b/contrib/pgcrypto/pgp-mpi.c @@ -0,0 +1,142 @@ +/* + * pgp-mpi.c + * OpenPGP MPI helper functions. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-mpi.c + */ +#include "postgres.h" + +#include "pgp.h" +#include "px.h" + +int +pgp_mpi_alloc(int bits, PGP_MPI **mpi) +{ + PGP_MPI *n; + int len = (bits + 7) / 8; + + if (bits < 0 || bits > 0xFFFF) + { + px_debug("pgp_mpi_alloc: unreasonable request: bits=%d", bits); + return PXE_PGP_CORRUPT_DATA; + } + n = palloc(sizeof(*n) + len); + n->bits = bits; + n->bytes = len; + n->data = (uint8 *) (n) + sizeof(*n); + *mpi = n; + return 0; +} + +int +pgp_mpi_create(uint8 *data, int bits, PGP_MPI **mpi) +{ + int res; + PGP_MPI *n; + + res = pgp_mpi_alloc(bits, &n); + if (res < 0) + return res; + memcpy(n->data, data, n->bytes); + *mpi = n; + return 0; +} + +int +pgp_mpi_free(PGP_MPI *mpi) +{ + if (mpi == NULL) + return 0; + px_memset(mpi, 0, sizeof(*mpi) + mpi->bytes); + pfree(mpi); + return 0; +} + +int +pgp_mpi_read(PullFilter *src, PGP_MPI **mpi) +{ + int res; + uint8 hdr[2]; + int bits; + PGP_MPI *n; + + res = pullf_read_fixed(src, 2, hdr); + if (res < 0) + return res; + bits = ((unsigned) hdr[0] << 8) + hdr[1]; + + res = pgp_mpi_alloc(bits, &n); + if (res < 0) + return res; + + res = pullf_read_fixed(src, n->bytes, n->data); + if (res < 0) + pgp_mpi_free(n); + else + *mpi = n; + return res; +} + +int +pgp_mpi_write(PushFilter *dst, PGP_MPI *n) +{ + int res; + uint8 buf[2]; + + buf[0] = n->bits >> 8; + buf[1] = n->bits & 0xFF; + res = pushf_write(dst, buf, 2); + if (res >= 0) + res = pushf_write(dst, n->data, n->bytes); + return res; +} + +int +pgp_mpi_hash(PX_MD *md, PGP_MPI *n) +{ + uint8 buf[2]; + + buf[0] = n->bits >> 8; + buf[1] = n->bits & 0xFF; + px_md_update(md, buf, 2); + px_md_update(md, n->data, n->bytes); + + return 0; +} + +unsigned +pgp_mpi_cksum(unsigned cksum, PGP_MPI *n) +{ + int i; + + cksum += n->bits >> 8; + cksum += n->bits & 0xFF; + for (i = 0; i < n->bytes; i++) + cksum += n->data[i]; + + return cksum & 0xFFFF; +} diff --git a/contrib/pgcrypto/pgp-pgsql.c b/contrib/pgcrypto/pgp-pgsql.c new file mode 100644 index 0000000..d9b15b0 --- /dev/null +++ b/contrib/pgcrypto/pgp-pgsql.c @@ -0,0 +1,1001 @@ +/* + * pgp-pgsql.c + * PostgreSQL wrappers for pgp. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-pgsql.c + */ + +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "common/string.h" +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "mb/pg_wchar.h" +#include "mbuf.h" +#include "pgp.h" +#include "px.h" +#include "utils/array.h" +#include "utils/builtins.h" + +/* + * public functions + */ +PG_FUNCTION_INFO_V1(pgp_sym_encrypt_bytea); +PG_FUNCTION_INFO_V1(pgp_sym_encrypt_text); +PG_FUNCTION_INFO_V1(pgp_sym_decrypt_bytea); +PG_FUNCTION_INFO_V1(pgp_sym_decrypt_text); + +PG_FUNCTION_INFO_V1(pgp_pub_encrypt_bytea); +PG_FUNCTION_INFO_V1(pgp_pub_encrypt_text); +PG_FUNCTION_INFO_V1(pgp_pub_decrypt_bytea); +PG_FUNCTION_INFO_V1(pgp_pub_decrypt_text); + +PG_FUNCTION_INFO_V1(pgp_key_id_w); + +PG_FUNCTION_INFO_V1(pg_armor); +PG_FUNCTION_INFO_V1(pg_dearmor); +PG_FUNCTION_INFO_V1(pgp_armor_headers); + +/* + * returns src in case of no conversion or error + */ +static text * +convert_charset(text *src, int cset_from, int cset_to) +{ + int src_len = VARSIZE_ANY_EXHDR(src); + unsigned char *dst; + unsigned char *csrc = (unsigned char *) VARDATA_ANY(src); + text *res; + + dst = pg_do_encoding_conversion(csrc, src_len, cset_from, cset_to); + if (dst == csrc) + return src; + + res = cstring_to_text((char *) dst); + pfree(dst); + return res; +} + +static text * +convert_from_utf8(text *src) +{ + return convert_charset(src, PG_UTF8, GetDatabaseEncoding()); +} + +static text * +convert_to_utf8(text *src) +{ + return convert_charset(src, GetDatabaseEncoding(), PG_UTF8); +} + +static void +clear_and_pfree(text *p) +{ + px_memset(p, 0, VARSIZE_ANY(p)); + pfree(p); +} + +/* + * expect-* arguments storage + */ +struct debug_expect +{ + int debug; + int expect; + int cipher_algo; + int s2k_mode; + int s2k_count; + int s2k_cipher_algo; + int s2k_digest_algo; + int compress_algo; + int use_sess_key; + int disable_mdc; + int unicode_mode; +}; + +static void +fill_expect(struct debug_expect *ex, int text_mode) +{ + ex->debug = 0; + ex->expect = 0; + ex->cipher_algo = -1; + ex->s2k_mode = -1; + ex->s2k_count = -1; + ex->s2k_cipher_algo = -1; + ex->s2k_digest_algo = -1; + ex->compress_algo = -1; + ex->use_sess_key = -1; + ex->disable_mdc = -1; + ex->unicode_mode = -1; +} + +#define EX_MSG(arg) \ + ereport(NOTICE, (errmsg( \ + "pgp_decrypt: unexpected %s: expected %d got %d", \ + CppAsString(arg), ex->arg, ctx->arg))) + +#define EX_CHECK(arg) do { \ + if (ex->arg >= 0 && ex->arg != ctx->arg) EX_MSG(arg); \ + } while (0) + +static void +check_expect(PGP_Context *ctx, struct debug_expect *ex) +{ + EX_CHECK(cipher_algo); + EX_CHECK(s2k_mode); + EX_CHECK(s2k_count); + EX_CHECK(s2k_digest_algo); + EX_CHECK(use_sess_key); + if (ctx->use_sess_key) + EX_CHECK(s2k_cipher_algo); + EX_CHECK(disable_mdc); + EX_CHECK(compress_algo); + EX_CHECK(unicode_mode); +} + +static void +show_debug(const char *msg) +{ + ereport(NOTICE, (errmsg("dbg: %s", msg))); +} + +static int +set_arg(PGP_Context *ctx, char *key, char *val, + struct debug_expect *ex) +{ + int res = 0; + + if (strcmp(key, "cipher-algo") == 0) + res = pgp_set_cipher_algo(ctx, val); + else if (strcmp(key, "disable-mdc") == 0) + res = pgp_disable_mdc(ctx, atoi(val)); + else if (strcmp(key, "sess-key") == 0) + res = pgp_set_sess_key(ctx, atoi(val)); + else if (strcmp(key, "s2k-mode") == 0) + res = pgp_set_s2k_mode(ctx, atoi(val)); + else if (strcmp(key, "s2k-count") == 0) + res = pgp_set_s2k_count(ctx, atoi(val)); + else if (strcmp(key, "s2k-digest-algo") == 0) + res = pgp_set_s2k_digest_algo(ctx, val); + else if (strcmp(key, "s2k-cipher-algo") == 0) + res = pgp_set_s2k_cipher_algo(ctx, val); + else if (strcmp(key, "compress-algo") == 0) + res = pgp_set_compress_algo(ctx, atoi(val)); + else if (strcmp(key, "compress-level") == 0) + res = pgp_set_compress_level(ctx, atoi(val)); + else if (strcmp(key, "convert-crlf") == 0) + res = pgp_set_convert_crlf(ctx, atoi(val)); + else if (strcmp(key, "unicode-mode") == 0) + res = pgp_set_unicode_mode(ctx, atoi(val)); + + /* + * The remaining options are for debugging/testing and are therefore not + * documented in the user-facing docs. + */ + else if (ex != NULL && strcmp(key, "debug") == 0) + ex->debug = atoi(val); + else if (ex != NULL && strcmp(key, "expect-cipher-algo") == 0) + { + ex->expect = 1; + ex->cipher_algo = pgp_get_cipher_code(val); + } + else if (ex != NULL && strcmp(key, "expect-disable-mdc") == 0) + { + ex->expect = 1; + ex->disable_mdc = atoi(val); + } + else if (ex != NULL && strcmp(key, "expect-sess-key") == 0) + { + ex->expect = 1; + ex->use_sess_key = atoi(val); + } + else if (ex != NULL && strcmp(key, "expect-s2k-mode") == 0) + { + ex->expect = 1; + ex->s2k_mode = atoi(val); + } + else if (ex != NULL && strcmp(key, "expect-s2k-count") == 0) + { + ex->expect = 1; + ex->s2k_count = atoi(val); + } + else if (ex != NULL && strcmp(key, "expect-s2k-digest-algo") == 0) + { + ex->expect = 1; + ex->s2k_digest_algo = pgp_get_digest_code(val); + } + else if (ex != NULL && strcmp(key, "expect-s2k-cipher-algo") == 0) + { + ex->expect = 1; + ex->s2k_cipher_algo = pgp_get_cipher_code(val); + } + else if (ex != NULL && strcmp(key, "expect-compress-algo") == 0) + { + ex->expect = 1; + ex->compress_algo = atoi(val); + } + else if (ex != NULL && strcmp(key, "expect-unicode-mode") == 0) + { + ex->expect = 1; + ex->unicode_mode = atoi(val); + } + else + res = PXE_ARGUMENT_ERROR; + + return res; +} + +/* + * Find next word. Handle ',' and '=' as words. Skip whitespace. + * Put word info into res_p, res_len. + * Returns ptr to next word. + */ +static char * +getword(char *p, char **res_p, int *res_len) +{ + /* whitespace at start */ + while (*p && (*p == ' ' || *p == '\t' || *p == '\n')) + p++; + + /* word data */ + *res_p = p; + if (*p == '=' || *p == ',') + p++; + else + while (*p && !(*p == ' ' || *p == '\t' || *p == '\n' + || *p == '=' || *p == ',')) + p++; + + /* word end */ + *res_len = p - *res_p; + + /* whitespace at end */ + while (*p && (*p == ' ' || *p == '\t' || *p == '\n')) + p++; + + return p; +} + +/* + * Convert to lowercase asciiz string. + */ +static char * +downcase_convert(const uint8 *s, int len) +{ + int c, + i; + char *res = palloc(len + 1); + + for (i = 0; i < len; i++) + { + c = s[i]; + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + res[i] = c; + } + res[len] = 0; + return res; +} + +static int +parse_args(PGP_Context *ctx, uint8 *args, int arg_len, + struct debug_expect *ex) +{ + char *str = downcase_convert(args, arg_len); + char *key, + *val; + int key_len, + val_len; + int res = 0; + char *p = str; + + while (*p) + { + res = PXE_ARGUMENT_ERROR; + p = getword(p, &key, &key_len); + if (*p++ != '=') + break; + p = getword(p, &val, &val_len); + if (*p == '\0') + ; + else if (*p++ != ',') + break; + + if (*key == 0 || *val == 0 || val_len == 0) + break; + + key[key_len] = 0; + val[val_len] = 0; + + res = set_arg(ctx, key, val, ex); + if (res < 0) + break; + } + pfree(str); + return res; +} + +static MBuf * +create_mbuf_from_vardata(text *data) +{ + return mbuf_create_from_data((uint8 *) VARDATA_ANY(data), + VARSIZE_ANY_EXHDR(data)); +} + +static void +init_work(PGP_Context **ctx_p, int is_text, + text *args, struct debug_expect *ex) +{ + int err = pgp_init(ctx_p); + + fill_expect(ex, is_text); + + if (err == 0 && args != NULL) + err = parse_args(*ctx_p, (uint8 *) VARDATA_ANY(args), + VARSIZE_ANY_EXHDR(args), ex); + + if (err) + px_THROW_ERROR(err); + + if (ex->debug) + px_set_debug_handler(show_debug); + + pgp_set_text_mode(*ctx_p, is_text); +} + +static bytea * +encrypt_internal(int is_pubenc, int is_text, + text *data, text *key, text *args) +{ + MBuf *src, + *dst; + uint8 tmp[VARHDRSZ]; + uint8 *restmp; + bytea *res; + int res_len; + PGP_Context *ctx; + int err; + struct debug_expect ex; + text *tmp_data = NULL; + + init_work(&ctx, is_text, args, &ex); + + if (is_text && pgp_get_unicode_mode(ctx)) + { + tmp_data = convert_to_utf8(data); + if (tmp_data == data) + tmp_data = NULL; + else + data = tmp_data; + } + + src = create_mbuf_from_vardata(data); + dst = mbuf_create(VARSIZE_ANY(data) + 128); + + /* + * reserve room for header + */ + mbuf_append(dst, tmp, VARHDRSZ); + + /* + * set key + */ + if (is_pubenc) + { + MBuf *kbuf = create_mbuf_from_vardata(key); + + err = pgp_set_pubkey(ctx, kbuf, + NULL, 0, 0); + mbuf_free(kbuf); + } + else + err = pgp_set_symkey(ctx, (uint8 *) VARDATA_ANY(key), + VARSIZE_ANY_EXHDR(key)); + + /* + * encrypt + */ + if (err >= 0) + err = pgp_encrypt(ctx, src, dst); + + /* + * check for error + */ + if (err) + { + if (ex.debug) + px_set_debug_handler(NULL); + if (tmp_data) + clear_and_pfree(tmp_data); + pgp_free(ctx); + mbuf_free(src); + mbuf_free(dst); + px_THROW_ERROR(err); + } + + /* res_len includes VARHDRSZ */ + res_len = mbuf_steal_data(dst, &restmp); + res = (bytea *) restmp; + SET_VARSIZE(res, res_len); + + if (tmp_data) + clear_and_pfree(tmp_data); + pgp_free(ctx); + mbuf_free(src); + mbuf_free(dst); + + px_set_debug_handler(NULL); + + return res; +} + +static bytea * +decrypt_internal(int is_pubenc, int need_text, text *data, + text *key, text *keypsw, text *args) +{ + int err; + MBuf *src = NULL, + *dst = NULL; + uint8 tmp[VARHDRSZ]; + uint8 *restmp; + bytea *res; + int res_len; + PGP_Context *ctx = NULL; + struct debug_expect ex; + int got_unicode = 0; + + + init_work(&ctx, need_text, args, &ex); + + src = mbuf_create_from_data((uint8 *) VARDATA_ANY(data), + VARSIZE_ANY_EXHDR(data)); + dst = mbuf_create(VARSIZE_ANY(data) + 2048); + + /* + * reserve room for header + */ + mbuf_append(dst, tmp, VARHDRSZ); + + /* + * set key + */ + if (is_pubenc) + { + uint8 *psw = NULL; + int psw_len = 0; + MBuf *kbuf; + + if (keypsw) + { + psw = (uint8 *) VARDATA_ANY(keypsw); + psw_len = VARSIZE_ANY_EXHDR(keypsw); + } + kbuf = create_mbuf_from_vardata(key); + err = pgp_set_pubkey(ctx, kbuf, psw, psw_len, 1); + mbuf_free(kbuf); + } + else + err = pgp_set_symkey(ctx, (uint8 *) VARDATA_ANY(key), + VARSIZE_ANY_EXHDR(key)); + + /* decrypt */ + if (err >= 0) + { + err = pgp_decrypt(ctx, src, dst); + + if (ex.expect) + check_expect(ctx, &ex); + + /* remember the setting */ + got_unicode = pgp_get_unicode_mode(ctx); + } + + mbuf_free(src); + pgp_free(ctx); + + if (err) + { + px_set_debug_handler(NULL); + mbuf_free(dst); + px_THROW_ERROR(err); + } + + res_len = mbuf_steal_data(dst, &restmp); + mbuf_free(dst); + + /* res_len includes VARHDRSZ */ + res = (bytea *) restmp; + SET_VARSIZE(res, res_len); + + if (need_text && got_unicode) + { + text *utf = convert_from_utf8(res); + + if (utf != res) + { + clear_and_pfree(res); + res = utf; + } + } + px_set_debug_handler(NULL); + + return res; +} + +/* + * Wrappers for symmetric-key functions + */ +Datum +pgp_sym_encrypt_bytea(PG_FUNCTION_ARGS) +{ + bytea *data, + *key; + text *arg = NULL; + text *res; + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + if (PG_NARGS() > 2) + arg = PG_GETARG_BYTEA_PP(2); + + res = encrypt_internal(0, 0, data, key, arg); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + if (PG_NARGS() > 2) + PG_FREE_IF_COPY(arg, 2); + PG_RETURN_TEXT_P(res); +} + +Datum +pgp_sym_encrypt_text(PG_FUNCTION_ARGS) +{ + bytea *data, + *key; + text *arg = NULL; + text *res; + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + if (PG_NARGS() > 2) + arg = PG_GETARG_BYTEA_PP(2); + + res = encrypt_internal(0, 1, data, key, arg); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + if (PG_NARGS() > 2) + PG_FREE_IF_COPY(arg, 2); + PG_RETURN_TEXT_P(res); +} + + +Datum +pgp_sym_decrypt_bytea(PG_FUNCTION_ARGS) +{ + bytea *data, + *key; + text *arg = NULL; + text *res; + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + if (PG_NARGS() > 2) + arg = PG_GETARG_BYTEA_PP(2); + + res = decrypt_internal(0, 0, data, key, NULL, arg); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + if (PG_NARGS() > 2) + PG_FREE_IF_COPY(arg, 2); + PG_RETURN_TEXT_P(res); +} + +Datum +pgp_sym_decrypt_text(PG_FUNCTION_ARGS) +{ + bytea *data, + *key; + text *arg = NULL; + text *res; + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + if (PG_NARGS() > 2) + arg = PG_GETARG_BYTEA_PP(2); + + res = decrypt_internal(0, 1, data, key, NULL, arg); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + if (PG_NARGS() > 2) + PG_FREE_IF_COPY(arg, 2); + PG_RETURN_TEXT_P(res); +} + +/* + * Wrappers for public-key functions + */ + +Datum +pgp_pub_encrypt_bytea(PG_FUNCTION_ARGS) +{ + bytea *data, + *key; + text *arg = NULL; + text *res; + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + if (PG_NARGS() > 2) + arg = PG_GETARG_BYTEA_PP(2); + + res = encrypt_internal(1, 0, data, key, arg); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + if (PG_NARGS() > 2) + PG_FREE_IF_COPY(arg, 2); + PG_RETURN_TEXT_P(res); +} + +Datum +pgp_pub_encrypt_text(PG_FUNCTION_ARGS) +{ + bytea *data, + *key; + text *arg = NULL; + text *res; + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + if (PG_NARGS() > 2) + arg = PG_GETARG_BYTEA_PP(2); + + res = encrypt_internal(1, 1, data, key, arg); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + if (PG_NARGS() > 2) + PG_FREE_IF_COPY(arg, 2); + PG_RETURN_TEXT_P(res); +} + + +Datum +pgp_pub_decrypt_bytea(PG_FUNCTION_ARGS) +{ + bytea *data, + *key; + text *psw = NULL, + *arg = NULL; + text *res; + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + if (PG_NARGS() > 2) + psw = PG_GETARG_BYTEA_PP(2); + if (PG_NARGS() > 3) + arg = PG_GETARG_BYTEA_PP(3); + + res = decrypt_internal(1, 0, data, key, psw, arg); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + if (PG_NARGS() > 2) + PG_FREE_IF_COPY(psw, 2); + if (PG_NARGS() > 3) + PG_FREE_IF_COPY(arg, 3); + PG_RETURN_TEXT_P(res); +} + +Datum +pgp_pub_decrypt_text(PG_FUNCTION_ARGS) +{ + bytea *data, + *key; + text *psw = NULL, + *arg = NULL; + text *res; + + data = PG_GETARG_BYTEA_PP(0); + key = PG_GETARG_BYTEA_PP(1); + if (PG_NARGS() > 2) + psw = PG_GETARG_BYTEA_PP(2); + if (PG_NARGS() > 3) + arg = PG_GETARG_BYTEA_PP(3); + + res = decrypt_internal(1, 1, data, key, psw, arg); + + PG_FREE_IF_COPY(data, 0); + PG_FREE_IF_COPY(key, 1); + if (PG_NARGS() > 2) + PG_FREE_IF_COPY(psw, 2); + if (PG_NARGS() > 3) + PG_FREE_IF_COPY(arg, 3); + PG_RETURN_TEXT_P(res); +} + + +/* + * Wrappers for PGP ascii armor + */ + +/* + * Helper function for pg_armor. Converts arrays of keys and values into + * plain C arrays, and checks that they don't contain invalid characters. + */ +static int +parse_key_value_arrays(ArrayType *key_array, ArrayType *val_array, + char ***p_keys, char ***p_values) +{ + int nkdims = ARR_NDIM(key_array); + int nvdims = ARR_NDIM(val_array); + char **keys, + **values; + Datum *key_datums, + *val_datums; + bool *key_nulls, + *val_nulls; + int key_count, + val_count; + int i; + + if (nkdims > 1 || nkdims != nvdims) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("wrong number of array subscripts"))); + if (nkdims == 0) + return 0; + + deconstruct_array_builtin(key_array, TEXTOID, &key_datums, &key_nulls, &key_count); + deconstruct_array_builtin(val_array, TEXTOID, &val_datums, &val_nulls, &val_count); + + if (key_count != val_count) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("mismatched array dimensions"))); + + keys = (char **) palloc(sizeof(char *) * key_count); + values = (char **) palloc(sizeof(char *) * val_count); + + for (i = 0; i < key_count; i++) + { + char *v; + + /* Check that the key doesn't contain anything funny */ + if (key_nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for header key"))); + + v = TextDatumGetCString(key_datums[i]); + + if (!pg_is_ascii(v)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("header key must not contain non-ASCII characters"))); + if (strstr(v, ": ")) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("header key must not contain \": \""))); + if (strchr(v, '\n')) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("header key must not contain newlines"))); + keys[i] = v; + + /* And the same for the value */ + if (val_nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value not allowed for header value"))); + + v = TextDatumGetCString(val_datums[i]); + + if (!pg_is_ascii(v)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("header value must not contain non-ASCII characters"))); + if (strchr(v, '\n')) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("header value must not contain newlines"))); + + values[i] = v; + } + + *p_keys = keys; + *p_values = values; + return key_count; +} + +Datum +pg_armor(PG_FUNCTION_ARGS) +{ + bytea *data; + text *res; + int data_len; + StringInfoData buf; + int num_headers; + char **keys = NULL, + **values = NULL; + + data = PG_GETARG_BYTEA_PP(0); + data_len = VARSIZE_ANY_EXHDR(data); + if (PG_NARGS() == 3) + { + num_headers = parse_key_value_arrays(PG_GETARG_ARRAYTYPE_P(1), + PG_GETARG_ARRAYTYPE_P(2), + &keys, &values); + } + else if (PG_NARGS() == 1) + num_headers = 0; + else + elog(ERROR, "unexpected number of arguments %d", PG_NARGS()); + + initStringInfo(&buf); + + pgp_armor_encode((uint8 *) VARDATA_ANY(data), data_len, &buf, + num_headers, keys, values); + + res = palloc(VARHDRSZ + buf.len); + SET_VARSIZE(res, VARHDRSZ + buf.len); + memcpy(VARDATA(res), buf.data, buf.len); + pfree(buf.data); + + PG_FREE_IF_COPY(data, 0); + PG_RETURN_TEXT_P(res); +} + +Datum +pg_dearmor(PG_FUNCTION_ARGS) +{ + text *data; + bytea *res; + int data_len; + int ret; + StringInfoData buf; + + data = PG_GETARG_TEXT_PP(0); + data_len = VARSIZE_ANY_EXHDR(data); + + initStringInfo(&buf); + + ret = pgp_armor_decode((uint8 *) VARDATA_ANY(data), data_len, &buf); + if (ret < 0) + px_THROW_ERROR(ret); + res = palloc(VARHDRSZ + buf.len); + SET_VARSIZE(res, VARHDRSZ + buf.len); + memcpy(VARDATA(res), buf.data, buf.len); + pfree(buf.data); + + PG_FREE_IF_COPY(data, 0); + PG_RETURN_TEXT_P(res); +} + +/* cross-call state for pgp_armor_headers */ +typedef struct +{ + int nheaders; + char **keys; + char **values; +} pgp_armor_headers_state; + +Datum +pgp_armor_headers(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + pgp_armor_headers_state *state; + char *utf8key; + char *utf8val; + HeapTuple tuple; + TupleDesc tupdesc; + AttInMetadata *attinmeta; + + if (SRF_IS_FIRSTCALL()) + { + text *data = PG_GETARG_TEXT_PP(0); + int res; + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + + /* we need the state allocated in the multi call context */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* 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"); + + attinmeta = TupleDescGetAttInMetadata(tupdesc); + funcctx->attinmeta = attinmeta; + + state = (pgp_armor_headers_state *) palloc(sizeof(pgp_armor_headers_state)); + + res = pgp_extract_armor_headers((uint8 *) VARDATA_ANY(data), + VARSIZE_ANY_EXHDR(data), + &state->nheaders, &state->keys, + &state->values); + if (res < 0) + px_THROW_ERROR(res); + + MemoryContextSwitchTo(oldcontext); + funcctx->user_fctx = state; + } + + funcctx = SRF_PERCALL_SETUP(); + state = (pgp_armor_headers_state *) funcctx->user_fctx; + + if (funcctx->call_cntr >= state->nheaders) + SRF_RETURN_DONE(funcctx); + else + { + char *values[2]; + + /* we assume that the keys (and values) are in UTF-8. */ + utf8key = state->keys[funcctx->call_cntr]; + utf8val = state->values[funcctx->call_cntr]; + + values[0] = pg_any_to_server(utf8key, strlen(utf8key), PG_UTF8); + values[1] = pg_any_to_server(utf8val, strlen(utf8val), PG_UTF8); + + /* build a tuple */ + tuple = BuildTupleFromCStrings(funcctx->attinmeta, values); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } +} + + + +/* + * Wrappers for PGP key id + */ + +Datum +pgp_key_id_w(PG_FUNCTION_ARGS) +{ + bytea *data; + text *res; + int res_len; + MBuf *buf; + + data = PG_GETARG_BYTEA_PP(0); + buf = create_mbuf_from_vardata(data); + res = palloc(VARHDRSZ + 17); + + res_len = pgp_get_keyid(buf, VARDATA(res)); + mbuf_free(buf); + if (res_len < 0) + px_THROW_ERROR(res_len); + SET_VARSIZE(res, VARHDRSZ + res_len); + + PG_FREE_IF_COPY(data, 0); + PG_RETURN_TEXT_P(res); +} diff --git a/contrib/pgcrypto/pgp-pubdec.c b/contrib/pgcrypto/pgp-pubdec.c new file mode 100644 index 0000000..a0a5738 --- /dev/null +++ b/contrib/pgcrypto/pgp-pubdec.c @@ -0,0 +1,235 @@ +/* + * pgp-pubdec.c + * Decrypt public-key encrypted session key. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-pubdec.c + */ +#include "postgres.h" + +#include "pgp.h" +#include "px.h" + +/* + * padded msg = 02 || PS || 00 || M + * PS - pad bytes + * M - msg + */ +static uint8 * +check_eme_pkcs1_v15(uint8 *data, int len) +{ + uint8 *data_end = data + len; + uint8 *p = data; + int rnd = 0; + + if (len < 1 + 8 + 1) + return NULL; + + if (*p++ != 2) + return NULL; + + while (p < data_end && *p) + { + p++; + rnd++; + } + + if (p == data_end) + return NULL; + if (*p != 0) + return NULL; + if (rnd < 8) + return NULL; + return p + 1; +} + +/* + * secret message: 1 byte algo, sesskey, 2 byte cksum + * ignore algo in cksum + */ +static int +control_cksum(uint8 *msg, int msglen) +{ + int i; + unsigned my_cksum, + got_cksum; + + if (msglen < 3) + return PXE_PGP_WRONG_KEY; + + my_cksum = 0; + for (i = 1; i < msglen - 2; i++) + my_cksum += msg[i]; + my_cksum &= 0xFFFF; + got_cksum = ((unsigned) (msg[msglen - 2]) << 8) + msg[msglen - 1]; + if (my_cksum != got_cksum) + { + px_debug("pubenc cksum failed"); + return PXE_PGP_WRONG_KEY; + } + return 0; +} + +static int +decrypt_elgamal(PGP_PubKey *pk, PullFilter *pkt, PGP_MPI **m_p) +{ + int res; + PGP_MPI *c1 = NULL; + PGP_MPI *c2 = NULL; + + if (pk->algo != PGP_PUB_ELG_ENCRYPT) + return PXE_PGP_WRONG_KEY; + + /* read elgamal encrypted data */ + res = pgp_mpi_read(pkt, &c1); + if (res < 0) + goto out; + res = pgp_mpi_read(pkt, &c2); + if (res < 0) + goto out; + + /* decrypt */ + res = pgp_elgamal_decrypt(pk, c1, c2, m_p); + +out: + pgp_mpi_free(c1); + pgp_mpi_free(c2); + return res; +} + +static int +decrypt_rsa(PGP_PubKey *pk, PullFilter *pkt, PGP_MPI **m_p) +{ + int res; + PGP_MPI *c; + + if (pk->algo != PGP_PUB_RSA_ENCRYPT + && pk->algo != PGP_PUB_RSA_ENCRYPT_SIGN) + return PXE_PGP_WRONG_KEY; + + /* read rsa encrypted data */ + res = pgp_mpi_read(pkt, &c); + if (res < 0) + return res; + + /* decrypt */ + res = pgp_rsa_decrypt(pk, c, m_p); + + pgp_mpi_free(c); + return res; +} + +/* key id is missing - user is expected to try all keys */ +static const uint8 + any_key[] = {0, 0, 0, 0, 0, 0, 0, 0}; + +int +pgp_parse_pubenc_sesskey(PGP_Context *ctx, PullFilter *pkt) +{ + int ver; + int algo; + int res; + uint8 key_id[8]; + PGP_PubKey *pk; + uint8 *msg; + int msglen; + PGP_MPI *m; + + pk = ctx->pub_key; + if (pk == NULL) + { + px_debug("no pubkey?"); + return PXE_BUG; + } + + GETBYTE(pkt, ver); + if (ver != 3) + { + px_debug("unknown pubenc_sesskey pkt ver=%d", ver); + return PXE_PGP_CORRUPT_DATA; + } + + /* + * check if keyid's match - user-friendly msg + */ + res = pullf_read_fixed(pkt, 8, key_id); + if (res < 0) + return res; + if (memcmp(key_id, any_key, 8) != 0 + && memcmp(key_id, pk->key_id, 8) != 0) + { + px_debug("key_id's does not match"); + return PXE_PGP_WRONG_KEY; + } + + /* + * Decrypt + */ + GETBYTE(pkt, algo); + switch (algo) + { + case PGP_PUB_ELG_ENCRYPT: + res = decrypt_elgamal(pk, pkt, &m); + break; + case PGP_PUB_RSA_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT_SIGN: + res = decrypt_rsa(pk, pkt, &m); + break; + default: + res = PXE_PGP_UNKNOWN_PUBALGO; + } + if (res < 0) + return res; + + /* + * extract message + */ + msg = check_eme_pkcs1_v15(m->data, m->bytes); + if (msg == NULL) + { + px_debug("check_eme_pkcs1_v15 failed"); + res = PXE_PGP_WRONG_KEY; + goto out; + } + msglen = m->bytes - (msg - m->data); + + res = control_cksum(msg, msglen); + if (res < 0) + goto out; + + /* + * got sesskey + */ + ctx->cipher_algo = *msg; + ctx->sess_key_len = msglen - 3; + memcpy(ctx->sess_key, msg + 1, ctx->sess_key_len); + +out: + pgp_mpi_free(m); + if (res < 0) + return res; + return pgp_expect_packet_end(pkt); +} diff --git a/contrib/pgcrypto/pgp-pubenc.c b/contrib/pgcrypto/pgp-pubenc.c new file mode 100644 index 0000000..c254a37 --- /dev/null +++ b/contrib/pgcrypto/pgp-pubenc.c @@ -0,0 +1,244 @@ +/* + * pgp-pubenc.c + * Encrypt session key with public key. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-pubenc.c + */ +#include "postgres.h" + +#include "pgp.h" +#include "px.h" + +/* + * padded msg: 02 || non-zero pad bytes || 00 || msg + */ +static int +pad_eme_pkcs1_v15(uint8 *data, int data_len, int res_len, uint8 **res_p) +{ + uint8 *buf, + *p; + int pad_len = res_len - 2 - data_len; + + if (pad_len < 8) + return PXE_BUG; + + buf = palloc(res_len); + buf[0] = 0x02; + + if (!pg_strong_random(buf + 1, pad_len)) + { + pfree(buf); + return PXE_NO_RANDOM; + } + + /* pad must not contain zero bytes */ + p = buf + 1; + while (p < buf + 1 + pad_len) + { + if (*p == 0) + { + if (!pg_strong_random(p, 1)) + { + px_memset(buf, 0, res_len); + pfree(buf); + return PXE_NO_RANDOM; + } + } + if (*p != 0) + p++; + } + + buf[pad_len + 1] = 0; + memcpy(buf + pad_len + 2, data, data_len); + *res_p = buf; + + return 0; +} + +static int +create_secmsg(PGP_Context *ctx, PGP_MPI **msg_p, int full_bytes) +{ + uint8 *secmsg; + int res, + i; + unsigned cksum = 0; + int klen = ctx->sess_key_len; + uint8 *padded = NULL; + PGP_MPI *m = NULL; + + /* calc checksum */ + for (i = 0; i < klen; i++) + cksum += ctx->sess_key[i]; + + /* + * create "secret message" + */ + secmsg = palloc(klen + 3); + secmsg[0] = ctx->cipher_algo; + memcpy(secmsg + 1, ctx->sess_key, klen); + secmsg[klen + 1] = (cksum >> 8) & 0xFF; + secmsg[klen + 2] = cksum & 0xFF; + + /* + * now create a large integer of it + */ + res = pad_eme_pkcs1_v15(secmsg, klen + 3, full_bytes, &padded); + if (res >= 0) + { + /* first byte will be 0x02 */ + int full_bits = full_bytes * 8 - 6; + + res = pgp_mpi_create(padded, full_bits, &m); + } + + if (padded) + { + px_memset(padded, 0, full_bytes); + pfree(padded); + } + px_memset(secmsg, 0, klen + 3); + pfree(secmsg); + + if (res >= 0) + *msg_p = m; + + return res; +} + +static int +encrypt_and_write_elgamal(PGP_Context *ctx, PGP_PubKey *pk, PushFilter *pkt) +{ + int res; + PGP_MPI *m = NULL, + *c1 = NULL, + *c2 = NULL; + + /* create padded msg */ + res = create_secmsg(ctx, &m, pk->pub.elg.p->bytes - 1); + if (res < 0) + goto err; + + /* encrypt it */ + res = pgp_elgamal_encrypt(pk, m, &c1, &c2); + if (res < 0) + goto err; + + /* write out */ + res = pgp_mpi_write(pkt, c1); + if (res < 0) + goto err; + res = pgp_mpi_write(pkt, c2); + +err: + pgp_mpi_free(m); + pgp_mpi_free(c1); + pgp_mpi_free(c2); + return res; +} + +static int +encrypt_and_write_rsa(PGP_Context *ctx, PGP_PubKey *pk, PushFilter *pkt) +{ + int res; + PGP_MPI *m = NULL, + *c = NULL; + + /* create padded msg */ + res = create_secmsg(ctx, &m, pk->pub.rsa.n->bytes - 1); + if (res < 0) + goto err; + + /* encrypt it */ + res = pgp_rsa_encrypt(pk, m, &c); + if (res < 0) + goto err; + + /* write out */ + res = pgp_mpi_write(pkt, c); + +err: + pgp_mpi_free(m); + pgp_mpi_free(c); + return res; +} + +int +pgp_write_pubenc_sesskey(PGP_Context *ctx, PushFilter *dst) +{ + int res; + PGP_PubKey *pk = ctx->pub_key; + uint8 ver = 3; + PushFilter *pkt = NULL; + uint8 algo; + + if (pk == NULL) + { + px_debug("no pubkey?\n"); + return PXE_BUG; + } + + algo = pk->algo; + + /* + * now write packet + */ + res = pgp_create_pkt_writer(dst, PGP_PKT_PUBENCRYPTED_SESSKEY, &pkt); + if (res < 0) + goto err; + res = pushf_write(pkt, &ver, 1); + if (res < 0) + goto err; + res = pushf_write(pkt, pk->key_id, 8); + if (res < 0) + goto err; + res = pushf_write(pkt, &algo, 1); + if (res < 0) + goto err; + + switch (algo) + { + case PGP_PUB_ELG_ENCRYPT: + res = encrypt_and_write_elgamal(ctx, pk, pkt); + break; + case PGP_PUB_RSA_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT_SIGN: + res = encrypt_and_write_rsa(ctx, pk, pkt); + break; + } + if (res < 0) + goto err; + + /* + * done, signal packet end + */ + res = pushf_flush(pkt); +err: + if (pkt) + pushf_free(pkt); + + return res; +} diff --git a/contrib/pgcrypto/pgp-pubkey.c b/contrib/pgcrypto/pgp-pubkey.c new file mode 100644 index 0000000..9a6561c --- /dev/null +++ b/contrib/pgcrypto/pgp-pubkey.c @@ -0,0 +1,583 @@ +/* + * pgp-pubkey.c + * Read public or secret key. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-pubkey.c + */ +#include "postgres.h" + +#include "mbuf.h" +#include "pgp.h" +#include "px.h" + +int +pgp_key_alloc(PGP_PubKey **pk_p) +{ + PGP_PubKey *pk; + + pk = palloc0(sizeof(*pk)); + *pk_p = pk; + return 0; +} + +void +pgp_key_free(PGP_PubKey *pk) +{ + if (pk == NULL) + return; + + switch (pk->algo) + { + case PGP_PUB_ELG_ENCRYPT: + pgp_mpi_free(pk->pub.elg.p); + pgp_mpi_free(pk->pub.elg.g); + pgp_mpi_free(pk->pub.elg.y); + pgp_mpi_free(pk->sec.elg.x); + break; + case PGP_PUB_RSA_SIGN: + case PGP_PUB_RSA_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT_SIGN: + pgp_mpi_free(pk->pub.rsa.n); + pgp_mpi_free(pk->pub.rsa.e); + pgp_mpi_free(pk->sec.rsa.d); + pgp_mpi_free(pk->sec.rsa.p); + pgp_mpi_free(pk->sec.rsa.q); + pgp_mpi_free(pk->sec.rsa.u); + break; + case PGP_PUB_DSA_SIGN: + pgp_mpi_free(pk->pub.dsa.p); + pgp_mpi_free(pk->pub.dsa.q); + pgp_mpi_free(pk->pub.dsa.g); + pgp_mpi_free(pk->pub.dsa.y); + pgp_mpi_free(pk->sec.dsa.x); + break; + } + px_memset(pk, 0, sizeof(*pk)); + pfree(pk); +} + +static int +calc_key_id(PGP_PubKey *pk) +{ + int res; + PX_MD *md; + int len; + uint8 hdr[3]; + uint8 hash[20]; + + res = pgp_load_digest(PGP_DIGEST_SHA1, &md); + if (res < 0) + return res; + + len = 1 + 4 + 1; + switch (pk->algo) + { + case PGP_PUB_ELG_ENCRYPT: + len += 2 + pk->pub.elg.p->bytes; + len += 2 + pk->pub.elg.g->bytes; + len += 2 + pk->pub.elg.y->bytes; + break; + case PGP_PUB_RSA_SIGN: + case PGP_PUB_RSA_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT_SIGN: + len += 2 + pk->pub.rsa.n->bytes; + len += 2 + pk->pub.rsa.e->bytes; + break; + case PGP_PUB_DSA_SIGN: + len += 2 + pk->pub.dsa.p->bytes; + len += 2 + pk->pub.dsa.q->bytes; + len += 2 + pk->pub.dsa.g->bytes; + len += 2 + pk->pub.dsa.y->bytes; + break; + } + + hdr[0] = 0x99; + hdr[1] = len >> 8; + hdr[2] = len & 0xFF; + px_md_update(md, hdr, 3); + + px_md_update(md, &pk->ver, 1); + px_md_update(md, pk->time, 4); + px_md_update(md, &pk->algo, 1); + + switch (pk->algo) + { + case PGP_PUB_ELG_ENCRYPT: + pgp_mpi_hash(md, pk->pub.elg.p); + pgp_mpi_hash(md, pk->pub.elg.g); + pgp_mpi_hash(md, pk->pub.elg.y); + break; + case PGP_PUB_RSA_SIGN: + case PGP_PUB_RSA_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT_SIGN: + pgp_mpi_hash(md, pk->pub.rsa.n); + pgp_mpi_hash(md, pk->pub.rsa.e); + break; + case PGP_PUB_DSA_SIGN: + pgp_mpi_hash(md, pk->pub.dsa.p); + pgp_mpi_hash(md, pk->pub.dsa.q); + pgp_mpi_hash(md, pk->pub.dsa.g); + pgp_mpi_hash(md, pk->pub.dsa.y); + break; + } + + px_md_finish(md, hash); + px_md_free(md); + + memcpy(pk->key_id, hash + 12, 8); + px_memset(hash, 0, 20); + + return 0; +} + +int +_pgp_read_public_key(PullFilter *pkt, PGP_PubKey **pk_p) +{ + int res; + PGP_PubKey *pk; + + res = pgp_key_alloc(&pk); + if (res < 0) + return res; + + /* get version */ + GETBYTE(pkt, pk->ver); + if (pk->ver != 4) + { + res = PXE_PGP_NOT_V4_KEYPKT; + goto out; + } + + /* read time */ + res = pullf_read_fixed(pkt, 4, pk->time); + if (res < 0) + goto out; + + /* pubkey algorithm */ + GETBYTE(pkt, pk->algo); + + switch (pk->algo) + { + case PGP_PUB_DSA_SIGN: + res = pgp_mpi_read(pkt, &pk->pub.dsa.p); + if (res < 0) + break; + res = pgp_mpi_read(pkt, &pk->pub.dsa.q); + if (res < 0) + break; + res = pgp_mpi_read(pkt, &pk->pub.dsa.g); + if (res < 0) + break; + res = pgp_mpi_read(pkt, &pk->pub.dsa.y); + if (res < 0) + break; + + res = calc_key_id(pk); + break; + + case PGP_PUB_RSA_SIGN: + case PGP_PUB_RSA_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT_SIGN: + res = pgp_mpi_read(pkt, &pk->pub.rsa.n); + if (res < 0) + break; + res = pgp_mpi_read(pkt, &pk->pub.rsa.e); + if (res < 0) + break; + + res = calc_key_id(pk); + + if (pk->algo != PGP_PUB_RSA_SIGN) + pk->can_encrypt = 1; + break; + + case PGP_PUB_ELG_ENCRYPT: + res = pgp_mpi_read(pkt, &pk->pub.elg.p); + if (res < 0) + break; + res = pgp_mpi_read(pkt, &pk->pub.elg.g); + if (res < 0) + break; + res = pgp_mpi_read(pkt, &pk->pub.elg.y); + if (res < 0) + break; + + res = calc_key_id(pk); + + pk->can_encrypt = 1; + break; + + default: + px_debug("unknown public algo: %d", pk->algo); + res = PXE_PGP_UNKNOWN_PUBALGO; + } + +out: + if (res < 0) + pgp_key_free(pk); + else + *pk_p = pk; + + return res; +} + +#define HIDE_CLEAR 0 +#define HIDE_CKSUM 255 +#define HIDE_SHA1 254 + +static int +check_key_sha1(PullFilter *src, PGP_PubKey *pk) +{ + int res; + uint8 got_sha1[20]; + uint8 my_sha1[20]; + PX_MD *md; + + res = pullf_read_fixed(src, 20, got_sha1); + if (res < 0) + return res; + + res = pgp_load_digest(PGP_DIGEST_SHA1, &md); + if (res < 0) + goto err; + switch (pk->algo) + { + case PGP_PUB_ELG_ENCRYPT: + pgp_mpi_hash(md, pk->sec.elg.x); + break; + case PGP_PUB_RSA_SIGN: + case PGP_PUB_RSA_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT_SIGN: + pgp_mpi_hash(md, pk->sec.rsa.d); + pgp_mpi_hash(md, pk->sec.rsa.p); + pgp_mpi_hash(md, pk->sec.rsa.q); + pgp_mpi_hash(md, pk->sec.rsa.u); + break; + case PGP_PUB_DSA_SIGN: + pgp_mpi_hash(md, pk->sec.dsa.x); + break; + } + px_md_finish(md, my_sha1); + px_md_free(md); + + if (memcmp(my_sha1, got_sha1, 20) != 0) + { + px_debug("key sha1 check failed"); + res = PXE_PGP_KEYPKT_CORRUPT; + } +err: + px_memset(got_sha1, 0, 20); + px_memset(my_sha1, 0, 20); + return res; +} + +static int +check_key_cksum(PullFilter *src, PGP_PubKey *pk) +{ + int res; + unsigned got_cksum, + my_cksum = 0; + uint8 buf[2]; + + res = pullf_read_fixed(src, 2, buf); + if (res < 0) + return res; + + got_cksum = ((unsigned) buf[0] << 8) + buf[1]; + switch (pk->algo) + { + case PGP_PUB_ELG_ENCRYPT: + my_cksum = pgp_mpi_cksum(0, pk->sec.elg.x); + break; + case PGP_PUB_RSA_SIGN: + case PGP_PUB_RSA_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT_SIGN: + my_cksum = pgp_mpi_cksum(0, pk->sec.rsa.d); + my_cksum = pgp_mpi_cksum(my_cksum, pk->sec.rsa.p); + my_cksum = pgp_mpi_cksum(my_cksum, pk->sec.rsa.q); + my_cksum = pgp_mpi_cksum(my_cksum, pk->sec.rsa.u); + break; + case PGP_PUB_DSA_SIGN: + my_cksum = pgp_mpi_cksum(0, pk->sec.dsa.x); + break; + } + if (my_cksum != got_cksum) + { + px_debug("key cksum check failed"); + return PXE_PGP_KEYPKT_CORRUPT; + } + return 0; +} + +static int +process_secret_key(PullFilter *pkt, PGP_PubKey **pk_p, + const uint8 *key, int key_len) +{ + int res; + int hide_type; + int cipher_algo; + int bs; + uint8 iv[512]; + PullFilter *pf_decrypt = NULL, + *pf_key; + PGP_CFB *cfb = NULL; + PGP_S2K s2k; + PGP_PubKey *pk; + + /* first read public key part */ + res = _pgp_read_public_key(pkt, &pk); + if (res < 0) + return res; + + /* + * is secret key encrypted? + */ + GETBYTE(pkt, hide_type); + if (hide_type == HIDE_SHA1 || hide_type == HIDE_CKSUM) + { + if (key == NULL) + return PXE_PGP_NEED_SECRET_PSW; + GETBYTE(pkt, cipher_algo); + res = pgp_s2k_read(pkt, &s2k); + if (res < 0) + return res; + + res = pgp_s2k_process(&s2k, cipher_algo, key, key_len); + if (res < 0) + return res; + + bs = pgp_get_cipher_block_size(cipher_algo); + if (bs == 0) + { + px_debug("unknown cipher algo=%d", cipher_algo); + return PXE_PGP_UNSUPPORTED_CIPHER; + } + res = pullf_read_fixed(pkt, bs, iv); + if (res < 0) + return res; + + /* + * create decrypt filter + */ + res = pgp_cfb_create(&cfb, cipher_algo, s2k.key, s2k.key_len, 0, iv); + if (res < 0) + return res; + res = pullf_create(&pf_decrypt, &pgp_decrypt_filter, cfb, pkt); + if (res < 0) + return res; + pf_key = pf_decrypt; + } + else if (hide_type == HIDE_CLEAR) + { + pf_key = pkt; + } + else + { + px_debug("unknown hide type"); + return PXE_PGP_KEYPKT_CORRUPT; + } + + /* read secret key */ + switch (pk->algo) + { + case PGP_PUB_RSA_SIGN: + case PGP_PUB_RSA_ENCRYPT: + case PGP_PUB_RSA_ENCRYPT_SIGN: + res = pgp_mpi_read(pf_key, &pk->sec.rsa.d); + if (res < 0) + break; + res = pgp_mpi_read(pf_key, &pk->sec.rsa.p); + if (res < 0) + break; + res = pgp_mpi_read(pf_key, &pk->sec.rsa.q); + if (res < 0) + break; + res = pgp_mpi_read(pf_key, &pk->sec.rsa.u); + if (res < 0) + break; + break; + case PGP_PUB_ELG_ENCRYPT: + res = pgp_mpi_read(pf_key, &pk->sec.elg.x); + break; + case PGP_PUB_DSA_SIGN: + res = pgp_mpi_read(pf_key, &pk->sec.dsa.x); + break; + default: + px_debug("unknown public algo: %d", pk->algo); + res = PXE_PGP_KEYPKT_CORRUPT; + } + /* read checksum / sha1 */ + if (res >= 0) + { + if (hide_type == HIDE_SHA1) + res = check_key_sha1(pf_key, pk); + else + res = check_key_cksum(pf_key, pk); + } + if (res >= 0) + res = pgp_expect_packet_end(pf_key); + + if (pf_decrypt) + pullf_free(pf_decrypt); + if (cfb) + pgp_cfb_free(cfb); + + if (res < 0) + pgp_key_free(pk); + else + *pk_p = pk; + + return res; +} + +static int +internal_read_key(PullFilter *src, PGP_PubKey **pk_p, + const uint8 *psw, int psw_len, int pubtype) +{ + PullFilter *pkt = NULL; + int res; + uint8 tag; + int len; + PGP_PubKey *enc_key = NULL; + PGP_PubKey *pk = NULL; + int got_main_key = 0; + + /* + * Search for encryption key. + * + * Error out on anything fancy. + */ + while (1) + { + res = pgp_parse_pkt_hdr(src, &tag, &len, 0); + if (res <= 0) + break; + res = pgp_create_pkt_reader(&pkt, src, len, res, NULL); + if (res < 0) + break; + + switch (tag) + { + case PGP_PKT_PUBLIC_KEY: + case PGP_PKT_SECRET_KEY: + if (got_main_key) + { + res = PXE_PGP_MULTIPLE_KEYS; + break; + } + got_main_key = 1; + res = pgp_skip_packet(pkt); + break; + + case PGP_PKT_PUBLIC_SUBKEY: + if (pubtype != 0) + res = PXE_PGP_EXPECT_SECRET_KEY; + else + res = _pgp_read_public_key(pkt, &pk); + break; + + case PGP_PKT_SECRET_SUBKEY: + if (pubtype != 1) + res = PXE_PGP_EXPECT_PUBLIC_KEY; + else + res = process_secret_key(pkt, &pk, psw, psw_len); + break; + + case PGP_PKT_SIGNATURE: + case PGP_PKT_MARKER: + case PGP_PKT_TRUST: + case PGP_PKT_USER_ID: + case PGP_PKT_USER_ATTR: + case PGP_PKT_PRIV_61: + res = pgp_skip_packet(pkt); + break; + default: + px_debug("unknown/unexpected packet: %d", tag); + res = PXE_PGP_UNEXPECTED_PKT; + } + pullf_free(pkt); + pkt = NULL; + + if (pk != NULL) + { + if (res >= 0 && pk->can_encrypt) + { + if (enc_key == NULL) + { + enc_key = pk; + pk = NULL; + } + else + res = PXE_PGP_MULTIPLE_SUBKEYS; + } + + if (pk) + pgp_key_free(pk); + pk = NULL; + } + + if (res < 0) + break; + } + + if (pkt) + pullf_free(pkt); + + if (res < 0) + { + if (enc_key) + pgp_key_free(enc_key); + return res; + } + + if (!enc_key) + res = PXE_PGP_NO_USABLE_KEY; + else + *pk_p = enc_key; + return res; +} + +int +pgp_set_pubkey(PGP_Context *ctx, MBuf *keypkt, + const uint8 *key, int key_len, int pubtype) +{ + int res; + PullFilter *src; + PGP_PubKey *pk = NULL; + + res = pullf_create_mbuf_reader(&src, keypkt); + if (res < 0) + return res; + + res = internal_read_key(src, &pk, key, key_len, pubtype); + pullf_free(src); + + if (res >= 0) + ctx->pub_key = pk; + + return res < 0 ? res : 0; +} diff --git a/contrib/pgcrypto/pgp-s2k.c b/contrib/pgcrypto/pgp-s2k.c new file mode 100644 index 0000000..81ca1f0 --- /dev/null +++ b/contrib/pgcrypto/pgp-s2k.c @@ -0,0 +1,308 @@ +/* + * pgp-s2k.c + * OpenPGP string2key functions. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp-s2k.c + */ + +#include "postgres.h" + +#include "pgp.h" +#include "px.h" + +static int +calc_s2k_simple(PGP_S2K *s2k, PX_MD *md, const uint8 *key, + unsigned key_len) +{ + unsigned md_rlen; + uint8 buf[PGP_MAX_DIGEST]; + unsigned preload; + unsigned remain; + uint8 *dst = s2k->key; + + md_rlen = px_md_result_size(md); + + remain = s2k->key_len; + preload = 0; + while (remain > 0) + { + px_md_reset(md); + + if (preload) + { + memset(buf, 0, preload); + px_md_update(md, buf, preload); + } + preload++; + + px_md_update(md, key, key_len); + px_md_finish(md, buf); + + if (remain > md_rlen) + { + memcpy(dst, buf, md_rlen); + dst += md_rlen; + remain -= md_rlen; + } + else + { + memcpy(dst, buf, remain); + remain = 0; + } + } + px_memset(buf, 0, sizeof(buf)); + return 0; +} + +static int +calc_s2k_salted(PGP_S2K *s2k, PX_MD *md, const uint8 *key, unsigned key_len) +{ + unsigned md_rlen; + uint8 buf[PGP_MAX_DIGEST]; + unsigned preload = 0; + uint8 *dst; + unsigned remain; + + md_rlen = px_md_result_size(md); + + dst = s2k->key; + remain = s2k->key_len; + while (remain > 0) + { + px_md_reset(md); + + if (preload > 0) + { + memset(buf, 0, preload); + px_md_update(md, buf, preload); + } + preload++; + + px_md_update(md, s2k->salt, PGP_S2K_SALT); + px_md_update(md, key, key_len); + px_md_finish(md, buf); + + if (remain > md_rlen) + { + memcpy(dst, buf, md_rlen); + remain -= md_rlen; + dst += md_rlen; + } + else + { + memcpy(dst, buf, remain); + remain = 0; + } + } + px_memset(buf, 0, sizeof(buf)); + return 0; +} + +static int +calc_s2k_iter_salted(PGP_S2K *s2k, PX_MD *md, const uint8 *key, + unsigned key_len) +{ + unsigned md_rlen; + uint8 buf[PGP_MAX_DIGEST]; + uint8 *dst; + unsigned preload = 0; + unsigned remain, + c, + curcnt, + count; + + count = s2k_decode_count(s2k->iter); + + md_rlen = px_md_result_size(md); + + remain = s2k->key_len; + dst = s2k->key; + while (remain > 0) + { + px_md_reset(md); + + if (preload) + { + memset(buf, 0, preload); + px_md_update(md, buf, preload); + } + preload++; + + px_md_update(md, s2k->salt, PGP_S2K_SALT); + px_md_update(md, key, key_len); + curcnt = PGP_S2K_SALT + key_len; + + while (curcnt < count) + { + if (curcnt + PGP_S2K_SALT < count) + c = PGP_S2K_SALT; + else + c = count - curcnt; + px_md_update(md, s2k->salt, c); + curcnt += c; + + if (curcnt + key_len < count) + c = key_len; + else if (curcnt < count) + c = count - curcnt; + else + break; + px_md_update(md, key, c); + curcnt += c; + } + px_md_finish(md, buf); + + if (remain > md_rlen) + { + memcpy(dst, buf, md_rlen); + remain -= md_rlen; + dst += md_rlen; + } + else + { + memcpy(dst, buf, remain); + remain = 0; + } + } + px_memset(buf, 0, sizeof(buf)); + return 0; +} + +/* + * Decide PGP_S2K_ISALTED iteration count (in OpenPGP one-byte representation) + * + * Too small: weak + * Too big: slow + * gpg defaults to 96 => 65536 iters + * + * For our default (count=-1) we let it float a bit: 96 + 32 => between 65536 + * and 262144 iterations. + * + * Otherwise, find the smallest number which provides at least the specified + * iteration count. + */ +static uint8 +decide_s2k_iter(unsigned rand_byte, int count) +{ + int iter; + + if (count == -1) + return 96 + (rand_byte & 0x1F); + /* this is a bit brute-force, but should be quick enough */ + for (iter = 0; iter <= 255; iter++) + if (s2k_decode_count(iter) >= count) + return iter; + return 255; +} + +int +pgp_s2k_fill(PGP_S2K *s2k, int mode, int digest_algo, int count) +{ + int res = 0; + uint8 tmp; + + s2k->mode = mode; + s2k->digest_algo = digest_algo; + + switch (s2k->mode) + { + case PGP_S2K_SIMPLE: + break; + case PGP_S2K_SALTED: + if (!pg_strong_random(s2k->salt, PGP_S2K_SALT)) + return PXE_NO_RANDOM; + break; + case PGP_S2K_ISALTED: + if (!pg_strong_random(s2k->salt, PGP_S2K_SALT)) + return PXE_NO_RANDOM; + if (!pg_strong_random(&tmp, 1)) + return PXE_NO_RANDOM; + s2k->iter = decide_s2k_iter(tmp, count); + break; + default: + res = PXE_PGP_BAD_S2K_MODE; + } + return res; +} + +int +pgp_s2k_read(PullFilter *src, PGP_S2K *s2k) +{ + int res = 0; + + GETBYTE(src, s2k->mode); + GETBYTE(src, s2k->digest_algo); + switch (s2k->mode) + { + case 0: + break; + case 1: + res = pullf_read_fixed(src, 8, s2k->salt); + break; + case 3: + res = pullf_read_fixed(src, 8, s2k->salt); + if (res < 0) + break; + GETBYTE(src, s2k->iter); + break; + default: + res = PXE_PGP_BAD_S2K_MODE; + } + return res; +} + +int +pgp_s2k_process(PGP_S2K *s2k, int cipher, const uint8 *key, int key_len) +{ + int res; + PX_MD *md; + + s2k->key_len = pgp_get_cipher_key_size(cipher); + if (s2k->key_len <= 0) + return PXE_PGP_UNSUPPORTED_CIPHER; + + res = pgp_load_digest(s2k->digest_algo, &md); + if (res < 0) + return res; + + switch (s2k->mode) + { + case 0: + res = calc_s2k_simple(s2k, md, key, key_len); + break; + case 1: + res = calc_s2k_salted(s2k, md, key, key_len); + break; + case 3: + res = calc_s2k_iter_salted(s2k, md, key, key_len); + break; + default: + res = PXE_PGP_BAD_S2K_MODE; + } + px_md_free(md); + return res; +} diff --git a/contrib/pgcrypto/pgp.c b/contrib/pgcrypto/pgp.c new file mode 100644 index 0000000..8a6a6c2 --- /dev/null +++ b/contrib/pgcrypto/pgp.c @@ -0,0 +1,360 @@ +/* + * pgp.c + * Various utility stuff. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp.c + */ + +#include "postgres.h" + +#include "pgp.h" +#include "px.h" + +/* + * Defaults. + */ +static int def_cipher_algo = PGP_SYM_AES_128; +static int def_s2k_cipher_algo = -1; +static int def_s2k_mode = PGP_S2K_ISALTED; +static int def_s2k_count = -1; +static int def_s2k_digest_algo = PGP_DIGEST_SHA1; +static int def_compress_algo = PGP_COMPR_NONE; +static int def_compress_level = 6; +static int def_disable_mdc = 0; +static int def_use_sess_key = 0; +static int def_text_mode = 0; +static int def_unicode_mode = 0; +static int def_convert_crlf = 0; + +struct digest_info +{ + const char *name; + int code; +}; + +struct cipher_info +{ + const char *name; + int code; + const char *int_name; + int key_len; + int block_len; +}; + +static const struct digest_info digest_list[] = { + {"md5", PGP_DIGEST_MD5}, + {"sha1", PGP_DIGEST_SHA1}, + {"sha-1", PGP_DIGEST_SHA1}, + {"ripemd160", PGP_DIGEST_RIPEMD160}, + {"sha256", PGP_DIGEST_SHA256}, + {"sha384", PGP_DIGEST_SHA384}, + {"sha512", PGP_DIGEST_SHA512}, + {NULL, 0} +}; + +static const struct cipher_info cipher_list[] = { + {"3des", PGP_SYM_DES3, "3des-ecb", 192 / 8, 64 / 8}, + {"cast5", PGP_SYM_CAST5, "cast5-ecb", 128 / 8, 64 / 8}, + {"bf", PGP_SYM_BLOWFISH, "bf-ecb", 128 / 8, 64 / 8}, + {"blowfish", PGP_SYM_BLOWFISH, "bf-ecb", 128 / 8, 64 / 8}, + {"aes", PGP_SYM_AES_128, "aes-ecb", 128 / 8, 128 / 8}, + {"aes128", PGP_SYM_AES_128, "aes-ecb", 128 / 8, 128 / 8}, + {"aes192", PGP_SYM_AES_192, "aes-ecb", 192 / 8, 128 / 8}, + {"aes256", PGP_SYM_AES_256, "aes-ecb", 256 / 8, 128 / 8}, + {"twofish", PGP_SYM_TWOFISH, "twofish-ecb", 256 / 8, 128 / 8}, + {NULL, 0, NULL} +}; + +static const struct cipher_info * +get_cipher_info(int code) +{ + const struct cipher_info *i; + + for (i = cipher_list; i->name; i++) + if (i->code == code) + return i; + return NULL; +} + +int +pgp_get_digest_code(const char *name) +{ + const struct digest_info *i; + + for (i = digest_list; i->name; i++) + if (pg_strcasecmp(i->name, name) == 0) + return i->code; + return PXE_PGP_UNSUPPORTED_HASH; +} + +int +pgp_get_cipher_code(const char *name) +{ + const struct cipher_info *i; + + for (i = cipher_list; i->name; i++) + if (pg_strcasecmp(i->name, name) == 0) + return i->code; + return PXE_PGP_UNSUPPORTED_CIPHER; +} + +const char * +pgp_get_digest_name(int code) +{ + const struct digest_info *i; + + for (i = digest_list; i->name; i++) + if (i->code == code) + return i->name; + return NULL; +} + +int +pgp_get_cipher_key_size(int code) +{ + const struct cipher_info *i = get_cipher_info(code); + + if (i != NULL) + return i->key_len; + return 0; +} + +int +pgp_get_cipher_block_size(int code) +{ + const struct cipher_info *i = get_cipher_info(code); + + if (i != NULL) + return i->block_len; + return 0; +} + +int +pgp_load_cipher(int code, PX_Cipher **res) +{ + int err; + const struct cipher_info *i = get_cipher_info(code); + + if (i == NULL) + return PXE_PGP_CORRUPT_DATA; + + err = px_find_cipher(i->int_name, res); + if (err == 0) + return 0; + + return PXE_PGP_UNSUPPORTED_CIPHER; +} + +int +pgp_load_digest(int code, PX_MD **res) +{ + int err; + const char *name = pgp_get_digest_name(code); + + if (name == NULL) + return PXE_PGP_CORRUPT_DATA; + + err = px_find_digest(name, res); + if (err == 0) + return 0; + + return PXE_PGP_UNSUPPORTED_HASH; +} + +int +pgp_init(PGP_Context **ctx_p) +{ + PGP_Context *ctx; + + ctx = palloc0(sizeof *ctx); + + ctx->cipher_algo = def_cipher_algo; + ctx->s2k_cipher_algo = def_s2k_cipher_algo; + ctx->s2k_mode = def_s2k_mode; + ctx->s2k_count = def_s2k_count; + ctx->s2k_digest_algo = def_s2k_digest_algo; + ctx->compress_algo = def_compress_algo; + ctx->compress_level = def_compress_level; + ctx->disable_mdc = def_disable_mdc; + ctx->use_sess_key = def_use_sess_key; + ctx->unicode_mode = def_unicode_mode; + ctx->convert_crlf = def_convert_crlf; + ctx->text_mode = def_text_mode; + + *ctx_p = ctx; + return 0; +} + +int +pgp_free(PGP_Context *ctx) +{ + if (ctx->pub_key) + pgp_key_free(ctx->pub_key); + px_memset(ctx, 0, sizeof *ctx); + pfree(ctx); + return 0; +} + +int +pgp_disable_mdc(PGP_Context *ctx, int disable) +{ + ctx->disable_mdc = disable ? 1 : 0; + return 0; +} + +int +pgp_set_sess_key(PGP_Context *ctx, int use) +{ + ctx->use_sess_key = use ? 1 : 0; + return 0; +} + +int +pgp_set_convert_crlf(PGP_Context *ctx, int doit) +{ + ctx->convert_crlf = doit ? 1 : 0; + return 0; +} + +int +pgp_set_s2k_mode(PGP_Context *ctx, int mode) +{ + int err = PXE_OK; + + switch (mode) + { + case PGP_S2K_SIMPLE: + case PGP_S2K_SALTED: + case PGP_S2K_ISALTED: + ctx->s2k_mode = mode; + break; + default: + err = PXE_ARGUMENT_ERROR; + break; + } + return err; +} + +int +pgp_set_s2k_count(PGP_Context *ctx, int count) +{ + if (ctx->s2k_mode == PGP_S2K_ISALTED && count >= 1024 && count <= 65011712) + { + ctx->s2k_count = count; + return PXE_OK; + } + return PXE_ARGUMENT_ERROR; +} + +int +pgp_set_compress_algo(PGP_Context *ctx, int algo) +{ + switch (algo) + { + case PGP_COMPR_NONE: + case PGP_COMPR_ZIP: + case PGP_COMPR_ZLIB: + case PGP_COMPR_BZIP2: + ctx->compress_algo = algo; + return 0; + } + return PXE_ARGUMENT_ERROR; +} + +int +pgp_set_compress_level(PGP_Context *ctx, int level) +{ + if (level >= 0 && level <= 9) + { + ctx->compress_level = level; + return 0; + } + return PXE_ARGUMENT_ERROR; +} + +int +pgp_set_text_mode(PGP_Context *ctx, int mode) +{ + ctx->text_mode = mode; + return 0; +} + +int +pgp_set_cipher_algo(PGP_Context *ctx, const char *name) +{ + int code = pgp_get_cipher_code(name); + + if (code < 0) + return code; + ctx->cipher_algo = code; + return 0; +} + +int +pgp_set_s2k_cipher_algo(PGP_Context *ctx, const char *name) +{ + int code = pgp_get_cipher_code(name); + + if (code < 0) + return code; + ctx->s2k_cipher_algo = code; + return 0; +} + +int +pgp_set_s2k_digest_algo(PGP_Context *ctx, const char *name) +{ + int code = pgp_get_digest_code(name); + + if (code < 0) + return code; + ctx->s2k_digest_algo = code; + return 0; +} + +int +pgp_get_unicode_mode(PGP_Context *ctx) +{ + return ctx->unicode_mode; +} + +int +pgp_set_unicode_mode(PGP_Context *ctx, int mode) +{ + ctx->unicode_mode = mode ? 1 : 0; + return 0; +} + +int +pgp_set_symkey(PGP_Context *ctx, const uint8 *key, int len) +{ + if (key == NULL || len < 1) + return PXE_ARGUMENT_ERROR; + ctx->sym_key = key; + ctx->sym_key_len = len; + return 0; +} diff --git a/contrib/pgcrypto/pgp.h b/contrib/pgcrypto/pgp.h new file mode 100644 index 0000000..cb8b32a --- /dev/null +++ b/contrib/pgcrypto/pgp.h @@ -0,0 +1,326 @@ +/* + * pgp.h + * OpenPGP implementation. + * + * Copyright (c) 2005 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/pgp.h + */ + +#include "lib/stringinfo.h" + +#include "mbuf.h" +#include "px.h" + +enum PGP_S2K_TYPE +{ + PGP_S2K_SIMPLE = 0, + PGP_S2K_SALTED = 1, + PGP_S2K_ISALTED = 3 +}; + +enum PGP_PKT_TYPE +{ + PGP_PKT_RESERVED = 0, + PGP_PKT_PUBENCRYPTED_SESSKEY = 1, + PGP_PKT_SIGNATURE = 2, + PGP_PKT_SYMENCRYPTED_SESSKEY = 3, + PGP_PKT_SECRET_KEY = 5, + PGP_PKT_PUBLIC_KEY = 6, + PGP_PKT_SECRET_SUBKEY = 7, + PGP_PKT_COMPRESSED_DATA = 8, + PGP_PKT_SYMENCRYPTED_DATA = 9, + PGP_PKT_MARKER = 10, + PGP_PKT_LITERAL_DATA = 11, + PGP_PKT_TRUST = 12, + PGP_PKT_USER_ID = 13, + PGP_PKT_PUBLIC_SUBKEY = 14, + PGP_PKT_USER_ATTR = 17, + PGP_PKT_SYMENCRYPTED_DATA_MDC = 18, + PGP_PKT_MDC = 19, + PGP_PKT_PRIV_61 = 61 /* occurs in gpg secring */ +}; + +enum PGP_PUB_ALGO_TYPE +{ + PGP_PUB_RSA_ENCRYPT_SIGN = 1, + PGP_PUB_RSA_ENCRYPT = 2, + PGP_PUB_RSA_SIGN = 3, + PGP_PUB_ELG_ENCRYPT = 16, + PGP_PUB_DSA_SIGN = 17 +}; + +enum PGP_SYMENC_TYPE +{ + PGP_SYM_PLAIN = 0, /* ?? */ + PGP_SYM_IDEA = 1, /* obsolete, PGP 2.6 compat */ + PGP_SYM_DES3 = 2, /* must */ + PGP_SYM_CAST5 = 3, /* should */ + PGP_SYM_BLOWFISH = 4, + PGP_SYM_SAFER_SK128 = 5, /* obsolete */ + PGP_SYM_DES_SK = 6, /* obsolete */ + PGP_SYM_AES_128 = 7, /* should */ + PGP_SYM_AES_192 = 8, + PGP_SYM_AES_256 = 9, + PGP_SYM_TWOFISH = 10 +}; + +enum PGP_COMPR_TYPE +{ + PGP_COMPR_NONE = 0, /* must */ + PGP_COMPR_ZIP = 1, /* should */ + PGP_COMPR_ZLIB = 2, + PGP_COMPR_BZIP2 = 3 +}; + +enum PGP_DIGEST_TYPE +{ + PGP_DIGEST_MD5 = 1, /* should, deprecated */ + PGP_DIGEST_SHA1 = 2, /* must */ + PGP_DIGEST_RIPEMD160 = 3, + PGP_DIGEST_XSHA = 4, /* obsolete */ + PGP_DIGEST_MD2 = 5, /* obsolete */ + PGP_DIGEST_TIGER192 = 6, /* obsolete */ + PGP_DIGEST_HAVAL5_160 = 7, /* obsolete */ + PGP_DIGEST_SHA256 = 8, + PGP_DIGEST_SHA384 = 9, + PGP_DIGEST_SHA512 = 10 +}; + +#define PGP_MAX_KEY (256/8) +#define PGP_MAX_BLOCK (256/8) +#define PGP_MAX_DIGEST (512/8) +#define PGP_S2K_SALT 8 + +typedef struct PGP_MPI PGP_MPI; +typedef struct PGP_PubKey PGP_PubKey; +typedef struct PGP_Context PGP_Context; +typedef struct PGP_S2K PGP_S2K; + +struct PGP_S2K +{ + uint8 mode; + uint8 digest_algo; + uint8 salt[8]; + uint8 iter; /* encoded (one-octet) count */ + /* calculated: */ + uint8 key[PGP_MAX_KEY]; + uint8 key_len; +}; + + +struct PGP_Context +{ + /* + * parameters + */ + PGP_S2K s2k; + int s2k_mode; + int s2k_count; /* 4-byte decoded count */ + int s2k_digest_algo; + int s2k_cipher_algo; + int cipher_algo; + int compress_algo; + int compress_level; + int disable_mdc; + int use_sess_key; + int text_mode; + int convert_crlf; + int unicode_mode; + + /* + * internal variables + */ + int mdc_checked; + int corrupt_prefix; /* prefix failed RFC 4880 "quick check" */ + int unsupported_compr; /* has bzip2 compression */ + int unexpected_binary; /* binary data seen in text_mode */ + int in_mdc_pkt; + int use_mdcbuf_filter; + PX_MD *mdc_ctx; + + PGP_PubKey *pub_key; /* ctx owns it */ + const uint8 *sym_key; /* ctx does not own it */ + int sym_key_len; + + /* + * read or generated data + */ + uint8 sess_key[PGP_MAX_KEY]; + unsigned sess_key_len; +}; + +/* from RFC 4880 3.7.1.3 */ +#define s2k_decode_count(cval) \ + (((unsigned) 16 + (cval & 15)) << ((cval >> 4) + 6)) + +struct PGP_MPI +{ + uint8 *data; + int bits; + int bytes; +}; + +struct PGP_PubKey +{ + uint8 ver; + uint8 time[4]; + uint8 algo; + + /* public part */ + union + { + struct + { + PGP_MPI *p; + PGP_MPI *g; + PGP_MPI *y; + } elg; + struct + { + PGP_MPI *n; + PGP_MPI *e; + } rsa; + struct + { + PGP_MPI *p; + PGP_MPI *q; + PGP_MPI *g; + PGP_MPI *y; + } dsa; + } pub; + + /* secret part */ + union + { + struct + { + PGP_MPI *x; + } elg; + struct + { + PGP_MPI *d; + PGP_MPI *p; + PGP_MPI *q; + PGP_MPI *u; + } rsa; + struct + { + PGP_MPI *x; + } dsa; + } sec; + + uint8 key_id[8]; + int can_encrypt; +}; + +int pgp_init(PGP_Context **ctx_p); +int pgp_encrypt(PGP_Context *ctx, MBuf *src, MBuf *dst); +int pgp_decrypt(PGP_Context *ctx, MBuf *msrc, MBuf *mdst); +int pgp_free(PGP_Context *ctx); + +int pgp_get_digest_code(const char *name); +int pgp_get_cipher_code(const char *name); +const char *pgp_get_digest_name(int code); + +int pgp_set_cipher_algo(PGP_Context *ctx, const char *name); +int pgp_set_s2k_mode(PGP_Context *ctx, int mode); +int pgp_set_s2k_count(PGP_Context *ctx, int count); +int pgp_set_s2k_cipher_algo(PGP_Context *ctx, const char *name); +int pgp_set_s2k_digest_algo(PGP_Context *ctx, const char *name); +int pgp_set_convert_crlf(PGP_Context *ctx, int doit); +int pgp_disable_mdc(PGP_Context *ctx, int disable); +int pgp_set_sess_key(PGP_Context *ctx, int use); +int pgp_set_compress_algo(PGP_Context *ctx, int algo); +int pgp_set_compress_level(PGP_Context *ctx, int level); +int pgp_set_text_mode(PGP_Context *ctx, int mode); +int pgp_set_unicode_mode(PGP_Context *ctx, int mode); +int pgp_get_unicode_mode(PGP_Context *ctx); + +int pgp_set_symkey(PGP_Context *ctx, const uint8 *key, int len); +int pgp_set_pubkey(PGP_Context *ctx, MBuf *keypkt, + const uint8 *key, int key_len, int pubtype); + +int pgp_get_keyid(MBuf *pgp_data, char *dst); + +/* internal functions */ + +int pgp_load_digest(int code, PX_MD **res); +int pgp_load_cipher(int code, PX_Cipher **res); +int pgp_get_cipher_key_size(int code); +int pgp_get_cipher_block_size(int code); + +int pgp_s2k_fill(PGP_S2K *s2k, int mode, int digest_algo, int count); +int pgp_s2k_read(PullFilter *src, PGP_S2K *s2k); +int pgp_s2k_process(PGP_S2K *s2k, int cipher, const uint8 *key, int key_len); + +typedef struct PGP_CFB PGP_CFB; +int pgp_cfb_create(PGP_CFB **ctx_p, int algo, + const uint8 *key, int key_len, int resync, uint8 *iv); +void pgp_cfb_free(PGP_CFB *ctx); +int pgp_cfb_encrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst); +int pgp_cfb_decrypt(PGP_CFB *ctx, const uint8 *data, int len, uint8 *dst); + +void pgp_armor_encode(const uint8 *src, unsigned len, StringInfo dst, + int num_headers, char **keys, char **values); +int pgp_armor_decode(const uint8 *src, int len, StringInfo dst); +int pgp_extract_armor_headers(const uint8 *src, unsigned len, + int *nheaders, char ***keys, char ***values); + +int pgp_compress_filter(PushFilter **res, PGP_Context *ctx, PushFilter *dst); +int pgp_decompress_filter(PullFilter **res, PGP_Context *ctx, PullFilter *src); + +int pgp_key_alloc(PGP_PubKey **pk_p); +void pgp_key_free(PGP_PubKey *pk); +int _pgp_read_public_key(PullFilter *pkt, PGP_PubKey **pk_p); + +int pgp_parse_pubenc_sesskey(PGP_Context *ctx, PullFilter *pkt); +int pgp_create_pkt_reader(PullFilter **pf_p, PullFilter *src, int len, + int pkttype, PGP_Context *ctx); +int pgp_parse_pkt_hdr(PullFilter *src, uint8 *tag, int *len_p, + int allow_ctx); + +int pgp_skip_packet(PullFilter *pkt); +int pgp_expect_packet_end(PullFilter *pkt); + +int pgp_write_pubenc_sesskey(PGP_Context *ctx, PushFilter *dst); +int pgp_create_pkt_writer(PushFilter *dst, int tag, PushFilter **res_p); + +int pgp_mpi_alloc(int bits, PGP_MPI **mpi); +int pgp_mpi_create(uint8 *data, int bits, PGP_MPI **mpi); +int pgp_mpi_free(PGP_MPI *mpi); +int pgp_mpi_read(PullFilter *src, PGP_MPI **mpi); +int pgp_mpi_write(PushFilter *dst, PGP_MPI *n); +int pgp_mpi_hash(PX_MD *md, PGP_MPI *n); +unsigned pgp_mpi_cksum(unsigned cksum, PGP_MPI *n); + +int pgp_elgamal_encrypt(PGP_PubKey *pk, PGP_MPI *_m, + PGP_MPI **c1_p, PGP_MPI **c2_p); +int pgp_elgamal_decrypt(PGP_PubKey *pk, PGP_MPI *_c1, PGP_MPI *_c2, + PGP_MPI **msg_p); +int pgp_rsa_encrypt(PGP_PubKey *pk, PGP_MPI *_m, PGP_MPI **c_p); +int pgp_rsa_decrypt(PGP_PubKey *pk, PGP_MPI *_c, PGP_MPI **m_p); + +extern struct PullFilterOps pgp_decrypt_filter; diff --git a/contrib/pgcrypto/px-crypt.c b/contrib/pgcrypto/px-crypt.c new file mode 100644 index 0000000..0913ff2 --- /dev/null +++ b/contrib/pgcrypto/px-crypt.c @@ -0,0 +1,164 @@ +/* + * px-crypt.c + * Wrapper for various crypt algorithms. + * + * Copyright (c) 2001 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/px-crypt.c + */ + +#include "postgres.h" + +#include "px-crypt.h" +#include "px.h" + +static char * +run_crypt_des(const char *psw, const char *salt, + char *buf, unsigned len) +{ + char *res; + + res = px_crypt_des(psw, salt); + if (res == NULL || strlen(res) > len - 1) + return NULL; + strcpy(buf, res); + return buf; +} + +static char * +run_crypt_md5(const char *psw, const char *salt, + char *buf, unsigned len) +{ + char *res; + + res = px_crypt_md5(psw, salt, buf, len); + return res; +} + +static char * +run_crypt_bf(const char *psw, const char *salt, + char *buf, unsigned len) +{ + char *res; + + res = _crypt_blowfish_rn(psw, salt, buf, len); + return res; +} + +struct px_crypt_algo +{ + char *id; + unsigned id_len; + char *(*crypt) (const char *psw, const char *salt, + char *buf, unsigned len); +}; + +static const struct px_crypt_algo + px_crypt_list[] = { + {"$2a$", 4, run_crypt_bf}, + {"$2x$", 4, run_crypt_bf}, + {"$2$", 3, NULL}, /* N/A */ + {"$1$", 3, run_crypt_md5}, + {"_", 1, run_crypt_des}, + {"", 0, run_crypt_des}, + {NULL, 0, NULL} +}; + +char * +px_crypt(const char *psw, const char *salt, char *buf, unsigned len) +{ + const struct px_crypt_algo *c; + + for (c = px_crypt_list; c->id; c++) + { + if (!c->id_len) + break; + if (strncmp(salt, c->id, c->id_len) == 0) + break; + } + + if (c->crypt == NULL) + return NULL; + + return c->crypt(psw, salt, buf, len); +} + +/* + * salt generators + */ + +struct generator +{ + char *name; + char *(*gen) (unsigned long count, const char *input, int size, + char *output, int output_size); + int input_len; + int def_rounds; + int min_rounds; + int max_rounds; +}; + +static struct generator gen_list[] = { + {"des", _crypt_gensalt_traditional_rn, 2, 0, 0, 0}, + {"md5", _crypt_gensalt_md5_rn, 6, 0, 0, 0}, + {"xdes", _crypt_gensalt_extended_rn, 3, PX_XDES_ROUNDS, 1, 0xFFFFFF}, + {"bf", _crypt_gensalt_blowfish_rn, 16, PX_BF_ROUNDS, 4, 31}, + {NULL, NULL, 0, 0, 0, 0} +}; + +int +px_gen_salt(const char *salt_type, char *buf, int rounds) +{ + struct generator *g; + char *p; + char rbuf[16]; + + for (g = gen_list; g->name; g++) + if (pg_strcasecmp(g->name, salt_type) == 0) + break; + + if (g->name == NULL) + return PXE_UNKNOWN_SALT_ALGO; + + if (g->def_rounds) + { + if (rounds == 0) + rounds = g->def_rounds; + + if (rounds < g->min_rounds || rounds > g->max_rounds) + return PXE_BAD_SALT_ROUNDS; + } + + if (!pg_strong_random(rbuf, g->input_len)) + return PXE_NO_RANDOM; + + p = g->gen(rounds, rbuf, g->input_len, buf, PX_MAX_SALT_LEN); + px_memset(rbuf, 0, sizeof(rbuf)); + + if (p == NULL) + return PXE_BAD_SALT_ROUNDS; + + return strlen(p); +} diff --git a/contrib/pgcrypto/px-crypt.h b/contrib/pgcrypto/px-crypt.h new file mode 100644 index 0000000..54de806 --- /dev/null +++ b/contrib/pgcrypto/px-crypt.h @@ -0,0 +1,82 @@ +/* + * px-crypt.h + * Header file for px_crypt(). + * + * Copyright (c) 2001 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/px-crypt.h + */ + +#ifndef _PX_CRYPT_H +#define _PX_CRYPT_H + +/* max room for result */ +#define PX_MAX_CRYPT 128 + +/* max salt returned by gen_salt() */ +#define PX_MAX_SALT_LEN 128 + +/* default rounds for xdes salt */ +/* NetBSD bin/passwd/local_passwd.c has (29 * 25)*/ +#define PX_XDES_ROUNDS (29 * 25) + +/* default for blowfish salt */ +#define PX_BF_ROUNDS 6 + +/* + * main interface + */ +char *px_crypt(const char *psw, const char *salt, char *buf, unsigned len); +int px_gen_salt(const char *salt_type, char *buf, int rounds); + +/* + * internal functions + */ + +/* crypt-gensalt.c */ +char *_crypt_gensalt_traditional_rn(unsigned long count, + const char *input, int size, char *output, int output_size); +char *_crypt_gensalt_extended_rn(unsigned long count, + const char *input, int size, char *output, int output_size); +char *_crypt_gensalt_md5_rn(unsigned long count, + const char *input, int size, char *output, int output_size); +char *_crypt_gensalt_blowfish_rn(unsigned long count, + const char *input, int size, char *output, int output_size); + +/* disable 'extended DES crypt' */ +/* #define DISABLE_XDES */ + +/* crypt-blowfish.c */ +char *_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size); + +/* crypt-des.c */ +char *px_crypt_des(const char *key, const char *setting); + +/* crypt-md5.c */ +char *px_crypt_md5(const char *pw, const char *salt, + char *passwd, unsigned dstlen); + +#endif /* _PX_CRYPT_H */ diff --git a/contrib/pgcrypto/px-hmac.c b/contrib/pgcrypto/px-hmac.c new file mode 100644 index 0000000..99174d2 --- /dev/null +++ b/contrib/pgcrypto/px-hmac.c @@ -0,0 +1,176 @@ +/* + * px-hmac.c + * HMAC implementation. + * + * Copyright (c) 2001 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/px-hmac.c + */ + +#include "postgres.h" + +#include "px.h" + +#define HMAC_IPAD 0x36 +#define HMAC_OPAD 0x5C + +static unsigned +hmac_result_size(PX_HMAC *h) +{ + return px_md_result_size(h->md); +} + +static unsigned +hmac_block_size(PX_HMAC *h) +{ + return px_md_block_size(h->md); +} + +static void +hmac_init(PX_HMAC *h, const uint8 *key, unsigned klen) +{ + unsigned bs, + i; + uint8 *keybuf; + PX_MD *md = h->md; + + bs = px_md_block_size(md); + keybuf = palloc0(bs); + + if (klen > bs) + { + px_md_update(md, key, klen); + px_md_finish(md, keybuf); + px_md_reset(md); + } + else + memcpy(keybuf, key, klen); + + for (i = 0; i < bs; i++) + { + h->p.ipad[i] = keybuf[i] ^ HMAC_IPAD; + h->p.opad[i] = keybuf[i] ^ HMAC_OPAD; + } + + px_memset(keybuf, 0, bs); + pfree(keybuf); + + px_md_update(md, h->p.ipad, bs); +} + +static void +hmac_reset(PX_HMAC *h) +{ + PX_MD *md = h->md; + unsigned bs = px_md_block_size(md); + + px_md_reset(md); + px_md_update(md, h->p.ipad, bs); +} + +static void +hmac_update(PX_HMAC *h, const uint8 *data, unsigned dlen) +{ + px_md_update(h->md, data, dlen); +} + +static void +hmac_finish(PX_HMAC *h, uint8 *dst) +{ + PX_MD *md = h->md; + unsigned bs, + hlen; + uint8 *buf; + + bs = px_md_block_size(md); + hlen = px_md_result_size(md); + + buf = palloc(hlen); + + px_md_finish(md, buf); + + px_md_reset(md); + px_md_update(md, h->p.opad, bs); + px_md_update(md, buf, hlen); + px_md_finish(md, dst); + + px_memset(buf, 0, hlen); + pfree(buf); +} + +static void +hmac_free(PX_HMAC *h) +{ + unsigned bs; + + bs = px_md_block_size(h->md); + px_md_free(h->md); + + px_memset(h->p.ipad, 0, bs); + px_memset(h->p.opad, 0, bs); + pfree(h->p.ipad); + pfree(h->p.opad); + pfree(h); +} + + +/* PUBLIC FUNCTIONS */ + +int +px_find_hmac(const char *name, PX_HMAC **res) +{ + int err; + PX_MD *md; + PX_HMAC *h; + unsigned bs; + + err = px_find_digest(name, &md); + if (err) + return err; + + bs = px_md_block_size(md); + if (bs < 2) + { + px_md_free(md); + return PXE_HASH_UNUSABLE_FOR_HMAC; + } + + h = palloc(sizeof(*h)); + h->p.ipad = palloc(bs); + h->p.opad = palloc(bs); + h->md = md; + + h->result_size = hmac_result_size; + h->block_size = hmac_block_size; + h->reset = hmac_reset; + h->update = hmac_update; + h->finish = hmac_finish; + h->free = hmac_free; + h->init = hmac_init; + + *res = h; + + return 0; +} diff --git a/contrib/pgcrypto/px.c b/contrib/pgcrypto/px.c new file mode 100644 index 0000000..d35ccca --- /dev/null +++ b/contrib/pgcrypto/px.c @@ -0,0 +1,340 @@ +/* + * px.c + * Various cryptographic stuff for PostgreSQL. + * + * Copyright (c) 2001 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/px.c + */ + +#include "postgres.h" + +#include "px.h" + +struct error_desc +{ + int err; + const char *desc; +}; + +static const struct error_desc px_err_list[] = { + {PXE_OK, "Everything ok"}, + {PXE_NO_HASH, "No such hash algorithm"}, + {PXE_NO_CIPHER, "No such cipher algorithm"}, + {PXE_BAD_OPTION, "Unknown option"}, + {PXE_BAD_FORMAT, "Badly formatted type"}, + {PXE_KEY_TOO_BIG, "Key was too big"}, + {PXE_CIPHER_INIT, "Cipher cannot be initialized"}, + {PXE_HASH_UNUSABLE_FOR_HMAC, "This hash algorithm is unusable for HMAC"}, + {PXE_BUG, "pgcrypto bug"}, + {PXE_ARGUMENT_ERROR, "Illegal argument to function"}, + {PXE_UNKNOWN_SALT_ALGO, "Unknown salt algorithm"}, + {PXE_BAD_SALT_ROUNDS, "Incorrect number of rounds"}, + {PXE_NO_RANDOM, "Failed to generate strong random bits"}, + {PXE_DECRYPT_FAILED, "Decryption failed"}, + {PXE_ENCRYPT_FAILED, "Encryption failed"}, + {PXE_PGP_CORRUPT_DATA, "Wrong key or corrupt data"}, + {PXE_PGP_CORRUPT_ARMOR, "Corrupt ascii-armor"}, + {PXE_PGP_UNSUPPORTED_COMPR, "Unsupported compression algorithm"}, + {PXE_PGP_UNSUPPORTED_CIPHER, "Unsupported cipher algorithm"}, + {PXE_PGP_UNSUPPORTED_HASH, "Unsupported digest algorithm"}, + {PXE_PGP_COMPRESSION_ERROR, "Compression error"}, + {PXE_PGP_NOT_TEXT, "Not text data"}, + {PXE_PGP_UNEXPECTED_PKT, "Unexpected packet in key data"}, + {PXE_PGP_MATH_FAILED, "Math operation failed"}, + {PXE_PGP_SHORT_ELGAMAL_KEY, "Elgamal keys must be at least 1024 bits long"}, + {PXE_PGP_UNKNOWN_PUBALGO, "Unknown public-key encryption algorithm"}, + {PXE_PGP_WRONG_KEY, "Wrong key"}, + {PXE_PGP_MULTIPLE_KEYS, + "Several keys given - pgcrypto does not handle keyring"}, + {PXE_PGP_EXPECT_PUBLIC_KEY, "Refusing to encrypt with secret key"}, + {PXE_PGP_EXPECT_SECRET_KEY, "Cannot decrypt with public key"}, + {PXE_PGP_NOT_V4_KEYPKT, "Only V4 key packets are supported"}, + {PXE_PGP_KEYPKT_CORRUPT, "Corrupt key packet"}, + {PXE_PGP_NO_USABLE_KEY, "No encryption key found"}, + {PXE_PGP_NEED_SECRET_PSW, "Need password for secret key"}, + {PXE_PGP_BAD_S2K_MODE, "Bad S2K mode"}, + {PXE_PGP_UNSUPPORTED_PUBALGO, "Unsupported public key algorithm"}, + {PXE_PGP_MULTIPLE_SUBKEYS, "Several subkeys not supported"}, + + {0, NULL}, +}; + +/* + * Call ereport(ERROR, ...), with an error code and message corresponding to + * the PXE_* error code given as argument. + * + * This is similar to px_strerror(err), but for some errors, we fill in the + * error code and detail fields more appropriately. + */ +void +px_THROW_ERROR(int err) +{ + if (err == PXE_NO_RANDOM) + { + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("could not generate a random number"))); + } + else + { + /* For other errors, use the message from the above list. */ + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), + errmsg("%s", px_strerror(err)))); + } +} + +const char * +px_strerror(int err) +{ + const struct error_desc *e; + + for (e = px_err_list; e->desc; e++) + if (e->err == err) + return e->desc; + return "Bad error code"; +} + +/* memset that must not be optimized away */ +void +px_memset(void *ptr, int c, size_t len) +{ + memset(ptr, c, len); +} + +const char * +px_resolve_alias(const PX_Alias *list, const char *name) +{ + while (list->name) + { + if (pg_strcasecmp(list->alias, name) == 0) + return list->name; + list++; + } + return name; +} + +static void (*debug_handler) (const char *) = NULL; + +void +px_set_debug_handler(void (*handler) (const char *)) +{ + debug_handler = handler; +} + +void +px_debug(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + if (debug_handler) + { + char buf[512]; + + vsnprintf(buf, sizeof(buf), fmt, ap); + debug_handler(buf); + } + va_end(ap); +} + +/* + * combo - cipher + padding (+ checksum) + */ + +static unsigned +combo_encrypt_len(PX_Combo *cx, unsigned dlen) +{ + return dlen + 512; +} + +static unsigned +combo_decrypt_len(PX_Combo *cx, unsigned dlen) +{ + return dlen; +} + +static int +combo_init(PX_Combo *cx, const uint8 *key, unsigned klen, + const uint8 *iv, unsigned ivlen) +{ + int err; + unsigned ks, + ivs; + PX_Cipher *c = cx->cipher; + uint8 *ivbuf = NULL; + uint8 *keybuf; + + ks = px_cipher_key_size(c); + + ivs = px_cipher_iv_size(c); + if (ivs > 0) + { + ivbuf = palloc0(ivs); + if (ivlen > ivs) + memcpy(ivbuf, iv, ivs); + else if (ivlen > 0) + memcpy(ivbuf, iv, ivlen); + } + + if (klen > ks) + klen = ks; + keybuf = palloc0(ks); + memcpy(keybuf, key, klen); + + err = px_cipher_init(c, keybuf, klen, ivbuf); + + if (ivbuf) + pfree(ivbuf); + pfree(keybuf); + + return err; +} + +static int +combo_encrypt(PX_Combo *cx, const uint8 *data, unsigned dlen, + uint8 *res, unsigned *rlen) +{ + return px_cipher_encrypt(cx->cipher, cx->padding, data, dlen, res, rlen); +} + +static int +combo_decrypt(PX_Combo *cx, const uint8 *data, unsigned dlen, + uint8 *res, unsigned *rlen) +{ + return px_cipher_decrypt(cx->cipher, cx->padding, data, dlen, res, rlen); +} + +static void +combo_free(PX_Combo *cx) +{ + if (cx->cipher) + px_cipher_free(cx->cipher); + px_memset(cx, 0, sizeof(*cx)); + pfree(cx); +} + +/* PARSER */ + +static int +parse_cipher_name(char *full, char **cipher, char **pad) +{ + char *p, + *p2, + *q; + + *cipher = full; + *pad = NULL; + + p = strchr(full, '/'); + if (p != NULL) + *p++ = 0; + while (p != NULL) + { + if ((q = strchr(p, '/')) != NULL) + *q++ = 0; + + if (!*p) + { + p = q; + continue; + } + p2 = strchr(p, ':'); + if (p2 != NULL) + { + *p2++ = 0; + if (strcmp(p, "pad") == 0) + *pad = p2; + else + return PXE_BAD_OPTION; + } + else + return PXE_BAD_FORMAT; + + p = q; + } + return 0; +} + +/* provider */ + +int +px_find_combo(const char *name, PX_Combo **res) +{ + int err; + char *buf, + *s_cipher, + *s_pad; + + PX_Combo *cx; + + cx = palloc0(sizeof(*cx)); + buf = pstrdup(name); + + err = parse_cipher_name(buf, &s_cipher, &s_pad); + if (err) + { + pfree(buf); + pfree(cx); + return err; + } + + err = px_find_cipher(s_cipher, &cx->cipher); + if (err) + goto err1; + + if (s_pad != NULL) + { + if (strcmp(s_pad, "pkcs") == 0) + cx->padding = 1; + else if (strcmp(s_pad, "none") == 0) + cx->padding = 0; + else + goto err1; + } + else + cx->padding = 1; + + cx->init = combo_init; + cx->encrypt = combo_encrypt; + cx->decrypt = combo_decrypt; + cx->encrypt_len = combo_encrypt_len; + cx->decrypt_len = combo_decrypt_len; + cx->free = combo_free; + + pfree(buf); + + *res = cx; + + return 0; + +err1: + if (cx->cipher) + px_cipher_free(cx->cipher); + pfree(cx); + pfree(buf); + return PXE_NO_CIPHER; +} diff --git a/contrib/pgcrypto/px.h b/contrib/pgcrypto/px.h new file mode 100644 index 0000000..471bb4e --- /dev/null +++ b/contrib/pgcrypto/px.h @@ -0,0 +1,228 @@ +/* + * px.h + * Header file for pgcrypto. + * + * Copyright (c) 2001 Marko Kreen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * contrib/pgcrypto/px.h + */ + +#ifndef __PX_H +#define __PX_H + +#include + +/* keep debug messages? */ +#define PX_DEBUG + +/* max salt returned */ +#define PX_MAX_SALT_LEN 128 + +/* + * PX error codes + */ +#define PXE_OK 0 +/* -1 is unused */ +#define PXE_NO_HASH -2 +#define PXE_NO_CIPHER -3 +/* -4 is unused */ +#define PXE_BAD_OPTION -5 +#define PXE_BAD_FORMAT -6 +#define PXE_KEY_TOO_BIG -7 +#define PXE_CIPHER_INIT -8 +#define PXE_HASH_UNUSABLE_FOR_HMAC -9 +/* -10 is unused */ +/* -11 is unused */ +#define PXE_BUG -12 +#define PXE_ARGUMENT_ERROR -13 +#define PXE_UNKNOWN_SALT_ALGO -14 +#define PXE_BAD_SALT_ROUNDS -15 +/* -16 is unused */ +#define PXE_NO_RANDOM -17 +#define PXE_DECRYPT_FAILED -18 +#define PXE_ENCRYPT_FAILED -19 + +#define PXE_PGP_CORRUPT_DATA -100 +#define PXE_PGP_CORRUPT_ARMOR -101 +#define PXE_PGP_UNSUPPORTED_COMPR -102 +#define PXE_PGP_UNSUPPORTED_CIPHER -103 +#define PXE_PGP_UNSUPPORTED_HASH -104 +#define PXE_PGP_COMPRESSION_ERROR -105 +#define PXE_PGP_NOT_TEXT -106 +#define PXE_PGP_UNEXPECTED_PKT -107 +/* -108 is unused */ +#define PXE_PGP_MATH_FAILED -109 +#define PXE_PGP_SHORT_ELGAMAL_KEY -110 +/* -111 is unused */ +#define PXE_PGP_UNKNOWN_PUBALGO -112 +#define PXE_PGP_WRONG_KEY -113 +#define PXE_PGP_MULTIPLE_KEYS -114 +#define PXE_PGP_EXPECT_PUBLIC_KEY -115 +#define PXE_PGP_EXPECT_SECRET_KEY -116 +#define PXE_PGP_NOT_V4_KEYPKT -117 +#define PXE_PGP_KEYPKT_CORRUPT -118 +#define PXE_PGP_NO_USABLE_KEY -119 +#define PXE_PGP_NEED_SECRET_PSW -120 +#define PXE_PGP_BAD_S2K_MODE -121 +#define PXE_PGP_UNSUPPORTED_PUBALGO -122 +#define PXE_PGP_MULTIPLE_SUBKEYS -123 + + +typedef struct px_digest PX_MD; +typedef struct px_alias PX_Alias; +typedef struct px_hmac PX_HMAC; +typedef struct px_cipher PX_Cipher; +typedef struct px_combo PX_Combo; + +struct px_digest +{ + unsigned (*result_size) (PX_MD *h); + unsigned (*block_size) (PX_MD *h); + void (*reset) (PX_MD *h); + void (*update) (PX_MD *h, const uint8 *data, unsigned dlen); + void (*finish) (PX_MD *h, uint8 *dst); + void (*free) (PX_MD *h); + /* private */ + union + { + unsigned code; + void *ptr; + } p; +}; + +struct px_alias +{ + char *alias; + char *name; +}; + +struct px_hmac +{ + unsigned (*result_size) (PX_HMAC *h); + unsigned (*block_size) (PX_HMAC *h); + void (*reset) (PX_HMAC *h); + void (*update) (PX_HMAC *h, const uint8 *data, unsigned dlen); + void (*finish) (PX_HMAC *h, uint8 *dst); + void (*free) (PX_HMAC *h); + void (*init) (PX_HMAC *h, const uint8 *key, unsigned klen); + + PX_MD *md; + /* private */ + struct + { + uint8 *ipad; + uint8 *opad; + } p; +}; + +struct px_cipher +{ + unsigned (*block_size) (PX_Cipher *c); + unsigned (*key_size) (PX_Cipher *c); /* max key len */ + unsigned (*iv_size) (PX_Cipher *c); + + int (*init) (PX_Cipher *c, const uint8 *key, unsigned klen, const uint8 *iv); + int (*encrypt) (PX_Cipher *c, int padding, const uint8 *data, unsigned dlen, uint8 *res, unsigned *rlen); + int (*decrypt) (PX_Cipher *c, int padding, const uint8 *data, unsigned dlen, uint8 *res, unsigned *rlen); + void (*free) (PX_Cipher *c); + /* private */ + void *ptr; + int pstat; /* mcrypt uses it */ +}; + +struct px_combo +{ + int (*init) (PX_Combo *cx, const uint8 *key, unsigned klen, + const uint8 *iv, unsigned ivlen); + int (*encrypt) (PX_Combo *cx, const uint8 *data, unsigned dlen, + uint8 *res, unsigned *rlen); + int (*decrypt) (PX_Combo *cx, const uint8 *data, unsigned dlen, + uint8 *res, unsigned *rlen); + unsigned (*encrypt_len) (PX_Combo *cx, unsigned dlen); + unsigned (*decrypt_len) (PX_Combo *cx, unsigned dlen); + void (*free) (PX_Combo *cx); + + PX_Cipher *cipher; + unsigned padding; +}; + +int px_find_digest(const char *name, PX_MD **res); +int px_find_hmac(const char *name, PX_HMAC **res); +int px_find_cipher(const char *name, PX_Cipher **res); +int px_find_combo(const char *name, PX_Combo **res); + +void px_THROW_ERROR(int err) pg_attribute_noreturn(); +const char *px_strerror(int err); + +const char *px_resolve_alias(const PX_Alias *list, const char *name); + +void px_set_debug_handler(void (*handler) (const char *)); + +void px_memset(void *ptr, int c, size_t len); + +#ifdef PX_DEBUG +void px_debug(const char *fmt,...) pg_attribute_printf(1, 2); +#else +#define px_debug(...) +#endif + +#define px_md_result_size(md) (md)->result_size(md) +#define px_md_block_size(md) (md)->block_size(md) +#define px_md_reset(md) (md)->reset(md) +#define px_md_update(md, data, dlen) (md)->update(md, data, dlen) +#define px_md_finish(md, buf) (md)->finish(md, buf) +#define px_md_free(md) (md)->free(md) + +#define px_hmac_result_size(hmac) (hmac)->result_size(hmac) +#define px_hmac_block_size(hmac) (hmac)->block_size(hmac) +#define px_hmac_reset(hmac) (hmac)->reset(hmac) +#define px_hmac_init(hmac, key, klen) (hmac)->init(hmac, key, klen) +#define px_hmac_update(hmac, data, dlen) (hmac)->update(hmac, data, dlen) +#define px_hmac_finish(hmac, buf) (hmac)->finish(hmac, buf) +#define px_hmac_free(hmac) (hmac)->free(hmac) + + +#define px_cipher_key_size(c) (c)->key_size(c) +#define px_cipher_block_size(c) (c)->block_size(c) +#define px_cipher_iv_size(c) (c)->iv_size(c) +#define px_cipher_init(c, k, klen, iv) (c)->init(c, k, klen, iv) +#define px_cipher_encrypt(c, padding, data, dlen, res, rlen) \ + (c)->encrypt(c, padding, data, dlen, res, rlen) +#define px_cipher_decrypt(c, padding, data, dlen, res, rlen) \ + (c)->decrypt(c, padding, data, dlen, res, rlen) +#define px_cipher_free(c) (c)->free(c) + + +#define px_combo_encrypt_len(c, dlen) (c)->encrypt_len(c, dlen) +#define px_combo_decrypt_len(c, dlen) (c)->decrypt_len(c, dlen) +#define px_combo_init(c, key, klen, iv, ivlen) \ + (c)->init(c, key, klen, iv, ivlen) +#define px_combo_encrypt(c, data, dlen, res, rlen) \ + (c)->encrypt(c, data, dlen, res, rlen) +#define px_combo_decrypt(c, data, dlen, res, rlen) \ + (c)->decrypt(c, data, dlen, res, rlen) +#define px_combo_free(c) (c)->free(c) + +#endif /* __PX_H */ diff --git a/contrib/pgcrypto/sql/3des.sql b/contrib/pgcrypto/sql/3des.sql new file mode 100644 index 0000000..1b71a10 --- /dev/null +++ b/contrib/pgcrypto/sql/3des.sql @@ -0,0 +1,25 @@ +-- +-- 3DES cipher +-- + +-- test vector from somewhere +SELECT encrypt('\x8000000000000000', + '\x010101010101010101010101010101010101010101010101', + '3des-ecb/pad:none'); + +select encrypt('', 'foo', '3des'); +-- 10 bytes key +select encrypt('foo', '0123456789', '3des'); +-- 22 bytes key +select encrypt('foo', '0123456789012345678901', '3des'); + +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', '3des'), '0123456', '3des'), 'escape'); + +-- iv +select encrypt_iv('foo', '0123456', 'abcd', '3des'); +select encode(decrypt_iv('\x50735067b073bb93', '0123456', 'abcd', '3des'), 'escape'); + +-- long message +select encrypt('Lets try a longer message.', '0123456789012345678901', '3des'); +select encode(decrypt(encrypt('Lets try a longer message.', '0123456789012345678901', '3des'), '0123456789012345678901', '3des'), 'escape'); diff --git a/contrib/pgcrypto/sql/blowfish.sql b/contrib/pgcrypto/sql/blowfish.sql new file mode 100644 index 0000000..c212cf2 --- /dev/null +++ b/contrib/pgcrypto/sql/blowfish.sql @@ -0,0 +1,53 @@ +-- +-- Blowfish cipher +-- + +-- some standard Blowfish testvalues +SELECT encrypt('\x0000000000000000', '\x0000000000000000', 'bf-ecb/pad:none'); +SELECT encrypt('\xffffffffffffffff', '\xffffffffffffffff', 'bf-ecb/pad:none'); +SELECT encrypt('\x1000000000000001', '\x3000000000000000', 'bf-ecb/pad:none'); +SELECT encrypt('\x1111111111111111', '\x1111111111111111', 'bf-ecb/pad:none'); +SELECT encrypt('\x0123456789abcdef', '\xfedcba9876543210', 'bf-ecb/pad:none'); +SELECT encrypt('\x01a1d6d039776742', '\xfedcba9876543210', 'bf-ecb/pad:none'); +SELECT encrypt('\xffffffffffffffff', '\x0000000000000000', 'bf-ecb/pad:none'); + +-- setkey +SELECT encrypt('\xfedcba9876543210', '\xf0e1d2c3b4a5968778695a4b3c2d1e0f', 'bf-ecb/pad:none'); + +-- with padding +SELECT encrypt('\x01234567890123456789', '\x33443344334433443344334433443344', 'bf-ecb'); + +-- cbc + +-- 28 bytes key +SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5', + '\x37363534333231204e6f77206973207468652074696d6520666f7220', + 'bf-cbc'); + +-- 29 bytes key +SELECT encrypt('\x6b77b4d63006dee605b156e27403979358deb9e7154616d959f1652bd5ff92cc', + '\x37363534333231204e6f77206973207468652074696d6520666f722000', + 'bf-cbc'); + +-- blowfish-448 +SELECT encrypt('\xfedcba9876543210', + '\xf0e1d2c3b4a5968778695a4b3c2d1e0f001122334455667704689104c2fd3b2f584023641aba61761f1f1f1f0e0e0e0effffffffffffffff', + 'bf-ecb/pad:none'); + +-- empty data +select encrypt('', 'foo', 'bf'); +-- 10 bytes key +select encrypt('foo', '0123456789', 'bf'); +-- 22 bytes key +select encrypt('foo', '0123456789012345678901', 'bf'); + +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'bf'), '0123456', 'bf'), 'escape'); + +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'bf'); +select encode(decrypt_iv('\x95c7e89322525d59', '0123456', 'abcd', 'bf'), 'escape'); + +-- long message +select encrypt('Lets try a longer message.', '0123456789', 'bf'); +select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'bf'), '0123456789', 'bf'), 'escape'); diff --git a/contrib/pgcrypto/sql/cast5.sql b/contrib/pgcrypto/sql/cast5.sql new file mode 100644 index 0000000..b3089b2 --- /dev/null +++ b/contrib/pgcrypto/sql/cast5.sql @@ -0,0 +1,32 @@ +-- +-- Cast5 cipher +-- + +-- test vectors from RFC2144 + +-- 128 bit key +SELECT encrypt('\x0123456789ABCDEF', '\x0123456712345678234567893456789A', 'cast5-ecb/pad:none'); + +-- 80 bit key +SELECT encrypt('\x0123456789ABCDEF', '\x01234567123456782345', 'cast5-ecb/pad:none'); + +-- 40 bit key +SELECT encrypt('\x0123456789ABCDEF', '\x0123456712', 'cast5-ecb/pad:none'); + +-- cbc + +-- empty data +select encrypt('', 'foo', 'cast5'); +-- 10 bytes key +select encrypt('foo', '0123456789', 'cast5'); + +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'cast5'), '0123456', 'cast5'), 'escape'); + +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'cast5'); +select encode(decrypt_iv('\x384a970695ce016a', '0123456', 'abcd', 'cast5'), 'escape'); + +-- long message +select encrypt('Lets try a longer message.', '0123456789', 'cast5'); +select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'cast5'), '0123456789', 'cast5'), 'escape'); diff --git a/contrib/pgcrypto/sql/crypt-blowfish.sql b/contrib/pgcrypto/sql/crypt-blowfish.sql new file mode 100644 index 0000000..3b5a681 --- /dev/null +++ b/contrib/pgcrypto/sql/crypt-blowfish.sql @@ -0,0 +1,26 @@ +-- +-- crypt() and gen_salt(): bcrypt +-- + +SELECT crypt('', '$2a$06$RQiOJ.3ELirrXwxIZY8q0O'); + +SELECT crypt('foox', '$2a$06$RQiOJ.3ELirrXwxIZY8q0O'); + +-- error, salt too short: +SELECT crypt('foox', '$2a$'); + +-- error, first digit of count in salt invalid +SELECT crypt('foox', '$2a$40$RQiOJ.3ELirrXwxIZY8q0O'); + +-- error, count in salt too small +SELECT crypt('foox', '$2a$00$RQiOJ.3ELirrXwxIZY8q0O'); + +CREATE TABLE ctest (data text, res text, salt text); +INSERT INTO ctest VALUES ('password', '', ''); + +UPDATE ctest SET salt = gen_salt('bf', 8); +UPDATE ctest SET res = crypt(data, salt); +SELECT res = crypt(data, res) AS "worked" +FROM ctest; + +DROP TABLE ctest; diff --git a/contrib/pgcrypto/sql/crypt-des.sql b/contrib/pgcrypto/sql/crypt-des.sql new file mode 100644 index 0000000..a85ec1e --- /dev/null +++ b/contrib/pgcrypto/sql/crypt-des.sql @@ -0,0 +1,21 @@ +-- +-- crypt() and gen_salt(): crypt-des +-- + +SELECT crypt('', 'NB'); + +SELECT crypt('foox', 'NB'); + +-- We are supposed to pass in a 2-character salt. +-- error since salt is too short: +SELECT crypt('password', 'a'); + +CREATE TABLE ctest (data text, res text, salt text); +INSERT INTO ctest VALUES ('password', '', ''); + +UPDATE ctest SET salt = gen_salt('des'); +UPDATE ctest SET res = crypt(data, salt); +SELECT res = crypt(data, res) AS "worked" +FROM ctest; + +DROP TABLE ctest; diff --git a/contrib/pgcrypto/sql/crypt-md5.sql b/contrib/pgcrypto/sql/crypt-md5.sql new file mode 100644 index 0000000..ba7befb --- /dev/null +++ b/contrib/pgcrypto/sql/crypt-md5.sql @@ -0,0 +1,17 @@ +-- +-- crypt() and gen_salt(): md5 +-- + +SELECT crypt('', '$1$Szzz0yzz'); + +SELECT crypt('foox', '$1$Szzz0yzz'); + +CREATE TABLE ctest (data text, res text, salt text); +INSERT INTO ctest VALUES ('password', '', ''); + +UPDATE ctest SET salt = gen_salt('md5'); +UPDATE ctest SET res = crypt(data, salt); +SELECT res = crypt(data, res) AS "worked" +FROM ctest; + +DROP TABLE ctest; diff --git a/contrib/pgcrypto/sql/crypt-xdes.sql b/contrib/pgcrypto/sql/crypt-xdes.sql new file mode 100644 index 0000000..8171cd8 --- /dev/null +++ b/contrib/pgcrypto/sql/crypt-xdes.sql @@ -0,0 +1,33 @@ +-- +-- crypt() and gen_salt(): extended des +-- + +SELECT crypt('', '_J9..j2zz'); + +SELECT crypt('foox', '_J9..j2zz'); + +-- check XDES handling of keys longer than 8 chars +SELECT crypt('longlongpassword', '_J9..j2zz'); + +-- error, salt too short +SELECT crypt('foox', '_J9..BWH'); + +-- error, count specified in the second argument is 0 +SELECT crypt('password', '_........'); + +-- error, count will wind up still being 0 due to invalid encoding +-- of the count: only chars ``./0-9A-Za-z' are valid +SELECT crypt('password', '_..!!!!!!'); + +-- count should be non-zero here, will work +SELECT crypt('password', '_/!!!!!!!'); + +CREATE TABLE ctest (data text, res text, salt text); +INSERT INTO ctest VALUES ('password', '', ''); + +UPDATE ctest SET salt = gen_salt('xdes', 1001); +UPDATE ctest SET res = crypt(data, salt); +SELECT res = crypt(data, res) AS "worked" +FROM ctest; + +DROP TABLE ctest; diff --git a/contrib/pgcrypto/sql/des.sql b/contrib/pgcrypto/sql/des.sql new file mode 100644 index 0000000..08c77a4 --- /dev/null +++ b/contrib/pgcrypto/sql/des.sql @@ -0,0 +1,24 @@ +-- +-- DES cipher +-- + +-- no official test vectors atm + +-- from blowfish.sql +SELECT encrypt('\x0123456789abcdef', '\xfedcba9876543210', 'des-ecb/pad:none'); + +-- empty data +select encrypt('', 'foo', 'des'); +-- 8 bytes key +select encrypt('foo', '01234589', 'des'); + +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'des'), '0123456', 'des'), 'escape'); + +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'des'); +select encode(decrypt_iv('\x50735067b073bb93', '0123456', 'abcd', 'des'), 'escape'); + +-- long message +select encrypt('Lets try a longer message.', '01234567', 'des'); +select encode(decrypt(encrypt('Lets try a longer message.', '01234567', 'des'), '01234567', 'des'), 'escape'); diff --git a/contrib/pgcrypto/sql/hmac-md5.sql b/contrib/pgcrypto/sql/hmac-md5.sql new file mode 100644 index 0000000..981ed09 --- /dev/null +++ b/contrib/pgcrypto/sql/hmac-md5.sql @@ -0,0 +1,44 @@ +-- +-- HMAC-MD5 +-- + +SELECT hmac( +'Hi There', +'\x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'::bytea, +'md5'); + +-- 2 +SELECT hmac( +'Jefe', +'what do ya want for nothing?', +'md5'); + +-- 3 +SELECT hmac( +'\xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'::bytea, +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'md5'); + +-- 4 +SELECT hmac( +'\xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'::bytea, +'\x0102030405060708090a0b0c0d0e0f10111213141516171819'::bytea, +'md5'); + +-- 5 +SELECT hmac( +'Test With Truncation', +'\x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c'::bytea, +'md5'); + +-- 6 +SELECT hmac( +'Test Using Larger Than Block-Size Key - Hash Key First', +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'md5'); + +-- 7 +SELECT hmac( +'Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data', +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'md5'); diff --git a/contrib/pgcrypto/sql/hmac-sha1.sql b/contrib/pgcrypto/sql/hmac-sha1.sql new file mode 100644 index 0000000..f9a7b71 --- /dev/null +++ b/contrib/pgcrypto/sql/hmac-sha1.sql @@ -0,0 +1,44 @@ +-- +-- HMAC-SHA1 +-- + +SELECT hmac( +'Hi There', +'\x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'::bytea, +'sha1'); + +-- 2 +SELECT hmac( +'Jefe', +'what do ya want for nothing?', +'sha1'); + +-- 3 +SELECT hmac( +'\xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'::bytea, +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'sha1'); + +-- 4 +SELECT hmac( +'\xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'::bytea, +'\x0102030405060708090a0b0c0d0e0f10111213141516171819'::bytea, +'sha1'); + +-- 5 +SELECT hmac( +'Test With Truncation', +'\x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c'::bytea, +'sha1'); + +-- 6 +SELECT hmac( +'Test Using Larger Than Block-Size Key - Hash Key First', +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'sha1'); + +-- 7 +SELECT hmac( +'Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data', +'\xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'::bytea, +'sha1'); diff --git a/contrib/pgcrypto/sql/init.sql b/contrib/pgcrypto/sql/init.sql new file mode 100644 index 0000000..6388187 --- /dev/null +++ b/contrib/pgcrypto/sql/init.sql @@ -0,0 +1,11 @@ +-- +-- init pgcrypto +-- + +CREATE EXTENSION pgcrypto; + +-- check error handling +select gen_salt('foo'); +select digest('foo', 'foo'); +select hmac('foo', 'foo', 'foo'); +select encrypt('foo', 'foo', 'foo'); diff --git a/contrib/pgcrypto/sql/md5.sql b/contrib/pgcrypto/sql/md5.sql new file mode 100644 index 0000000..0403613 --- /dev/null +++ b/contrib/pgcrypto/sql/md5.sql @@ -0,0 +1,11 @@ +-- +-- MD5 message digest +-- + +SELECT digest('', 'md5'); +SELECT digest('a', 'md5'); +SELECT digest('abc', 'md5'); +SELECT digest('message digest', 'md5'); +SELECT digest('abcdefghijklmnopqrstuvwxyz', 'md5'); +SELECT digest('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 'md5'); +SELECT digest('12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'md5'); diff --git a/contrib/pgcrypto/sql/pgp-armor.sql b/contrib/pgcrypto/sql/pgp-armor.sql new file mode 100644 index 0000000..736b542 --- /dev/null +++ b/contrib/pgcrypto/sql/pgp-armor.sql @@ -0,0 +1,214 @@ +-- +-- PGP Armor +-- + +select armor(''); +select armor('test'); +select encode(dearmor(armor('')), 'escape'); +select encode(dearmor(armor('zooka')), 'escape'); + +select armor('0123456789abcdef0123456789abcdef0123456789abcdef +0123456789abcdef0123456789abcdef0123456789abcdef'); + +-- lots formatting +select encode(dearmor(' a pgp msg: + +-----BEGIN PGP MESSAGE----- +Comment: Some junk + +em9va2E= + + =D5cR + +-----END PGP MESSAGE-----'), 'escape'); + +-- lots messages +select encode(dearmor(' +wrong packet: + -----BEGIN PGP MESSAGE----- + + d3Jvbmc= + =vCYP + -----END PGP MESSAGE----- + +right packet: +-----BEGIN PGP MESSAGE----- + +cmlnaHQ= +=nbpj +-----END PGP MESSAGE----- + +use only first packet +-----BEGIN PGP MESSAGE----- + +d3Jvbmc= +=vCYP +-----END PGP MESSAGE----- +'), 'escape'); + +-- bad crc +select dearmor(' +-----BEGIN PGP MESSAGE----- + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- corrupt (no space after the colon) +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +foo: + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- corrupt (no empty line) +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- no headers +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- header with empty value +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +foo: + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- simple +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +fookey: foovalue +barkey: barvalue + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- insane keys, part 1 +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +insane:key : + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- insane keys, part 2 +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +insane:key : text value here + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- long value +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +long: this value is more than 76 characters long, but it should still parse correctly as that''s permitted by RFC 4880 + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- long value, split up +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +long: this value is more than 76 characters long, but it should still +long: parse correctly as that''s permitted by RFC 4880 + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- long value, split up, part 2 +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +long: this value is more than +long: 76 characters long, but it should still +long: parse correctly as that''s permitted by RFC 4880 + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +-- long value, split up, part 3 +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +emptykey: +long: this value is more than +emptykey: +long: 76 characters long, but it should still +emptykey: +long: parse correctly as that''s permitted by RFC 4880 +emptykey: + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +'); + +select * from pgp_armor_headers(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.blowfish.sha1.mdc.s2k3.z0 + +jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS +yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE= +=JcP+ +-----END PGP MESSAGE----- +'); + +-- test CR+LF line endings +select * from pgp_armor_headers(replace(' +-----BEGIN PGP MESSAGE----- +fookey: foovalue +barkey: barvalue + +em9va2E= +=ZZZZ +-----END PGP MESSAGE----- +', E'\n', E'\r\n')); + +-- test header generation +select armor('zooka', array['foo'], array['bar']); +select armor('zooka', array['Version', 'Comment'], array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database']); +select * from pgp_armor_headers( + armor('zooka', array['Version', 'Comment'], + array['Created by pgcrypto', 'PostgreSQL, the world''s most advanced open source database'])); + +-- error/corner cases +select armor('', array['foo'], array['too', 'many']); +select armor('', array['too', 'many'], array['foo']); +select armor('', array[['']], array['foo']); +select armor('', array['foo'], array[['']]); +select armor('', array[null], array['foo']); +select armor('', array['foo'], array[null]); +select armor('', '[0:0]={"foo"}', array['foo']); +select armor('', array['foo'], '[0:0]={"foo"}'); +select armor('', array[E'embedded\nnewline'], array['foo']); +select armor('', array['foo'], array[E'embedded\nnewline']); +select armor('', array['embedded: colon+space'], array['foo']); diff --git a/contrib/pgcrypto/sql/pgp-compression.sql b/contrib/pgcrypto/sql/pgp-compression.sql new file mode 100644 index 0000000..87c59c6 --- /dev/null +++ b/contrib/pgcrypto/sql/pgp-compression.sql @@ -0,0 +1,51 @@ +-- +-- PGP compression support +-- + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0ECQMCsci6AdHnELlh0kQB4jFcVwHMJg0Bulop7m3Mi36s15TAhBo0AnzIrRFrdLVCkKohsS6+ +DMcmR53SXfLoDJOv/M8uKj3QSq7oWNIp95pxfA== +=tbSn +-----END PGP MESSAGE----- +'), 'key', 'expect-compress-algo=1'); + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret message', 'key', 'compress-algo=0'), + 'key', 'expect-compress-algo=0'); + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret message', 'key', 'compress-algo=1'), + 'key', 'expect-compress-algo=1'); + +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret message', 'key', 'compress-algo=2'), + 'key', 'expect-compress-algo=2'); + +-- level=0 should turn compression off +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret message', 'key', + 'compress-algo=2, compress-level=0'), + 'key', 'expect-compress-algo=0'); + +-- check corner case involving an input string of 16kB, as per bug #16476. +SELECT setseed(0); +WITH random_string AS +( + -- This generates a random string of 16366 bytes. This is chosen + -- as random so that it does not get compressed, and the decompression + -- would work on a string with the same length as the origin, making the + -- test behavior more predictible. lpad() ensures that the generated + -- hexadecimal value is completed by extra zero characters if random() + -- has generated a value strictly lower than 16. + SELECT string_agg(decode(lpad(to_hex((random()*256)::int), 2, '0'), 'hex'), '') as bytes + FROM generate_series(0, 16365) +) +SELECT bytes = + pgp_sym_decrypt_bytea( + pgp_sym_encrypt_bytea(bytes, 'key', + 'compress-algo=1,compress-level=1'), + 'key', 'expect-compress-algo=1') + AS is_same + FROM random_string; diff --git a/contrib/pgcrypto/sql/pgp-decrypt.sql b/contrib/pgcrypto/sql/pgp-decrypt.sql new file mode 100644 index 0000000..49a0267 --- /dev/null +++ b/contrib/pgcrypto/sql/pgp-decrypt.sql @@ -0,0 +1,309 @@ +-- +-- pgp decrypt tests +-- + +-- Checking ciphers +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.blowfish.sha1.mdc.s2k3.z0 + +jA0EBAMCfFNwxnvodX9g0jwB4n4s26/g5VmKzVab1bX1SmwY7gvgvlWdF3jKisvS +yA6Ce1QTMK3KdL2MPfamsTUSAML8huCJMwYQFfE= +=JcP+ +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCci97v0Q6Z0Zg0kQBsVf5Oe3iC+FBzUmuMV9KxmAyOMyjCc/5i8f1Eest +UTAsG35A1vYs02VARKzGz6xI2UHwFUirP+brPBg3Ee7muOx8pA== +=XtrP +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCI7YQpWqp3D1g0kQBCjB7GlX7+SQeXNleXeXQ78ZAPNliquGDq9u378zI +5FPTqAhIB2/2fjY8QEIs1ai00qphjX2NitxV/3Wn+6dufB4Q4g== +=rCZt +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMC4f/5djqCC1Rg0kQBTHEPsD+Sw7biBsM2er3vKyGPAQkuTBGKC5ie7hT/ +lceMfQdbAg6oTFyJpk/wH18GzRDphCofg0X8uLgkAKMrpcmgog== +=fB6S +-----END PGP MESSAGE----- +'), 'foobar'); + +-- Checking MDC modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.nomdc.s2k3.z0 + +jA0EBwMCnv07rlXqWctgyS2Dm2JfOKCRL4sLSLJUC8RS2cH7cIhKSuLitOtyquB+ +u9YkgfJfsuRJmgQ9tmo= +=60ui +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEeP3idNjQ1Bg0kQBf4G0wX+2QNzLh2YNwYkQgQkfYhn/hLXjV4nK9nsE +8Ex1Dsdt5UPvOz8W8VKQRS6loOfOe+yyXil8W3IYFwUpdDUi+Q== +=moGf +-----END PGP MESSAGE----- +'), 'foobar'); + +-- Checking hashes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.md5.mdc.s2k3.z0 + +jA0EBwMClrXXtOXetohg0kQBn0Kl1ymevQZRHkdoYRHgzCwSQEiss7zYff2UNzgO +KyRrHf7zEBuZiZ2AG34jNVMOLToj1jJUg5zTSdecUzQVCykWTA== +=NyLk +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCApbdlrURoWJg0kQBzHM/E0o7djY82bNuspjxjAcPFrrtp0uvDdMQ4z2m +/PM8jhgI5vxFYfNQjLl8y3fHYIomk9YflN9K/Q13iq8A8sjeTw== +=FxbQ +-----END PGP MESSAGE----- +'), 'foobar'); + +-- Checking S2K modes +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k0.z0 + +jAQEBwAC0kQBKTaLAKE3xzps+QIZowqRNb2eAdzBw2LxEW2YD5PgNlbhJdGg+dvw +Ah9GXjGS1TVALzTImJbz1uHUZRfhJlFbc5yGQw== +=YvkV +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k1.z0 + +jAwEBwEC/QTByBLI3b/SRAHPxKzI6SZBo5lAEOD+EsvKQWO4adL9tDY+++Iqy1xK +4IaWXVKEj9R2Lr2xntWWMGZtcKtjD2lFFRXXd9dZp1ZThNDz +=dbXm +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCEq4Su3ZqNEJg0kQB4QG5jBTKF0i04xtH+avzmLhstBNRxvV3nsmB3cwl +z+9ZaA/XdSx5ZiFnMym8P6r8uY9rLjjNptvvRHlxIReF+p9MNg== +=VJKg +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k0.z0 + +jAQECAAC0kQBBDnQWkgsx9YFaqDfWmpsiyAJ6y2xG/sBvap1dySYEMuZ+wJTXQ9E +Cr3i2M7TgVZ0M4jp4QL0adG1lpN5iK7aQeOwMw== +=cg+i +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k1.z0 + +jAwECAECruOfyNDFiTnSRAEVoGXm4A9UZKkWljdzjEO/iaE7mIraltIpQMkiqCh9 +7h8uZ2u9uRBOv222fZodGvc6bvq/4R4hAa/6qSHtm8mdmvGt +=aHmC +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes192.sha1.mdc.s2k3.z0 + +jA0ECAMCjFn6SRi3SONg0kQBqtSHPaD0m7rXfDAhCWU/ypAsI93GuHGRyM99cvMv +q6eF6859ZVnli3BFSDSk3a4e/pXhglxmDYCfjAXkozKNYLo6yw== +=K0LS +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k0.z0 + +jAQECQAC0kQB4L1eMbani07XF2ZYiXNK9LW3v8w41oUPl7dStmrJPQFwsdxmrDHu +rQr3WbdKdY9ufjOE5+mXI+EFkSPrF9rL9NCq6w== +=RGts +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k1.z0 + +jAwECQECKHhrou7ZOIXSRAHWIVP+xjVQcjAVBTt+qh9SNzYe248xFTwozkwev3mO ++KVJW0qhk0An+Y2KF99/bYFl9cL5D3Tl43fC8fXGl3x3m7pR +=SUrU +-----END PGP MESSAGE----- +'), 'foobar'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes256.sha1.mdc.s2k3.z0 + +jA0ECQMCjc8lwZu8Fz1g0kQBkEzjImi21liep5jj+3dAJ2aZFfUkohi8b3n9z+7+ +4+NRzL7cMW2RLAFnJbiqXDlRHMwleeuLN1up2WIxsxtYYuaBjA== +=XZrG +-----END PGP MESSAGE----- +'), 'foobar'); + +-- Checking longer passwords +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCx6dBiuqrYNRg0kQBEo63AvA1SCslxP7ayanLf1H0/hlk2nONVhTwVEWi +tTGup1mMz6Cfh1uDRErUuXpx9A0gdMu7zX0o5XjrL7WGDAZdSw== +=XKKG +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCBDvYuS990iFg0kQBW31UK5OiCjWf5x6KJ8qNNT2HZWQCjCBZMU0XsOC6 +CMxFKadf144H/vpoV9GA0f22keQgCl0EsTE4V4lweVOPTKCMJg== +=gWDh +-----END PGP MESSAGE----- +'), '0123456789abcdefghij2jk4h5g2j54khg23h54g2kh54g2khj54g23hj54'); + +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCqXbFafC+ofVg0kQBejyiPqH0QMERVGfmPOjtAxvyG5KDIJPYojTgVSDt +FwsDabdQUz5O7bgNSnxfmyw1OifGF+W2bIn/8W+0rDf8u3+O+Q== +=OxOF +-----END PGP MESSAGE----- +'), 'x'); + +-- Checking various data +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat1.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCGJ+SpuOysINg0kQBJfSjzsW0x4OVcAyr17O7FBvMTwIGeGcJd99oTQU8 +Xtx3kDqnhUq9Z1fS3qPbi5iNP2A9NxOBxPWz2JzxhydANlgbxg== +=W/ik +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat2.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCvdpDvidNzMxg0jUBvj8eS2+1t/9/zgemxvhtc0fvdKGGbjH7dleaTJRB +SaV9L04ky1qECNDx3XjnoKLC+H7IOQ== +=Fxen +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: dat3.aes.sha1.mdc.s2k3.z0 + +jA0EBwMCxQvxJZ3G/HRg0lgBeYmTa7/uDAjPyFwSX4CYBgpZWVn/JS8JzILrcWF8 +gFnkUKIE0PSaYFp+Yi1VlRfUtRQ/X/LYNGa7tWZS+4VQajz2Xtz4vUeAEiYFYPXk +73Hb8m1yRhQK +=ivrD +-----END PGP MESSAGE----- +'), '0123456789abcdefghij'), 'sha1'); + +-- Checking CRLF +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=0'), 'sha1'); + +select digest(pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- +Comment: crlf mess + +ww0ECQMCt7VAtby6l4Bi0lgB5KMIZiiF/b3CfMfUyY0eDncsGXtkbu1X+l9brjpMP8eJnY79Amms +a3nsOzKTXUfS9VyaXo8IrncM6n7fdaXpwba/3tNsAhJG4lDv1k4g9v8Ix2dfv6Rs +=mBP9 +-----END PGP MESSAGE----- +'), 'key', 'convert-crlf=1'), 'sha1'); + +-- check BUG #11905, problem with messages 6 less than a power of 2. +select pgp_sym_decrypt(pgp_sym_encrypt(repeat('x',65530),'1'),'1') = repeat('x',65530); + + +-- Negative tests + +-- Decryption with a certain incorrect key yields an apparent Literal Data +-- packet reporting its content to be binary data. Ciphertext source: +-- iterative pgp_sym_encrypt('secret', 'key') until the random prefix gave +-- rise to that property. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMCxf8PTrQBmJdl0jcB6y2joE7GSLKRv7trbNsF5Z8ou5NISLUg31llVH/S0B2wl4bvzZjV +VsxxqLSPzNLAeIspJk5G +=mSd/ +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); + +-- Routine text/binary mismatch. +select pgp_sym_decrypt(pgp_sym_encrypt_bytea('P', 'key'), 'key', 'debug=1'); + +-- Decryption with a certain incorrect key yields an apparent BZip2-compressed +-- plaintext. Ciphertext source: iterative pgp_sym_encrypt('secret', 'key') +-- until the random prefix gave rise to that property. +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +ww0EBwMC9rK/dMkF5Zlt0jcBlzAQ1mQY2qYbKYbw8h3EZ5Jk0K2IiY92R82TRhWzBIF/8cmXDPtP +GXsd65oYJZp3Khz0qfyn +=Nmpq +-----END PGP MESSAGE----- +'), 'wrong-key', 'debug=1'); + +-- Routine use of BZip2 compression. Ciphertext source: +-- echo x | gpg --homedir /nonexistent --personal-compress-preferences bzip2 \ +-- --personal-cipher-preferences aes --no-emit-version --batch \ +-- --symmetric --passphrase key --armor +select pgp_sym_decrypt(dearmor(' +-----BEGIN PGP MESSAGE----- + +jA0EBwMCRhFrAKNcLVJg0mMBLJG1cCASNk/x/3dt1zJ+2eo7jHfjgg3N6wpB3XIe +QCwkWJwlBG5pzbO5gu7xuPQN+TbPJ7aQ2sLx3bAHhtYb0i3vV9RO10Gw++yUyd4R +UCAAw2JRIISttRHMfDpDuZJpvYo= +=AZ9M +-----END PGP MESSAGE----- +'), 'key', 'debug=1'); diff --git a/contrib/pgcrypto/sql/pgp-encrypt.sql b/contrib/pgcrypto/sql/pgp-encrypt.sql new file mode 100644 index 0000000..ed8b177 --- /dev/null +++ b/contrib/pgcrypto/sql/pgp-encrypt.sql @@ -0,0 +1,104 @@ +-- +-- PGP encrypt +-- + +select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), 'key'); + +-- check whether the defaults are ok +select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), + 'key', 'expect-cipher-algo=aes128, + expect-disable-mdc=0, + expect-sess-key=0, + expect-s2k-mode=3, + expect-s2k-digest-algo=sha1, + expect-compress-algo=0 + '); + +-- maybe the expect- stuff simply does not work +select pgp_sym_decrypt(pgp_sym_encrypt('Secret.', 'key'), + 'key', 'expect-cipher-algo=bf, + expect-disable-mdc=1, + expect-sess-key=1, + expect-s2k-mode=0, + expect-s2k-digest-algo=md5, + expect-compress-algo=1 + '); + +-- bytea as text +select pgp_sym_decrypt(pgp_sym_encrypt_bytea('Binary', 'baz'), 'baz'); + +-- text as bytea +select encode(pgp_sym_decrypt_bytea(pgp_sym_encrypt('Text', 'baz'), 'baz'), 'escape'); + + +-- algorithm change +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=bf'), + 'key', 'expect-cipher-algo=bf'); +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes'), + 'key', 'expect-cipher-algo=aes128'); +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'cipher-algo=aes192'), + 'key', 'expect-cipher-algo=aes192'); + +-- s2k change +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-mode=0'), + 'key', 'expect-s2k-mode=0'); +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-mode=1'), + 'key', 'expect-s2k-mode=1'); +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-mode=3'), + 'key', 'expect-s2k-mode=3'); + +-- s2k count change +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-count=1024'), + 'key', 'expect-s2k-count=1024'); +-- s2k_count rounds up +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-count=65000000'), + 'key', 'expect-s2k-count=65000000'); + +-- s2k digest change +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=md5'), + 'key', 'expect-s2k-digest-algo=md5'); +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 's2k-digest-algo=sha1'), + 'key', 'expect-s2k-digest-algo=sha1'); + +-- sess key +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'sess-key=0'), + 'key', 'expect-sess-key=0'); +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'sess-key=1'), + 'key', 'expect-sess-key=1'); +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=bf'), + 'key', 'expect-sess-key=1, expect-cipher-algo=bf'); +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes192'), + 'key', 'expect-sess-key=1, expect-cipher-algo=aes192'); +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'sess-key=1, cipher-algo=aes256'), + 'key', 'expect-sess-key=1, expect-cipher-algo=aes256'); + +-- no mdc +select pgp_sym_decrypt( + pgp_sym_encrypt('Secret.', 'key', 'disable-mdc=1'), + 'key', 'expect-disable-mdc=1'); + +-- crlf +select pgp_sym_decrypt_bytea( + pgp_sym_encrypt(E'1\n2\n3\r\n', 'key', 'convert-crlf=1'), + 'key'); + +-- conversion should be lossless +select digest(pgp_sym_decrypt( + pgp_sym_encrypt(E'\r\n0\n1\r\r\n\n2\r', 'key', 'convert-crlf=1'), + 'key', 'convert-crlf=1'), 'sha1') as result, + digest(E'\r\n0\n1\r\r\n\n2\r', 'sha1') as expect; diff --git a/contrib/pgcrypto/sql/pgp-info.sql b/contrib/pgcrypto/sql/pgp-info.sql new file mode 100644 index 0000000..8e1d72a --- /dev/null +++ b/contrib/pgcrypto/sql/pgp-info.sql @@ -0,0 +1,22 @@ +-- +-- PGP info functions +-- + +-- pgp_key_id + +select pgp_key_id(dearmor(pubkey)) from keytbl where id=1; +select pgp_key_id(dearmor(pubkey)) from keytbl where id=2; +select pgp_key_id(dearmor(pubkey)) from keytbl where id=3; +select pgp_key_id(dearmor(pubkey)) from keytbl where id=4; -- should fail +select pgp_key_id(dearmor(pubkey)) from keytbl where id=5; +select pgp_key_id(dearmor(pubkey)) from keytbl where id=6; + +select pgp_key_id(dearmor(seckey)) from keytbl where id=1; +select pgp_key_id(dearmor(seckey)) from keytbl where id=2; +select pgp_key_id(dearmor(seckey)) from keytbl where id=3; +select pgp_key_id(dearmor(seckey)) from keytbl where id=4; -- should fail +select pgp_key_id(dearmor(seckey)) from keytbl where id=5; +select pgp_key_id(dearmor(seckey)) from keytbl where id=6; + +select pgp_key_id(dearmor(data)) as data_key_id +from encdata order by id; diff --git a/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql b/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql new file mode 100644 index 0000000..3f2bae9 --- /dev/null +++ b/contrib/pgcrypto/sql/pgp-pubkey-decrypt.sql @@ -0,0 +1,647 @@ +-- +-- PGP Public Key Encryption +-- + +-- As most of the low-level stuff is tested in symmetric key +-- tests, here's only public-key specific tests + +create table keytbl ( + id int4, + name text, + pubkey text, + seckey text +); +create table encdata ( + id int4, + data text +); + +insert into keytbl (id, name, pubkey, seckey) +values (1, 'elg1024', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx +MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV +AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc +AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd +ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P +sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI ++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9 +6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF +k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v +iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F +ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80 +=RWci +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQG7BELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQAAAnj4i4st+s+C6 +WKTIDcL1Iy0Saq8lCp60H0VsZ2FtYWwgMTAyNCA8dGVzdEBleGFtcGxlLm9yZz6I +XgQTEQIAHgUCQsghSAIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRAcKbwNGBdz +ZDrbAJ9cp6AsjOhiLxwznsMJheGf4xkH8wCfUPjMCLm4tAEnyYn2hDNt7CB8B6Kd +ATEEQsghShAEAIeUjW2yTALCfrEG3FhM3ZLvAHWAec2O0Mn/RDr59IN/W8wDYcZp +m+oG0ZUDdIqMppQ8K2kylAH7gmYDXIP9D7MRRm/Zw3L4yFfKnVaZ6tT7szBbgW5h +iOsHoOz49NXZT4jtMLdZS1/krm5Lam2MSPod9XN0Q2asY/igIMUfGDRjAAMGA/sE +LNh3tWefqeDkoDBEYjcxdGnGVGJnNHvv/eoHy9H7dyD/kkhaOoRAa5ClYWSqD0kk +a+SqTWhKG4XcbJyo1GsP6sqGhXDTM2+LBZPMKuVJQpEfoe9ruob/BbpXglfEiVE9 +VNiY7ZVyUdj3svYn4fK2X7ue1G3cHR2tL4lnOA4pYQAA9030E4u2ZKOfJBpUM+EM +m9VmsGjaQZV4teB0R/q3W8sRIYhJBBgRAgAJBQJCyCFKAhsMAAoJEBwpvA0YF3Nk +7a8AniFFotw1x2X+oryu3Q3nNtmxoKHpAJ9HU7jw7ydg33dI9J8gVkrmsSZ2/w== +=nvqq +-----END PGP PRIVATE KEY BLOCK----- +'); + +insert into keytbl (id, name, pubkey, seckey) +values (2, 'elg2048', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n +vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke +5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO +RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij +8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl +Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt +J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/ +T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5 +0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6a7QjRWxnYW1hbCAy +MDQ4IDx0ZXN0MjA0OEBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgiCgIbAwYLCQgH +AwIDFQIDAxYCAQIeAQIXgAAKCRBI6c1W/qZo29PDAKCG724enIxRog1j+aeCp/uq +or6mbwCePuKy2/1kD1FvnhkZ/R5fpm+pdm25Ag0EQsgiIhAIAJI3Gb2Ehtz1taQ9 +AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMX +MhoWyw8ZF5Zs1mLIjFGVorePrm94N3MNPWM7x9M36bHUjx0vCZKFIhcGY1g+htE/ +QweaJzNVeA5z4qZmik41FbQyQSyHa3bOkTZu++/U6ghP+iDp5UDBjMTkVyqITUVN +gC+MR+da/I60irBVhue7younh4ovF+CrVDQJC06HZl6CAJJyA81SmRfi+dmKbbjZ +LF6rhz0norPjISJvkIqvdtM4VPBKI5wpgwCzpEqjuiKrAVujRT68zvBvJ4aVqb11 +k5QdJscAAwUH/jVJh0HbWAoiFTe+NvohfrA8vPcD0rtU3Y+siiqrabotnxJd2NuC +bxghJYGfNtnx0KDjFbCRKJVeTFok4UnuVYhXdH/c6i0/rCTNdeW2D6pmR4GfBozR +Pw/ARf+jONawGLyUj7uq13iquwMSE7VyNuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0R +QsetMq/iNBWraayKZnWUd+eQqNzE+NUo7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiF +Z1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qunfGW00ZMMTCWabg0ZgxPzMfMeIcm6525A +Yn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/RdWaISQQYEQIACQUCQsgiIgIbDAAKCRBI +6c1W/qZo25ZSAJ98WTrtl2HiX8ZqZq95v1+9cHtZPQCfZDoWQPybkNescLmXC7q5 +1kNTmEU= +=8QM5 +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQG7BELIIgoRBAC1onBpxKYgDvrgCaUWPY34947X3ogxGOfCN0p6Eqrx+2PUhm4n +vFvmczpMT4iDc0mUO+iwnwsEkXQI1eC99g8c0jnZAvzJZ5miAHL8hukMAMfDkYke +5aVvcPPc8uPDlItpszGmH0rM0V9TIt/i9QEXetpyNWhk4jj5qnohYhLeZwCgkOdO +RFAdNi4vfFPivvtAp2ffjU8D/R3x/UJCvkzi7i9rQHGo313xxmQu5BuqIjANBUij +8IE7LRPI/Qhg2hYy3sTJwImDi7VkS+fuvNVk0d6MTWplAXYU96bn12JaD21R9sKl +Fzcc+0iZI1wYA1PczisUkoTISE+dQFUsoGHfpDLhoBuesXQrhBavI8t8VPd+nkdt +J+oKA/9iRQ87FzxdYTkh2drrv69FZHc3Frsjw9nPcBq/voAvXH0MRilqyCg7HpW/ +T9naeOERksa+Rj4R57IF1l4e5oiiGJo9QmaKZcsCsXrREJCycrlEtMqXfSPy+bi5 +0yDZE/Qm1dwu13+OXOsRvkoNYjO8Mzo9K8wU12hMqN0a2bu6awAAn2F+iNBElfJS +8azqO/kEiIfpqu6/DQG0I0VsZ2FtYWwgMjA0OCA8dGVzdDIwNDhAZXhhbXBsZS5v +cmc+iF0EExECAB4FAkLIIgoCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQSOnN +Vv6maNvTwwCYkpcJmpl3aHCQdGomz7dFohDgjgCgiThZt2xTEi6GhBB1vuhk+f55 +n3+dAj0EQsgiIhAIAJI3Gb2Ehtz1taQ9AhPY4Avad2BsqD3S5X/R11Cm0KBE/04D +29dxn3f8QfxDsexYvNIZjoJPBqqZ7iMXMhoWyw8ZF5Zs1mLIjFGVorePrm94N3MN +PWM7x9M36bHUjx0vCZKFIhcGY1g+htE/QweaJzNVeA5z4qZmik41FbQyQSyHa3bO +kTZu++/U6ghP+iDp5UDBjMTkVyqITUVNgC+MR+da/I60irBVhue7younh4ovF+Cr +VDQJC06HZl6CAJJyA81SmRfi+dmKbbjZLF6rhz0norPjISJvkIqvdtM4VPBKI5wp +gwCzpEqjuiKrAVujRT68zvBvJ4aVqb11k5QdJscAAwUH/jVJh0HbWAoiFTe+Nvoh +frA8vPcD0rtU3Y+siiqrabotnxJd2NuCbxghJYGfNtnx0KDjFbCRKJVeTFok4Unu +VYhXdH/c6i0/rCTNdeW2D6pmR4GfBozRPw/ARf+jONawGLyUj7uq13iquwMSE7Vy +NuF3ycL2OxXjgOWMjkH8c+zfHHpjaZ0RQsetMq/iNBWraayKZnWUd+eQqNzE+NUo +7w1jAu7oDpy+8a1eipxzK+O0HfU5LTiFZ1Oe4Um0P2l3Xtx8nEgj4vSeoEkl2qun +fGW00ZMMTCWabg0ZgxPzMfMeIcm6525AYn2qL+X/qBJTInAl7/hgPz2D1Yd7d5/R +dWYAAVQKFPXbRaxbdArwRVXMzSD3qj/+VwwhwEDt8zmBGnlBfwVdkjQQrDUMmV1S +EwyISQQYEQIACQUCQsgiIgIbDAAKCRBI6c1W/qZo25ZSAJ4sgUfHTVsG/x3p3fcM +3b5R86qKEACggYKSwPWCs0YVRHOWqZY0pnHtLH8= +=3Dgk +-----END PGP PRIVATE KEY BLOCK----- +'); + +insert into keytbl (id, name, pubkey, seckey) +values (3, 'elg4096', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELII7wRBACFuaAvb11cIvjJK9LkZr4cYuYhLWh3DJdojNNnLNiym5OEksvY +05cw8OgqKtPzICU7o/mHXTWhzJYUt3i50/AeYygI8Q0uATS6RnDAKNlES1EMoHKz +2a5iFbYs4bm4IwlkvYd8uWjcu+U0YLbxir39u+anIc6eT+q3WiH/q3zDRwCgkT98 +cnIG8iO8PdwDSP8G4Lt6TYED/R45GvCzJ4onQALLE92KkLUz8aFWSl05r84kczEN +SxiP9Ss6m465RmwWHfwYAu4b+c4GeNyU8fIU2EM8cezchC+edEi3xu1s+pCV0Dk4 +18DGC8WKCICO30vBynuNmYg7W/7Zd4wtjss454fMW7+idVDNM701mmXBtI1nsBtG +7Z4tA/9FxjFbJK9jh24RewfjHpLYqcfCo2SsUjOwsnMZ5yg2yv9KyVVQhRqwmrqt +q8MRyjGmfoD9PPdCgvqgzy0hHvAHUtTm2zUczGTG+0g4hNIklxC/Mv6J4KE+NWTh +uB4acqofHyaw2WnKOuRUsoDi6rG5AyjNMyAK/vVcEGj7J1tk27QjRWxnYW1hbCA0 +MDk2IDx0ZXN0NDA5NkBleGFtcGxlLm9yZz6IXgQTEQIAHgUCQsgjvAIbAwYLCQgH +AwIDFQIDAxYCAQIeAQIXgAAKCRBj+HX2P2d0oAEDAJ9lI+CNmb42z3+a6TnVusM6 +FI7oLwCfUwA1zEcRdsT3nIkoYh0iKxFSDFW5BA0EQsgkdhAQAJQbLXlgcJ/jq+Xh +Eujb77/eeftFJObNIRYD9fmJ7HFIXbUcknEpbs+cRH/nrj5dGSY3OT3jCXOUtvec +sCoX/CpZWL0oqDjAiZtNSFiulw5Gav4gHYkWKgKdSo+2rkavEPqKIVHvMeXaJtGT +d7v/AmL/P8T7gls93o5WFBOLtPbDvWqaKRy2U5TAhl1laiM0vGALRVjvSCgnGw9g +FpSnXbO3AfenUSjDzZujfGLHtU44ixHSS/D4DepiF3YaYLsN4CBqZRv6FbMZD5W3 +DnJY4kS1kH0MzdcF19TlcZ3itTCcGIt1tMKf84mccPoqdMzH7vumBGTeFEly5Afp +9berJcirqh2fzlunN0GS02z6SGWnjTbDlkNDxuxPSBbpcpNyD3jpYAUqSwRsZ/+5 +zkzcbGtDmvy9sJ5lAXkxGoIoQ1tEVX/LOHnh2NQHK8ourVOnr7MS0nozssITZJ5E +XqtHiREjiYEuPyZiVZKJHLWuYYaF+n40znnz3sJuXFRreHhHbbvRdlYUU5mJV+XZ +BLgKuS33NdpGeMIngnCc/9IQ6OZb6ixc94kbkd3w2PVr8CbKlu/IHTjWOO2mAo+D ++OydlYl23FiM3KOyMP1HcEOJMB/nwkMtrvd+522Lu9n77ktKfot9IPrQDIQTyXjR +3pCOFtCOBnk2tJHMPoG9jn9ah/LHAAMHEACDZ5I/MHGfmiKg2hrmqBu2J2j/deC8 +CpwcyDH1ovQ0gHvb9ESa+CVRU2Wdy2CD7Q9SmtMverB5eneL418iPVRcQdwRmQ2y +IH4udlBa6ce9HTUCaecAZ4/tYBnaC0Av/9l9tz14eYcwRMDpB+bnkhgF+PZ1KAfD +9wcY2aHbtsf3lZBc5h4owPJkxpe/BNzuJxW3q4VpSbLsZhwnCZ2wg7DRwP44wFIk +00ptmoBY59gsU6I40XtzrF8JDr0cA57xND5RY21Z8lnnYRE1Tc8h5REps9ZIxW3/ +yl91404bPLqxczpUHQAMSTAmBaStPYX1nS51uofOhLs5SKPCUmxfGKIOhsD0oLUn +78DnkONVGeXzBibSwwtbgfMzee4G8wSUfJ7w8WXz1TyanaGLnJ+DuKASSOrFoBCD +HEDuWZWgSL74NOQupFRk0gxOPmqU94Y8HziQWma/cETbmD83q8rxN+GM2oBxQkQG +xcbqMTHE7aVhV3tymbSWVaYhww3oIwsZS9oUIi1DnPEowS6CpVRrwdvLjLJnJzzV +O3AFPn9eZ1Q7R1tNx+zZ4OOfhvI/OlRJ3HBx2L53embkbdY9gFYCCdTjPyjKoDIx +kALgCajjCYMNUsAKNSd6mMCQ8TtvukSzkZS1RGKP27ohsdnzIVsiEAbxDMMcI4k1 +ul0LExUTCXSjeIhJBBgRAgAJBQJCyCR2AhsMAAoJEGP4dfY/Z3Sg19sAn0NDS8pb +qrMpQAxSb7zRTmcXEFd9AJ435H0ttP/NhLHXC9ezgbCMmpXMOQ== +=kRxT +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQG7BELII7wRBACFuaAvb11cIvjJK9LkZr4cYuYhLWh3DJdojNNnLNiym5OEksvY +05cw8OgqKtPzICU7o/mHXTWhzJYUt3i50/AeYygI8Q0uATS6RnDAKNlES1EMoHKz +2a5iFbYs4bm4IwlkvYd8uWjcu+U0YLbxir39u+anIc6eT+q3WiH/q3zDRwCgkT98 +cnIG8iO8PdwDSP8G4Lt6TYED/R45GvCzJ4onQALLE92KkLUz8aFWSl05r84kczEN +SxiP9Ss6m465RmwWHfwYAu4b+c4GeNyU8fIU2EM8cezchC+edEi3xu1s+pCV0Dk4 +18DGC8WKCICO30vBynuNmYg7W/7Zd4wtjss454fMW7+idVDNM701mmXBtI1nsBtG +7Z4tA/9FxjFbJK9jh24RewfjHpLYqcfCo2SsUjOwsnMZ5yg2yv9KyVVQhRqwmrqt +q8MRyjGmfoD9PPdCgvqgzy0hHvAHUtTm2zUczGTG+0g4hNIklxC/Mv6J4KE+NWTh +uB4acqofHyaw2WnKOuRUsoDi6rG5AyjNMyAK/vVcEGj7J1tk2wAAoJCUNy6awTkw +XfbLbpqh0fvDst7jDLa0I0VsZ2FtYWwgNDA5NiA8dGVzdDQwOTZAZXhhbXBsZS5v +cmc+iF4EExECAB4FAkLII7wCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQY/h1 +9j9ndKABAwCeNEOVK87EzXYbtxYBsnjrUI948NIAn2+f3BXiBFDV5NvqPwIZ0m77 +Fwy4nQRMBELIJHYQEACUGy15YHCf46vl4RLo2++/3nn7RSTmzSEWA/X5iexxSF21 +HJJxKW7PnER/564+XRkmNzk94wlzlLb3nLAqF/wqWVi9KKg4wImbTUhYrpcORmr+ +IB2JFioCnUqPtq5GrxD6iiFR7zHl2ibRk3e7/wJi/z/E+4JbPd6OVhQTi7T2w71q +mikctlOUwIZdZWojNLxgC0VY70goJxsPYBaUp12ztwH3p1Eow82bo3xix7VOOIsR +0kvw+A3qYhd2GmC7DeAgamUb+hWzGQ+Vtw5yWOJEtZB9DM3XBdfU5XGd4rUwnBiL +dbTCn/OJnHD6KnTMx+77pgRk3hRJcuQH6fW3qyXIq6odn85bpzdBktNs+khlp402 +w5ZDQ8bsT0gW6XKTcg946WAFKksEbGf/uc5M3GxrQ5r8vbCeZQF5MRqCKENbRFV/ +yzh54djUByvKLq1Tp6+zEtJ6M7LCE2SeRF6rR4kRI4mBLj8mYlWSiRy1rmGGhfp+ +NM55897CblxUa3h4R2270XZWFFOZiVfl2QS4Crkt9zXaRnjCJ4JwnP/SEOjmW+os +XPeJG5Hd8Nj1a/AmypbvyB041jjtpgKPg/jsnZWJdtxYjNyjsjD9R3BDiTAf58JD +La73fudti7vZ++5LSn6LfSD60AyEE8l40d6QjhbQjgZ5NrSRzD6BvY5/WofyxwAD +BxAAg2eSPzBxn5oioNoa5qgbtido/3XgvAqcHMgx9aL0NIB72/REmvglUVNlnctg +g+0PUprTL3qweXp3i+NfIj1UXEHcEZkNsiB+LnZQWunHvR01AmnnAGeP7WAZ2gtA +L//Zfbc9eHmHMETA6Qfm55IYBfj2dSgHw/cHGNmh27bH95WQXOYeKMDyZMaXvwTc +7icVt6uFaUmy7GYcJwmdsIOw0cD+OMBSJNNKbZqAWOfYLFOiONF7c6xfCQ69HAOe +8TQ+UWNtWfJZ52ERNU3PIeURKbPWSMVt/8pfdeNOGzy6sXM6VB0ADEkwJgWkrT2F +9Z0udbqHzoS7OUijwlJsXxiiDobA9KC1J+/A55DjVRnl8wYm0sMLW4HzM3nuBvME +lHye8PFl89U8mp2hi5yfg7igEkjqxaAQgxxA7lmVoEi++DTkLqRUZNIMTj5qlPeG +PB84kFpmv3BE25g/N6vK8TfhjNqAcUJEBsXG6jExxO2lYVd7cpm0llWmIcMN6CML +GUvaFCItQ5zxKMEugqVUa8Hby4yyZyc81TtwBT5/XmdUO0dbTcfs2eDjn4byPzpU +Sdxwcdi+d3pm5G3WPYBWAgnU4z8oyqAyMZAC4Amo4wmDDVLACjUnepjAkPE7b7pE +s5GUtURij9u6IbHZ8yFbIhAG8QzDHCOJNbpdCxMVEwl0o3gAAckBdfKuasiNUn5G +L5XRnSvaOFzftr8zteOlZChCSNvzH5k+i1j7RJbWq06OeKRywPzjfjgM2MvRzI43 +ICeISQQYEQIACQUCQsgkdgIbDAAKCRBj+HX2P2d0oNfbAJ9+G3SeXrk+dWwo9EGi +hqMi2GVTsgCfeoQJPsc8FLYUgfymc/3xqAVLUtg= +=Gjq6 +-----END PGP PRIVATE KEY BLOCK----- +'); + +insert into keytbl (id, name, pubkey, seckey) +values (4, 'rsa2048', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQELBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP +ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2 +55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx +5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K +MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz +R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYptB5SU0EgMjA0OCA8cnNhMjA0 +OEBleGFtcGxlLm9yZz6JATQEEwECAB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgEC +HgECF4AACgkQnc+OnJvTHyQqHwf8DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liR +nrLuVlLBpdO6CWmMUzfKRvyZlx54GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzw +bLZyM5Gb3lsE/FEmE7Dxw/0Utf59uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDP +D3dnU4uzKPhMcjnSN00pzjusP7C9NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv +9bgGWopumlOkt8Zu5YG6+CtTbJXprPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhv +S3NZKoJ/1DrGgoDAu1mGkM4KvLAxfDs/qQ9dZhtEmDbKPLTVEA== +=lR4n +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQOWBELIJbEBCADAIdtcoLAmQfl8pb73pPRuEYx8qW9klLfCGG5A4OUOi00JHNwP +ZaABe1PGzjoeXrgM1MTQZhoZu1Vdg+KDI6XAtiy9P6bLg7ntsXksD4wBoIKtQKc2 +55pdukxTiu+xeJJG2q8ZZPOp97CV9fbQ9vPCwgnuSsDCoQlibZikDVPAyVTvp7Jx +5rz8yXsl4sxvaeMZPqqFPtA/ENeQ3cpsyR1BQXSvoZpH1Fq0b8GcZTEdWWD/w6/K +MCRC8TmgEd+z3e8kIsCwFQ+TSHbCcxRWdgZE7gE31sJHHVkrZlXtLU8MPXWqslVz +R0cX+yC8j6bXI6/BqZ2SvRndJwuunRAr4um7AAYpAAf/QZsrrz0c7dgWwGqMIpw6 +fP+/lLa74+fa2CFRWtYowEiKsfDg/wN7Ua07036dNhPa8aZPsU6SRzm5PybKOURe +D9pNt0FxJkX0j5pCWfjSJgTbc1rCdqZ/oyBk/U6pQtf//zfw3PbDl7I8TC6GOt2w +5NgcXdsWHP7LAmPctOVUyzFsenevR0MFTHkMbmKI1HpFm8XN/e1Fl+qIAD+OagTF +5B32VvpoJtkh5nxnIuToNJsa9Iy7F9MM2CeFOyTMihMcjXKBBUaAYoF115irBvqu +7N/qWmzqLg8yxBZ56mh6meCF3+67VA2y7fL8rhw2QuqgLg1JFlKAVL+9crCSrn// +GQQA1kT7FytW6BNOffblFYZkrJer3icoRDqa/ljgH/yVaWoVT1igy0E9XzYO7MwP +2usj/resLy0NC1qCthk51cZ/wthooMl88e5Wb4l5FYwBEac7muSBTo4W8cAH1hFj +TWL6XAGvEzGX3Mt9pn8uYGlQLZAhJoNCAU2EOCbN1PchDvsEAOWNKYesuUVk8+sQ +St0NDNhd9BWtTWTHkCZb1dKC3JTfr9PqkTBLrWFbYjkOtvdPAW7FDaXXXZfdH1jH +WfwP3Q+I6sqgSaWpCS4dBAns3/RVtO7czVgyIwma04iIvJqderYrfvkUq95KfwP2 +V8wXkhrPPPxyrg5y3wQlpY2jb5RBBAC17SK1ms+DBtck4vpdjp3SJ32SbyC/DU30 +89Q12j74S7Zdu1qZlKnvy3kWPYX/hMuSzGZ+mLVJNFEqH2X01aFzppYz0hdI9PGB +9tTFEqZWQL9ZkXfjc79Cgnt12pNukRbtw0N/kyutOdIFHVT79wVAd+powqziXJsC +Kc+4xjwSCkZitB5SU0EgMjA0OCA8cnNhMjA0OEBleGFtcGxlLm9yZz6JATQEEwEC +AB4FAkLIJbECGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQnc+OnJvTHyQqHwf8 +DtzuAGmObfe3ggtn14x2wnU1Nigebe1K5liRnrLuVlLBpdO6CWmMUzfKRvyZlx54 +GlA9uUQSjW+RlgejdOTQqesDrcTEukYd4yzwbLZyM5Gb3lsE/FEmE7Dxw/0Utf59 +uACqzG8LACQn9J6sEgZWKxAupuYTHXd12lDPD3dnU4uzKPhMcjnSN00pzjusP7C9 +NZd3OLkAx2vw/dmb4Q+/QxeZhVYYsAUuR2hv9bgGWopumlOkt8Zu5YG6+CtTbJXp +rPI7pJ1jHbeE+q/29hWJQtS8Abx82AcOkzhvS3NZKoJ/1DrGgoDAu1mGkM4KvLAx +fDs/qQ9dZhtEmDbKPLTVEA== +=WKAv +-----END PGP PRIVATE KEY BLOCK----- +'); + +insert into keytbl (id, name, pubkey, seckey) +values (5, 'psw-elg1024', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQGiBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQLQfRWxnYW1hbCAx +MDI0IDx0ZXN0QGV4YW1wbGUub3JnPoheBBMRAgAeBQJCyCFIAhsDBgsJCAcDAgMV +AgMDFgIBAh4BAheAAAoJEBwpvA0YF3NkOtsAniI9W2bC3CxARTpYrev7ihreDzFc +AJ9WYLQxDQAi5Ec9AQoodPkIagzZ4LkBDQRCyCFKEAQAh5SNbbJMAsJ+sQbcWEzd +ku8AdYB5zY7Qyf9EOvn0g39bzANhxmmb6gbRlQN0ioymlDwraTKUAfuCZgNcg/0P +sxFGb9nDcvjIV8qdVpnq1PuzMFuBbmGI6weg7Pj01dlPiO0wt1lLX+SubktqbYxI ++h31c3RDZqxj+KAgxR8YNGMAAwYD+wQs2He1Z5+p4OSgMERiNzF0acZUYmc0e+/9 +6gfL0ft3IP+SSFo6hEBrkKVhZKoPSSRr5KpNaEobhdxsnKjUaw/qyoaFcNMzb4sF +k8wq5UlCkR+h72u6hv8FuleCV8SJUT1U2JjtlXJR2Pey9ifh8rZfu57UbdwdHa0v +iWc4DilhiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCfdPom+HlNVE9F +ig3hGY1Rb4NEk1gAn1u9IuQB+BgDP40YHHz6bKWS/x80 +=RWci +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQHpBELIIUgRBACp401L6jXrLB28c3YA4sM3OJKnxM1GT9YTkWyE3Vyte65H8WU9 +tGPBX7OMuaX5eGZ84LFUGvaP0k7anfmXcDkCO3P9GgL+ro/dS2Ps/vChQPZqHaxE +xpKDUt47B7DGdRJrC8DRnIR4wbSyQA6ma3S1yFqC5pJhSs+mqf9eExOjiwCgntth +klRxIYw352ZX9Ov9oht/p/ED/1Xi4PS+tkXVvyIw5aZfa61bT6XvDkoPI0Aj3GE5 +YmCHJlKA/IhEr8QJOLV++5VEv4l6KQ1/DFoJzoNdr1AGJukgTc6X/WcQRzfQtUic +PHQme5oAWoHa6bVQZOwvbJh3mOXDq/Tk/KF22go8maM44vMn4bvv+SBbslviYLiL +jZJ1A/9JXF1esNq+X9HehJyqHHU7LEEf/ck6zC7o2erM3/LZlZuLNPD2cv3oL3Nv +saEgcTSZl+8XmO8pLmzjKIb+hi70qVx3t2IhMqbb4B/dMY1Ck62gPBKa81/Wwi7v +IsEBQLEtyBmGmI64YpzoRNFeaaF9JY+sAKqROqe6dLjJ7vebQP4HAwImKZ5q2QwT +D2DDAY/IQBjes7WgqZeacfLPDoB8ecD/KLoSCH6Z3etvbPHSOKiazxoJ962Ix74H +ZAE6ZbMTtl5dZW1ptB9FbGdhbWFsIDEwMjQgPHRlc3RAZXhhbXBsZS5vcmc+iF4E +ExECAB4FAkLIIUgCGwMGCwkIBwMCAxUCAwMWAgECHgECF4AACgkQHCm8DRgXc2Q6 +2wCfXKegLIzoYi8cM57DCYXhn+MZB/MAn1D4zAi5uLQBJ8mJ9oQzbewgfAeinQFf +BELIIUoQBACHlI1tskwCwn6xBtxYTN2S7wB1gHnNjtDJ/0Q6+fSDf1vMA2HGaZvq +BtGVA3SKjKaUPCtpMpQB+4JmA1yD/Q+zEUZv2cNy+MhXyp1WmerU+7MwW4FuYYjr +B6Ds+PTV2U+I7TC3WUtf5K5uS2ptjEj6HfVzdENmrGP4oCDFHxg0YwADBgP7BCzY +d7Vnn6ng5KAwRGI3MXRpxlRiZzR77/3qB8vR+3cg/5JIWjqEQGuQpWFkqg9JJGvk +qk1oShuF3GycqNRrD+rKhoVw0zNviwWTzCrlSUKRH6Hva7qG/wW6V4JXxIlRPVTY +mO2VclHY97L2J+Hytl+7ntRt3B0drS+JZzgOKWH+BwMCJimeatkMEw9gRkFjt4Xa +9rX8awMBE5+vVcGKv/DNiCvJnlYvSdCj8VfuHsYFliiJo6u17NJon+K43e3yvDNk +f631VOVanGEz7TyqOkWQiEkEGBECAAkFAkLIIUoCGwwACgkQHCm8DRgXc2TtrwCe +IUWi3DXHZf6ivK7dDec22bGgoekAn0dTuPDvJ2Dfd0j0nyBWSuaxJnb/ +=SNvr +-----END PGP PRIVATE KEY BLOCK----- +'); + +insert into keytbl (id, name, pubkey, seckey) +values (6, 'rsaenc2048', ' +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +mQELBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj +UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW +czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT +4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ +dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4 +NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYptCVSU0EgMjA0OCBFbmMgPHJz +YTIwNDhlbmNAZXhhbXBsZS5vcmc+iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMV +AgMDFgIBAh4BAheAAAoJEMiZ6pNEGVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8 +JCaxHa31wH3PKqGtq2M+cpb2rXf7gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw ++IkhihEb5bWxQBNj+3zAFs1YX6v2HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzku +UOhcgfpBgIt3Q+MpT6M2+OIF7lVfSb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQ +RJ6DWH61F8fFIDJg1z+A/Obx4fqX6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO8 +0g/oVYBbuvOYedffDBeQarhERZ5W2TnIE+nqY61YOLBqosliygdZTXULzNi5AQsE +QuvaugEIAOuCJZdkzORA6e1lr81Lnr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPX +z1YIrMTu+1rMIiy10IWbA6zgMTpzPhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfK +lAXIxJurCHXTbEa+YvPdn76vJ3HsXOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zE +FZcAoKbFqAAjDLEai64SoOFh0W3CsD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9U +rHlolqYNhYze/uRLyfnUx9PN4r/GhEzauyDMV0smo91uB3aewPft+eCpmeWnu0PF +JVK4xyRmhIq2rVCw16a1pBJirvGM+y0ABimJAR8EGAECAAkFAkLr2roCGwwACgkQ +yJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+1iqYE0B0 +WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwarANhHxkWg +w/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx2eVM1k+X +dmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOyla+wJdro +PYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CChbt6LhKh +CLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug== +=pwU2 +-----END PGP PUBLIC KEY BLOCK----- +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.1 (GNU/Linux) + +lQOWBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj +UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW +czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT +4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ +dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4 +NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYpAAf9GuKpxrXp267eSPw9ZeSw +Ik6ob1I0MHbhhHeaXQnF0SuOViJ1+Bs74hUB3/F5fqrnjVLIS/ysYzegYpbpXOIa +MZwYcp2e+dpmVb7tkGQgzXH0igGtBQBqoSUVq9mG2XKPVh2JmiYgOH6GrHSGmnCq +GCgEK4ezSomB/3OtPFSjAxOlSw6dXSkapSxW3pEGvCdaWd9p8yl4rSpGsZEErPPL +uSbZZrHtWfgq5UXdPeE1UnMlBcvSruvpN4qgWMgSMs4d2lXvzXJLcht/nryP+atT +H1gwnRmlDCVv5BeJepKo3ORJDvcPlXkJPhqS9If3BhTqt6QgQEFI4aIYYZOZpZoi +2QQA2Zckzktmsc1MS04zS9gm1CbxM9d2KK8EOlh7fycRQhYYqqavhTBH2MgEp+Dd +ZtuEN5saNDe9x/fwi2ok1Bq6luGMWPZU/nZe7fxadzwfliy/qPzStWFW3vY9mMLu +6uEqgjin/lf4YrAswXDZaEc5e4GuNgGfwr27hpjxE1jg3PsEAPMqXEOMT2yh+yRu +DlLRbFhYOI4aUHY2CGoQQONnwv2O5gFvmOcPlg3J5lvnwlOYCx0c3bDxAtHyjPJq +FAZqcJBaB9RDhKHwlWDrbx/6FPH2SuKE+u4msIhPFin4V3FAP+yTem/TKrdnaWy6 +EUrhCWTXVRTijBaCudfjFd/ipHZbA/0dv7UAcoWK6kiVLzyE+jOvtN+ZxTzxq7CW +mlFPgAC966hgJmz9IXqadtMgPAoL3PK9q1DbPM3JhsQcJrNzTJqZrdN1/kPU0HHa ++aof1BVy3wSvp2mXgaRUULStyhUIyBRM6hAYp3/MoWEYn/bwr+zQkIU8Zsk6OsZ6 +q1xE3cowrUWFtCVSU0EgMjA0OCBFbmMgPHJzYTIwNDhlbmNAZXhhbXBsZS5vcmc+ +iQE0BBMBAgAeBQJC69ptAhsDBgsJCAcDAgMVAgMDFgIBAh4BAheAAAoJEMiZ6pNE +GVVZHMkIAJtGHHZ9iM8Yq1rr0zl1L6SvlQP8JCaxHa31wH3PKqGtq2M+cpb2rXf7 +gAY/doHJPXggfVzkyFrysmQ1gPbDGYLyOutw+IkhihEb5bWxQBNj+3zAFs1YX6v2 +HXWbSUSmyY1V9/+NTtKk03olDc/swd3lXzkuUOhcgfpBgIt3Q+MpT6M2+OIF7lVf +Sb1rWdpwTfGhZzW9szQOeoS4gPvxCCRyuabQRJ6DWH61F8fFIDJg1z+A/Obx4fqX +6GOA69RzgZ3oukFBIXxNwV9PZNnAmHtZVYO80g/oVYBbuvOYedffDBeQarhERZ5W +2TnIE+nqY61YOLBqosliygdZTXULzNidA5YEQuvaugEIAOuCJZdkzORA6e1lr81L +nr4JzMsVBFA+X/yIkBbV6qX/A4nVSLAZKNPXz1YIrMTu+1rMIiy10IWbA6zgMTpz +PhJRfgePONgdnCYyK5Ksh5/C5ntzKwwGwxfKlAXIxJurCHXTbEa+YvPdn76vJ3Hs +XOXVEL+fLb4U3l3Ng87YM202Lh1Ha2MeS2zEFZcAoKbFqAAjDLEai64SoOFh0W3C +sD1DL4zmfp+YZrUPHTtZadsi53i4KKW/ws9UrHlolqYNhYze/uRLyfnUx9PN4r/G +hEzauyDMV0smo91uB3aewPft+eCpmeWnu0PFJVK4xyRmhIq2rVCw16a1pBJirvGM ++y0ABikAB/oC3z7lv6sVg+ngjbpWy9lZu2/ECZ9FqViVz7bUkjfvSuowgpncryLW +4EpVV4U6mMSgU6kAi5VGT/BvYGSAtnqDWGiPs7Kk+h4Adz74bEAXzU280pNBtSfX +tGvzlS4a376KzYFSCJDRBdMebEhJMbY0wQmR8lTZu5JSUI4YYEuN0c7ckdsw8w42 +QWTLonG8HC6h8UPKS0EAcaCo7tFubMIesU6cWuTYucsHE+wjbADjuSNX968qczNe +NoL2BUznXOQoPu6HQO4/8cr7ib+VQkB2bHQcMoZazPUStIID1e4CL4XcxfuAmT8o +3XDvMLgVqNp5W2f8Mzmk3/DbtsLXLOv5BADsCzQpseC8ikSYJC72hcon1wlUmGeH +3qgGiiHhYXFa18xgI5juoO8DaWno0rPPlgr36Y8mSB5qjYHMXwjKnKyUmt11H+hU ++6uk4hq3Rjd8l+vfuOSr1xoTrtBUg9Rwfw6JVo0DC+8CWg4oBWsLXVM6KQXPFdJs +8kyFQplR/iP1XQQA/2tbDANjAYGNNDjJO9/0kEnSAUyYMasFJDrA2q17J5CroVQw +QpMmWwdDkRANUVPKnWHS5sS65BRc7UytKe2f3A3ZInGXJIK2Hl+TzapWYcYxql+4 +ol5mEDDMDbhEE8Wmj9KyB6iifdLI0K+yxNb9T4Jpj3J18+St+G8+9AcFcBEEAM1b +M9C+/05cnV8gjcByqH9M9ypo8fzPvMKVXWwCLQXpaL50QIkzLURkiMoEWrCdELaA +sVPotRzePTIQ1ooLeDxd1gRnDqjZiIR0kwmv6vq8tfzY96O2ZbGWFI5eth89aWEJ +WB8AR3zYcXpwJLwPuhXW2/NlZF0bclJ3jNzAfTIeQmeJAR8EGAECAAkFAkLr2roC +GwwACgkQyJnqk0QZVVku1wgAg1bLSjPkhw+ldG5HzumpqR84+JKyozdJaJzefu2+ +1iqYE0B0WLz2PJVIiK41xiEkKhBvTOQYuXmtWqAWXptD91P5SoXoNJWLQO3TNwar +ANhHxkWgw/TOUxQqoctlRUej5NDD+4eW5G9lcS1FEGuKDWtX096u80vO+TbyJjvx +2eVM1k+XdmeYsGOiNgDimCreJGYc14G7eY9jt24gw10n1sMAKI1qm6lcoHqZ9OOy +la+wJdroPYZGO7R8+1O9R22WrK6BYDT5j/1JwMZqbOESjNvDEVT0yOHClCHRN4CC +hbt6LhKhCLUNdz/udIt0JAC6c/HdPLSW3HnmM3+iNj+Kug== +=UKh3 +-----END PGP PRIVATE KEY BLOCK----- +'); + +insert into keytbl (id, name, pubkey, seckey) +values (7, 'rsaenc2048-psw', ' +same key with password +', ' +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.11 (GNU/Linux) + +lQPEBELr2m0BCADOrnknlnXI0EzRExf/TgoHvK7Xx/E0keWqV3KrOyC3/tY2KOrj +UVxaAX5pkFX9wdQObGPIJm06u6D16CH6CildX/vxG7YgvvKzK8JGAbwrXAfk7OIW +czO2zRaZGDynoK3mAxHRBReyTKtNv8rDQhuZs6AOozJNARdbyUO/yqUnqNNygWuT +4htFDEuLPIJwAbMSD0BvFW6YQaPdxzaAZm3EWVNbwDzjgbBUdBiUUwRdZIFUhsjJ +dirFdy5+uuZru6y6CNC1OERkJ7P8EyoFiZckAIE5gshVZzNuyLOZjc5DhWBvLbX4 +NZElAnfiv+4nA6y8wQLSIbmHA3nqJaBklj85AAYp/gcDCNnoEKwFo86JYCE1J92R +HRQ7DoyAZpW1O0dTXL8Epk0sKsKDrCJOrIkDymsjfyBexADIeqOkioy/50wD2Mku +CVHKWO2duAiJN5t/FoRgpR1/Q11K6QdfqOG0HxwfIXLcPv7eSIso8kWorj+I01BP +Fn/atGEbIjdWaz/q2XHbu0Q3x6Et2gIsbLRVMhiYz1UG9uzGJ0TYCdBa2SFhs184 +52akMpD+XVdM0Sq9/Cx40Seo8hzERB96+GXnQ48q2OhlvcEXiFyD6M6wYCWbEV+6 +XQVMymbl22FPP/bD9ReQX2kjrkQlFAtmhr+0y8reMCbcxwLuQfA3173lSPo7jrbH +oLrGhkRpqd2bYCelqdy/XMmRFso0+7uytHfTFrUNfDWfmHVrygoVrNnarCbxMMI0 +I8Q+tKHMThWgf0rIOSh0+w38kOXFCEqEWF8YkAqCrMZIlJIed78rOCFgG4aHajZR +D8rpXdUOIr/WeUddK25Tu8IuNJb0kFf12IMgNh0nS+mzlqWiofS5kA0TeB8wBV6t +RotaeyDNSsMoowfN8cf1yHMTxli+K1Tasg003WVUoWgUc+EsJ5+KTNwaX5uGv0Cs +j6dg6/FVeVRL9UsyF+2kt7euX3mABuUtcVGx/ZKTq/MNGEh6/r3B5U37qt+FDRbw +ppKPc2AP+yBUWsQskyrxFgv4eSpcLEg+lgdz/zLyG4qW4lrFUoO790Cm/J6C7/WQ +Z+E8kcS8aINJkg1skahH31d59ZkbW9PVeJMFGzNb0Z2LowngNP/BMrJ0LT2CQyLs +UxbT16S/gwAyUpJnbhWYr3nDdlwtC0rVopVTPD7khPRppcsq1f8D70rdIxI4Ouuw +vbjNZ1EWRJ9f2Ywb++k/xgSXwJkGodUlrUr+3i8cv8mPx+fWvif9q7Y5Ex1wCRa8 +8FAj/o+hEbQlUlNBIDIwNDggRW5jIDxyc2EyMDQ4ZW5jQGV4YW1wbGUub3JnPokB +NAQTAQIAHgUCQuvabQIbAwYLCQgHAwIDFQIDAxYCAQIeAQIXgAAKCRDImeqTRBlV +WRzJCACbRhx2fYjPGKta69M5dS+kr5UD/CQmsR2t9cB9zyqhratjPnKW9q13+4AG +P3aByT14IH1c5Mha8rJkNYD2wxmC8jrrcPiJIYoRG+W1sUATY/t8wBbNWF+r9h11 +m0lEpsmNVff/jU7SpNN6JQ3P7MHd5V85LlDoXIH6QYCLd0PjKU+jNvjiBe5VX0m9 +a1nacE3xoWc1vbM0DnqEuID78Qgkcrmm0ESeg1h+tRfHxSAyYNc/gPzm8eH6l+hj +gOvUc4Gd6LpBQSF8TcFfT2TZwJh7WVWDvNIP6FWAW7rzmHnX3wwXkGq4REWeVtk5 +yBPp6mOtWDiwaqLJYsoHWU11C8zYnQPEBELr2roBCADrgiWXZMzkQOntZa/NS56+ +CczLFQRQPl/8iJAW1eql/wOJ1UiwGSjT189WCKzE7vtazCIstdCFmwOs4DE6cz4S +UX4HjzjYHZwmMiuSrIefwuZ7cysMBsMXypQFyMSbqwh102xGvmLz3Z++rydx7Fzl +1RC/ny2+FN5dzYPO2DNtNi4dR2tjHktsxBWXAKCmxagAIwyxGouuEqDhYdFtwrA9 +Qy+M5n6fmGa1Dx07WWnbIud4uCilv8LPVKx5aJamDYWM3v7kS8n51MfTzeK/xoRM +2rsgzFdLJqPdbgd2nsD37fngqZnlp7tDxSVSuMckZoSKtq1QsNemtaQSYq7xjPst +AAYp/gcDCNnoEKwFo86JYAsxoD+wQ0zBi5RBM5EphXTpM1qKxmigsKOvBSaMmr0y +VjHtGY3poyV3t6VboOGCsFcaKm0tIdDL7vrxxwyYESETpF29b7QrYcoaLKMG7fsy +t9SUI3UV2H9uUquHgqHtsqz0jYOgm9tYnpesgQ/kOAWI/tej1ZJXUIWEmZMH/W6d +ATNvZ3ivwApfC0qF5G3oPgBSoIuQ/8I+pN/kmuyNAnJWNgagFhA/2VFBvh5XgztV +NW7G//KpR1scsn140SO/wpGBM3Kr4m8ztl9w9U6a7NlQZ2ub3/pIUTpSzyLBxJZ/ +RfuZI7ROdgDMKmEgCYrN2kfp0LIxnYL6ZJu3FDcS4V098lyf5rHvB3PAEdL6Zyhd +qYp3Sx68r0F4vzk5iAIWf6pG2YdfoP2Z48Pmq9xW8qD9iwFcoz9oAzDEMENn6dfq +6MzfoaXEoYp8cR/o+aeEaGUtYBHiaxQcJYx35B9IhsXXA49yRORK8qdwhSHxB3NQ +H3pUWkfw368f/A207hQVs9yYXlEvMZikxl58gldCd3BAPqHm/XzgknRRNQZBPPKJ +BMZebZ22Dm0qDuIqW4GXLB4sLf0+UXydVINIUOlzg+S4jrwx7eZqb6UkRXTIWVo5 +psTsD14wzWBRdUQHZOZD33+M8ugmewvLY/0Uix+2RorkmB7/jqoZvx/MehDwmCZd +VH8sb2wpZ55sj7gCXxvrfieQD/VeH54OwjjbtK56iYq56RVD0h1az8xDY2GZXeT7 +J0c3BGpuoca5xOFWr1SylAr/miEPxOBfnfk8oZQJvZrjSBGjsTbALep2vDJk8ROD +sdQCJuU1RHDrwKHlbUL0NbGRO2juJGsatdWnuVKsFbaFW2pHHkezKuwOcaAJv7Xt +8LRF17czAJ1uaLKwV8Paqx6UIv+089GbWZi7HIkBHwQYAQIACQUCQuvaugIbDAAK +CRDImeqTRBlVWS7XCACDVstKM+SHD6V0bkfO6ampHzj4krKjN0lonN5+7b7WKpgT +QHRYvPY8lUiIrjXGISQqEG9M5Bi5ea1aoBZem0P3U/lKheg0lYtA7dM3BqsA2EfG +RaDD9M5TFCqhy2VFR6Pk0MP7h5bkb2VxLUUQa4oNa1fT3q7zS875NvImO/HZ5UzW +T5d2Z5iwY6I2AOKYKt4kZhzXgbt5j2O3biDDXSfWwwAojWqbqVygepn047KVr7Al +2ug9hkY7tHz7U71HbZasroFgNPmP/UnAxmps4RKM28MRVPTI4cKUIdE3gIKFu3ou +EqEItQ13P+50i3QkALpz8d08tJbceeYzf6I2P4q6 +=QFm5 +-----END PGP PRIVATE KEY BLOCK----- +'); + + +-- elg1024 / aes128 +insert into encdata (id, data) values (1, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQEOA9k2z2S7c/RmEAQAgVWW0DeLrZ+1thWJGBPp2WRFL9HeNqqWHbKJCXJbz1Uy +faUY7yxVvG5Eutmo+JMiY3mg23/DgVVXHQZsTWpGvGM6djgUNGKUjZDbW6Nog7Mr +e78IywattCOmgUP9vIwwg3OVjuDCN/nVirGQFnXpJBc8DzWqDMWRWDy1M0ZsK7AD +/2JTosSFxUdpON0DKtIY3GLzmh6Nk3iV0g8VgJKUBT1rhCXuMDj3snm//EMm7hTY +PlnObq4mIhgz8NqprmhooxnU0Kapofb3P3wCHPpU14zxhXY8iKO/3JhBq2uFcx4X +uBMwkW4AdNxY/mzJZELteTL8Tr0s7PISk+owb4URpG3n0jsBc0CVULxrjh5Ejkdw +wCM195J6+KbQxOOFQ0b3uOVvv4dEgd/hRERCOq5EPaFhlHegyYJ7YO842vnSDA== +=PABx +-----END PGP MESSAGE----- +'); + +-- elg2048 / blowfish +insert into encdata (id, data) values (2, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQIOAywibh/+XMfUEAf+OINhBngEsw4a/IJIeJvUgv1gTQzBwOdQEuc/runr4Oa8 +Skw/Bj0X/zgABVZLem1a35NHaNwaQaCFwMQ41YyWCu+jTdsiyX/Nw0w8LKKz0rNC +vVpG6YuV7Turtsf8a5lXy1K0SHkLlgxQ6c76GS4gtSl5+bsL2+5R1gSRJ9NXqCQP +OHRipEiYwBPqr5R21ZG0FXXNKGOGkj6jt/M/wh3WVtAhYuBI+HPKRfAEjd/Pu/eD +e1zYtkH1dKKFmp44+nF0tTI274xpuso7ShfKYrOK3saFWrl0DWiWteUinjSA1YBY +m7dG7NZ8PW+g1SZWhEoPjEEEHz3kWMvlKheMRDudnQf/dDyX6kZVIAQF/5B012hq +QyVewgTGysowFIDn01uIewoEA9cASw699jw9IoJp+k5WZXnU+INllBLzQxniQCSu +iEcr0x3fYqNtj9QBfbIqyRcY6HTWcmzyOUeGaSyX76j+tRAvtVtXpraFFFnaHB70 +YpXTjLkp8EBafzMghFaKDeXlr2TG/T7rbwcwWrFIwPqEAUKWN5m97Q3eyo8/ioMd +YoFD64J9ovSsgbuU5IpIGAsjxK+NKzg/2STH7zZFEVCtgcIXsTHTZfiwS98/+1H9 +p1DIDaXIcUFV2ztmcKxh9gt2sXRz1W+x6D8O0k3nanU5yGG4miLKaq18fbcA0BD1 ++NIzAfelq6nvvxYKcGcamBMgLo5JkZOBHvyr6RsAKIT5QYc0QTjysTk9l0Am3gYc +G2pAE+3k +=TBHV +-----END PGP MESSAGE----- +'); + +-- elg4096 / aes256 +insert into encdata (id, data) values (3, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQQOA7aFBP0Sjh/5EA/+JCgncc8IZmmRjPStWnGf9tVJhgHTn+smIclibGzs0deS +SPSCitzpblwbUDvu964+/5e5Q1l7rRuNN+AgETlEd4eppv7Swn2ChdgOXxRwukcT +Nh3G+PTFvD4ayi7w1db3qvXIt0MwN4Alt436wJmK1oz2Ka9IcyO+wHWrDy1nSGSx +z5x7YEj+EZPgWc/YAvudqE8Jpzd/OT5zSHN09UFkIAk6NxisKaIstbEGFgpqtoDZ +1SJM84XAdL2IcaJ3YY7k/yzwlawhsakKd4GSd5vWmAwvyzzbSiBMfKsDE16ePLNU +ZBF7CzmlCBPZ7YrFAHLpXBXXkCQvzD2BEYOjse50ZEfJ036T7950Ozcdy1EQbGon +nyQ4Gh0PBpnMcBuiXOceWuYzhlzFOzDtlVKdNTxFRDcbEyW2jo9xQYvCCLnYy8EH +2M7S8jCtVYJBbn63a82ELv+3+kWYcsvBJv2ZVBh4ncrBu9o0P+OYS7ApoOU+j6p2 ++t0RXHksqXS1YiUwYF5KSw09EbYMgNZ9G04Px/PxLU6fSC9iDrGX7Xt3kOUP0mku +C518fPckT0zzRXqfFruJNRzDytW50KxkOQZzU1/Az1YlYN9QzWeU4EtLPb2fftZo +D0qH/ln+f9Op5t6sD2fcxZVECU1b/bFtZsxvwH406YL+UQ7hU/XnZrzVVzODal8P +/j1hg7v7BdJqu1DTp9nFWUuwMFcYAczuXn29IG183NZ7Ts4whDeYEhS8eNoLPX4j +txY12ILD/w/3Q4LoW/hPa6OdfEzsn0U5GLf1WiGmJE1H6ft2U/xUnerc/u0kt+FU +WAisArd4MuKtf7B5Vu/VF3kUdrR0hTniUKUivmC4o1jSId31Dufxj4aadVyldXAr +6TNBcdyragZjxEZ6hsBCYzA0Rd1a8atd6OaQoIEEfAzCu5Ks29pydHErStYGjWJ1 +KA5KPLVvjbHpDmRhlCcm8vgpYQsBYEB5gE9fx5yCTlsVhCB6y23h7hfdMqerDqkO +ZOPsO5h+tiHCdIrQ36sMjuINy1/K2rYcXd+Crh2iHcfidpU9fvDz2ihTRNQlhjuT +0cQZM5JhctEx4VXF4LDctRhit7Hn0iqsk604woQfJVvP8O673xSXT/kBY0A/v9C0 +3C4YoFNeSaKwbfZQ/4u1ZFPJxK2IIJa8UGpyAUewLMlzGVVagljybv/f4Z9ERAhy +huq5sMmw8UPsrJF2TUGHz5WSIwoh0J/qovoQI09I9sdEnFczDvRavMO2Mldy3E5i +exz9oewtel6GOmsZQSYWT/vJzbYMmvHNmNpVwwoKrLV6oI3kyQ80GHBwI1WlwHoK +2iRB0w8q4VVvJeYAz8ZIp380cqC3pfO0uZsrOx4g3k4X0jsB5y7rF5xXcZfnVbvG +DYKcOy60/OHMWVvpw6trAoA+iP+cVWPtrbRvLglTVTfYmi1ToZDDipkALBhndQ== +=L/M/ +-----END PGP MESSAGE----- +'); + +-- rsaenc2048 / aes128 +insert into encdata (id, data) values (4, ' +-----BEGIN PGP MESSAGE----- +Version: GnuPG v1.4.1 (GNU/Linux) + +hQEMA/0CBsQJt0h1AQf+JyYnCiortj26P11zk28MKOGfWpWyAhuIgwbJXsdQ+e6r +pEyyqs9GC6gI7SNF6+J8B/gsMwvkAL4FHAQCvA4ZZ6eeXR1Of4YG22JQGmpWVWZg +DTyfhA2vkczuqfAD2tgUpMT6sdyGkQ/fnQ0lknlfHgC5GRx7aavOoAKtMqiZW5PR +yae/qR48mjX7Mb+mLvbagv9mHEgQSmHwFpaq2k456BbcZ23bvCmBnCvqV/90Ggfb +VP6gkSoFVsJ19RHsOhW1dk9ehbl51WB3zUOO5FZWwUTY9DJvKblRK/frF0+CXjE4 +HfcZXHSpSjx4haGGTsMvEJ85qFjZpr0eTGOdY5cFhNJAAVP8MZfji7OhPRAoOOIK +eRGOCkao12pvPyFTFnPd5vqmyBbdNpK4Q0hS82ljugMJvM0p3vJZVzW402Kz6iBL +GQ== +=XHkF +-----END PGP MESSAGE----- +'); + +-- rsaenc2048 / aes128 (not from gnupg) +insert into encdata (id, data) values (5, ' +-----BEGIN PGP MESSAGE----- + +wcBMA/0CBsQJt0h1AQgAzxZ8j+OTeZ8IlLxfZ/mVd28/gUsCY+xigWBk/anZlK3T +p2tNU2idHzKdAttH2Hu/PWbZp4kwjl9spezYxMqCeBZqtfGED88Y+rqK0n/ul30A +7jjFHaw0XUOqFNlST1v6H2i7UXndnp+kcLfHPhnO5BIYWxB2CYBehItqtrn75eqr +C7trGzU/cr74efcWagbCDSNjiAV7GlEptlzmgVMmNikyI6w0ojEUx8lCLc/OsFz9 +pJUAX8xuwjxDVv+W7xk6c96grQiQlm+FLDYGiGNXoAzx3Wi/howu3uV40dXfY+jx +3WBrhEew5Pkpt1SsWoFnJWOfJ8GLd0ec8vfRCqAIVdLgAeS7NyawQYtd6wuVrEAj +5SMg4Thb4d+g45RksuGLHUUr4qO9tiXglODa4InhmJfgNuLk+RGz4LXjq8wepEmW +vRbgFOG54+Cf4C/gC+HkreDm5JKSKjvvw4B/jC6CDxq+JoziEe2Z1uEjCuEcr+Es +/eGzeOi36BejXPMHeKxXejj5qBBHKV0pHVhZSgffR0TtlXdB967Yl/5agV0R89hI +7Gw52emfnH4Z0Y4V0au2H0k1dR/2IxXdJEWSTG7Be1JHT59p9ei2gSEOrdBMIOjP +tbYYUlmmbvD49bHfThkDiC+oc9947LgQsk3kOOLbNHcjkbrjH8R5kjII4m/SEZA1 +g09T+338SzevBcVXh/cFrQ6/Et+lyyO2LJRUMs69g/HyzJOVWT2Iu8E0eS9MWevY +Qtrkrhrpkl3Y02qEp/j6M03Yu2t6ZF7dp51aJ5VhO2mmmtHaTnCyCc8Fcf72LmD8 +blH2nKZC9d6fi4YzSYMepZpMOFR65M80MCMiDUGnZBB8sEADu2/iVtqDUeG8mAA= +=PHJ1 +-----END PGP MESSAGE----- +'); + +-- successful decrypt +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=1 and encdata.id=1; + +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=2 and encdata.id=2; + +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=3 and encdata.id=3; + +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=6 and encdata.id=4; + +-- wrong key +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=2 and encdata.id=1; + +-- sign-only key +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=4 and encdata.id=1; + +-- rsa: password-protected secret key, wrong password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), '123') +from keytbl, encdata where keytbl.id=7 and encdata.id=4; + +-- rsa: password-protected secret key, right password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool') +from keytbl, encdata where keytbl.id=7 and encdata.id=4; + +-- password-protected secret key, no password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=5 and encdata.id=1; + +-- password-protected secret key, wrong password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'foo') +from keytbl, encdata where keytbl.id=5 and encdata.id=1; + +-- password-protected secret key, right password +select pgp_pub_decrypt(dearmor(data), dearmor(seckey), 'parool') +from keytbl, encdata where keytbl.id=5 and encdata.id=1; + +-- test for a short read from prefix_init +select pgp_pub_decrypt(dearmor(data), dearmor(seckey)) +from keytbl, encdata where keytbl.id=6 and encdata.id=5; diff --git a/contrib/pgcrypto/sql/pgp-pubkey-encrypt.sql b/contrib/pgcrypto/sql/pgp-pubkey-encrypt.sql new file mode 100644 index 0000000..c9edbc6 --- /dev/null +++ b/contrib/pgcrypto/sql/pgp-pubkey-encrypt.sql @@ -0,0 +1,48 @@ +-- +-- PGP Public Key Encryption +-- + +-- successful encrypt/decrypt +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=1; + +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=2; + +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=3; + +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=6; + +-- try with rsa-sign only +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=4; + +-- try with secret key +select pgp_pub_decrypt( + pgp_pub_encrypt('Secret msg', dearmor(seckey)), + dearmor(seckey)) +from keytbl where keytbl.id=1; + +-- does text-to-bytea works +select encode(pgp_pub_decrypt_bytea( + pgp_pub_encrypt('Secret msg', dearmor(pubkey)), + dearmor(seckey)), 'escape') +from keytbl where keytbl.id=1; + +-- and bytea-to-text? +select pgp_pub_decrypt( + pgp_pub_encrypt_bytea('Secret msg', dearmor(pubkey)), + dearmor(seckey)) +from keytbl where keytbl.id=1; diff --git a/contrib/pgcrypto/sql/pgp-zlib-DISABLED.sql b/contrib/pgcrypto/sql/pgp-zlib-DISABLED.sql new file mode 100644 index 0000000..6f4eccd --- /dev/null +++ b/contrib/pgcrypto/sql/pgp-zlib-DISABLED.sql @@ -0,0 +1 @@ +-- zlib is disabled diff --git a/contrib/pgcrypto/sql/rijndael.sql b/contrib/pgcrypto/sql/rijndael.sql new file mode 100644 index 0000000..a276641 --- /dev/null +++ b/contrib/pgcrypto/sql/rijndael.sql @@ -0,0 +1,72 @@ +-- +-- AES cipher (aka Rijndael-128, -192, or -256) +-- + +-- some standard Rijndael testvalues +SELECT encrypt( +'\x00112233445566778899aabbccddeeff', +'\x000102030405060708090a0b0c0d0e0f', +'aes-ecb/pad:none'); + +SELECT encrypt( +'\x00112233445566778899aabbccddeeff', +'\x000102030405060708090a0b0c0d0e0f1011121314151617', +'aes-ecb/pad:none'); + +SELECT encrypt( +'\x00112233445566778899aabbccddeeff', +'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', +'aes-ecb/pad:none'); + +-- cbc +SELECT encrypt( +'\x00112233445566778899aabbccddeeff', +'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', +'aes-cbc/pad:none'); + +-- without padding, input not multiple of block size +SELECT encrypt( +'\x00112233445566778899aabbccddeeff00', +'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', +'aes-cbc/pad:none'); + +-- key padding + +SELECT encrypt( +'\x0011223344', +'\x000102030405', +'aes-cbc'); + +SELECT encrypt( +'\x0011223344', +'\x000102030405060708090a0b0c0d0e0f10111213', +'aes-cbc'); + +SELECT encrypt( +'\x0011223344', +'\x000102030405060708090a0b0c0d0e0f101112131415161718191a1b', +'aes-cbc'); + +-- empty data +select encrypt('', 'foo', 'aes'); +-- 10 bytes key +select encrypt('foo', '0123456789', 'aes'); +-- 22 bytes key +select encrypt('foo', '0123456789012345678901', 'aes'); + +-- decrypt +select encode(decrypt(encrypt('foo', '0123456', 'aes'), '0123456', 'aes'), 'escape'); +-- data not multiple of block size +select encode(decrypt(encrypt('foo', '0123456', 'aes') || '\x00'::bytea, '0123456', 'aes'), 'escape'); +-- bad padding +-- (The input value is the result of encrypt_iv('abcdefghijklmnopqrstuvwxyz', '0123456', 'abcd', 'aes') +-- with the 16th byte changed (s/db/eb/) to corrupt the padding of the last block.) +select encode(decrypt_iv('\xa21a9c15231465964e3396d32095e67eb52bab05f556a581621dee1b85385789', '0123456', 'abcd', 'aes'), 'escape'); + +-- iv +select encrypt_iv('foo', '0123456', 'abcd', 'aes'); +select encode(decrypt_iv('\x2c24cb7da91d6d5699801268b0f5adad', '0123456', 'abcd', 'aes'), 'escape'); + +-- long message +select encrypt('Lets try a longer message.', '0123456789', 'aes'); +select encode(decrypt(encrypt('Lets try a longer message.', '0123456789', 'aes'), '0123456789', 'aes'), 'escape'); diff --git a/contrib/pgcrypto/sql/sha1.sql b/contrib/pgcrypto/sql/sha1.sql new file mode 100644 index 0000000..6d1f24e --- /dev/null +++ b/contrib/pgcrypto/sql/sha1.sql @@ -0,0 +1,11 @@ +-- +-- SHA1 message digest +-- + +SELECT digest('', 'sha1'); +SELECT digest('a', 'sha1'); +SELECT digest('abc', 'sha1'); +SELECT digest('message digest', 'sha1'); +SELECT digest('abcdefghijklmnopqrstuvwxyz', 'sha1'); +SELECT digest('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 'sha1'); +SELECT digest('12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'sha1'); diff --git a/contrib/pgcrypto/sql/sha2.sql b/contrib/pgcrypto/sql/sha2.sql new file mode 100644 index 0000000..3aafd35 --- /dev/null +++ b/contrib/pgcrypto/sql/sha2.sql @@ -0,0 +1,33 @@ +-- +-- SHA2 family +-- + +-- SHA224 +SELECT digest('', 'sha224'); +SELECT digest('a', 'sha224'); +SELECT digest('abc', 'sha224'); +SELECT digest('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', 'sha224'); +SELECT digest('12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'sha224'); + +-- SHA256 +SELECT digest('', 'sha256'); +SELECT digest('a', 'sha256'); +SELECT digest('abc', 'sha256'); +SELECT digest('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', 'sha256'); +SELECT digest('12345678901234567890123456789012345678901234567890123456789012345678901234567890', 'sha256'); + +-- SHA384 +SELECT digest('', 'sha384'); +SELECT digest('a', 'sha384'); +SELECT digest('abc', 'sha384'); +SELECT digest('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', 'sha384'); +SELECT digest('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu', 'sha384'); +SELECT digest('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz', 'sha384'); + +-- SHA512 +SELECT digest('', 'sha512'); +SELECT digest('a', 'sha512'); +SELECT digest('abc', 'sha512'); +SELECT digest('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq', 'sha512'); +SELECT digest('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu', 'sha512'); +SELECT digest('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz', 'sha512'); diff --git a/contrib/pgrowlocks/.gitignore b/contrib/pgrowlocks/.gitignore new file mode 100644 index 0000000..b4903eb --- /dev/null +++ b/contrib/pgrowlocks/.gitignore @@ -0,0 +1,6 @@ +# Generated subdirectories +/log/ +/results/ +/output_iso/ +/tmp_check/ +/tmp_check_iso/ diff --git a/contrib/pgrowlocks/Makefile b/contrib/pgrowlocks/Makefile new file mode 100644 index 0000000..e808064 --- /dev/null +++ b/contrib/pgrowlocks/Makefile @@ -0,0 +1,24 @@ +# contrib/pgrowlocks/Makefile + +MODULE_big = pgrowlocks +OBJS = \ + $(WIN32RES) \ + pgrowlocks.o + +EXTENSION = pgrowlocks +DATA = pgrowlocks--1.2.sql pgrowlocks--1.1--1.2.sql pgrowlocks--1.0--1.1.sql +PGFILEDESC = "pgrowlocks - display row locking information" + +ISOLATION = pgrowlocks +ISOLATION_OPTS = --load-extension=pgrowlocks + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pgrowlocks +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pgrowlocks/expected/pgrowlocks.out b/contrib/pgrowlocks/expected/pgrowlocks.out new file mode 100644 index 0000000..7254672 --- /dev/null +++ b/contrib/pgrowlocks/expected/pgrowlocks.out @@ -0,0 +1,233 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_begin s1_tuplock1 s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+----------------- +(0,1) |f |{"For Key Share"} +(0,2) |f |{"For Key Share"} +(2 rows) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_tuplock2 s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+------------- +(0,1) |f |{"For Share"} +(0,2) |f |{"For Share"} +(2 rows) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_tuplock3 s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+--------------------- +(0,1) |f |{"For No Key Update"} +(0,2) |f |{"For No Key Update"} +(2 rows) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_tuplock4 s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+-------------- +(0,1) |f |{"For Update"} +(0,2) |f |{"For Update"} +(2 rows) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_updatea s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_updatea: UPDATE multixact_conflict SET a = 10 WHERE a = 1; +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+-------- +(0,1) |f |{Update} +(1 row) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_updateb s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_updateb: UPDATE multixact_conflict SET b = 11 WHERE b = 4; +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+----------------- +(0,2) |f |{"No Key Update"} +(1 row) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_lcksvpt s1_tuplock1 s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s1_tuplock1: SELECT * FROM multixact_conflict FOR KEY SHARE; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+----------------- +(0,1) |f |{"For Key Share"} +(0,2) |f |{"For Key Share"} +(2 rows) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_lcksvpt s1_tuplock2 s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s1_tuplock2: SELECT * FROM multixact_conflict FOR SHARE; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+------------------- +(0,1) |t |{"Key Share",Share} +(0,2) |t |{"Key Share",Share} +(2 rows) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_lcksvpt s1_tuplock3 s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s1_tuplock3: SELECT * FROM multixact_conflict FOR NO KEY UPDATE; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+--------------------------------- +(0,1) |t |{"Key Share","For No Key Update"} +(0,2) |t |{"Key Share","For No Key Update"} +(2 rows) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_lcksvpt s1_tuplock4 s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s1_tuplock4: SELECT * FROM multixact_conflict FOR UPDATE; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+-------------------------- +(0,1) |t |{"Key Share","For Update"} +(0,2) |t |{"Key Share","For Update"} +(2 rows) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_lcksvpt s1_updatea s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s1_updatea: UPDATE multixact_conflict SET a = 10 WHERE a = 1; +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+-------------------- +(0,1) |t |{"Key Share",Update} +(0,2) |f |{"For Key Share"} +(2 rows) + +step s1_commit: COMMIT; + +starting permutation: s1_begin s1_lcksvpt s1_updateb s2_rowlocks s1_commit +step s1_begin: BEGIN; +step s1_lcksvpt: SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s; +a|b +-+- +1|2 +3|4 +(2 rows) + +step s1_updateb: UPDATE multixact_conflict SET b = 11 WHERE b = 4; +step s2_rowlocks: SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); +locked_row|multi|modes +----------+-----+----------------------------- +(0,1) |f |{"For Key Share"} +(0,2) |t |{"Key Share","No Key Update"} +(2 rows) + +step s1_commit: COMMIT; diff --git a/contrib/pgrowlocks/meson.build b/contrib/pgrowlocks/meson.build new file mode 100644 index 0000000..48bce00 --- /dev/null +++ b/contrib/pgrowlocks/meson.build @@ -0,0 +1,37 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pgrowlocks_sources = files( + 'pgrowlocks.c', +) + +if host_system == 'windows' + pgrowlocks_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pgrowlocks', + '--FILEDESC', 'pgrowlocks - display row locking information',]) +endif + +pgrowlocks = shared_module('pgrowlocks', + pgrowlocks_sources, + kwargs: contrib_mod_args, +) +contrib_targets += pgrowlocks + +install_data( + 'pgrowlocks--1.0--1.1.sql', + 'pgrowlocks--1.1--1.2.sql', + 'pgrowlocks--1.2.sql', + 'pgrowlocks.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'pgrowlocks', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'isolation': { + 'specs': [ + 'pgrowlocks', + ], + 'regress_args': ['--load-extension=pgrowlocks'], + }, +} diff --git a/contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql b/contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql new file mode 100644 index 0000000..3d5ca34 --- /dev/null +++ b/contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql @@ -0,0 +1,17 @@ +/* contrib/pgrowlocks/pgrowlocks--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgrowlocks UPDATE TO '1.1'" to load this file. \quit + +ALTER EXTENSION pgrowlocks DROP FUNCTION pgrowlocks(text); +DROP FUNCTION pgrowlocks(text); +CREATE FUNCTION pgrowlocks(IN relname text, + OUT locked_row TID, -- row TID + OUT locker XID, -- locking XID + OUT multi bool, -- multi XID? + OUT xids xid[], -- multi XIDs + OUT modes text[], -- multi XID statuses + OUT pids INTEGER[]) -- locker's process id +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pgrowlocks' +LANGUAGE C STRICT; diff --git a/contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql b/contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql new file mode 100644 index 0000000..94ebf54 --- /dev/null +++ b/contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql @@ -0,0 +1,6 @@ +/* contrib/pgrowlocks/pgrowlocks--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgrowlocks UPDATE TO '1.2'" to load this file. \quit + +ALTER FUNCTION pgrowlocks(text) PARALLEL SAFE; diff --git a/contrib/pgrowlocks/pgrowlocks--1.2.sql b/contrib/pgrowlocks/pgrowlocks--1.2.sql new file mode 100644 index 0000000..ff76b8b --- /dev/null +++ b/contrib/pgrowlocks/pgrowlocks--1.2.sql @@ -0,0 +1,15 @@ +/* contrib/pgrowlocks/pgrowlocks--1.2.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pgrowlocks" to load this file. \quit + +CREATE FUNCTION pgrowlocks(IN relname text, + OUT locked_row TID, -- row TID + OUT locker XID, -- locking XID + OUT multi bool, -- multi XID? + OUT xids xid[], -- multi XIDs + OUT modes text[], -- multi XID statuses + OUT pids INTEGER[]) -- locker's process id +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'pgrowlocks' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c new file mode 100644 index 0000000..fd104e3 --- /dev/null +++ b/contrib/pgrowlocks/pgrowlocks.c @@ -0,0 +1,276 @@ +/* + * contrib/pgrowlocks/pgrowlocks.c + * + * Copyright (c) 2005-2006 Tatsuo Ishii + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose, without fee, and without a + * written agreement is hereby granted, provided that the above + * copyright notice and this paragraph and the following two + * paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, + * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS + * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, + * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ + +#include "postgres.h" + +#include "access/heapam.h" +#include "access/multixact.h" +#include "access/relscan.h" +#include "access/tableam.h" +#include "access/xact.h" +#include "catalog/namespace.h" +#include "catalog/pg_am_d.h" +#include "catalog/pg_authid.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/procarray.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/varlena.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(pgrowlocks); + +/* ---------- + * pgrowlocks: + * returns tids of rows being locked + * ---------- + */ + +#define NCHARS 32 + +#define Atnum_tid 0 +#define Atnum_xmax 1 +#define Atnum_ismulti 2 +#define Atnum_xids 3 +#define Atnum_modes 4 +#define Atnum_pids 5 + +Datum +pgrowlocks(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + AttInMetadata *attinmeta; + Relation rel; + RangeVar *relrv; + TableScanDesc scan; + HeapScanDesc hscan; + HeapTuple tuple; + AclResult aclresult; + char **values; + + InitMaterializedSRF(fcinfo, 0); + + /* Access the table */ + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is a partitioned table", + RelationGetRelationName(rel)), + errdetail("Partitioned tables do not contain rows."))); + else if (rel->rd_rel->relkind != RELKIND_RELATION) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table", + RelationGetRelationName(rel)))); + else if (rel->rd_rel->relam != HEAP_TABLE_AM_OID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only heap AM is supported"))); + + /* + * check permissions: must have SELECT on table or be in + * pg_stat_scan_tables + */ + aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), + ACL_SELECT); + if (aclresult != ACLCHECK_OK) + aclresult = has_privs_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV; + + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind), + RelationGetRelationName(rel)); + + /* Scan the relation */ + scan = table_beginscan(rel, GetActiveSnapshot(), 0, NULL); + hscan = (HeapScanDesc) scan; + + attinmeta = TupleDescGetAttInMetadata(rsinfo->setDesc); + + values = (char **) palloc(rsinfo->setDesc->natts * sizeof(char *)); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + TM_Result htsu; + TransactionId xmax; + uint16 infomask; + + /* must hold a buffer lock to call HeapTupleSatisfiesUpdate */ + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE); + + htsu = HeapTupleSatisfiesUpdate(tuple, + GetCurrentCommandId(false), + hscan->rs_cbuf); + xmax = HeapTupleHeaderGetRawXmax(tuple->t_data); + infomask = tuple->t_data->t_infomask; + + /* + * A tuple is locked if HTSU returns BeingModified. + */ + if (htsu == TM_BeingModified) + { + values[Atnum_tid] = (char *) DirectFunctionCall1(tidout, + PointerGetDatum(&tuple->t_self)); + + values[Atnum_xmax] = palloc(NCHARS * sizeof(char)); + snprintf(values[Atnum_xmax], NCHARS, "%u", xmax); + if (infomask & HEAP_XMAX_IS_MULTI) + { + MultiXactMember *members; + int nmembers; + bool first = true; + bool allow_old; + + values[Atnum_ismulti] = pstrdup("true"); + + allow_old = HEAP_LOCKED_UPGRADED(infomask); + nmembers = GetMultiXactIdMembers(xmax, &members, allow_old, + false); + if (nmembers == -1) + { + values[Atnum_xids] = "{0}"; + values[Atnum_modes] = "{transient upgrade status}"; + values[Atnum_pids] = "{0}"; + } + else + { + int j; + + values[Atnum_xids] = palloc(NCHARS * nmembers); + values[Atnum_modes] = palloc(NCHARS * nmembers); + values[Atnum_pids] = palloc(NCHARS * nmembers); + + strcpy(values[Atnum_xids], "{"); + strcpy(values[Atnum_modes], "{"); + strcpy(values[Atnum_pids], "{"); + + for (j = 0; j < nmembers; j++) + { + char buf[NCHARS]; + + if (!first) + { + strcat(values[Atnum_xids], ","); + strcat(values[Atnum_modes], ","); + strcat(values[Atnum_pids], ","); + } + snprintf(buf, NCHARS, "%u", members[j].xid); + strcat(values[Atnum_xids], buf); + switch (members[j].status) + { + case MultiXactStatusUpdate: + snprintf(buf, NCHARS, "Update"); + break; + case MultiXactStatusNoKeyUpdate: + snprintf(buf, NCHARS, "No Key Update"); + break; + case MultiXactStatusForUpdate: + snprintf(buf, NCHARS, "For Update"); + break; + case MultiXactStatusForNoKeyUpdate: + snprintf(buf, NCHARS, "For No Key Update"); + break; + case MultiXactStatusForShare: + snprintf(buf, NCHARS, "Share"); + break; + case MultiXactStatusForKeyShare: + snprintf(buf, NCHARS, "Key Share"); + break; + } + strcat(values[Atnum_modes], buf); + snprintf(buf, NCHARS, "%d", + BackendXidGetPid(members[j].xid)); + strcat(values[Atnum_pids], buf); + + first = false; + } + + strcat(values[Atnum_xids], "}"); + strcat(values[Atnum_modes], "}"); + strcat(values[Atnum_pids], "}"); + } + } + else + { + values[Atnum_ismulti] = pstrdup("false"); + + values[Atnum_xids] = palloc(NCHARS * sizeof(char)); + snprintf(values[Atnum_xids], NCHARS, "{%u}", xmax); + + values[Atnum_modes] = palloc(NCHARS); + if (infomask & HEAP_XMAX_LOCK_ONLY) + { + if (HEAP_XMAX_IS_SHR_LOCKED(infomask)) + snprintf(values[Atnum_modes], NCHARS, "{For Share}"); + else if (HEAP_XMAX_IS_KEYSHR_LOCKED(infomask)) + snprintf(values[Atnum_modes], NCHARS, "{For Key Share}"); + else if (HEAP_XMAX_IS_EXCL_LOCKED(infomask)) + { + if (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED) + snprintf(values[Atnum_modes], NCHARS, "{For Update}"); + else + snprintf(values[Atnum_modes], NCHARS, "{For No Key Update}"); + } + else + /* neither keyshare nor exclusive bit it set */ + snprintf(values[Atnum_modes], NCHARS, + "{transient upgrade status}"); + } + else + { + if (tuple->t_data->t_infomask2 & HEAP_KEYS_UPDATED) + snprintf(values[Atnum_modes], NCHARS, "{Update}"); + else + snprintf(values[Atnum_modes], NCHARS, "{No Key Update}"); + } + + values[Atnum_pids] = palloc(NCHARS * sizeof(char)); + snprintf(values[Atnum_pids], NCHARS, "{%d}", + BackendXidGetPid(xmax)); + } + + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); + + /* build a tuple */ + tuple = BuildTupleFromCStrings(attinmeta, values); + tuplestore_puttuple(rsinfo->setResult, tuple); + } + else + { + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); + } + } + + table_endscan(scan); + table_close(rel, AccessShareLock); + return (Datum) 0; +} diff --git a/contrib/pgrowlocks/pgrowlocks.control b/contrib/pgrowlocks/pgrowlocks.control new file mode 100644 index 0000000..9f92b2f --- /dev/null +++ b/contrib/pgrowlocks/pgrowlocks.control @@ -0,0 +1,5 @@ +# pgrowlocks extension +comment = 'show row-level locking information' +default_version = '1.2' +module_pathname = '$libdir/pgrowlocks' +relocatable = true diff --git a/contrib/pgrowlocks/specs/pgrowlocks.spec b/contrib/pgrowlocks/specs/pgrowlocks.spec new file mode 100644 index 0000000..4f4013c --- /dev/null +++ b/contrib/pgrowlocks/specs/pgrowlocks.spec @@ -0,0 +1,39 @@ +# Tests for contrib/pgrowlocks + +setup { + CREATE TABLE multixact_conflict (a int PRIMARY KEY, b int); + INSERT INTO multixact_conflict VALUES (1, 2), (3, 4); +} + +teardown { + DROP TABLE multixact_conflict; +} + +session s1 +step s1_begin { BEGIN; } +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_updatea { UPDATE multixact_conflict SET a = 10 WHERE a = 1; } +step s1_updateb { UPDATE multixact_conflict SET b = 11 WHERE b = 4; } +step s1_lcksvpt { SELECT * FROM multixact_conflict FOR KEY SHARE; SAVEPOINT s; } +step s1_commit { COMMIT; } + +session s2 +step s2_rowlocks { SELECT locked_row, multi, modes FROM pgrowlocks('multixact_conflict'); } + +permutation s1_begin s1_tuplock1 s2_rowlocks s1_commit +permutation s1_begin s1_tuplock2 s2_rowlocks s1_commit +permutation s1_begin s1_tuplock3 s2_rowlocks s1_commit +permutation s1_begin s1_tuplock4 s2_rowlocks s1_commit +permutation s1_begin s1_updatea s2_rowlocks s1_commit +permutation s1_begin s1_updateb s2_rowlocks s1_commit + +# test multixact cases using savepoints +permutation s1_begin s1_lcksvpt s1_tuplock1 s2_rowlocks s1_commit +permutation s1_begin s1_lcksvpt s1_tuplock2 s2_rowlocks s1_commit +permutation s1_begin s1_lcksvpt s1_tuplock3 s2_rowlocks s1_commit +permutation s1_begin s1_lcksvpt s1_tuplock4 s2_rowlocks s1_commit +permutation s1_begin s1_lcksvpt s1_updatea s2_rowlocks s1_commit +permutation s1_begin s1_lcksvpt s1_updateb s2_rowlocks s1_commit diff --git a/contrib/pgstattuple/.gitignore b/contrib/pgstattuple/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/pgstattuple/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/pgstattuple/Makefile b/contrib/pgstattuple/Makefile new file mode 100644 index 0000000..c5b17fc --- /dev/null +++ b/contrib/pgstattuple/Makefile @@ -0,0 +1,27 @@ +# contrib/pgstattuple/Makefile + +MODULE_big = pgstattuple +OBJS = \ + $(WIN32RES) \ + pgstatapprox.o \ + pgstatindex.o \ + pgstattuple.o + +EXTENSION = pgstattuple +DATA = pgstattuple--1.4.sql pgstattuple--1.4--1.5.sql \ + pgstattuple--1.3--1.4.sql pgstattuple--1.2--1.3.sql \ + pgstattuple--1.1--1.2.sql pgstattuple--1.0--1.1.sql +PGFILEDESC = "pgstattuple - tuple-level statistics" + +REGRESS = pgstattuple + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/pgstattuple +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/pgstattuple/expected/pgstattuple.out b/contrib/pgstattuple/expected/pgstattuple.out new file mode 100644 index 0000000..283856e --- /dev/null +++ b/contrib/pgstattuple/expected/pgstattuple.out @@ -0,0 +1,280 @@ +CREATE EXTENSION pgstattuple; +-- +-- It's difficult to come up with platform-independent test cases for +-- the pgstattuple functions, but the results for empty tables and +-- indexes should be that. +-- +create table test (a int primary key, b int[]); +select * from pgstattuple('test'); + table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent +-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+-------------- + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 +(1 row) + +select * from pgstattuple('test'::text); + table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent +-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+-------------- + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 +(1 row) + +select * from pgstattuple('test'::name); + table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent +-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+-------------- + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 +(1 row) + +select * from pgstattuple('test'::regclass); + table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent +-----------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+-------------- + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 +(1 row) + +select pgstattuple(oid) from pg_class where relname = 'test'; + pgstattuple +--------------------- + (0,0,0,0,0,0,0,0,0) +(1 row) + +select pgstattuple(relname) from pg_class where relname = 'test'; + pgstattuple +--------------------- + (0,0,0,0,0,0,0,0,0) +(1 row) + +select version, tree_level, + index_size / current_setting('block_size')::int as index_size, + root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages, + avg_leaf_density, leaf_fragmentation + from pgstatindex('test_pkey'); + version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation +---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+-------------------- + 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN +(1 row) + +select version, tree_level, + index_size / current_setting('block_size')::int as index_size, + root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages, + avg_leaf_density, leaf_fragmentation + from pgstatindex('test_pkey'::text); + version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation +---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+-------------------- + 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN +(1 row) + +select version, tree_level, + index_size / current_setting('block_size')::int as index_size, + root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages, + avg_leaf_density, leaf_fragmentation + from pgstatindex('test_pkey'::name); + version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation +---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+-------------------- + 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN +(1 row) + +select version, tree_level, + index_size / current_setting('block_size')::int as index_size, + root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages, + avg_leaf_density, leaf_fragmentation + from pgstatindex('test_pkey'::regclass); + version | tree_level | index_size | root_block_no | internal_pages | leaf_pages | empty_pages | deleted_pages | avg_leaf_density | leaf_fragmentation +---------+------------+------------+---------------+----------------+------------+-------------+---------------+------------------+-------------------- + 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | NaN | NaN +(1 row) + +select pg_relpages('test'); + pg_relpages +------------- + 0 +(1 row) + +select pg_relpages('test_pkey'); + pg_relpages +------------- + 1 +(1 row) + +select pg_relpages('test_pkey'::text); + pg_relpages +------------- + 1 +(1 row) + +select pg_relpages('test_pkey'::name); + pg_relpages +------------- + 1 +(1 row) + +select pg_relpages('test_pkey'::regclass); + pg_relpages +------------- + 1 +(1 row) + +select pg_relpages(oid) from pg_class where relname = 'test_pkey'; + pg_relpages +------------- + 1 +(1 row) + +select pg_relpages(relname) from pg_class where relname = 'test_pkey'; + pg_relpages +------------- + 1 +(1 row) + +create index test_ginidx on test using gin (b); +select * from pgstatginindex('test_ginidx'); + version | pending_pages | pending_tuples +---------+---------------+---------------- + 2 | 0 | 0 +(1 row) + +create index test_hashidx on test using hash (b); +select * from pgstathashindex('test_hashidx'); + version | bucket_pages | overflow_pages | bitmap_pages | unused_pages | live_items | dead_items | free_percent +---------+--------------+----------------+--------------+--------------+------------+------------+-------------- + 4 | 4 | 0 | 1 | 0 | 0 | 0 | 100 +(1 row) + +-- these should error with the wrong type +select pgstatginindex('test_pkey'); +ERROR: relation "test_pkey" is not a GIN index +select pgstathashindex('test_pkey'); +ERROR: relation "test_pkey" is not a hash index +select pgstatindex('test_ginidx'); +ERROR: relation "test_ginidx" is not a btree index +select pgstathashindex('test_ginidx'); +ERROR: relation "test_ginidx" is not a hash index +select pgstatindex('test_hashidx'); +ERROR: relation "test_hashidx" is not a btree index +select pgstatginindex('test_hashidx'); +ERROR: relation "test_hashidx" is not a GIN index +-- check that using any of these functions with unsupported relations will fail +create table test_partitioned (a int) partition by range (a); +create index test_partitioned_index on test_partitioned(a); +create index test_partitioned_hash_index on test_partitioned using hash(a); +-- these should all fail +select pgstattuple('test_partitioned'); +ERROR: cannot get tuple-level statistics for relation "test_partitioned" +DETAIL: This operation is not supported for partitioned tables. +select pgstattuple('test_partitioned_index'); +ERROR: cannot get tuple-level statistics for relation "test_partitioned_index" +DETAIL: This operation is not supported for partitioned indexes. +select pgstattuple_approx('test_partitioned'); +ERROR: relation "test_partitioned" is of wrong relation kind +DETAIL: This operation is not supported for partitioned tables. +select pg_relpages('test_partitioned'); +ERROR: cannot get page count of relation "test_partitioned" +DETAIL: This operation is not supported for partitioned tables. +select pgstatindex('test_partitioned'); +ERROR: relation "test_partitioned" is not a btree index +select pgstatginindex('test_partitioned'); +ERROR: relation "test_partitioned" is not a GIN index +select pgstathashindex('test_partitioned'); +ERROR: relation "test_partitioned" is not a hash index +select pgstathashindex('test_partitioned_hash_index'); +ERROR: relation "test_partitioned_hash_index" is not a hash index +create view test_view as select 1; +-- these should all fail +select pgstattuple('test_view'); +ERROR: cannot get tuple-level statistics for relation "test_view" +DETAIL: This operation is not supported for views. +select pgstattuple_approx('test_view'); +ERROR: relation "test_view" is of wrong relation kind +DETAIL: This operation is not supported for views. +select pg_relpages('test_view'); +ERROR: cannot get page count of relation "test_view" +DETAIL: This operation is not supported for views. +select pgstatindex('test_view'); +ERROR: relation "test_view" is not a btree index +select pgstatginindex('test_view'); +ERROR: relation "test_view" is not a GIN index +select pgstathashindex('test_view'); +ERROR: relation "test_view" is not a hash index +create foreign data wrapper dummy; +create server dummy_server foreign data wrapper dummy; +create foreign table test_foreign_table () server dummy_server; +-- these should all fail +select pgstattuple('test_foreign_table'); +ERROR: cannot get tuple-level statistics for relation "test_foreign_table" +DETAIL: This operation is not supported for foreign tables. +select pgstattuple_approx('test_foreign_table'); +ERROR: relation "test_foreign_table" is of wrong relation kind +DETAIL: This operation is not supported for foreign tables. +select pg_relpages('test_foreign_table'); +ERROR: cannot get page count of relation "test_foreign_table" +DETAIL: This operation is not supported for foreign tables. +select pgstatindex('test_foreign_table'); +ERROR: relation "test_foreign_table" is not a btree index +select pgstatginindex('test_foreign_table'); +ERROR: relation "test_foreign_table" is not a GIN index +select pgstathashindex('test_foreign_table'); +ERROR: relation "test_foreign_table" is not a hash index +-- a partition of a partitioned table should work though +create table test_partition partition of test_partitioned for values from (1) to (100); +select pgstattuple('test_partition'); + pgstattuple +--------------------- + (0,0,0,0,0,0,0,0,0) +(1 row) + +select pgstattuple_approx('test_partition'); + pgstattuple_approx +----------------------- + (0,0,0,0,0,0,0,0,0,0) +(1 row) + +select pg_relpages('test_partition'); + pg_relpages +------------- + 0 +(1 row) + +-- toast tables should work +select pgstattuple((select reltoastrelid from pg_class where relname = 'test')); + pgstattuple +--------------------- + (0,0,0,0,0,0,0,0,0) +(1 row) + +select pgstattuple_approx((select reltoastrelid from pg_class where relname = 'test')); + pgstattuple_approx +----------------------- + (0,0,0,0,0,0,0,0,0,0) +(1 row) + +select pg_relpages((select reltoastrelid from pg_class where relname = 'test')); + pg_relpages +------------- + 0 +(1 row) + +-- not for the index calls though, of course +select pgstatindex('test_partition'); +ERROR: relation "test_partition" is not a btree index +select pgstatginindex('test_partition'); +ERROR: relation "test_partition" is not a GIN index +select pgstathashindex('test_partition'); +ERROR: relation "test_partition" is not a hash index +-- an actual index of a partitioned table should work though +create index test_partition_idx on test_partition(a); +create index test_partition_hash_idx on test_partition using hash (a); +-- these should work +select pgstatindex('test_partition_idx'); + pgstatindex +------------------------------ + (4,0,8192,0,0,0,0,0,NaN,NaN) +(1 row) + +select pgstathashindex('test_partition_hash_idx'); + pgstathashindex +--------------------- + (4,8,0,1,0,0,0,100) +(1 row) + +drop table test_partitioned; +drop view test_view; +drop foreign table test_foreign_table; +drop server dummy_server; +drop foreign data wrapper dummy; diff --git a/contrib/pgstattuple/meson.build b/contrib/pgstattuple/meson.build new file mode 100644 index 0000000..6ed84fa --- /dev/null +++ b/contrib/pgstattuple/meson.build @@ -0,0 +1,42 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +pgstattuple_sources = files( + 'pgstatapprox.c', + 'pgstatindex.c', + 'pgstattuple.c', +) + +if host_system == 'windows' + pgstattuple_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pgstattuple', + '--FILEDESC', 'pgstattuple - tuple-level statistics',]) +endif + +pgstattuple = shared_module('pgstattuple', + pgstattuple_sources, + c_pch: pch_postgres_h, + kwargs: contrib_mod_args, +) +contrib_targets += pgstattuple + +install_data( + 'pgstattuple--1.0--1.1.sql', + 'pgstattuple--1.1--1.2.sql', + 'pgstattuple--1.2--1.3.sql', + 'pgstattuple--1.3--1.4.sql', + 'pgstattuple--1.4--1.5.sql', + 'pgstattuple--1.4.sql', + 'pgstattuple.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'pgstattuple', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'pgstattuple', + ], + }, +} diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c new file mode 100644 index 0000000..f601dc6 --- /dev/null +++ b/contrib/pgstattuple/pgstatapprox.c @@ -0,0 +1,319 @@ +/*------------------------------------------------------------------------- + * + * pgstatapprox.c + * Bloat estimation functions + * + * Copyright (c) 2014-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/pgstattuple/pgstatapprox.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/multixact.h" +#include "access/relation.h" +#include "access/transam.h" +#include "access/visibilitymap.h" +#include "access/xact.h" +#include "catalog/namespace.h" +#include "catalog/pg_am_d.h" +#include "commands/vacuum.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/freespace.h" +#include "storage/lmgr.h" +#include "storage/procarray.h" +#include "utils/builtins.h" + +PG_FUNCTION_INFO_V1(pgstattuple_approx); +PG_FUNCTION_INFO_V1(pgstattuple_approx_v1_5); + +Datum pgstattuple_approx_internal(Oid relid, FunctionCallInfo fcinfo); + +typedef struct output_type +{ + uint64 table_len; + double scanned_percent; + uint64 tuple_count; + uint64 tuple_len; + double tuple_percent; + uint64 dead_tuple_count; + uint64 dead_tuple_len; + double dead_tuple_percent; + uint64 free_space; + double free_percent; +} output_type; + +#define NUM_OUTPUT_COLUMNS 10 + +/* + * This function takes an already open relation and scans its pages, + * skipping those that have the corresponding visibility map bit set. + * For pages we skip, we find the free space from the free space map + * and approximate tuple_len on that basis. For the others, we count + * the exact number of dead tuples etc. + * + * This scan is loosely based on vacuumlazy.c:lazy_scan_heap(), but + * we do not try to avoid skipping single pages. + */ +static void +statapprox_heap(Relation rel, output_type *stat) +{ + BlockNumber scanned, + nblocks, + blkno; + Buffer vmbuffer = InvalidBuffer; + BufferAccessStrategy bstrategy; + TransactionId OldestXmin; + + OldestXmin = GetOldestNonRemovableTransactionId(rel); + bstrategy = GetAccessStrategy(BAS_BULKREAD); + + nblocks = RelationGetNumberOfBlocks(rel); + scanned = 0; + + for (blkno = 0; blkno < nblocks; blkno++) + { + Buffer buf; + Page page; + OffsetNumber offnum, + maxoff; + Size freespace; + + CHECK_FOR_INTERRUPTS(); + + /* + * If the page has only visible tuples, then we can find out the free + * space from the FSM and move on. + */ + if (VM_ALL_VISIBLE(rel, blkno, &vmbuffer)) + { + freespace = GetRecordedFreeSpace(rel, blkno); + stat->tuple_len += BLCKSZ - freespace; + stat->free_space += freespace; + continue; + } + + buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, + RBM_NORMAL, bstrategy); + + LockBuffer(buf, BUFFER_LOCK_SHARE); + + page = BufferGetPage(buf); + + /* + * It's not safe to call PageGetHeapFreeSpace() on new pages, so we + * treat them as being free space for our purposes. + */ + if (!PageIsNew(page)) + stat->free_space += PageGetHeapFreeSpace(page); + else + stat->free_space += BLCKSZ - SizeOfPageHeaderData; + + /* We may count the page as scanned even if it's new/empty */ + scanned++; + + if (PageIsNew(page) || PageIsEmpty(page)) + { + UnlockReleaseBuffer(buf); + continue; + } + + /* + * Look at each tuple on the page and decide whether it's live or + * dead, then count it and its size. Unlike lazy_scan_heap, we can + * afford to ignore problems and special cases. + */ + maxoff = PageGetMaxOffsetNumber(page); + + for (offnum = FirstOffsetNumber; + offnum <= maxoff; + offnum = OffsetNumberNext(offnum)) + { + ItemId itemid; + HeapTupleData tuple; + + itemid = PageGetItemId(page, offnum); + + if (!ItemIdIsUsed(itemid) || ItemIdIsRedirected(itemid) || + ItemIdIsDead(itemid)) + { + continue; + } + + Assert(ItemIdIsNormal(itemid)); + + ItemPointerSet(&(tuple.t_self), blkno, offnum); + + tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid); + tuple.t_len = ItemIdGetLength(itemid); + tuple.t_tableOid = RelationGetRelid(rel); + + /* + * We follow VACUUM's lead in counting INSERT_IN_PROGRESS tuples + * as "dead" while DELETE_IN_PROGRESS tuples are "live". We don't + * bother distinguishing tuples inserted/deleted by our own + * transaction. + */ + switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf)) + { + case HEAPTUPLE_LIVE: + case HEAPTUPLE_DELETE_IN_PROGRESS: + stat->tuple_len += tuple.t_len; + stat->tuple_count++; + break; + case HEAPTUPLE_DEAD: + case HEAPTUPLE_RECENTLY_DEAD: + case HEAPTUPLE_INSERT_IN_PROGRESS: + stat->dead_tuple_len += tuple.t_len; + stat->dead_tuple_count++; + break; + default: + elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result"); + break; + } + } + + UnlockReleaseBuffer(buf); + } + + stat->table_len = (uint64) nblocks * BLCKSZ; + + /* + * We don't know how many tuples are in the pages we didn't scan, so + * extrapolate the live-tuple count to the whole table in the same way + * that VACUUM does. (Like VACUUM, we're not taking a random sample, so + * just extrapolating linearly seems unsafe.) There should be no dead + * tuples in all-visible pages, so no correction is needed for that, and + * we already accounted for the space in those pages, too. + */ + stat->tuple_count = vac_estimate_reltuples(rel, nblocks, scanned, + stat->tuple_count); + + /* It's not clear if we could get -1 here, but be safe. */ + stat->tuple_count = Max(stat->tuple_count, 0); + + /* + * Calculate percentages if the relation has one or more pages. + */ + if (nblocks != 0) + { + stat->scanned_percent = 100.0 * scanned / nblocks; + stat->tuple_percent = 100.0 * stat->tuple_len / stat->table_len; + stat->dead_tuple_percent = 100.0 * stat->dead_tuple_len / stat->table_len; + stat->free_percent = 100.0 * stat->free_space / stat->table_len; + } + + if (BufferIsValid(vmbuffer)) + { + ReleaseBuffer(vmbuffer); + vmbuffer = InvalidBuffer; + } +} + +/* + * Returns estimated live/dead tuple statistics for the given relid. + * + * The superuser() check here must be kept as the library might be upgraded + * without the extension being upgraded, meaning that in pre-1.5 installations + * these functions could be called by any user. + */ +Datum +pgstattuple_approx(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pgstattuple functions"))); + + PG_RETURN_DATUM(pgstattuple_approx_internal(relid, fcinfo)); +} + +/* + * As of pgstattuple version 1.5, we no longer need to check if the user + * is a superuser because we REVOKE EXECUTE on the SQL function from PUBLIC. + * Users can then grant access to it based on their policies. + * + * Otherwise identical to pgstattuple_approx (above). + */ +Datum +pgstattuple_approx_v1_5(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + + PG_RETURN_DATUM(pgstattuple_approx_internal(relid, fcinfo)); +} + +Datum +pgstattuple_approx_internal(Oid relid, FunctionCallInfo fcinfo) +{ + Relation rel; + output_type stat = {0}; + TupleDesc tupdesc; + bool nulls[NUM_OUTPUT_COLUMNS]; + Datum values[NUM_OUTPUT_COLUMNS]; + HeapTuple ret; + int i = 0; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (tupdesc->natts != NUM_OUTPUT_COLUMNS) + elog(ERROR, "incorrect number of output arguments"); + + rel = relation_open(relid, AccessShareLock); + + /* + * Reject attempts to read non-local temporary relations; we would be + * likely to get wrong data since we have no visibility into the owning + * session's local buffers. + */ + if (RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary tables of other sessions"))); + + /* + * We support only relation kinds with a visibility map and a free space + * map. + */ + if (!(rel->rd_rel->relkind == RELKIND_RELATION || + rel->rd_rel->relkind == RELKIND_MATVIEW || + rel->rd_rel->relkind == RELKIND_TOASTVALUE)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("relation \"%s\" is of wrong relation kind", + RelationGetRelationName(rel)), + errdetail_relkind_not_supported(rel->rd_rel->relkind))); + + if (rel->rd_rel->relam != HEAP_TABLE_AM_OID) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only heap AM is supported"))); + + statapprox_heap(rel, &stat); + + relation_close(rel, AccessShareLock); + + memset(nulls, 0, sizeof(nulls)); + + values[i++] = Int64GetDatum(stat.table_len); + values[i++] = Float8GetDatum(stat.scanned_percent); + values[i++] = Int64GetDatum(stat.tuple_count); + values[i++] = Int64GetDatum(stat.tuple_len); + values[i++] = Float8GetDatum(stat.tuple_percent); + values[i++] = Int64GetDatum(stat.dead_tuple_count); + values[i++] = Int64GetDatum(stat.dead_tuple_len); + values[i++] = Float8GetDatum(stat.dead_tuple_percent); + values[i++] = Int64GetDatum(stat.free_space); + values[i++] = Float8GetDatum(stat.free_percent); + + ret = heap_form_tuple(tupdesc, values, nulls); + return HeapTupleGetDatum(ret); +} diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c new file mode 100644 index 0000000..5c06ba6 --- /dev/null +++ b/contrib/pgstattuple/pgstatindex.c @@ -0,0 +1,761 @@ +/* + * contrib/pgstattuple/pgstatindex.c + * + * + * pgstatindex + * + * Copyright (c) 2006 Satoshi Nagayasu + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose, without fee, and without a + * written agreement is hereby granted, provided that the above + * copyright notice and this paragraph and the following two + * paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, + * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS + * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, + * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ + +#include "postgres.h" + +#include "access/gin_private.h" +#include "access/hash.h" +#include "access/htup_details.h" +#include "access/nbtree.h" +#include "access/relation.h" +#include "access/table.h" +#include "catalog/namespace.h" +#include "catalog/pg_am.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/lmgr.h" +#include "utils/builtins.h" +#include "utils/rel.h" +#include "utils/varlena.h" + + +/* + * Because of backward-compatibility issue, we have decided to have + * two types of interfaces, with regclass-type input arg and text-type + * input arg, for each function. + * + * Those functions which have text-type input arg will be deprecated + * in the future release. + */ +PG_FUNCTION_INFO_V1(pgstatindex); +PG_FUNCTION_INFO_V1(pgstatindexbyid); +PG_FUNCTION_INFO_V1(pg_relpages); +PG_FUNCTION_INFO_V1(pg_relpagesbyid); +PG_FUNCTION_INFO_V1(pgstatginindex); +PG_FUNCTION_INFO_V1(pgstathashindex); + +PG_FUNCTION_INFO_V1(pgstatindex_v1_5); +PG_FUNCTION_INFO_V1(pgstatindexbyid_v1_5); +PG_FUNCTION_INFO_V1(pg_relpages_v1_5); +PG_FUNCTION_INFO_V1(pg_relpagesbyid_v1_5); +PG_FUNCTION_INFO_V1(pgstatginindex_v1_5); + +Datum pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo); + +#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX) +#define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID) +#define IS_GIN(r) ((r)->rd_rel->relam == GIN_AM_OID) +#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID) + +/* ------------------------------------------------ + * A structure for a whole btree index statistics + * used by pgstatindex(). + * ------------------------------------------------ + */ +typedef struct BTIndexStat +{ + uint32 version; + uint32 level; + BlockNumber root_blkno; + + uint64 internal_pages; + uint64 leaf_pages; + uint64 empty_pages; + uint64 deleted_pages; + + uint64 max_avail; + uint64 free_space; + + uint64 fragments; +} BTIndexStat; + +/* ------------------------------------------------ + * A structure for a whole GIN index statistics + * used by pgstatginindex(). + * ------------------------------------------------ + */ +typedef struct GinIndexStat +{ + int32 version; + + BlockNumber pending_pages; + int64 pending_tuples; +} GinIndexStat; + +/* ------------------------------------------------ + * A structure for a whole HASH index statistics + * used by pgstathashindex(). + * ------------------------------------------------ + */ +typedef struct HashIndexStat +{ + int32 version; + int32 space_per_page; + + BlockNumber bucket_pages; + BlockNumber overflow_pages; + BlockNumber bitmap_pages; + BlockNumber unused_pages; + + int64 live_items; + int64 dead_items; + uint64 free_space; +} HashIndexStat; + +static Datum pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo); +static int64 pg_relpages_impl(Relation rel); +static void GetHashPageStats(Page page, HashIndexStat *stats); + +/* ------------------------------------------------------ + * pgstatindex() + * + * Usage: SELECT * FROM pgstatindex('t1_pkey'); + * + * The superuser() check here must be kept as the library might be upgraded + * without the extension being upgraded, meaning that in pre-1.5 installations + * these functions could be called by any user. + * ------------------------------------------------------ + */ +Datum +pgstatindex(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + Relation rel; + RangeVar *relrv; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pgstattuple functions"))); + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo)); +} + +/* + * As of pgstattuple version 1.5, we no longer need to check if the user + * is a superuser because we REVOKE EXECUTE on the function from PUBLIC. + * Users can then grant access to it based on their policies. + * + * Otherwise identical to pgstatindex (above). + */ +Datum +pgstatindex_v1_5(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + Relation rel; + RangeVar *relrv; + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo)); +} + +/* + * The superuser() check here must be kept as the library might be upgraded + * without the extension being upgraded, meaning that in pre-1.5 installations + * these functions could be called by any user. + */ +Datum +pgstatindexbyid(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Relation rel; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pgstattuple functions"))); + + rel = relation_open(relid, AccessShareLock); + + PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo)); +} + +/* No need for superuser checks in v1.5, see above */ +Datum +pgstatindexbyid_v1_5(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Relation rel; + + rel = relation_open(relid, AccessShareLock); + + PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo)); +} + +static Datum +pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo) +{ + Datum result; + BlockNumber nblocks; + BlockNumber blkno; + BTIndexStat indexStat; + BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD); + + if (!IS_INDEX(rel) || !IS_BTREE(rel)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("relation \"%s\" is not a btree index", + RelationGetRelationName(rel)))); + + /* + * Reject attempts to read non-local temporary relations; we would be + * likely to get wrong data since we have no visibility into the owning + * session's local buffers. + */ + if (RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary tables of other sessions"))); + + /* + * A !indisready index could lead to ERRCODE_DATA_CORRUPTED later, so exit + * early. We're capable of assessing an indisready&&!indisvalid index, + * but the results could be confusing. For example, the index's size + * could be too low for a valid index of the table. + */ + if (!rel->rd_index->indisvalid) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("index \"%s\" is not valid", + RelationGetRelationName(rel)))); + + /* + * Read metapage + */ + { + Buffer buffer = ReadBufferExtended(rel, MAIN_FORKNUM, 0, RBM_NORMAL, bstrategy); + Page page = BufferGetPage(buffer); + BTMetaPageData *metad = BTPageGetMeta(page); + + indexStat.version = metad->btm_version; + indexStat.level = metad->btm_level; + indexStat.root_blkno = metad->btm_root; + + ReleaseBuffer(buffer); + } + + /* -- init counters -- */ + indexStat.internal_pages = 0; + indexStat.leaf_pages = 0; + indexStat.empty_pages = 0; + indexStat.deleted_pages = 0; + + indexStat.max_avail = 0; + indexStat.free_space = 0; + + indexStat.fragments = 0; + + /* + * Scan all blocks except the metapage + */ + nblocks = RelationGetNumberOfBlocks(rel); + + for (blkno = 1; blkno < nblocks; blkno++) + { + Buffer buffer; + Page page; + BTPageOpaque opaque; + + CHECK_FOR_INTERRUPTS(); + + /* Read and lock buffer */ + buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + + page = BufferGetPage(buffer); + opaque = BTPageGetOpaque(page); + + /* + * Determine page type, and update totals. + * + * Note that we arbitrarily bucket deleted pages together without + * considering if they're leaf pages or internal pages. + */ + if (P_ISDELETED(opaque)) + indexStat.deleted_pages++; + else if (P_IGNORE(opaque)) + indexStat.empty_pages++; /* this is the "half dead" state */ + else if (P_ISLEAF(opaque)) + { + int max_avail; + + max_avail = BLCKSZ - (BLCKSZ - ((PageHeader) page)->pd_special + SizeOfPageHeaderData); + indexStat.max_avail += max_avail; + indexStat.free_space += PageGetFreeSpace(page); + + indexStat.leaf_pages++; + + /* + * If the next leaf is on an earlier block, it means a + * fragmentation. + */ + if (opaque->btpo_next != P_NONE && opaque->btpo_next < blkno) + indexStat.fragments++; + } + else + indexStat.internal_pages++; + + /* Unlock and release buffer */ + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + ReleaseBuffer(buffer); + } + + relation_close(rel, AccessShareLock); + + /*---------------------------- + * Build a result tuple + *---------------------------- + */ + { + TupleDesc tupleDesc; + int j; + char *values[10]; + HeapTuple tuple; + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + j = 0; + values[j++] = psprintf("%d", indexStat.version); + values[j++] = psprintf("%d", indexStat.level); + values[j++] = psprintf(INT64_FORMAT, + (1 + /* include the metapage in index_size */ + indexStat.leaf_pages + + indexStat.internal_pages + + indexStat.deleted_pages + + indexStat.empty_pages) * BLCKSZ); + values[j++] = psprintf("%u", indexStat.root_blkno); + values[j++] = psprintf(INT64_FORMAT, indexStat.internal_pages); + values[j++] = psprintf(INT64_FORMAT, indexStat.leaf_pages); + values[j++] = psprintf(INT64_FORMAT, indexStat.empty_pages); + values[j++] = psprintf(INT64_FORMAT, indexStat.deleted_pages); + if (indexStat.max_avail > 0) + values[j++] = psprintf("%.2f", + 100.0 - (double) indexStat.free_space / (double) indexStat.max_avail * 100.0); + else + values[j++] = pstrdup("NaN"); + if (indexStat.leaf_pages > 0) + values[j++] = psprintf("%.2f", + (double) indexStat.fragments / (double) indexStat.leaf_pages * 100.0); + else + values[j++] = pstrdup("NaN"); + + tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc), + values); + + result = HeapTupleGetDatum(tuple); + } + + return result; +} + +/* -------------------------------------------------------- + * pg_relpages() + * + * Get the number of pages of the table/index. + * + * Usage: SELECT pg_relpages('t1'); + * SELECT pg_relpages('t1_pkey'); + * + * Must keep superuser() check, see above. + * -------------------------------------------------------- + */ +Datum +pg_relpages(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + Relation rel; + RangeVar *relrv; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pgstattuple functions"))); + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + PG_RETURN_INT64(pg_relpages_impl(rel)); +} + +/* No need for superuser checks in v1.5, see above */ +Datum +pg_relpages_v1_5(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + Relation rel; + RangeVar *relrv; + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + PG_RETURN_INT64(pg_relpages_impl(rel)); +} + +/* Must keep superuser() check, see above. */ +Datum +pg_relpagesbyid(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Relation rel; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pgstattuple functions"))); + + rel = relation_open(relid, AccessShareLock); + + PG_RETURN_INT64(pg_relpages_impl(rel)); +} + +/* No need for superuser checks in v1.5, see above */ +Datum +pg_relpagesbyid_v1_5(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Relation rel; + + rel = relation_open(relid, AccessShareLock); + + PG_RETURN_INT64(pg_relpages_impl(rel)); +} + +static int64 +pg_relpages_impl(Relation rel) +{ + int64 relpages; + + if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot get page count of relation \"%s\"", + RelationGetRelationName(rel)), + errdetail_relkind_not_supported(rel->rd_rel->relkind))); + + /* note: this will work OK on non-local temp tables */ + + relpages = RelationGetNumberOfBlocks(rel); + + relation_close(rel, AccessShareLock); + + return relpages; +} + +/* ------------------------------------------------------ + * pgstatginindex() + * + * Usage: SELECT * FROM pgstatginindex('ginindex'); + * + * Must keep superuser() check, see above. + * ------------------------------------------------------ + */ +Datum +pgstatginindex(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pgstattuple functions"))); + + PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo)); +} + +/* No need for superuser checks in v1.5, see above */ +Datum +pgstatginindex_v1_5(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + + PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo)); +} + +Datum +pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo) +{ + Relation rel; + Buffer buffer; + Page page; + GinMetaPageData *metadata; + GinIndexStat stats; + HeapTuple tuple; + TupleDesc tupleDesc; + Datum values[3]; + bool nulls[3] = {false, false, false}; + Datum result; + + rel = relation_open(relid, AccessShareLock); + + if (!IS_INDEX(rel) || !IS_GIN(rel)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("relation \"%s\" is not a GIN index", + RelationGetRelationName(rel)))); + + /* + * Reject attempts to read non-local temporary relations; we would be + * likely to get wrong data since we have no visibility into the owning + * session's local buffers. + */ + if (RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary indexes of other sessions"))); + + /* see pgstatindex_impl */ + if (!rel->rd_index->indisvalid) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("index \"%s\" is not valid", + RelationGetRelationName(rel)))); + + /* + * Read metapage + */ + buffer = ReadBuffer(rel, GIN_METAPAGE_BLKNO); + LockBuffer(buffer, GIN_SHARE); + page = BufferGetPage(buffer); + metadata = GinPageGetMeta(page); + + stats.version = metadata->ginVersion; + stats.pending_pages = metadata->nPendingPages; + stats.pending_tuples = metadata->nPendingHeapTuples; + + UnlockReleaseBuffer(buffer); + relation_close(rel, AccessShareLock); + + /* + * Build a tuple descriptor for our result type + */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + values[0] = Int32GetDatum(stats.version); + values[1] = UInt32GetDatum(stats.pending_pages); + values[2] = Int64GetDatum(stats.pending_tuples); + + /* + * Build and return the tuple + */ + tuple = heap_form_tuple(tupleDesc, values, nulls); + result = HeapTupleGetDatum(tuple); + + return result; +} + +/* ------------------------------------------------------ + * pgstathashindex() + * + * Usage: SELECT * FROM pgstathashindex('hashindex'); + * ------------------------------------------------------ + */ +Datum +pgstathashindex(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + BlockNumber nblocks; + BlockNumber blkno; + Relation rel; + HashIndexStat stats; + BufferAccessStrategy bstrategy; + HeapTuple tuple; + TupleDesc tupleDesc; + Datum values[8]; + bool nulls[8] = {0}; + Buffer metabuf; + HashMetaPage metap; + float8 free_percent; + uint64 total_space; + + rel = relation_open(relid, AccessShareLock); + + if (!IS_INDEX(rel) || !IS_HASH(rel)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("relation \"%s\" is not a hash index", + RelationGetRelationName(rel)))); + + /* + * Reject attempts to read non-local temporary relations; we would be + * likely to get wrong data since we have no visibility into the owning + * session's local buffers. + */ + if (RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary indexes of other sessions"))); + + /* see pgstatindex_impl */ + if (!rel->rd_index->indisvalid) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("index \"%s\" is not valid", + RelationGetRelationName(rel)))); + + /* Get the information we need from the metapage. */ + memset(&stats, 0, sizeof(stats)); + metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE); + metap = HashPageGetMeta(BufferGetPage(metabuf)); + stats.version = metap->hashm_version; + stats.space_per_page = metap->hashm_bsize; + _hash_relbuf(rel, metabuf); + + /* Get the current relation length */ + nblocks = RelationGetNumberOfBlocks(rel); + + /* prepare access strategy for this index */ + bstrategy = GetAccessStrategy(BAS_BULKREAD); + + /* Start from blkno 1 as 0th block is metapage */ + for (blkno = 1; blkno < nblocks; blkno++) + { + Buffer buf; + Page page; + + CHECK_FOR_INTERRUPTS(); + + buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, + bstrategy); + LockBuffer(buf, BUFFER_LOCK_SHARE); + page = (Page) BufferGetPage(buf); + + if (PageIsNew(page)) + stats.unused_pages++; + else if (PageGetSpecialSize(page) != + MAXALIGN(sizeof(HashPageOpaqueData))) + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("index \"%s\" contains corrupted page at block %u", + RelationGetRelationName(rel), + BufferGetBlockNumber(buf)))); + else + { + HashPageOpaque opaque; + int pagetype; + + opaque = HashPageGetOpaque(page); + pagetype = opaque->hasho_flag & LH_PAGE_TYPE; + + if (pagetype == LH_BUCKET_PAGE) + { + stats.bucket_pages++; + GetHashPageStats(page, &stats); + } + else if (pagetype == LH_OVERFLOW_PAGE) + { + stats.overflow_pages++; + GetHashPageStats(page, &stats); + } + else if (pagetype == LH_BITMAP_PAGE) + stats.bitmap_pages++; + else if (pagetype == LH_UNUSED_PAGE) + stats.unused_pages++; + else + ereport(ERROR, + (errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("unexpected page type 0x%04X in HASH index \"%s\" block %u", + opaque->hasho_flag, RelationGetRelationName(rel), + BufferGetBlockNumber(buf)))); + } + UnlockReleaseBuffer(buf); + } + + /* Done accessing the index */ + index_close(rel, AccessShareLock); + + /* Count unused pages as free space. */ + stats.free_space += (uint64) stats.unused_pages * stats.space_per_page; + + /* + * Total space available for tuples excludes the metapage and the bitmap + * pages. + */ + total_space = (uint64) (nblocks - (stats.bitmap_pages + 1)) * + stats.space_per_page; + + if (total_space == 0) + free_percent = 0.0; + else + free_percent = 100.0 * stats.free_space / total_space; + + /* + * Build a tuple descriptor for our result type + */ + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + tupleDesc = BlessTupleDesc(tupleDesc); + + /* + * Build and return the tuple + */ + values[0] = Int32GetDatum(stats.version); + values[1] = Int64GetDatum((int64) stats.bucket_pages); + values[2] = Int64GetDatum((int64) stats.overflow_pages); + values[3] = Int64GetDatum((int64) stats.bitmap_pages); + values[4] = Int64GetDatum((int64) stats.unused_pages); + values[5] = Int64GetDatum(stats.live_items); + values[6] = Int64GetDatum(stats.dead_items); + values[7] = Float8GetDatum(free_percent); + tuple = heap_form_tuple(tupleDesc, values, nulls); + + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); +} + +/* ------------------------------------------------- + * GetHashPageStats() + * + * Collect statistics of single hash page + * ------------------------------------------------- + */ +static void +GetHashPageStats(Page page, HashIndexStat *stats) +{ + OffsetNumber maxoff = PageGetMaxOffsetNumber(page); + int off; + + /* count live and dead tuples, and free space */ + for (off = FirstOffsetNumber; off <= maxoff; off++) + { + ItemId id = PageGetItemId(page, off); + + if (!ItemIdIsDead(id)) + stats->live_items++; + else + stats->dead_items++; + } + stats->free_space += PageGetExactFreeSpace(page); +} diff --git a/contrib/pgstattuple/pgstattuple--1.0--1.1.sql b/contrib/pgstattuple/pgstattuple--1.0--1.1.sql new file mode 100644 index 0000000..cf582a0 --- /dev/null +++ b/contrib/pgstattuple/pgstattuple--1.0--1.1.sql @@ -0,0 +1,11 @@ +/* contrib/pgstattuple/pgstattuple--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgstattuple UPDATE TO '1.1'" to load this file. \quit + +CREATE FUNCTION pgstatginindex(IN relname regclass, + OUT version INT4, + OUT pending_pages INT4, + OUT pending_tuples BIGINT) +AS 'MODULE_PATHNAME', 'pgstatginindex' +LANGUAGE C STRICT; diff --git a/contrib/pgstattuple/pgstattuple--1.1--1.2.sql b/contrib/pgstattuple/pgstattuple--1.1--1.2.sql new file mode 100644 index 0000000..2783a63 --- /dev/null +++ b/contrib/pgstattuple/pgstattuple--1.1--1.2.sql @@ -0,0 +1,39 @@ +/* contrib/pgstattuple/pgstattuple--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgstattuple UPDATE TO '1.2'" to load this file. \quit + +ALTER EXTENSION pgstattuple DROP FUNCTION pgstattuple(oid); +DROP FUNCTION pgstattuple(oid); + +CREATE FUNCTION pgstattuple(IN reloid regclass, + OUT table_len BIGINT, -- physical table length in bytes + OUT tuple_count BIGINT, -- number of live tuples + OUT tuple_len BIGINT, -- total tuples length in bytes + OUT tuple_percent FLOAT8, -- live tuples in % + OUT dead_tuple_count BIGINT, -- number of dead tuples + OUT dead_tuple_len BIGINT, -- total dead tuples length in bytes + OUT dead_tuple_percent FLOAT8, -- dead tuples in % + OUT free_space BIGINT, -- free space in bytes + OUT free_percent FLOAT8) -- free space in % +AS 'MODULE_PATHNAME', 'pgstattuplebyid' +LANGUAGE C STRICT; + +CREATE FUNCTION pgstatindex(IN relname regclass, + OUT version INT, + OUT tree_level INT, + OUT index_size BIGINT, + OUT root_block_no BIGINT, + OUT internal_pages BIGINT, + OUT leaf_pages BIGINT, + OUT empty_pages BIGINT, + OUT deleted_pages BIGINT, + OUT avg_leaf_density FLOAT8, + OUT leaf_fragmentation FLOAT8) +AS 'MODULE_PATHNAME', 'pgstatindexbyid' +LANGUAGE C STRICT; + +CREATE FUNCTION pg_relpages(IN relname regclass) +RETURNS BIGINT +AS 'MODULE_PATHNAME', 'pg_relpagesbyid' +LANGUAGE C STRICT; diff --git a/contrib/pgstattuple/pgstattuple--1.2--1.3.sql b/contrib/pgstattuple/pgstattuple--1.2--1.3.sql new file mode 100644 index 0000000..99301a2 --- /dev/null +++ b/contrib/pgstattuple/pgstattuple--1.2--1.3.sql @@ -0,0 +1,18 @@ +/* contrib/pgstattuple/pgstattuple--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgstattuple UPDATE TO '1.3'" to load this file. \quit + +CREATE FUNCTION pgstattuple_approx(IN reloid regclass, + OUT table_len BIGINT, -- physical table length in bytes + OUT scanned_percent FLOAT8, -- what percentage of the table's pages was scanned + OUT approx_tuple_count BIGINT, -- estimated number of live tuples + OUT approx_tuple_len BIGINT, -- estimated total length in bytes of live tuples + OUT approx_tuple_percent FLOAT8, -- live tuples in % (based on estimate) + OUT dead_tuple_count BIGINT, -- exact number of dead tuples + OUT dead_tuple_len BIGINT, -- exact total length in bytes of dead tuples + OUT dead_tuple_percent FLOAT8, -- dead tuples in % (based on estimate) + OUT approx_free_space BIGINT, -- estimated free space in bytes + OUT approx_free_percent FLOAT8) -- free space in % (based on estimate) +AS 'MODULE_PATHNAME', 'pgstattuple_approx' +LANGUAGE C STRICT; diff --git a/contrib/pgstattuple/pgstattuple--1.3--1.4.sql b/contrib/pgstattuple/pgstattuple--1.3--1.4.sql new file mode 100644 index 0000000..9130650 --- /dev/null +++ b/contrib/pgstattuple/pgstattuple--1.3--1.4.sql @@ -0,0 +1,13 @@ +/* contrib/pgstattuple/pgstattuple--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgstattuple UPDATE TO '1.4'" to load this file. \quit + +ALTER FUNCTION pgstattuple(text) PARALLEL SAFE; +ALTER FUNCTION pgstatindex(text) PARALLEL SAFE; +ALTER FUNCTION pg_relpages(text) PARALLEL SAFE; +ALTER FUNCTION pgstatginindex(regclass) PARALLEL SAFE; +ALTER FUNCTION pgstattuple(regclass) PARALLEL SAFE; +ALTER FUNCTION pgstatindex(regclass) PARALLEL SAFE; +ALTER FUNCTION pg_relpages(regclass) PARALLEL SAFE; +ALTER FUNCTION pgstattuple_approx(regclass) PARALLEL SAFE; diff --git a/contrib/pgstattuple/pgstattuple--1.4--1.5.sql b/contrib/pgstattuple/pgstattuple--1.4--1.5.sql new file mode 100644 index 0000000..5d03544 --- /dev/null +++ b/contrib/pgstattuple/pgstattuple--1.4--1.5.sql @@ -0,0 +1,136 @@ +/* contrib/pgstattuple/pgstattuple--1.4--1.5.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pgstattuple UPDATE TO '1.5'" to load this file. \quit + +CREATE OR REPLACE FUNCTION pgstattuple(IN relname text, + OUT table_len BIGINT, -- physical table length in bytes + OUT tuple_count BIGINT, -- number of live tuples + OUT tuple_len BIGINT, -- total tuples length in bytes + OUT tuple_percent FLOAT8, -- live tuples in % + OUT dead_tuple_count BIGINT, -- number of dead tuples + OUT dead_tuple_len BIGINT, -- total dead tuples length in bytes + OUT dead_tuple_percent FLOAT8, -- dead tuples in % + OUT free_space BIGINT, -- free space in bytes + OUT free_percent FLOAT8) -- free space in % +AS 'MODULE_PATHNAME', 'pgstattuple_v1_5' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pgstattuple(text) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgstattuple(text) TO pg_stat_scan_tables; + +CREATE OR REPLACE FUNCTION pgstatindex(IN relname text, + OUT version INT, + OUT tree_level INT, + OUT index_size BIGINT, + OUT root_block_no BIGINT, + OUT internal_pages BIGINT, + OUT leaf_pages BIGINT, + OUT empty_pages BIGINT, + OUT deleted_pages BIGINT, + OUT avg_leaf_density FLOAT8, + OUT leaf_fragmentation FLOAT8) +AS 'MODULE_PATHNAME', 'pgstatindex_v1_5' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pgstatindex(text) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgstatindex(text) TO pg_stat_scan_tables; + +CREATE OR REPLACE FUNCTION pg_relpages(IN relname text) +RETURNS BIGINT +AS 'MODULE_PATHNAME', 'pg_relpages_v1_5' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pg_relpages(text) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_relpages(text) TO pg_stat_scan_tables; + +/* New stuff in 1.1 begins here */ + +CREATE OR REPLACE FUNCTION pgstatginindex(IN relname regclass, + OUT version INT4, + OUT pending_pages INT4, + OUT pending_tuples BIGINT) +AS 'MODULE_PATHNAME', 'pgstatginindex_v1_5' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pgstatginindex(regclass) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgstatginindex(regclass) TO pg_stat_scan_tables; + +/* New stuff in 1.2 begins here */ + +CREATE OR REPLACE FUNCTION pgstattuple(IN reloid regclass, + OUT table_len BIGINT, -- physical table length in bytes + OUT tuple_count BIGINT, -- number of live tuples + OUT tuple_len BIGINT, -- total tuples length in bytes + OUT tuple_percent FLOAT8, -- live tuples in % + OUT dead_tuple_count BIGINT, -- number of dead tuples + OUT dead_tuple_len BIGINT, -- total dead tuples length in bytes + OUT dead_tuple_percent FLOAT8, -- dead tuples in % + OUT free_space BIGINT, -- free space in bytes + OUT free_percent FLOAT8) -- free space in % +AS 'MODULE_PATHNAME', 'pgstattuplebyid_v1_5' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pgstattuple(regclass) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgstattuple(regclass) TO pg_stat_scan_tables; + +CREATE OR REPLACE FUNCTION pgstatindex(IN relname regclass, + OUT version INT, + OUT tree_level INT, + OUT index_size BIGINT, + OUT root_block_no BIGINT, + OUT internal_pages BIGINT, + OUT leaf_pages BIGINT, + OUT empty_pages BIGINT, + OUT deleted_pages BIGINT, + OUT avg_leaf_density FLOAT8, + OUT leaf_fragmentation FLOAT8) +AS 'MODULE_PATHNAME', 'pgstatindexbyid_v1_5' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pgstatindex(regclass) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgstatindex(regclass) TO pg_stat_scan_tables; + +CREATE OR REPLACE FUNCTION pg_relpages(IN relname regclass) +RETURNS BIGINT +AS 'MODULE_PATHNAME', 'pg_relpagesbyid_v1_5' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pg_relpages(regclass) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pg_relpages(regclass) TO pg_stat_scan_tables; + +/* New stuff in 1.3 begins here */ + +CREATE OR REPLACE FUNCTION pgstattuple_approx(IN reloid regclass, + OUT table_len BIGINT, -- physical table length in bytes + OUT scanned_percent FLOAT8, -- what percentage of the table's pages was scanned + OUT approx_tuple_count BIGINT, -- estimated number of live tuples + OUT approx_tuple_len BIGINT, -- estimated total length in bytes of live tuples + OUT approx_tuple_percent FLOAT8, -- live tuples in % (based on estimate) + OUT dead_tuple_count BIGINT, -- exact number of dead tuples + OUT dead_tuple_len BIGINT, -- exact total length in bytes of dead tuples + OUT dead_tuple_percent FLOAT8, -- dead tuples in % (based on estimate) + OUT approx_free_space BIGINT, -- estimated free space in bytes + OUT approx_free_percent FLOAT8) -- free space in % (based on estimate) +AS 'MODULE_PATHNAME', 'pgstattuple_approx_v1_5' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pgstattuple_approx(regclass) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgstattuple_approx(regclass) TO pg_stat_scan_tables; + +/* New stuff in 1.5 begins here */ + +CREATE OR REPLACE FUNCTION pgstathashindex(IN relname regclass, + OUT version INTEGER, + OUT bucket_pages BIGINT, + OUT overflow_pages BIGINT, + OUT bitmap_pages BIGINT, + OUT unused_pages BIGINT, + OUT live_items BIGINT, + OUT dead_items BIGINT, + OUT free_percent FLOAT8) +AS 'MODULE_PATHNAME', 'pgstathashindex' +LANGUAGE C STRICT PARALLEL SAFE; + +REVOKE EXECUTE ON FUNCTION pgstathashindex(regclass) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION pgstathashindex(regclass) TO pg_stat_scan_tables; diff --git a/contrib/pgstattuple/pgstattuple--1.4.sql b/contrib/pgstattuple/pgstattuple--1.4.sql new file mode 100644 index 0000000..47377eb --- /dev/null +++ b/contrib/pgstattuple/pgstattuple--1.4.sql @@ -0,0 +1,95 @@ +/* contrib/pgstattuple/pgstattuple--1.4.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION pgstattuple" to load this file. \quit + +CREATE FUNCTION pgstattuple(IN relname text, + OUT table_len BIGINT, -- physical table length in bytes + OUT tuple_count BIGINT, -- number of live tuples + OUT tuple_len BIGINT, -- total tuples length in bytes + OUT tuple_percent FLOAT8, -- live tuples in % + OUT dead_tuple_count BIGINT, -- number of dead tuples + OUT dead_tuple_len BIGINT, -- total dead tuples length in bytes + OUT dead_tuple_percent FLOAT8, -- dead tuples in % + OUT free_space BIGINT, -- free space in bytes + OUT free_percent FLOAT8) -- free space in % +AS 'MODULE_PATHNAME', 'pgstattuple' +LANGUAGE C STRICT PARALLEL SAFE; + +CREATE FUNCTION pgstatindex(IN relname text, + OUT version INT, + OUT tree_level INT, + OUT index_size BIGINT, + OUT root_block_no BIGINT, + OUT internal_pages BIGINT, + OUT leaf_pages BIGINT, + OUT empty_pages BIGINT, + OUT deleted_pages BIGINT, + OUT avg_leaf_density FLOAT8, + OUT leaf_fragmentation FLOAT8) +AS 'MODULE_PATHNAME', 'pgstatindex' +LANGUAGE C STRICT PARALLEL SAFE; + +CREATE FUNCTION pg_relpages(IN relname text) +RETURNS BIGINT +AS 'MODULE_PATHNAME', 'pg_relpages' +LANGUAGE C STRICT PARALLEL SAFE; + +/* New stuff in 1.1 begins here */ + +CREATE FUNCTION pgstatginindex(IN relname regclass, + OUT version INT4, + OUT pending_pages INT4, + OUT pending_tuples BIGINT) +AS 'MODULE_PATHNAME', 'pgstatginindex' +LANGUAGE C STRICT PARALLEL SAFE; + +/* New stuff in 1.2 begins here */ + +CREATE FUNCTION pgstattuple(IN reloid regclass, + OUT table_len BIGINT, -- physical table length in bytes + OUT tuple_count BIGINT, -- number of live tuples + OUT tuple_len BIGINT, -- total tuples length in bytes + OUT tuple_percent FLOAT8, -- live tuples in % + OUT dead_tuple_count BIGINT, -- number of dead tuples + OUT dead_tuple_len BIGINT, -- total dead tuples length in bytes + OUT dead_tuple_percent FLOAT8, -- dead tuples in % + OUT free_space BIGINT, -- free space in bytes + OUT free_percent FLOAT8) -- free space in % +AS 'MODULE_PATHNAME', 'pgstattuplebyid' +LANGUAGE C STRICT PARALLEL SAFE; + +CREATE FUNCTION pgstatindex(IN relname regclass, + OUT version INT, + OUT tree_level INT, + OUT index_size BIGINT, + OUT root_block_no BIGINT, + OUT internal_pages BIGINT, + OUT leaf_pages BIGINT, + OUT empty_pages BIGINT, + OUT deleted_pages BIGINT, + OUT avg_leaf_density FLOAT8, + OUT leaf_fragmentation FLOAT8) +AS 'MODULE_PATHNAME', 'pgstatindexbyid' +LANGUAGE C STRICT PARALLEL SAFE; + +CREATE FUNCTION pg_relpages(IN relname regclass) +RETURNS BIGINT +AS 'MODULE_PATHNAME', 'pg_relpagesbyid' +LANGUAGE C STRICT PARALLEL SAFE; + +/* New stuff in 1.3 begins here */ + +CREATE FUNCTION pgstattuple_approx(IN reloid regclass, + OUT table_len BIGINT, -- physical table length in bytes + OUT scanned_percent FLOAT8, -- what percentage of the table's pages was scanned + OUT approx_tuple_count BIGINT, -- estimated number of live tuples + OUT approx_tuple_len BIGINT, -- estimated total length in bytes of live tuples + OUT approx_tuple_percent FLOAT8, -- live tuples in % (based on estimate) + OUT dead_tuple_count BIGINT, -- exact number of dead tuples + OUT dead_tuple_len BIGINT, -- exact total length in bytes of dead tuples + OUT dead_tuple_percent FLOAT8, -- dead tuples in % (based on estimate) + OUT approx_free_space BIGINT, -- estimated free space in bytes + OUT approx_free_percent FLOAT8) -- free space in % (based on estimate) +AS 'MODULE_PATHNAME', 'pgstattuple_approx' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c new file mode 100644 index 0000000..3bd8b96 --- /dev/null +++ b/contrib/pgstattuple/pgstattuple.c @@ -0,0 +1,585 @@ +/* + * contrib/pgstattuple/pgstattuple.c + * + * Copyright (c) 2001,2002 Tatsuo Ishii + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose, without fee, and without a + * written agreement is hereby granted, provided that the above + * copyright notice and this paragraph and the following two + * paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, + * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS + * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, + * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ + +#include "postgres.h" + +#include "access/gist_private.h" +#include "access/hash.h" +#include "access/heapam.h" +#include "access/nbtree.h" +#include "access/relscan.h" +#include "access/tableam.h" +#include "catalog/namespace.h" +#include "catalog/pg_am_d.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "storage/bufmgr.h" +#include "storage/lmgr.h" +#include "utils/builtins.h" +#include "utils/varlena.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(pgstattuple); +PG_FUNCTION_INFO_V1(pgstattuple_v1_5); +PG_FUNCTION_INFO_V1(pgstattuplebyid); +PG_FUNCTION_INFO_V1(pgstattuplebyid_v1_5); + +/* + * struct pgstattuple_type + * + * tuple_percent, dead_tuple_percent and free_percent are computable, + * so not defined here. + */ +typedef struct pgstattuple_type +{ + uint64 table_len; + uint64 tuple_count; + uint64 tuple_len; + uint64 dead_tuple_count; + uint64 dead_tuple_len; + uint64 free_space; /* free/reusable space in bytes */ +} pgstattuple_type; + +typedef void (*pgstat_page) (pgstattuple_type *, Relation, BlockNumber, + BufferAccessStrategy); + +static Datum build_pgstattuple_type(pgstattuple_type *stat, + FunctionCallInfo fcinfo); +static Datum pgstat_relation(Relation rel, FunctionCallInfo fcinfo); +static Datum pgstat_heap(Relation rel, FunctionCallInfo fcinfo); +static void pgstat_btree_page(pgstattuple_type *stat, + Relation rel, BlockNumber blkno, + BufferAccessStrategy bstrategy); +static void pgstat_hash_page(pgstattuple_type *stat, + Relation rel, BlockNumber blkno, + BufferAccessStrategy bstrategy); +static void pgstat_gist_page(pgstattuple_type *stat, + Relation rel, BlockNumber blkno, + BufferAccessStrategy bstrategy); +static Datum pgstat_index(Relation rel, BlockNumber start, + pgstat_page pagefn, FunctionCallInfo fcinfo); +static void pgstat_index_page(pgstattuple_type *stat, Page page, + OffsetNumber minoff, OffsetNumber maxoff); + +/* + * build_pgstattuple_type -- build a pgstattuple_type tuple + */ +static Datum +build_pgstattuple_type(pgstattuple_type *stat, FunctionCallInfo fcinfo) +{ +#define NCOLUMNS 9 +#define NCHARS 314 + + HeapTuple tuple; + char *values[NCOLUMNS]; + char values_buf[NCOLUMNS][NCHARS]; + int i; + double tuple_percent; + double dead_tuple_percent; + double free_percent; /* free/reusable space in % */ + TupleDesc tupdesc; + AttInMetadata *attinmeta; + + /* 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"); + + /* + * Generate attribute metadata needed later to produce tuples from raw C + * strings + */ + attinmeta = TupleDescGetAttInMetadata(tupdesc); + + if (stat->table_len == 0) + { + tuple_percent = 0.0; + dead_tuple_percent = 0.0; + free_percent = 0.0; + } + else + { + tuple_percent = 100.0 * stat->tuple_len / stat->table_len; + dead_tuple_percent = 100.0 * stat->dead_tuple_len / stat->table_len; + free_percent = 100.0 * stat->free_space / stat->table_len; + } + + /* + * Prepare a values array for constructing the tuple. This should be an + * array of C strings which will be processed later by the appropriate + * "in" functions. + */ + for (i = 0; i < NCOLUMNS; i++) + values[i] = values_buf[i]; + i = 0; + snprintf(values[i++], NCHARS, INT64_FORMAT, stat->table_len); + snprintf(values[i++], NCHARS, INT64_FORMAT, stat->tuple_count); + snprintf(values[i++], NCHARS, INT64_FORMAT, stat->tuple_len); + snprintf(values[i++], NCHARS, "%.2f", tuple_percent); + snprintf(values[i++], NCHARS, INT64_FORMAT, stat->dead_tuple_count); + snprintf(values[i++], NCHARS, INT64_FORMAT, stat->dead_tuple_len); + snprintf(values[i++], NCHARS, "%.2f", dead_tuple_percent); + snprintf(values[i++], NCHARS, INT64_FORMAT, stat->free_space); + snprintf(values[i++], NCHARS, "%.2f", free_percent); + + /* build a tuple */ + tuple = BuildTupleFromCStrings(attinmeta, values); + + /* make the tuple into a datum */ + return HeapTupleGetDatum(tuple); +} + +/* ---------- + * pgstattuple: + * returns live/dead tuples info + * + * C FUNCTION definition + * pgstattuple(text) returns pgstattuple_type + * + * The superuser() check here must be kept as the library might be upgraded + * without the extension being upgraded, meaning that in pre-1.5 installations + * these functions could be called by any user. + * ---------- + */ + +Datum +pgstattuple(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + RangeVar *relrv; + Relation rel; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pgstattuple functions"))); + + /* open relation */ + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + PG_RETURN_DATUM(pgstat_relation(rel, fcinfo)); +} + +/* + * As of pgstattuple version 1.5, we no longer need to check if the user + * is a superuser because we REVOKE EXECUTE on the function from PUBLIC. + * Users can then grant access to it based on their policies. + * + * Otherwise identical to pgstattuple (above). + */ +Datum +pgstattuple_v1_5(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_PP(0); + RangeVar *relrv; + Relation rel; + + /* open relation */ + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + PG_RETURN_DATUM(pgstat_relation(rel, fcinfo)); +} + +/* Must keep superuser() check, see above. */ +Datum +pgstattuplebyid(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Relation rel; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use pgstattuple functions"))); + + /* open relation */ + rel = relation_open(relid, AccessShareLock); + + PG_RETURN_DATUM(pgstat_relation(rel, fcinfo)); +} + +/* Remove superuser() check for 1.5 version, see above */ +Datum +pgstattuplebyid_v1_5(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + Relation rel; + + /* open relation */ + rel = relation_open(relid, AccessShareLock); + + PG_RETURN_DATUM(pgstat_relation(rel, fcinfo)); +} + +/* + * pgstat_relation + */ +static Datum +pgstat_relation(Relation rel, FunctionCallInfo fcinfo) +{ + const char *err; + + /* + * Reject attempts to read non-local temporary relations; we would be + * likely to get wrong data since we have no visibility into the owning + * session's local buffers. + */ + if (RELATION_IS_OTHER_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot access temporary tables of other sessions"))); + + if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) || + rel->rd_rel->relkind == RELKIND_SEQUENCE) + { + return pgstat_heap(rel, fcinfo); + } + else if (rel->rd_rel->relkind == RELKIND_INDEX) + { + /* see pgstatindex_impl */ + if (!rel->rd_index->indisvalid) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("index \"%s\" is not valid", + RelationGetRelationName(rel)))); + + switch (rel->rd_rel->relam) + { + case BTREE_AM_OID: + return pgstat_index(rel, BTREE_METAPAGE + 1, + pgstat_btree_page, fcinfo); + case HASH_AM_OID: + return pgstat_index(rel, HASH_METAPAGE + 1, + pgstat_hash_page, fcinfo); + case GIST_AM_OID: + return pgstat_index(rel, GIST_ROOT_BLKNO + 1, + pgstat_gist_page, fcinfo); + case GIN_AM_OID: + err = "gin index"; + break; + case SPGIST_AM_OID: + err = "spgist index"; + break; + case BRIN_AM_OID: + err = "brin index"; + break; + default: + err = "unknown index"; + break; + } + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("index \"%s\" (%s) is not supported", + RelationGetRelationName(rel), err))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot get tuple-level statistics for relation \"%s\"", + RelationGetRelationName(rel)), + errdetail_relkind_not_supported(rel->rd_rel->relkind))); + } + + return 0; /* should not happen */ +} + +/* + * pgstat_heap -- returns live/dead tuples info in a heap + */ +static Datum +pgstat_heap(Relation rel, FunctionCallInfo fcinfo) +{ + TableScanDesc scan; + HeapScanDesc hscan; + HeapTuple tuple; + BlockNumber nblocks; + BlockNumber block = 0; /* next block to count free space in */ + BlockNumber tupblock; + Buffer buffer; + pgstattuple_type stat = {0}; + SnapshotData SnapshotDirty; + + if (rel->rd_rel->relam != HEAP_TABLE_AM_OID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only heap AM is supported"))); + + /* Disable syncscan because we assume we scan from block zero upwards */ + scan = table_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false); + hscan = (HeapScanDesc) scan; + + InitDirtySnapshot(SnapshotDirty); + + nblocks = hscan->rs_nblocks; /* # blocks to be scanned */ + + /* scan the relation */ + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + CHECK_FOR_INTERRUPTS(); + + /* must hold a buffer lock to call HeapTupleSatisfiesVisibility */ + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE); + + if (HeapTupleSatisfiesVisibility(tuple, &SnapshotDirty, hscan->rs_cbuf)) + { + stat.tuple_len += tuple->t_len; + stat.tuple_count++; + } + else + { + stat.dead_tuple_len += tuple->t_len; + stat.dead_tuple_count++; + } + + LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK); + + /* + * To avoid physically reading the table twice, try to do the + * free-space scan in parallel with the heap scan. However, + * heap_getnext may find no tuples on a given page, so we cannot + * simply examine the pages returned by the heap scan. + */ + tupblock = ItemPointerGetBlockNumber(&tuple->t_self); + + while (block <= tupblock) + { + CHECK_FOR_INTERRUPTS(); + + buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block, + RBM_NORMAL, hscan->rs_strategy); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer)); + UnlockReleaseBuffer(buffer); + block++; + } + } + + while (block < nblocks) + { + CHECK_FOR_INTERRUPTS(); + + buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block, + RBM_NORMAL, hscan->rs_strategy); + LockBuffer(buffer, BUFFER_LOCK_SHARE); + stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer)); + UnlockReleaseBuffer(buffer); + block++; + } + + table_endscan(scan); + relation_close(rel, AccessShareLock); + + stat.table_len = (uint64) nblocks * BLCKSZ; + + return build_pgstattuple_type(&stat, fcinfo); +} + +/* + * pgstat_btree_page -- check tuples in a btree page + */ +static void +pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno, + BufferAccessStrategy bstrategy) +{ + Buffer buf; + Page page; + + buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy); + LockBuffer(buf, BT_READ); + page = BufferGetPage(buf); + + /* Page is valid, see what to do with it */ + if (PageIsNew(page)) + { + /* fully empty page */ + stat->free_space += BLCKSZ; + } + else + { + BTPageOpaque opaque; + + opaque = BTPageGetOpaque(page); + if (P_IGNORE(opaque)) + { + /* deleted or half-dead page */ + stat->free_space += BLCKSZ; + } + else if (P_ISLEAF(opaque)) + { + pgstat_index_page(stat, page, P_FIRSTDATAKEY(opaque), + PageGetMaxOffsetNumber(page)); + } + else + { + /* internal page */ + } + } + + _bt_relbuf(rel, buf); +} + +/* + * pgstat_hash_page -- check tuples in a hash page + */ +static void +pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno, + BufferAccessStrategy bstrategy) +{ + Buffer buf; + Page page; + + buf = _hash_getbuf_with_strategy(rel, blkno, HASH_READ, 0, bstrategy); + page = BufferGetPage(buf); + + if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData))) + { + HashPageOpaque opaque; + + opaque = HashPageGetOpaque(page); + switch (opaque->hasho_flag & LH_PAGE_TYPE) + { + case LH_UNUSED_PAGE: + stat->free_space += BLCKSZ; + break; + case LH_BUCKET_PAGE: + case LH_OVERFLOW_PAGE: + pgstat_index_page(stat, page, FirstOffsetNumber, + PageGetMaxOffsetNumber(page)); + break; + case LH_BITMAP_PAGE: + case LH_META_PAGE: + default: + break; + } + } + else + { + /* maybe corrupted */ + } + + _hash_relbuf(rel, buf); +} + +/* + * pgstat_gist_page -- check tuples in a gist page + */ +static void +pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno, + BufferAccessStrategy bstrategy) +{ + Buffer buf; + Page page; + + buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy); + LockBuffer(buf, GIST_SHARE); + gistcheckpage(rel, buf); + page = BufferGetPage(buf); + + if (GistPageIsLeaf(page)) + { + pgstat_index_page(stat, page, FirstOffsetNumber, + PageGetMaxOffsetNumber(page)); + } + else + { + /* root or node */ + } + + UnlockReleaseBuffer(buf); +} + +/* + * pgstat_index -- returns live/dead tuples info in a generic index + */ +static Datum +pgstat_index(Relation rel, BlockNumber start, pgstat_page pagefn, + FunctionCallInfo fcinfo) +{ + BlockNumber nblocks; + BlockNumber blkno; + BufferAccessStrategy bstrategy; + pgstattuple_type stat = {0}; + + /* prepare access strategy for this index */ + bstrategy = GetAccessStrategy(BAS_BULKREAD); + + blkno = start; + for (;;) + { + /* Get the current relation length */ + LockRelationForExtension(rel, ExclusiveLock); + nblocks = RelationGetNumberOfBlocks(rel); + UnlockRelationForExtension(rel, ExclusiveLock); + + /* Quit if we've scanned the whole relation */ + if (blkno >= nblocks) + { + stat.table_len = (uint64) nblocks * BLCKSZ; + + break; + } + + for (; blkno < nblocks; blkno++) + { + CHECK_FOR_INTERRUPTS(); + + pagefn(&stat, rel, blkno, bstrategy); + } + } + + relation_close(rel, AccessShareLock); + + return build_pgstattuple_type(&stat, fcinfo); +} + +/* + * pgstat_index_page -- for generic index page + */ +static void +pgstat_index_page(pgstattuple_type *stat, Page page, + OffsetNumber minoff, OffsetNumber maxoff) +{ + OffsetNumber i; + + stat->free_space += PageGetFreeSpace(page); + + for (i = minoff; i <= maxoff; i = OffsetNumberNext(i)) + { + ItemId itemid = PageGetItemId(page, i); + + if (ItemIdIsDead(itemid)) + { + stat->dead_tuple_count++; + stat->dead_tuple_len += ItemIdGetLength(itemid); + } + else + { + stat->tuple_count++; + stat->tuple_len += ItemIdGetLength(itemid); + } + } +} diff --git a/contrib/pgstattuple/pgstattuple.control b/contrib/pgstattuple/pgstattuple.control new file mode 100644 index 0000000..6af4075 --- /dev/null +++ b/contrib/pgstattuple/pgstattuple.control @@ -0,0 +1,5 @@ +# pgstattuple extension +comment = 'show tuple-level statistics' +default_version = '1.5' +module_pathname = '$libdir/pgstattuple' +relocatable = true diff --git a/contrib/pgstattuple/sql/pgstattuple.sql b/contrib/pgstattuple/sql/pgstattuple.sql new file mode 100644 index 0000000..b08c31c --- /dev/null +++ b/contrib/pgstattuple/sql/pgstattuple.sql @@ -0,0 +1,126 @@ +CREATE EXTENSION pgstattuple; + +-- +-- It's difficult to come up with platform-independent test cases for +-- the pgstattuple functions, but the results for empty tables and +-- indexes should be that. +-- + +create table test (a int primary key, b int[]); + +select * from pgstattuple('test'); +select * from pgstattuple('test'::text); +select * from pgstattuple('test'::name); +select * from pgstattuple('test'::regclass); +select pgstattuple(oid) from pg_class where relname = 'test'; +select pgstattuple(relname) from pg_class where relname = 'test'; + +select version, tree_level, + index_size / current_setting('block_size')::int as index_size, + root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages, + avg_leaf_density, leaf_fragmentation + from pgstatindex('test_pkey'); +select version, tree_level, + index_size / current_setting('block_size')::int as index_size, + root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages, + avg_leaf_density, leaf_fragmentation + from pgstatindex('test_pkey'::text); +select version, tree_level, + index_size / current_setting('block_size')::int as index_size, + root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages, + avg_leaf_density, leaf_fragmentation + from pgstatindex('test_pkey'::name); +select version, tree_level, + index_size / current_setting('block_size')::int as index_size, + root_block_no, internal_pages, leaf_pages, empty_pages, deleted_pages, + avg_leaf_density, leaf_fragmentation + from pgstatindex('test_pkey'::regclass); + +select pg_relpages('test'); +select pg_relpages('test_pkey'); +select pg_relpages('test_pkey'::text); +select pg_relpages('test_pkey'::name); +select pg_relpages('test_pkey'::regclass); +select pg_relpages(oid) from pg_class where relname = 'test_pkey'; +select pg_relpages(relname) from pg_class where relname = 'test_pkey'; + +create index test_ginidx on test using gin (b); + +select * from pgstatginindex('test_ginidx'); + +create index test_hashidx on test using hash (b); + +select * from pgstathashindex('test_hashidx'); + +-- these should error with the wrong type +select pgstatginindex('test_pkey'); +select pgstathashindex('test_pkey'); + +select pgstatindex('test_ginidx'); +select pgstathashindex('test_ginidx'); + +select pgstatindex('test_hashidx'); +select pgstatginindex('test_hashidx'); + +-- check that using any of these functions with unsupported relations will fail +create table test_partitioned (a int) partition by range (a); +create index test_partitioned_index on test_partitioned(a); +create index test_partitioned_hash_index on test_partitioned using hash(a); +-- these should all fail +select pgstattuple('test_partitioned'); +select pgstattuple('test_partitioned_index'); +select pgstattuple_approx('test_partitioned'); +select pg_relpages('test_partitioned'); +select pgstatindex('test_partitioned'); +select pgstatginindex('test_partitioned'); +select pgstathashindex('test_partitioned'); +select pgstathashindex('test_partitioned_hash_index'); + +create view test_view as select 1; +-- these should all fail +select pgstattuple('test_view'); +select pgstattuple_approx('test_view'); +select pg_relpages('test_view'); +select pgstatindex('test_view'); +select pgstatginindex('test_view'); +select pgstathashindex('test_view'); + +create foreign data wrapper dummy; +create server dummy_server foreign data wrapper dummy; +create foreign table test_foreign_table () server dummy_server; +-- these should all fail +select pgstattuple('test_foreign_table'); +select pgstattuple_approx('test_foreign_table'); +select pg_relpages('test_foreign_table'); +select pgstatindex('test_foreign_table'); +select pgstatginindex('test_foreign_table'); +select pgstathashindex('test_foreign_table'); + +-- a partition of a partitioned table should work though +create table test_partition partition of test_partitioned for values from (1) to (100); +select pgstattuple('test_partition'); +select pgstattuple_approx('test_partition'); +select pg_relpages('test_partition'); + +-- toast tables should work +select pgstattuple((select reltoastrelid from pg_class where relname = 'test')); +select pgstattuple_approx((select reltoastrelid from pg_class where relname = 'test')); +select pg_relpages((select reltoastrelid from pg_class where relname = 'test')); + +-- not for the index calls though, of course +select pgstatindex('test_partition'); +select pgstatginindex('test_partition'); +select pgstathashindex('test_partition'); + +-- an actual index of a partitioned table should work though +create index test_partition_idx on test_partition(a); +create index test_partition_hash_idx on test_partition using hash (a); +-- these should work +select pgstatindex('test_partition_idx'); +select pgstathashindex('test_partition_hash_idx'); + +drop table test_partitioned; +drop view test_view; +drop foreign table test_foreign_table; +drop server dummy_server; +drop foreign data wrapper dummy; diff --git a/contrib/postgres_fdw/.gitignore b/contrib/postgres_fdw/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/postgres_fdw/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile new file mode 100644 index 0000000..c1b0cad --- /dev/null +++ b/contrib/postgres_fdw/Makefile @@ -0,0 +1,31 @@ +# contrib/postgres_fdw/Makefile + +MODULE_big = postgres_fdw +OBJS = \ + $(WIN32RES) \ + connection.o \ + deparse.o \ + option.o \ + postgres_fdw.o \ + shippable.o +PGFILEDESC = "postgres_fdw - foreign data wrapper for PostgreSQL" + +PG_CPPFLAGS = -I$(libpq_srcdir) +SHLIB_LINK_INTERNAL = $(libpq) + +EXTENSION = postgres_fdw +DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql + +REGRESS = postgres_fdw + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +SHLIB_PREREQS = submake-libpq +subdir = contrib/postgres_fdw +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c new file mode 100644 index 0000000..f839308 --- /dev/null +++ b/contrib/postgres_fdw/connection.c @@ -0,0 +1,2226 @@ +/*------------------------------------------------------------------------- + * + * connection.c + * Connection management functions for postgres_fdw + * + * Portions Copyright (c) 2012-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/postgres_fdw/connection.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/xact.h" +#include "catalog/pg_user_mapping.h" +#include "commands/defrem.h" +#include "funcapi.h" +#include "libpq/libpq-be.h" +#include "libpq/libpq-be-fe-helpers.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "postgres_fdw.h" +#include "storage/fd.h" +#include "storage/latch.h" +#include "utils/builtins.h" +#include "utils/datetime.h" +#include "utils/hsearch.h" +#include "utils/inval.h" +#include "utils/memutils.h" +#include "utils/syscache.h" + +/* + * Connection cache hash table entry + * + * The lookup key in this hash table is the user mapping OID. We use just one + * connection per user mapping ID, which ensures that all the scans use the + * same snapshot during a query. Using the user mapping OID rather than + * the foreign server OID + user OID avoids creating multiple connections when + * the public user mapping applies to all user OIDs. + * + * The "conn" pointer can be NULL if we don't currently have a live connection. + * When we do have a connection, xact_depth tracks the current depth of + * transactions and subtransactions open on the remote side. We need to issue + * commands at the same nesting depth on the remote as we're executing at + * ourselves, so that rolling back a subtransaction will kill the right + * queries and not the wrong ones. + */ +typedef Oid ConnCacheKey; + +typedef struct ConnCacheEntry +{ + ConnCacheKey key; /* hash key (must be first) */ + PGconn *conn; /* connection to foreign server, or NULL */ + /* Remaining fields are invalid when conn is NULL: */ + int xact_depth; /* 0 = no xact open, 1 = main xact open, 2 = + * one level of subxact open, etc */ + bool have_prep_stmt; /* have we prepared any stmts in this xact? */ + bool have_error; /* have any subxacts aborted in this xact? */ + bool changing_xact_state; /* xact state change in process */ + bool parallel_commit; /* do we commit (sub)xacts in parallel? */ + bool parallel_abort; /* do we abort (sub)xacts in parallel? */ + bool invalidated; /* true if reconnect is pending */ + bool keep_connections; /* setting value of keep_connections + * server option */ + Oid serverid; /* foreign server OID used to get server name */ + uint32 server_hashvalue; /* hash value of foreign server OID */ + uint32 mapping_hashvalue; /* hash value of user mapping OID */ + PgFdwConnState state; /* extra per-connection state */ +} ConnCacheEntry; + +/* + * Connection cache (initialized on first use) + */ +static HTAB *ConnectionHash = NULL; + +/* for assigning cursor numbers and prepared statement numbers */ +static unsigned int cursor_number = 0; +static unsigned int prep_stmt_number = 0; + +/* tracks whether any work is needed in callback functions */ +static bool xact_got_connection = false; + +/* + * Milliseconds to wait to cancel an in-progress query or execute a cleanup + * query; if it takes longer than 30 seconds to do these, we assume the + * connection is dead. + */ +#define CONNECTION_CLEANUP_TIMEOUT 30000 + +/* Macro for constructing abort command to be sent */ +#define CONSTRUCT_ABORT_COMMAND(sql, entry, toplevel) \ + do { \ + if (toplevel) \ + snprintf((sql), sizeof(sql), \ + "ABORT TRANSACTION"); \ + else \ + snprintf((sql), sizeof(sql), \ + "ROLLBACK TO SAVEPOINT s%d; RELEASE SAVEPOINT s%d", \ + (entry)->xact_depth, (entry)->xact_depth); \ + } while(0) + +/* + * SQL functions + */ +PG_FUNCTION_INFO_V1(postgres_fdw_get_connections); +PG_FUNCTION_INFO_V1(postgres_fdw_disconnect); +PG_FUNCTION_INFO_V1(postgres_fdw_disconnect_all); + +/* prototypes of private functions */ +static void make_new_connection(ConnCacheEntry *entry, UserMapping *user); +static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user); +static void disconnect_pg_server(ConnCacheEntry *entry); +static void check_conn_params(const char **keywords, const char **values, UserMapping *user); +static void configure_remote_session(PGconn *conn); +static void do_sql_command_begin(PGconn *conn, const char *sql); +static void do_sql_command_end(PGconn *conn, const char *sql, + bool consume_input); +static void begin_remote_xact(ConnCacheEntry *entry); +static void pgfdw_xact_callback(XactEvent event, void *arg); +static void pgfdw_subxact_callback(SubXactEvent event, + SubTransactionId mySubid, + SubTransactionId parentSubid, + void *arg); +static void pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue); +static void pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry); +static void pgfdw_reset_xact_state(ConnCacheEntry *entry, bool toplevel); +static bool pgfdw_cancel_query(PGconn *conn); +static bool pgfdw_cancel_query_begin(PGconn *conn); +static bool pgfdw_cancel_query_end(PGconn *conn, TimestampTz endtime, + bool consume_input); +static bool pgfdw_exec_cleanup_query(PGconn *conn, const char *query, + bool ignore_errors); +static bool pgfdw_exec_cleanup_query_begin(PGconn *conn, const char *query); +static bool pgfdw_exec_cleanup_query_end(PGconn *conn, const char *query, + TimestampTz endtime, + bool consume_input, + bool ignore_errors); +static bool pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime, + PGresult **result, bool *timed_out); +static void pgfdw_abort_cleanup(ConnCacheEntry *entry, bool toplevel); +static bool pgfdw_abort_cleanup_begin(ConnCacheEntry *entry, bool toplevel, + List **pending_entries, + List **cancel_requested); +static void pgfdw_finish_pre_commit_cleanup(List *pending_entries); +static void pgfdw_finish_pre_subcommit_cleanup(List *pending_entries, + int curlevel); +static void pgfdw_finish_abort_cleanup(List *pending_entries, + List *cancel_requested, + bool toplevel); +static void pgfdw_security_check(const char **keywords, const char **values, + UserMapping *user, PGconn *conn); +static bool UserMappingPasswordRequired(UserMapping *user); +static bool disconnect_cached_connections(Oid serverid); + +/* + * Get a PGconn which can be used to execute queries on the remote PostgreSQL + * server with the user's authorization. A new connection is established + * if we don't already have a suitable one, and a transaction is opened at + * the right subtransaction nesting depth if we didn't do that already. + * + * will_prep_stmt must be true if caller intends to create any prepared + * statements. Since those don't go away automatically at transaction end + * (not even on error), we need this flag to cue manual cleanup. + * + * If state is not NULL, *state receives the per-connection state associated + * with the PGconn. + */ +PGconn * +GetConnection(UserMapping *user, bool will_prep_stmt, PgFdwConnState **state) +{ + bool found; + bool retry = false; + ConnCacheEntry *entry; + ConnCacheKey key; + MemoryContext ccxt = CurrentMemoryContext; + + /* First time through, initialize connection cache hashtable */ + if (ConnectionHash == NULL) + { + HASHCTL ctl; + + ctl.keysize = sizeof(ConnCacheKey); + ctl.entrysize = sizeof(ConnCacheEntry); + ConnectionHash = hash_create("postgres_fdw connections", 8, + &ctl, + HASH_ELEM | HASH_BLOBS); + + /* + * Register some callback functions that manage connection cleanup. + * This should be done just once in each backend. + */ + RegisterXactCallback(pgfdw_xact_callback, NULL); + RegisterSubXactCallback(pgfdw_subxact_callback, NULL); + CacheRegisterSyscacheCallback(FOREIGNSERVEROID, + pgfdw_inval_callback, (Datum) 0); + CacheRegisterSyscacheCallback(USERMAPPINGOID, + pgfdw_inval_callback, (Datum) 0); + } + + /* Set flag that we did GetConnection during the current transaction */ + xact_got_connection = true; + + /* Create hash key for the entry. Assume no pad bytes in key struct */ + key = user->umid; + + /* + * Find or create cached entry for requested connection. + */ + entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found); + if (!found) + { + /* + * We need only clear "conn" here; remaining fields will be filled + * later when "conn" is set. + */ + entry->conn = NULL; + } + + /* Reject further use of connections which failed abort cleanup. */ + pgfdw_reject_incomplete_xact_state_change(entry); + + /* + * If the connection needs to be remade due to invalidation, disconnect as + * soon as we're out of all transactions. + */ + if (entry->conn != NULL && entry->invalidated && entry->xact_depth == 0) + { + elog(DEBUG3, "closing connection %p for option changes to take effect", + entry->conn); + disconnect_pg_server(entry); + } + + /* + * If cache entry doesn't have a connection, we have to establish a new + * connection. (If connect_pg_server throws an error, the cache entry + * will remain in a valid empty state, ie conn == NULL.) + */ + if (entry->conn == NULL) + make_new_connection(entry, user); + + /* + * We check the health of the cached connection here when using it. In + * cases where we're out of all transactions, if a broken connection is + * detected, we try to reestablish a new connection later. + */ + PG_TRY(); + { + /* Process a pending asynchronous request if any. */ + if (entry->state.pendingAreq) + process_pending_request(entry->state.pendingAreq); + /* Start a new transaction or subtransaction if needed. */ + begin_remote_xact(entry); + } + PG_CATCH(); + { + MemoryContext ecxt = MemoryContextSwitchTo(ccxt); + ErrorData *errdata = CopyErrorData(); + + /* + * Determine whether to try to reestablish the connection. + * + * After a broken connection is detected in libpq, any error other + * than connection failure (e.g., out-of-memory) can be thrown + * somewhere between return from libpq and the expected ereport() call + * in pgfdw_report_error(). In this case, since PQstatus() indicates + * CONNECTION_BAD, checking only PQstatus() causes the false detection + * of connection failure. To avoid this, we also verify that the + * error's sqlstate is ERRCODE_CONNECTION_FAILURE. Note that also + * checking only the sqlstate can cause another false detection + * because pgfdw_report_error() may report ERRCODE_CONNECTION_FAILURE + * for any libpq-originated error condition. + */ + if (errdata->sqlerrcode != ERRCODE_CONNECTION_FAILURE || + PQstatus(entry->conn) != CONNECTION_BAD || + entry->xact_depth > 0) + { + MemoryContextSwitchTo(ecxt); + PG_RE_THROW(); + } + + /* Clean up the error state */ + FlushErrorState(); + FreeErrorData(errdata); + errdata = NULL; + + retry = true; + } + PG_END_TRY(); + + /* + * If a broken connection is detected, disconnect it, reestablish a new + * connection and retry a new remote transaction. If connection failure is + * reported again, we give up getting a connection. + */ + if (retry) + { + Assert(entry->xact_depth == 0); + + ereport(DEBUG3, + (errmsg_internal("could not start remote transaction on connection %p", + entry->conn)), + errdetail_internal("%s", pchomp(PQerrorMessage(entry->conn)))); + + elog(DEBUG3, "closing connection %p to reestablish a new one", + entry->conn); + disconnect_pg_server(entry); + + make_new_connection(entry, user); + + begin_remote_xact(entry); + } + + /* Remember if caller will prepare statements */ + entry->have_prep_stmt |= will_prep_stmt; + + /* If caller needs access to the per-connection state, return it. */ + if (state) + *state = &entry->state; + + return entry->conn; +} + +/* + * Reset all transient state fields in the cached connection entry and + * establish new connection to the remote server. + */ +static void +make_new_connection(ConnCacheEntry *entry, UserMapping *user) +{ + ForeignServer *server = GetForeignServer(user->serverid); + ListCell *lc; + + Assert(entry->conn == NULL); + + /* Reset all transient state fields, to be sure all are clean */ + entry->xact_depth = 0; + entry->have_prep_stmt = false; + entry->have_error = false; + entry->changing_xact_state = false; + entry->invalidated = false; + entry->serverid = server->serverid; + entry->server_hashvalue = + GetSysCacheHashValue1(FOREIGNSERVEROID, + ObjectIdGetDatum(server->serverid)); + entry->mapping_hashvalue = + GetSysCacheHashValue1(USERMAPPINGOID, + ObjectIdGetDatum(user->umid)); + memset(&entry->state, 0, sizeof(entry->state)); + + /* + * Determine whether to keep the connection that we're about to make here + * open even after the transaction using it ends, so that the subsequent + * transactions can re-use it. + * + * By default, all the connections to any foreign servers are kept open. + * + * Also determine whether to commit/abort (sub)transactions opened on the + * remote server in parallel at (sub)transaction end, which is disabled by + * default. + * + * Note: it's enough to determine these only when making a new connection + * because if these settings for it are changed, it will be closed and + * re-made later. + */ + entry->keep_connections = true; + entry->parallel_commit = false; + entry->parallel_abort = false; + foreach(lc, server->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "keep_connections") == 0) + entry->keep_connections = defGetBoolean(def); + else if (strcmp(def->defname, "parallel_commit") == 0) + entry->parallel_commit = defGetBoolean(def); + else if (strcmp(def->defname, "parallel_abort") == 0) + entry->parallel_abort = defGetBoolean(def); + } + + /* Now try to make the connection */ + entry->conn = connect_pg_server(server, user); + + elog(DEBUG3, "new postgres_fdw connection %p for server \"%s\" (user mapping oid %u, userid %u)", + entry->conn, server->servername, user->umid, user->userid); +} + +/* + * Check that non-superuser has used password or delegated credentials + * to establish connection; otherwise, he's piggybacking on the + * postgres server's user identity. See also dblink_security_check() + * in contrib/dblink and check_conn_params. + */ +static void +pgfdw_security_check(const char **keywords, const char **values, UserMapping *user, PGconn *conn) +{ + /* Superusers bypass the check */ + if (superuser_arg(user->userid)) + return; + +#ifdef ENABLE_GSS + /* Connected via GSSAPI with delegated credentials- all good. */ + if (PQconnectionUsedGSSAPI(conn) && be_gssapi_get_delegation(MyProcPort)) + return; +#endif + + /* Ok if superuser set PW required false. */ + if (!UserMappingPasswordRequired(user)) + return; + + /* Connected via PW, with PW required true, and provided non-empty PW. */ + if (PQconnectionUsedPassword(conn)) + { + /* ok if params contain a non-empty password */ + for (int i = 0; keywords[i] != NULL; i++) + { + if (strcmp(keywords[i], "password") == 0 && values[i][0] != '\0') + return; + } + } + + ereport(ERROR, + (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED), + errmsg("password or GSSAPI delegated credentials required"), + errdetail("Non-superuser cannot connect if the server does not request a password or use GSSAPI with delegated credentials."), + errhint("Target server's authentication method must be changed or password_required=false set in the user mapping attributes."))); +} + +/* + * Connect to remote server using specified server and user mapping properties. + */ +static PGconn * +connect_pg_server(ForeignServer *server, UserMapping *user) +{ + PGconn *volatile conn = NULL; + + /* + * Use PG_TRY block to ensure closing connection on error. + */ + PG_TRY(); + { + const char **keywords; + const char **values; + char *appname = NULL; + int n; + + /* + * Construct connection params from generic options of ForeignServer + * and UserMapping. (Some of them might not be libpq options, in + * which case we'll just waste a few array slots.) Add 4 extra slots + * for application_name, fallback_application_name, client_encoding, + * end marker. + */ + n = list_length(server->options) + list_length(user->options) + 4; + keywords = (const char **) palloc(n * sizeof(char *)); + values = (const char **) palloc(n * sizeof(char *)); + + n = 0; + n += ExtractConnectionOptions(server->options, + keywords + n, values + n); + n += ExtractConnectionOptions(user->options, + keywords + n, values + n); + + /* + * Use pgfdw_application_name as application_name if set. + * + * PQconnectdbParams() processes the parameter arrays from start to + * end. If any key word is repeated, the last value is used. Therefore + * note that pgfdw_application_name must be added to the arrays after + * options of ForeignServer are, so that it can override + * application_name set in ForeignServer. + */ + if (pgfdw_application_name && *pgfdw_application_name != '\0') + { + keywords[n] = "application_name"; + values[n] = pgfdw_application_name; + n++; + } + + /* + * Search the parameter arrays to find application_name setting, and + * replace escape sequences in it with status information if found. + * The arrays are searched backwards because the last value is used if + * application_name is repeatedly set. + */ + for (int i = n - 1; i >= 0; i--) + { + if (strcmp(keywords[i], "application_name") == 0 && + *(values[i]) != '\0') + { + /* + * Use this application_name setting if it's not empty string + * even after any escape sequences in it are replaced. + */ + appname = process_pgfdw_appname(values[i]); + if (appname[0] != '\0') + { + values[i] = appname; + break; + } + + /* + * This empty application_name is not used, so we set + * values[i] to NULL and keep searching the array to find the + * next one. + */ + values[i] = NULL; + pfree(appname); + appname = NULL; + } + } + + /* Use "postgres_fdw" as fallback_application_name */ + keywords[n] = "fallback_application_name"; + values[n] = "postgres_fdw"; + n++; + + /* Set client_encoding so that libpq can convert encoding properly. */ + keywords[n] = "client_encoding"; + values[n] = GetDatabaseEncodingName(); + n++; + + keywords[n] = values[n] = NULL; + + /* verify the set of connection parameters */ + check_conn_params(keywords, values, user); + + /* OK to make connection */ + conn = libpqsrv_connect_params(keywords, values, + false, /* expand_dbname */ + PG_WAIT_EXTENSION); + + if (!conn || PQstatus(conn) != CONNECTION_OK) + ereport(ERROR, + (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION), + errmsg("could not connect to server \"%s\"", + server->servername), + errdetail_internal("%s", pchomp(PQerrorMessage(conn))))); + + /* Perform post-connection security checks */ + pgfdw_security_check(keywords, values, user, conn); + + /* Prepare new session for use */ + configure_remote_session(conn); + + if (appname != NULL) + pfree(appname); + pfree(keywords); + pfree(values); + } + PG_CATCH(); + { + libpqsrv_disconnect(conn); + PG_RE_THROW(); + } + PG_END_TRY(); + + return conn; +} + +/* + * Disconnect any open connection for a connection cache entry. + */ +static void +disconnect_pg_server(ConnCacheEntry *entry) +{ + if (entry->conn != NULL) + { + libpqsrv_disconnect(entry->conn); + entry->conn = NULL; + } +} + +/* + * Return true if the password_required is defined and false for this user + * mapping, otherwise false. The mapping has been pre-validated. + */ +static bool +UserMappingPasswordRequired(UserMapping *user) +{ + ListCell *cell; + + foreach(cell, user->options) + { + DefElem *def = (DefElem *) lfirst(cell); + + if (strcmp(def->defname, "password_required") == 0) + return defGetBoolean(def); + } + + return true; +} + +/* + * For non-superusers, insist that the connstr specify a password or that the + * user provided their own GSSAPI delegated credentials. This + * prevents a password from being picked up from .pgpass, a service file, the + * environment, etc. We don't want the postgres user's passwords, + * certificates, etc to be accessible to non-superusers. (See also + * dblink_connstr_check in contrib/dblink.) + */ +static void +check_conn_params(const char **keywords, const char **values, UserMapping *user) +{ + int i; + + /* no check required if superuser */ + if (superuser_arg(user->userid)) + return; + +#ifdef ENABLE_GSS + /* ok if the user provided their own delegated credentials */ + if (be_gssapi_get_delegation(MyProcPort)) + return; +#endif + + /* ok if params contain a non-empty password */ + for (i = 0; keywords[i] != NULL; i++) + { + if (strcmp(keywords[i], "password") == 0 && values[i][0] != '\0') + return; + } + + /* ok if the superuser explicitly said so at user mapping creation time */ + if (!UserMappingPasswordRequired(user)) + return; + + ereport(ERROR, + (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED), + errmsg("password or GSSAPI delegated credentials required"), + errdetail("Non-superusers must delegate GSSAPI credentials or provide a password in the user mapping."))); +} + +/* + * Issue SET commands to make sure remote session is configured properly. + * + * We do this just once at connection, assuming nothing will change the + * values later. Since we'll never send volatile function calls to the + * remote, there shouldn't be any way to break this assumption from our end. + * It's possible to think of ways to break it at the remote end, eg making + * a foreign table point to a view that includes a set_config call --- + * but once you admit the possibility of a malicious view definition, + * there are any number of ways to break things. + */ +static void +configure_remote_session(PGconn *conn) +{ + int remoteversion = PQserverVersion(conn); + + /* Force the search path to contain only pg_catalog (see deparse.c) */ + do_sql_command(conn, "SET search_path = pg_catalog"); + + /* + * Set remote timezone; this is basically just cosmetic, since all + * transmitted and returned timestamptzs should specify a zone explicitly + * anyway. However it makes the regression test outputs more predictable. + * + * We don't risk setting remote zone equal to ours, since the remote + * server might use a different timezone database. Instead, use UTC + * (quoted, because very old servers are picky about case). + */ + do_sql_command(conn, "SET timezone = 'UTC'"); + + /* + * Set values needed to ensure unambiguous data output from remote. (This + * logic should match what pg_dump does. See also set_transmission_modes + * in postgres_fdw.c.) + */ + do_sql_command(conn, "SET datestyle = ISO"); + if (remoteversion >= 80400) + do_sql_command(conn, "SET intervalstyle = postgres"); + if (remoteversion >= 90000) + do_sql_command(conn, "SET extra_float_digits = 3"); + else + do_sql_command(conn, "SET extra_float_digits = 2"); +} + +/* + * Convenience subroutine to issue a non-data-returning SQL command to remote + */ +void +do_sql_command(PGconn *conn, const char *sql) +{ + do_sql_command_begin(conn, sql); + do_sql_command_end(conn, sql, false); +} + +static void +do_sql_command_begin(PGconn *conn, const char *sql) +{ + if (!PQsendQuery(conn, sql)) + pgfdw_report_error(ERROR, NULL, conn, false, sql); +} + +static void +do_sql_command_end(PGconn *conn, const char *sql, bool consume_input) +{ + PGresult *res; + + /* + * If requested, consume whatever data is available from the socket. (Note + * that if all data is available, this allows pgfdw_get_result to call + * PQgetResult without forcing the overhead of WaitLatchOrSocket, which + * would be large compared to the overhead of PQconsumeInput.) + */ + if (consume_input && !PQconsumeInput(conn)) + pgfdw_report_error(ERROR, NULL, conn, false, sql); + res = pgfdw_get_result(conn, sql); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pgfdw_report_error(ERROR, res, conn, true, sql); + PQclear(res); +} + +/* + * Start remote transaction or subtransaction, if needed. + * + * Note that we always use at least REPEATABLE READ in the remote session. + * This is so that, if a query initiates multiple scans of the same or + * different foreign tables, we will get snapshot-consistent results from + * those scans. A disadvantage is that we can't provide sane emulation of + * READ COMMITTED behavior --- it would be nice if we had some other way to + * control which remote queries share a snapshot. + */ +static void +begin_remote_xact(ConnCacheEntry *entry) +{ + int curlevel = GetCurrentTransactionNestLevel(); + + /* Start main transaction if we haven't yet */ + if (entry->xact_depth <= 0) + { + const char *sql; + + elog(DEBUG3, "starting remote transaction on connection %p", + entry->conn); + + if (IsolationIsSerializable()) + sql = "START TRANSACTION ISOLATION LEVEL SERIALIZABLE"; + else + sql = "START TRANSACTION ISOLATION LEVEL REPEATABLE READ"; + entry->changing_xact_state = true; + do_sql_command(entry->conn, sql); + entry->xact_depth = 1; + entry->changing_xact_state = false; + } + + /* + * If we're in a subtransaction, stack up savepoints to match our level. + * This ensures we can rollback just the desired effects when a + * subtransaction aborts. + */ + while (entry->xact_depth < curlevel) + { + char sql[64]; + + snprintf(sql, sizeof(sql), "SAVEPOINT s%d", entry->xact_depth + 1); + entry->changing_xact_state = true; + do_sql_command(entry->conn, sql); + entry->xact_depth++; + entry->changing_xact_state = false; + } +} + +/* + * Release connection reference count created by calling GetConnection. + */ +void +ReleaseConnection(PGconn *conn) +{ + /* + * Currently, we don't actually track connection references because all + * cleanup is managed on a transaction or subtransaction basis instead. So + * there's nothing to do here. + */ +} + +/* + * Assign a "unique" number for a cursor. + * + * These really only need to be unique per connection within a transaction. + * For the moment we ignore the per-connection point and assign them across + * all connections in the transaction, but we ask for the connection to be + * supplied in case we want to refine that. + * + * Note that even if wraparound happens in a very long transaction, actual + * collisions are highly improbable; just be sure to use %u not %d to print. + */ +unsigned int +GetCursorNumber(PGconn *conn) +{ + return ++cursor_number; +} + +/* + * Assign a "unique" number for a prepared statement. + * + * This works much like GetCursorNumber, except that we never reset the counter + * within a session. That's because we can't be 100% sure we've gotten rid + * of all prepared statements on all connections, and it's not really worth + * increasing the risk of prepared-statement name collisions by resetting. + */ +unsigned int +GetPrepStmtNumber(PGconn *conn) +{ + return ++prep_stmt_number; +} + +/* + * Submit a query and wait for the result. + * + * This function is interruptible by signals. + * + * Caller is responsible for the error handling on the result. + */ +PGresult * +pgfdw_exec_query(PGconn *conn, const char *query, PgFdwConnState *state) +{ + /* First, process a pending asynchronous request, if any. */ + if (state && state->pendingAreq) + process_pending_request(state->pendingAreq); + + /* + * Submit a query. Since we don't use non-blocking mode, this also can + * block. But its risk is relatively small, so we ignore that for now. + */ + if (!PQsendQuery(conn, query)) + pgfdw_report_error(ERROR, NULL, conn, false, query); + + /* Wait for the result. */ + return pgfdw_get_result(conn, query); +} + +/* + * Wait for the result from a prior asynchronous execution function call. + * + * This function offers quick responsiveness by checking for any interruptions. + * + * This function emulates PQexec()'s behavior of returning the last result + * when there are many. + * + * Caller is responsible for the error handling on the result. + */ +PGresult * +pgfdw_get_result(PGconn *conn, const char *query) +{ + PGresult *volatile last_res = NULL; + + /* In what follows, do not leak any PGresults on an error. */ + PG_TRY(); + { + for (;;) + { + PGresult *res; + + while (PQisBusy(conn)) + { + int wc; + + /* Sleep until there's something to do */ + wc = WaitLatchOrSocket(MyLatch, + WL_LATCH_SET | WL_SOCKET_READABLE | + WL_EXIT_ON_PM_DEATH, + PQsocket(conn), + -1L, PG_WAIT_EXTENSION); + ResetLatch(MyLatch); + + CHECK_FOR_INTERRUPTS(); + + /* Data available in socket? */ + if (wc & WL_SOCKET_READABLE) + { + if (!PQconsumeInput(conn)) + pgfdw_report_error(ERROR, NULL, conn, false, query); + } + } + + res = PQgetResult(conn); + if (res == NULL) + break; /* query is complete */ + + PQclear(last_res); + last_res = res; + } + } + PG_CATCH(); + { + PQclear(last_res); + PG_RE_THROW(); + } + PG_END_TRY(); + + return last_res; +} + +/* + * Report an error we got from the remote server. + * + * elevel: error level to use (typically ERROR, but might be less) + * res: PGresult containing the error + * conn: connection we did the query on + * clear: if true, PQclear the result (otherwise caller will handle it) + * sql: NULL, or text of remote command we tried to execute + * + * Note: callers that choose not to throw ERROR for a remote error are + * responsible for making sure that the associated ConnCacheEntry gets + * marked with have_error = true. + */ +void +pgfdw_report_error(int elevel, PGresult *res, PGconn *conn, + bool clear, const char *sql) +{ + /* If requested, PGresult must be released before leaving this function. */ + PG_TRY(); + { + char *diag_sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE); + char *message_primary = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY); + char *message_detail = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL); + char *message_hint = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT); + char *message_context = PQresultErrorField(res, PG_DIAG_CONTEXT); + int sqlstate; + + if (diag_sqlstate) + sqlstate = MAKE_SQLSTATE(diag_sqlstate[0], + diag_sqlstate[1], + diag_sqlstate[2], + diag_sqlstate[3], + diag_sqlstate[4]); + else + sqlstate = ERRCODE_CONNECTION_FAILURE; + + /* + * If we don't get a message from the PGresult, try the PGconn. This + * is needed because for connection-level failures, PQexec may just + * return NULL, not a PGresult at all. + */ + if (message_primary == NULL) + message_primary = pchomp(PQerrorMessage(conn)); + + ereport(elevel, + (errcode(sqlstate), + (message_primary != NULL && message_primary[0] != '\0') ? + errmsg_internal("%s", message_primary) : + errmsg("could not obtain message string for remote error"), + message_detail ? errdetail_internal("%s", message_detail) : 0, + message_hint ? errhint("%s", message_hint) : 0, + message_context ? errcontext("%s", message_context) : 0, + sql ? errcontext("remote SQL command: %s", sql) : 0)); + } + PG_FINALLY(); + { + if (clear) + PQclear(res); + } + PG_END_TRY(); +} + +/* + * pgfdw_xact_callback --- cleanup at main-transaction end. + * + * This runs just late enough that it must not enter user-defined code + * locally. (Entering such code on the remote side is fine. Its remote + * COMMIT TRANSACTION may run deferred triggers.) + */ +static void +pgfdw_xact_callback(XactEvent event, void *arg) +{ + HASH_SEQ_STATUS scan; + ConnCacheEntry *entry; + List *pending_entries = NIL; + List *cancel_requested = NIL; + + /* Quick exit if no connections were touched in this transaction. */ + if (!xact_got_connection) + return; + + /* + * Scan all connection cache entries to find open remote transactions, and + * close them. + */ + hash_seq_init(&scan, ConnectionHash); + while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) + { + PGresult *res; + + /* Ignore cache entry if no open connection right now */ + if (entry->conn == NULL) + continue; + + /* If it has an open remote transaction, try to close it */ + if (entry->xact_depth > 0) + { + elog(DEBUG3, "closing remote transaction on connection %p", + entry->conn); + + switch (event) + { + case XACT_EVENT_PARALLEL_PRE_COMMIT: + case XACT_EVENT_PRE_COMMIT: + + /* + * If abort cleanup previously failed for this connection, + * we can't issue any more commands against it. + */ + pgfdw_reject_incomplete_xact_state_change(entry); + + /* Commit all remote transactions during pre-commit */ + entry->changing_xact_state = true; + if (entry->parallel_commit) + { + do_sql_command_begin(entry->conn, "COMMIT TRANSACTION"); + pending_entries = lappend(pending_entries, entry); + continue; + } + do_sql_command(entry->conn, "COMMIT TRANSACTION"); + entry->changing_xact_state = false; + + /* + * If there were any errors in subtransactions, and we + * made prepared statements, do a DEALLOCATE ALL to make + * sure we get rid of all prepared statements. This is + * annoying and not terribly bulletproof, but it's + * probably not worth trying harder. + * + * DEALLOCATE ALL only exists in 8.3 and later, so this + * constrains how old a server postgres_fdw can + * communicate with. We intentionally ignore errors in + * the DEALLOCATE, so that we can hobble along to some + * extent with older servers (leaking prepared statements + * as we go; but we don't really support update operations + * pre-8.3 anyway). + */ + if (entry->have_prep_stmt && entry->have_error) + { + res = PQexec(entry->conn, "DEALLOCATE ALL"); + PQclear(res); + } + entry->have_prep_stmt = false; + entry->have_error = false; + break; + case XACT_EVENT_PRE_PREPARE: + + /* + * We disallow any remote transactions, since it's not + * very reasonable to hold them open until the prepared + * transaction is committed. For the moment, throw error + * unconditionally; later we might allow read-only cases. + * Note that the error will cause us to come right back + * here with event == XACT_EVENT_ABORT, so we'll clean up + * the connection state at that point. + */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot PREPARE a transaction that has operated on postgres_fdw foreign tables"))); + break; + case XACT_EVENT_PARALLEL_COMMIT: + case XACT_EVENT_COMMIT: + case XACT_EVENT_PREPARE: + /* Pre-commit should have closed the open transaction */ + elog(ERROR, "missed cleaning up connection during pre-commit"); + break; + case XACT_EVENT_PARALLEL_ABORT: + case XACT_EVENT_ABORT: + /* Rollback all remote transactions during abort */ + if (entry->parallel_abort) + { + if (pgfdw_abort_cleanup_begin(entry, true, + &pending_entries, + &cancel_requested)) + continue; + } + else + pgfdw_abort_cleanup(entry, true); + break; + } + } + + /* Reset state to show we're out of a transaction */ + pgfdw_reset_xact_state(entry, true); + } + + /* If there are any pending connections, finish cleaning them up */ + if (pending_entries || cancel_requested) + { + if (event == XACT_EVENT_PARALLEL_PRE_COMMIT || + event == XACT_EVENT_PRE_COMMIT) + { + Assert(cancel_requested == NIL); + pgfdw_finish_pre_commit_cleanup(pending_entries); + } + else + { + Assert(event == XACT_EVENT_PARALLEL_ABORT || + event == XACT_EVENT_ABORT); + pgfdw_finish_abort_cleanup(pending_entries, cancel_requested, + true); + } + } + + /* + * Regardless of the event type, we can now mark ourselves as out of the + * transaction. (Note: if we are here during PRE_COMMIT or PRE_PREPARE, + * this saves a useless scan of the hashtable during COMMIT or PREPARE.) + */ + xact_got_connection = false; + + /* Also reset cursor numbering for next transaction */ + cursor_number = 0; +} + +/* + * pgfdw_subxact_callback --- cleanup at subtransaction end. + */ +static void +pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid, + SubTransactionId parentSubid, void *arg) +{ + HASH_SEQ_STATUS scan; + ConnCacheEntry *entry; + int curlevel; + List *pending_entries = NIL; + List *cancel_requested = NIL; + + /* Nothing to do at subxact start, nor after commit. */ + if (!(event == SUBXACT_EVENT_PRE_COMMIT_SUB || + event == SUBXACT_EVENT_ABORT_SUB)) + return; + + /* Quick exit if no connections were touched in this transaction. */ + if (!xact_got_connection) + return; + + /* + * Scan all connection cache entries to find open remote subtransactions + * of the current level, and close them. + */ + curlevel = GetCurrentTransactionNestLevel(); + hash_seq_init(&scan, ConnectionHash); + while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) + { + char sql[100]; + + /* + * We only care about connections with open remote subtransactions of + * the current level. + */ + if (entry->conn == NULL || entry->xact_depth < curlevel) + continue; + + if (entry->xact_depth > curlevel) + elog(ERROR, "missed cleaning up remote subtransaction at level %d", + entry->xact_depth); + + if (event == SUBXACT_EVENT_PRE_COMMIT_SUB) + { + /* + * If abort cleanup previously failed for this connection, we + * can't issue any more commands against it. + */ + pgfdw_reject_incomplete_xact_state_change(entry); + + /* Commit all remote subtransactions during pre-commit */ + snprintf(sql, sizeof(sql), "RELEASE SAVEPOINT s%d", curlevel); + entry->changing_xact_state = true; + if (entry->parallel_commit) + { + do_sql_command_begin(entry->conn, sql); + pending_entries = lappend(pending_entries, entry); + continue; + } + do_sql_command(entry->conn, sql); + entry->changing_xact_state = false; + } + else + { + /* Rollback all remote subtransactions during abort */ + if (entry->parallel_abort) + { + if (pgfdw_abort_cleanup_begin(entry, false, + &pending_entries, + &cancel_requested)) + continue; + } + else + pgfdw_abort_cleanup(entry, false); + } + + /* OK, we're outta that level of subtransaction */ + pgfdw_reset_xact_state(entry, false); + } + + /* If there are any pending connections, finish cleaning them up */ + if (pending_entries || cancel_requested) + { + if (event == SUBXACT_EVENT_PRE_COMMIT_SUB) + { + Assert(cancel_requested == NIL); + pgfdw_finish_pre_subcommit_cleanup(pending_entries, curlevel); + } + else + { + Assert(event == SUBXACT_EVENT_ABORT_SUB); + pgfdw_finish_abort_cleanup(pending_entries, cancel_requested, + false); + } + } +} + +/* + * Connection invalidation callback function + * + * After a change to a pg_foreign_server or pg_user_mapping catalog entry, + * close connections depending on that entry immediately if current transaction + * has not used those connections yet. Otherwise, mark those connections as + * invalid and then make pgfdw_xact_callback() close them at the end of current + * transaction, since they cannot be closed in the midst of the transaction + * using them. Closed connections will be remade at the next opportunity if + * necessary. + * + * Although most cache invalidation callbacks blow away all the related stuff + * regardless of the given hashvalue, connections are expensive enough that + * it's worth trying to avoid that. + * + * NB: We could avoid unnecessary disconnection more strictly by examining + * individual option values, but it seems too much effort for the gain. + */ +static void +pgfdw_inval_callback(Datum arg, int cacheid, uint32 hashvalue) +{ + HASH_SEQ_STATUS scan; + ConnCacheEntry *entry; + + Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID); + + /* ConnectionHash must exist already, if we're registered */ + hash_seq_init(&scan, ConnectionHash); + while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) + { + /* Ignore invalid entries */ + if (entry->conn == NULL) + continue; + + /* hashvalue == 0 means a cache reset, must clear all state */ + if (hashvalue == 0 || + (cacheid == FOREIGNSERVEROID && + entry->server_hashvalue == hashvalue) || + (cacheid == USERMAPPINGOID && + entry->mapping_hashvalue == hashvalue)) + { + /* + * Close the connection immediately if it's not used yet in this + * transaction. Otherwise mark it as invalid so that + * pgfdw_xact_callback() can close it at the end of this + * transaction. + */ + if (entry->xact_depth == 0) + { + elog(DEBUG3, "discarding connection %p", entry->conn); + disconnect_pg_server(entry); + } + else + entry->invalidated = true; + } + } +} + +/* + * Raise an error if the given connection cache entry is marked as being + * in the middle of an xact state change. This should be called at which no + * such change is expected to be in progress; if one is found to be in + * progress, it means that we aborted in the middle of a previous state change + * and now don't know what the remote transaction state actually is. + * Such connections can't safely be further used. Re-establishing the + * connection would change the snapshot and roll back any writes already + * performed, so that's not an option, either. Thus, we must abort. + */ +static void +pgfdw_reject_incomplete_xact_state_change(ConnCacheEntry *entry) +{ + ForeignServer *server; + + /* nothing to do for inactive entries and entries of sane state */ + if (entry->conn == NULL || !entry->changing_xact_state) + return; + + /* make sure this entry is inactive */ + disconnect_pg_server(entry); + + /* find server name to be shown in the message below */ + server = GetForeignServer(entry->serverid); + + ereport(ERROR, + (errcode(ERRCODE_CONNECTION_EXCEPTION), + errmsg("connection to server \"%s\" was lost", + server->servername))); +} + +/* + * Reset state to show we're out of a (sub)transaction. + */ +static void +pgfdw_reset_xact_state(ConnCacheEntry *entry, bool toplevel) +{ + if (toplevel) + { + /* Reset state to show we're out of a transaction */ + entry->xact_depth = 0; + + /* + * If the connection isn't in a good idle state, it is marked as + * invalid or keep_connections option of its server is disabled, then + * discard it to recover. Next GetConnection will open a new + * connection. + */ + if (PQstatus(entry->conn) != CONNECTION_OK || + PQtransactionStatus(entry->conn) != PQTRANS_IDLE || + entry->changing_xact_state || + entry->invalidated || + !entry->keep_connections) + { + elog(DEBUG3, "discarding connection %p", entry->conn); + disconnect_pg_server(entry); + } + } + else + { + /* Reset state to show we're out of a subtransaction */ + entry->xact_depth--; + } +} + +/* + * Cancel the currently-in-progress query (whose query text we do not have) + * and ignore the result. Returns true if we successfully cancel the query + * and discard any pending result, and false if not. + * + * It's not a huge problem if we throw an ERROR here, but if we get into error + * recursion trouble, we'll end up slamming the connection shut, which will + * necessitate failing the entire toplevel transaction even if subtransactions + * were used. Try to use WARNING where we can. + * + * XXX: if the query was one sent by fetch_more_data_begin(), we could get the + * query text from the pendingAreq saved in the per-connection state, then + * report the query using it. + */ +static bool +pgfdw_cancel_query(PGconn *conn) +{ + TimestampTz endtime; + + /* + * If it takes too long to cancel the query and discard the result, assume + * the connection is dead. + */ + endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), + CONNECTION_CLEANUP_TIMEOUT); + + if (!pgfdw_cancel_query_begin(conn)) + return false; + return pgfdw_cancel_query_end(conn, endtime, false); +} + +static bool +pgfdw_cancel_query_begin(PGconn *conn) +{ + PGcancel *cancel; + char errbuf[256]; + + /* + * Issue cancel request. Unfortunately, there's no good way to limit the + * amount of time that we might block inside PQgetCancel(). + */ + if ((cancel = PQgetCancel(conn))) + { + if (!PQcancel(cancel, errbuf, sizeof(errbuf))) + { + ereport(WARNING, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("could not send cancel request: %s", + errbuf))); + PQfreeCancel(cancel); + return false; + } + PQfreeCancel(cancel); + } + + return true; +} + +static bool +pgfdw_cancel_query_end(PGconn *conn, TimestampTz endtime, bool consume_input) +{ + PGresult *result = NULL; + bool timed_out; + + /* + * If requested, consume whatever data is available from the socket. (Note + * that if all data is available, this allows pgfdw_get_cleanup_result to + * call PQgetResult without forcing the overhead of WaitLatchOrSocket, + * which would be large compared to the overhead of PQconsumeInput.) + */ + if (consume_input && !PQconsumeInput(conn)) + { + ereport(WARNING, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("could not get result of cancel request: %s", + pchomp(PQerrorMessage(conn))))); + return false; + } + + /* Get and discard the result of the query. */ + if (pgfdw_get_cleanup_result(conn, endtime, &result, &timed_out)) + { + if (timed_out) + ereport(WARNING, + (errmsg("could not get result of cancel request due to timeout"))); + else + ereport(WARNING, + (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("could not get result of cancel request: %s", + pchomp(PQerrorMessage(conn))))); + + return false; + } + PQclear(result); + + return true; +} + +/* + * Submit a query during (sub)abort cleanup and wait up to 30 seconds for the + * result. If the query is executed without error, the return value is true. + * If the query is executed successfully but returns an error, the return + * value is true if and only if ignore_errors is set. If the query can't be + * sent or times out, the return value is false. + * + * It's not a huge problem if we throw an ERROR here, but if we get into error + * recursion trouble, we'll end up slamming the connection shut, which will + * necessitate failing the entire toplevel transaction even if subtransactions + * were used. Try to use WARNING where we can. + */ +static bool +pgfdw_exec_cleanup_query(PGconn *conn, const char *query, bool ignore_errors) +{ + TimestampTz endtime; + + /* + * If it takes too long to execute a cleanup query, assume the connection + * is dead. It's fairly likely that this is why we aborted in the first + * place (e.g. statement timeout, user cancel), so the timeout shouldn't + * be too long. + */ + endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), + CONNECTION_CLEANUP_TIMEOUT); + + if (!pgfdw_exec_cleanup_query_begin(conn, query)) + return false; + return pgfdw_exec_cleanup_query_end(conn, query, endtime, + false, ignore_errors); +} + +static bool +pgfdw_exec_cleanup_query_begin(PGconn *conn, const char *query) +{ + /* + * Submit a query. Since we don't use non-blocking mode, this also can + * block. But its risk is relatively small, so we ignore that for now. + */ + if (!PQsendQuery(conn, query)) + { + pgfdw_report_error(WARNING, NULL, conn, false, query); + return false; + } + + return true; +} + +static bool +pgfdw_exec_cleanup_query_end(PGconn *conn, const char *query, + TimestampTz endtime, bool consume_input, + bool ignore_errors) +{ + PGresult *result = NULL; + bool timed_out; + + /* + * If requested, consume whatever data is available from the socket. (Note + * that if all data is available, this allows pgfdw_get_cleanup_result to + * call PQgetResult without forcing the overhead of WaitLatchOrSocket, + * which would be large compared to the overhead of PQconsumeInput.) + */ + if (consume_input && !PQconsumeInput(conn)) + { + pgfdw_report_error(WARNING, NULL, conn, false, query); + return false; + } + + /* Get the result of the query. */ + if (pgfdw_get_cleanup_result(conn, endtime, &result, &timed_out)) + { + if (timed_out) + ereport(WARNING, + (errmsg("could not get query result due to timeout"), + query ? errcontext("remote SQL command: %s", query) : 0)); + else + pgfdw_report_error(WARNING, NULL, conn, false, query); + + return false; + } + + /* Issue a warning if not successful. */ + if (PQresultStatus(result) != PGRES_COMMAND_OK) + { + pgfdw_report_error(WARNING, result, conn, true, query); + return ignore_errors; + } + PQclear(result); + + return true; +} + +/* + * Get, during abort cleanup, the result of a query that is in progress. This + * might be a query that is being interrupted by transaction abort, or it might + * be a query that was initiated as part of transaction abort to get the remote + * side back to the appropriate state. + * + * endtime is the time at which we should give up and assume the remote + * side is dead. Returns true if the timeout expired or connection trouble + * occurred, false otherwise. Sets *result except in case of a timeout. + * Sets timed_out to true only when the timeout expired. + */ +static bool +pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime, PGresult **result, + bool *timed_out) +{ + volatile bool failed = false; + PGresult *volatile last_res = NULL; + + *timed_out = false; + + /* In what follows, do not leak any PGresults on an error. */ + PG_TRY(); + { + for (;;) + { + PGresult *res; + + while (PQisBusy(conn)) + { + int wc; + TimestampTz now = GetCurrentTimestamp(); + long cur_timeout; + + /* If timeout has expired, give up, else get sleep time. */ + cur_timeout = TimestampDifferenceMilliseconds(now, endtime); + if (cur_timeout <= 0) + { + *timed_out = true; + failed = true; + goto exit; + } + + /* Sleep until there's something to do */ + wc = WaitLatchOrSocket(MyLatch, + WL_LATCH_SET | WL_SOCKET_READABLE | + WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, + PQsocket(conn), + cur_timeout, PG_WAIT_EXTENSION); + ResetLatch(MyLatch); + + CHECK_FOR_INTERRUPTS(); + + /* Data available in socket? */ + if (wc & WL_SOCKET_READABLE) + { + if (!PQconsumeInput(conn)) + { + /* connection trouble */ + failed = true; + goto exit; + } + } + } + + res = PQgetResult(conn); + if (res == NULL) + break; /* query is complete */ + + PQclear(last_res); + last_res = res; + } +exit: ; + } + PG_CATCH(); + { + PQclear(last_res); + PG_RE_THROW(); + } + PG_END_TRY(); + + if (failed) + PQclear(last_res); + else + *result = last_res; + return failed; +} + +/* + * Abort remote transaction or subtransaction. + * + * "toplevel" should be set to true if toplevel (main) transaction is + * rollbacked, false otherwise. + * + * Set entry->changing_xact_state to false on success, true on failure. + */ +static void +pgfdw_abort_cleanup(ConnCacheEntry *entry, bool toplevel) +{ + char sql[100]; + + /* + * Don't try to clean up the connection if we're already in error + * recursion trouble. + */ + if (in_error_recursion_trouble()) + entry->changing_xact_state = true; + + /* + * If connection is already unsalvageable, don't touch it further. + */ + if (entry->changing_xact_state) + return; + + /* + * Mark this connection as in the process of changing transaction state. + */ + entry->changing_xact_state = true; + + /* Assume we might have lost track of prepared statements */ + entry->have_error = true; + + /* + * If a command has been submitted to the remote server by using an + * asynchronous execution function, the command might not have yet + * completed. Check to see if a command is still being processed by the + * remote server, and if so, request cancellation of the command. + */ + if (PQtransactionStatus(entry->conn) == PQTRANS_ACTIVE && + !pgfdw_cancel_query(entry->conn)) + return; /* Unable to cancel running query */ + + CONSTRUCT_ABORT_COMMAND(sql, entry, toplevel); + if (!pgfdw_exec_cleanup_query(entry->conn, sql, false)) + return; /* Unable to abort remote (sub)transaction */ + + if (toplevel) + { + if (entry->have_prep_stmt && entry->have_error && + !pgfdw_exec_cleanup_query(entry->conn, + "DEALLOCATE ALL", + true)) + return; /* Trouble clearing prepared statements */ + + entry->have_prep_stmt = false; + entry->have_error = false; + } + + /* + * If pendingAreq of the per-connection state is not NULL, it means that + * an asynchronous fetch begun by fetch_more_data_begin() was not done + * successfully and thus the per-connection state was not reset in + * fetch_more_data(); in that case reset the per-connection state here. + */ + if (entry->state.pendingAreq) + memset(&entry->state, 0, sizeof(entry->state)); + + /* Disarm changing_xact_state if it all worked */ + entry->changing_xact_state = false; +} + +/* + * Like pgfdw_abort_cleanup, submit an abort command or cancel request, but + * don't wait for the result. + * + * Returns true if the abort command or cancel request is successfully issued, + * false otherwise. If the abort command is successfully issued, the given + * connection cache entry is appended to *pending_entries. Otherwise, if the + * cancel request is successfully issued, it is appended to *cancel_requested. + */ +static bool +pgfdw_abort_cleanup_begin(ConnCacheEntry *entry, bool toplevel, + List **pending_entries, List **cancel_requested) +{ + /* + * Don't try to clean up the connection if we're already in error + * recursion trouble. + */ + if (in_error_recursion_trouble()) + entry->changing_xact_state = true; + + /* + * If connection is already unsalvageable, don't touch it further. + */ + if (entry->changing_xact_state) + return false; + + /* + * Mark this connection as in the process of changing transaction state. + */ + entry->changing_xact_state = true; + + /* Assume we might have lost track of prepared statements */ + entry->have_error = true; + + /* + * If a command has been submitted to the remote server by using an + * asynchronous execution function, the command might not have yet + * completed. Check to see if a command is still being processed by the + * remote server, and if so, request cancellation of the command. + */ + if (PQtransactionStatus(entry->conn) == PQTRANS_ACTIVE) + { + if (!pgfdw_cancel_query_begin(entry->conn)) + return false; /* Unable to cancel running query */ + *cancel_requested = lappend(*cancel_requested, entry); + } + else + { + char sql[100]; + + CONSTRUCT_ABORT_COMMAND(sql, entry, toplevel); + if (!pgfdw_exec_cleanup_query_begin(entry->conn, sql)) + return false; /* Unable to abort remote transaction */ + *pending_entries = lappend(*pending_entries, entry); + } + + return true; +} + +/* + * Finish pre-commit cleanup of connections on each of which we've sent a + * COMMIT command to the remote server. + */ +static void +pgfdw_finish_pre_commit_cleanup(List *pending_entries) +{ + ConnCacheEntry *entry; + List *pending_deallocs = NIL; + ListCell *lc; + + Assert(pending_entries); + + /* + * Get the result of the COMMIT command for each of the pending entries + */ + foreach(lc, pending_entries) + { + entry = (ConnCacheEntry *) lfirst(lc); + + Assert(entry->changing_xact_state); + + /* + * We might already have received the result on the socket, so pass + * consume_input=true to try to consume it first + */ + do_sql_command_end(entry->conn, "COMMIT TRANSACTION", true); + entry->changing_xact_state = false; + + /* Do a DEALLOCATE ALL in parallel if needed */ + if (entry->have_prep_stmt && entry->have_error) + { + /* Ignore errors (see notes in pgfdw_xact_callback) */ + if (PQsendQuery(entry->conn, "DEALLOCATE ALL")) + { + pending_deallocs = lappend(pending_deallocs, entry); + continue; + } + } + entry->have_prep_stmt = false; + entry->have_error = false; + + pgfdw_reset_xact_state(entry, true); + } + + /* No further work if no pending entries */ + if (!pending_deallocs) + return; + + /* + * Get the result of the DEALLOCATE command for each of the pending + * entries + */ + foreach(lc, pending_deallocs) + { + PGresult *res; + + entry = (ConnCacheEntry *) lfirst(lc); + + /* Ignore errors (see notes in pgfdw_xact_callback) */ + while ((res = PQgetResult(entry->conn)) != NULL) + { + PQclear(res); + /* Stop if the connection is lost (else we'll loop infinitely) */ + if (PQstatus(entry->conn) == CONNECTION_BAD) + break; + } + entry->have_prep_stmt = false; + entry->have_error = false; + + pgfdw_reset_xact_state(entry, true); + } +} + +/* + * Finish pre-subcommit cleanup of connections on each of which we've sent a + * RELEASE command to the remote server. + */ +static void +pgfdw_finish_pre_subcommit_cleanup(List *pending_entries, int curlevel) +{ + ConnCacheEntry *entry; + char sql[100]; + ListCell *lc; + + Assert(pending_entries); + + /* + * Get the result of the RELEASE command for each of the pending entries + */ + snprintf(sql, sizeof(sql), "RELEASE SAVEPOINT s%d", curlevel); + foreach(lc, pending_entries) + { + entry = (ConnCacheEntry *) lfirst(lc); + + Assert(entry->changing_xact_state); + + /* + * We might already have received the result on the socket, so pass + * consume_input=true to try to consume it first + */ + do_sql_command_end(entry->conn, sql, true); + entry->changing_xact_state = false; + + pgfdw_reset_xact_state(entry, false); + } +} + +/* + * Finish abort cleanup of connections on each of which we've sent an abort + * command or cancel request to the remote server. + */ +static void +pgfdw_finish_abort_cleanup(List *pending_entries, List *cancel_requested, + bool toplevel) +{ + List *pending_deallocs = NIL; + ListCell *lc; + + /* + * For each of the pending cancel requests (if any), get and discard the + * result of the query, and submit an abort command to the remote server. + */ + if (cancel_requested) + { + foreach(lc, cancel_requested) + { + ConnCacheEntry *entry = (ConnCacheEntry *) lfirst(lc); + TimestampTz endtime; + char sql[100]; + + Assert(entry->changing_xact_state); + + /* + * Set end time. You might think we should do this before issuing + * cancel request like in normal mode, but that is problematic, + * because if, for example, it took longer than 30 seconds to + * process the first few entries in the cancel_requested list, it + * would cause a timeout error when processing each of the + * remaining entries in the list, leading to slamming that entry's + * connection shut. + */ + endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), + CONNECTION_CLEANUP_TIMEOUT); + + if (!pgfdw_cancel_query_end(entry->conn, endtime, true)) + { + /* Unable to cancel running query */ + pgfdw_reset_xact_state(entry, toplevel); + continue; + } + + /* Send an abort command in parallel if needed */ + CONSTRUCT_ABORT_COMMAND(sql, entry, toplevel); + if (!pgfdw_exec_cleanup_query_begin(entry->conn, sql)) + { + /* Unable to abort remote (sub)transaction */ + pgfdw_reset_xact_state(entry, toplevel); + } + else + pending_entries = lappend(pending_entries, entry); + } + } + + /* No further work if no pending entries */ + if (!pending_entries) + return; + + /* + * Get the result of the abort command for each of the pending entries + */ + foreach(lc, pending_entries) + { + ConnCacheEntry *entry = (ConnCacheEntry *) lfirst(lc); + TimestampTz endtime; + char sql[100]; + + Assert(entry->changing_xact_state); + + /* + * Set end time. We do this now, not before issuing the command like + * in normal mode, for the same reason as for the cancel_requested + * entries. + */ + endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), + CONNECTION_CLEANUP_TIMEOUT); + + CONSTRUCT_ABORT_COMMAND(sql, entry, toplevel); + if (!pgfdw_exec_cleanup_query_end(entry->conn, sql, endtime, + true, false)) + { + /* Unable to abort remote (sub)transaction */ + pgfdw_reset_xact_state(entry, toplevel); + continue; + } + + if (toplevel) + { + /* Do a DEALLOCATE ALL in parallel if needed */ + if (entry->have_prep_stmt && entry->have_error) + { + if (!pgfdw_exec_cleanup_query_begin(entry->conn, + "DEALLOCATE ALL")) + { + /* Trouble clearing prepared statements */ + pgfdw_reset_xact_state(entry, toplevel); + } + else + pending_deallocs = lappend(pending_deallocs, entry); + continue; + } + entry->have_prep_stmt = false; + entry->have_error = false; + } + + /* Reset the per-connection state if needed */ + if (entry->state.pendingAreq) + memset(&entry->state, 0, sizeof(entry->state)); + + /* We're done with this entry; unset the changing_xact_state flag */ + entry->changing_xact_state = false; + pgfdw_reset_xact_state(entry, toplevel); + } + + /* No further work if no pending entries */ + if (!pending_deallocs) + return; + Assert(toplevel); + + /* + * Get the result of the DEALLOCATE command for each of the pending + * entries + */ + foreach(lc, pending_deallocs) + { + ConnCacheEntry *entry = (ConnCacheEntry *) lfirst(lc); + TimestampTz endtime; + + Assert(entry->changing_xact_state); + Assert(entry->have_prep_stmt); + Assert(entry->have_error); + + /* + * Set end time. We do this now, not before issuing the command like + * in normal mode, for the same reason as for the cancel_requested + * entries. + */ + endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), + CONNECTION_CLEANUP_TIMEOUT); + + if (!pgfdw_exec_cleanup_query_end(entry->conn, "DEALLOCATE ALL", + endtime, true, true)) + { + /* Trouble clearing prepared statements */ + pgfdw_reset_xact_state(entry, toplevel); + continue; + } + entry->have_prep_stmt = false; + entry->have_error = false; + + /* Reset the per-connection state if needed */ + if (entry->state.pendingAreq) + memset(&entry->state, 0, sizeof(entry->state)); + + /* We're done with this entry; unset the changing_xact_state flag */ + entry->changing_xact_state = false; + pgfdw_reset_xact_state(entry, toplevel); + } +} + +/* + * List active foreign server connections. + * + * This function takes no input parameter and returns setof record made of + * following values: + * - server_name - server name of active connection. In case the foreign server + * is dropped but still the connection is active, then the server name will + * be NULL in output. + * - valid - true/false representing whether the connection is valid or not. + * Note that the connections can get invalidated in pgfdw_inval_callback. + * + * No records are returned when there are no cached connections at all. + */ +Datum +postgres_fdw_get_connections(PG_FUNCTION_ARGS) +{ +#define POSTGRES_FDW_GET_CONNECTIONS_COLS 2 + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + HASH_SEQ_STATUS scan; + ConnCacheEntry *entry; + + InitMaterializedSRF(fcinfo, 0); + + /* If cache doesn't exist, we return no records */ + if (!ConnectionHash) + PG_RETURN_VOID(); + + hash_seq_init(&scan, ConnectionHash); + while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) + { + ForeignServer *server; + Datum values[POSTGRES_FDW_GET_CONNECTIONS_COLS] = {0}; + bool nulls[POSTGRES_FDW_GET_CONNECTIONS_COLS] = {0}; + + /* We only look for open remote connections */ + if (!entry->conn) + continue; + + server = GetForeignServerExtended(entry->serverid, FSV_MISSING_OK); + + /* + * The foreign server may have been dropped in current explicit + * transaction. It is not possible to drop the server from another + * session when the connection associated with it is in use in the + * current transaction, if tried so, the drop query in another session + * blocks until the current transaction finishes. + * + * Even though the server is dropped in the current transaction, the + * cache can still have associated active connection entry, say we + * call such connections dangling. Since we can not fetch the server + * name from system catalogs for dangling connections, instead we show + * NULL value for server name in output. + * + * We could have done better by storing the server name in the cache + * entry instead of server oid so that it could be used in the output. + * But the server name in each cache entry requires 64 bytes of + * memory, which is huge, when there are many cached connections and + * the use case i.e. dropping the foreign server within the explicit + * current transaction seems rare. So, we chose to show NULL value for + * server name in output. + * + * Such dangling connections get closed either in next use or at the + * end of current explicit transaction in pgfdw_xact_callback. + */ + if (!server) + { + /* + * If the server has been dropped in the current explicit + * transaction, then this entry would have been invalidated in + * pgfdw_inval_callback at the end of drop server command. Note + * that this connection would not have been closed in + * pgfdw_inval_callback because it is still being used in the + * current explicit transaction. So, assert that here. + */ + Assert(entry->conn && entry->xact_depth > 0 && entry->invalidated); + + /* Show null, if no server name was found */ + nulls[0] = true; + } + else + values[0] = CStringGetTextDatum(server->servername); + + values[1] = BoolGetDatum(!entry->invalidated); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + PG_RETURN_VOID(); +} + +/* + * Disconnect the specified cached connections. + * + * This function discards the open connections that are established by + * postgres_fdw from the local session to the foreign server with + * the given name. Note that there can be multiple connections to + * the given server using different user mappings. If the connections + * are used in the current local transaction, they are not disconnected + * and warning messages are reported. This function returns true + * if it disconnects at least one connection, otherwise false. If no + * foreign server with the given name is found, an error is reported. + */ +Datum +postgres_fdw_disconnect(PG_FUNCTION_ARGS) +{ + ForeignServer *server; + char *servername; + + servername = text_to_cstring(PG_GETARG_TEXT_PP(0)); + server = GetForeignServerByName(servername, false); + + PG_RETURN_BOOL(disconnect_cached_connections(server->serverid)); +} + +/* + * Disconnect all the cached connections. + * + * This function discards all the open connections that are established by + * postgres_fdw from the local session to the foreign servers. + * If the connections are used in the current local transaction, they are + * not disconnected and warning messages are reported. This function + * returns true if it disconnects at least one connection, otherwise false. + */ +Datum +postgres_fdw_disconnect_all(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(disconnect_cached_connections(InvalidOid)); +} + +/* + * Workhorse to disconnect cached connections. + * + * This function scans all the connection cache entries and disconnects + * the open connections whose foreign server OID matches with + * the specified one. If InvalidOid is specified, it disconnects all + * the cached connections. + * + * This function emits a warning for each connection that's used in + * the current transaction and doesn't close it. It returns true if + * it disconnects at least one connection, otherwise false. + * + * Note that this function disconnects even the connections that are + * established by other users in the same local session using different + * user mappings. This leads even non-superuser to be able to close + * the connections established by superusers in the same local session. + * + * XXX As of now we don't see any security risk doing this. But we should + * set some restrictions on that, for example, prevent non-superuser + * from closing the connections established by superusers even + * in the same session? + */ +static bool +disconnect_cached_connections(Oid serverid) +{ + HASH_SEQ_STATUS scan; + ConnCacheEntry *entry; + bool all = !OidIsValid(serverid); + bool result = false; + + /* + * Connection cache hashtable has not been initialized yet in this + * session, so return false. + */ + if (!ConnectionHash) + return false; + + hash_seq_init(&scan, ConnectionHash); + while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) + { + /* Ignore cache entry if no open connection right now. */ + if (!entry->conn) + continue; + + if (all || entry->serverid == serverid) + { + /* + * Emit a warning because the connection to close is used in the + * current transaction and cannot be disconnected right now. + */ + if (entry->xact_depth > 0) + { + ForeignServer *server; + + server = GetForeignServerExtended(entry->serverid, + FSV_MISSING_OK); + + if (!server) + { + /* + * If the foreign server was dropped while its connection + * was used in the current transaction, the connection + * must have been marked as invalid by + * pgfdw_inval_callback at the end of DROP SERVER command. + */ + Assert(entry->invalidated); + + ereport(WARNING, + (errmsg("cannot close dropped server connection because it is still in use"))); + } + else + ereport(WARNING, + (errmsg("cannot close connection for server \"%s\" because it is still in use", + server->servername))); + } + else + { + elog(DEBUG3, "discarding connection %p", entry->conn); + disconnect_pg_server(entry); + result = true; + } + } + } + + return result; +} diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c new file mode 100644 index 0000000..09d6dd6 --- /dev/null +++ b/contrib/postgres_fdw/deparse.c @@ -0,0 +1,4049 @@ +/*------------------------------------------------------------------------- + * + * deparse.c + * Query deparser for postgres_fdw + * + * This file includes functions that examine query WHERE clauses to see + * whether they're safe to send to the remote server for execution, as + * well as functions to construct the query text to be sent. The latter + * functionality is annoyingly duplicative of ruleutils.c, but there are + * enough special considerations that it seems best to keep this separate. + * One saving grace is that we only need deparse logic for node types that + * we consider safe to send. + * + * We assume that the remote session's search_path is exactly "pg_catalog", + * and thus we need schema-qualify all and only names outside pg_catalog. + * + * We do not consider that it is ever safe to send COLLATE expressions to + * the remote server: it might not have the same collation names we do. + * (Later we might consider it safe to send COLLATE "C", but even that would + * fail on old remote servers.) An expression is considered safe to send + * only if all operator/function input collations used in it are traceable to + * Var(s) of the foreign table. That implies that if the remote server gets + * a different answer than we do, the foreign table's columns are not marked + * with collations that match the remote table's columns, which we can + * consider to be user error. + * + * Portions Copyright (c) 2012-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/postgres_fdw/deparse.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "nodes/plannodes.h" +#include "optimizer/optimizer.h" +#include "optimizer/prep.h" +#include "optimizer/tlist.h" +#include "parser/parsetree.h" +#include "postgres_fdw.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "utils/typcache.h" +#include "commands/tablecmds.h" + +/* + * Global context for foreign_expr_walker's search of an expression tree. + */ +typedef struct foreign_glob_cxt +{ + PlannerInfo *root; /* global planner state */ + RelOptInfo *foreignrel; /* the foreign relation we are planning for */ + Relids relids; /* relids of base relations in the underlying + * scan */ +} foreign_glob_cxt; + +/* + * Local (per-tree-level) context for foreign_expr_walker's search. + * This is concerned with identifying collations used in the expression. + */ +typedef enum +{ + FDW_COLLATE_NONE, /* expression is of a noncollatable type, or + * it has default collation that is not + * traceable to a foreign Var */ + FDW_COLLATE_SAFE, /* collation derives from a foreign Var */ + FDW_COLLATE_UNSAFE /* collation is non-default and derives from + * something other than a foreign Var */ +} FDWCollateState; + +typedef struct foreign_loc_cxt +{ + Oid collation; /* OID of current collation, if any */ + FDWCollateState state; /* state of current collation choice */ +} foreign_loc_cxt; + +/* + * Context for deparseExpr + */ +typedef struct deparse_expr_cxt +{ + PlannerInfo *root; /* global planner state */ + RelOptInfo *foreignrel; /* the foreign relation we are planning for */ + RelOptInfo *scanrel; /* the underlying scan relation. Same as + * foreignrel, when that represents a join or + * a base relation. */ + StringInfo buf; /* output buffer to append to */ + List **params_list; /* exprs that will become remote Params */ +} deparse_expr_cxt; + +#define REL_ALIAS_PREFIX "r" +/* Handy macro to add relation name qualification */ +#define ADD_REL_QUALIFIER(buf, varno) \ + appendStringInfo((buf), "%s%d.", REL_ALIAS_PREFIX, (varno)) +#define SUBQUERY_REL_ALIAS_PREFIX "s" +#define SUBQUERY_COL_ALIAS_PREFIX "c" + +/* + * Functions to determine whether an expression can be evaluated safely on + * remote server. + */ +static bool foreign_expr_walker(Node *node, + foreign_glob_cxt *glob_cxt, + foreign_loc_cxt *outer_cxt, + foreign_loc_cxt *case_arg_cxt); +static char *deparse_type_name(Oid type_oid, int32 typemod); + +/* + * Functions to construct string representation of a node tree. + */ +static void deparseTargetList(StringInfo buf, + RangeTblEntry *rte, + Index rtindex, + Relation rel, + bool is_returning, + Bitmapset *attrs_used, + bool qualify_col, + List **retrieved_attrs); +static void deparseExplicitTargetList(List *tlist, + bool is_returning, + List **retrieved_attrs, + deparse_expr_cxt *context); +static void deparseSubqueryTargetList(deparse_expr_cxt *context); +static void deparseReturningList(StringInfo buf, RangeTblEntry *rte, + Index rtindex, Relation rel, + bool trig_after_row, + List *withCheckOptionList, + List *returningList, + List **retrieved_attrs); +static void deparseColumnRef(StringInfo buf, int varno, int varattno, + RangeTblEntry *rte, bool qualify_col); +static void deparseRelation(StringInfo buf, Relation rel); +static void deparseExpr(Expr *node, deparse_expr_cxt *context); +static void deparseVar(Var *node, deparse_expr_cxt *context); +static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype); +static void deparseParam(Param *node, deparse_expr_cxt *context); +static void deparseSubscriptingRef(SubscriptingRef *node, deparse_expr_cxt *context); +static void deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context); +static void deparseOpExpr(OpExpr *node, deparse_expr_cxt *context); +static bool isPlainForeignVar(Expr *node, deparse_expr_cxt *context); +static void deparseOperatorName(StringInfo buf, Form_pg_operator opform); +static void deparseDistinctExpr(DistinctExpr *node, deparse_expr_cxt *context); +static void deparseScalarArrayOpExpr(ScalarArrayOpExpr *node, + deparse_expr_cxt *context); +static void deparseRelabelType(RelabelType *node, deparse_expr_cxt *context); +static void deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context); +static void deparseNullTest(NullTest *node, deparse_expr_cxt *context); +static void deparseCaseExpr(CaseExpr *node, deparse_expr_cxt *context); +static void deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context); +static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, + deparse_expr_cxt *context); +static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, + deparse_expr_cxt *context); +static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs, + deparse_expr_cxt *context); +static void deparseLockingClause(deparse_expr_cxt *context); +static void appendOrderByClause(List *pathkeys, bool has_final_sort, + deparse_expr_cxt *context); +static void appendLimitClause(deparse_expr_cxt *context); +static void appendConditions(List *exprs, deparse_expr_cxt *context); +static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, + RelOptInfo *foreignrel, bool use_alias, + Index ignore_rel, List **ignore_conds, + List **params_list); +static void deparseFromExpr(List *quals, deparse_expr_cxt *context); +static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, + RelOptInfo *foreignrel, bool make_subquery, + Index ignore_rel, List **ignore_conds, List **params_list); +static void deparseAggref(Aggref *node, deparse_expr_cxt *context); +static void appendGroupByClause(List *tlist, deparse_expr_cxt *context); +static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, + deparse_expr_cxt *context); +static void appendAggOrderBy(List *orderList, List *targetList, + deparse_expr_cxt *context); +static void appendFunctionName(Oid funcid, deparse_expr_cxt *context); +static Node *deparseSortGroupClause(Index ref, List *tlist, bool force_colno, + deparse_expr_cxt *context); + +/* + * Helper functions + */ +static bool is_subquery_var(Var *node, RelOptInfo *foreignrel, + int *relno, int *colno); +static void get_relation_column_alias_ids(Var *node, RelOptInfo *foreignrel, + int *relno, int *colno); + + +/* + * Examine each qual clause in input_conds, and classify them into two groups, + * which are returned as two lists: + * - remote_conds contains expressions that can be evaluated remotely + * - local_conds contains expressions that can't be evaluated remotely + */ +void +classifyConditions(PlannerInfo *root, + RelOptInfo *baserel, + List *input_conds, + List **remote_conds, + List **local_conds) +{ + ListCell *lc; + + *remote_conds = NIL; + *local_conds = NIL; + + foreach(lc, input_conds) + { + RestrictInfo *ri = lfirst_node(RestrictInfo, lc); + + if (is_foreign_expr(root, baserel, ri->clause)) + *remote_conds = lappend(*remote_conds, ri); + else + *local_conds = lappend(*local_conds, ri); + } +} + +/* + * Returns true if given expr is safe to evaluate on the foreign server. + */ +bool +is_foreign_expr(PlannerInfo *root, + RelOptInfo *baserel, + Expr *expr) +{ + foreign_glob_cxt glob_cxt; + foreign_loc_cxt loc_cxt; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) (baserel->fdw_private); + + /* + * Check that the expression consists of nodes that are safe to execute + * remotely. + */ + glob_cxt.root = root; + glob_cxt.foreignrel = baserel; + + /* + * For an upper relation, use relids from its underneath scan relation, + * because the upperrel's own relids currently aren't set to anything + * meaningful by the core code. For other relation, use their own relids. + */ + if (IS_UPPER_REL(baserel)) + glob_cxt.relids = fpinfo->outerrel->relids; + else + glob_cxt.relids = baserel->relids; + loc_cxt.collation = InvalidOid; + loc_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt, NULL)) + return false; + + /* + * If the expression has a valid collation that does not arise from a + * foreign var, the expression can not be sent over. + */ + if (loc_cxt.state == FDW_COLLATE_UNSAFE) + return false; + + /* + * An expression which includes any mutable functions can't be sent over + * because its result is not stable. For example, sending now() remote + * side could cause confusion from clock offsets. Future versions might + * be able to make this choice with more granularity. (We check this last + * because it requires a lot of expensive catalog lookups.) + */ + if (contain_mutable_functions((Node *) expr)) + return false; + + /* OK to evaluate on the remote server */ + return true; +} + +/* + * Check if expression is safe to execute remotely, and return true if so. + * + * In addition, *outer_cxt is updated with collation information. + * + * case_arg_cxt is NULL if this subexpression is not inside a CASE-with-arg. + * Otherwise, it points to the collation info derived from the arg expression, + * which must be consulted by any CaseTestExpr. + * + * We must check that the expression contains only node types we can deparse, + * that all types/functions/operators are safe to send (they are "shippable"), + * and that all collations used in the expression derive from Vars of the + * foreign table. Because of the latter, the logic is pretty close to + * assign_collations_walker() in parse_collate.c, though we can assume here + * that the given expression is valid. Note function mutability is not + * currently considered here. + */ +static bool +foreign_expr_walker(Node *node, + foreign_glob_cxt *glob_cxt, + foreign_loc_cxt *outer_cxt, + foreign_loc_cxt *case_arg_cxt) +{ + bool check_type = true; + PgFdwRelationInfo *fpinfo; + foreign_loc_cxt inner_cxt; + Oid collation; + FDWCollateState state; + + /* Need do nothing for empty subexpressions */ + if (node == NULL) + return true; + + /* May need server info from baserel's fdw_private struct */ + fpinfo = (PgFdwRelationInfo *) (glob_cxt->foreignrel->fdw_private); + + /* Set up inner_cxt for possible recursion to child nodes */ + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + + switch (nodeTag(node)) + { + case T_Var: + { + Var *var = (Var *) node; + + /* + * If the Var is from the foreign table, we consider its + * collation (if any) safe to use. If it is from another + * table, we treat its collation the same way as we would a + * Param's collation, ie it's not safe for it to have a + * non-default collation. + */ + if (bms_is_member(var->varno, glob_cxt->relids) && + var->varlevelsup == 0) + { + /* Var belongs to foreign table */ + + /* + * System columns other than ctid should not be sent to + * the remote, since we don't make any effort to ensure + * that local and remote values match (tableoid, in + * particular, almost certainly doesn't match). + */ + if (var->varattno < 0 && + var->varattno != SelfItemPointerAttributeNumber) + return false; + + /* Else check the collation */ + collation = var->varcollid; + state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE; + } + else + { + /* Var belongs to some other table */ + collation = var->varcollid; + if (collation == InvalidOid || + collation == DEFAULT_COLLATION_OID) + { + /* + * It's noncollatable, or it's safe to combine with a + * collatable foreign Var, so set state to NONE. + */ + state = FDW_COLLATE_NONE; + } + else + { + /* + * Do not fail right away, since the Var might appear + * in a collation-insensitive context. + */ + state = FDW_COLLATE_UNSAFE; + } + } + } + break; + case T_Const: + { + Const *c = (Const *) node; + + /* + * Constants of regproc and related types can't be shipped + * unless the referenced object is shippable. But NULL's ok. + * (See also the related code in dependency.c.) + */ + if (!c->constisnull) + { + switch (c->consttype) + { + case REGPROCOID: + case REGPROCEDUREOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), + ProcedureRelationId, fpinfo)) + return false; + break; + case REGOPEROID: + case REGOPERATOROID: + if (!is_shippable(DatumGetObjectId(c->constvalue), + OperatorRelationId, fpinfo)) + return false; + break; + case REGCLASSOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), + RelationRelationId, fpinfo)) + return false; + break; + case REGTYPEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), + TypeRelationId, fpinfo)) + return false; + break; + case REGCOLLATIONOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), + CollationRelationId, fpinfo)) + return false; + break; + case REGCONFIGOID: + + /* + * For text search objects only, we weaken the + * normal shippability criterion to allow all OIDs + * below FirstNormalObjectId. Without this, none + * of the initdb-installed TS configurations would + * be shippable, which would be quite annoying. + */ + if (DatumGetObjectId(c->constvalue) >= FirstNormalObjectId && + !is_shippable(DatumGetObjectId(c->constvalue), + TSConfigRelationId, fpinfo)) + return false; + break; + case REGDICTIONARYOID: + if (DatumGetObjectId(c->constvalue) >= FirstNormalObjectId && + !is_shippable(DatumGetObjectId(c->constvalue), + TSDictionaryRelationId, fpinfo)) + return false; + break; + case REGNAMESPACEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), + NamespaceRelationId, fpinfo)) + return false; + break; + case REGROLEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), + AuthIdRelationId, fpinfo)) + return false; + break; + } + } + + /* + * If the constant has nondefault collation, either it's of a + * non-builtin type, or it reflects folding of a CollateExpr. + * It's unsafe to send to the remote unless it's used in a + * non-collation-sensitive context. + */ + collation = c->constcollid; + if (collation == InvalidOid || + collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_Param: + { + Param *p = (Param *) node; + + /* + * If it's a MULTIEXPR Param, punt. We can't tell from here + * whether the referenced sublink/subplan contains any remote + * Vars; if it does, handling that is too complicated to + * consider supporting at present. Fortunately, MULTIEXPR + * Params are not reduced to plain PARAM_EXEC until the end of + * planning, so we can easily detect this case. (Normal + * PARAM_EXEC Params are safe to ship because their values + * come from somewhere else in the plan tree; but a MULTIEXPR + * references a sub-select elsewhere in the same targetlist, + * so we'd be on the hook to evaluate it somehow if we wanted + * to handle such cases as direct foreign updates.) + */ + if (p->paramkind == PARAM_MULTIEXPR) + return false; + + /* + * Collation rule is same as for Consts and non-foreign Vars. + */ + collation = p->paramcollid; + if (collation == InvalidOid || + collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_SubscriptingRef: + { + SubscriptingRef *sr = (SubscriptingRef *) node; + + /* Assignment should not be in restrictions. */ + if (sr->refassgnexpr != NULL) + return false; + + /* + * Recurse into the remaining subexpressions. The container + * subscripts will not affect collation of the SubscriptingRef + * result, so do those first and reset inner_cxt afterwards. + */ + if (!foreign_expr_walker((Node *) sr->refupperindexpr, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) sr->reflowerindexpr, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) sr->refexpr, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * Container subscripting typically yields same collation as + * refexpr's, but in case it doesn't, use same logic as for + * function nodes. + */ + collation = sr->refcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_FuncExpr: + { + FuncExpr *fe = (FuncExpr *) node; + + /* + * If function used by the expression is not shippable, it + * can't be sent to remote because it might have incompatible + * semantics on remote side. + */ + if (!is_shippable(fe->funcid, ProcedureRelationId, fpinfo)) + return false; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) fe->args, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * If function's input collation is not derived from a foreign + * Var, it can't be sent to remote. + */ + if (fe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || + fe->inputcollid != inner_cxt.collation) + return false; + + /* + * Detect whether node is introducing a collation not derived + * from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent + * node might not care.) + */ + collation = fe->funccollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_OpExpr: + case T_DistinctExpr: /* struct-equivalent to OpExpr */ + { + OpExpr *oe = (OpExpr *) node; + + /* + * Similarly, only shippable operators can be sent to remote. + * (If the operator is shippable, we assume its underlying + * function is too.) + */ + if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) + return false; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) oe->args, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * If operator's input collation is not derived from a foreign + * Var, it can't be sent to remote. + */ + if (oe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || + oe->inputcollid != inner_cxt.collation) + return false; + + /* Result-collation handling is same as for functions */ + collation = oe->opcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_ScalarArrayOpExpr: + { + ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node; + + /* + * Again, only shippable operators can be sent to remote. + */ + if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) + return false; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) oe->args, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * If operator's input collation is not derived from a foreign + * Var, it can't be sent to remote. + */ + if (oe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || + oe->inputcollid != inner_cxt.collation) + return false; + + /* Output is always boolean and so noncollatable. */ + collation = InvalidOid; + state = FDW_COLLATE_NONE; + } + break; + case T_RelabelType: + { + RelabelType *r = (RelabelType *) node; + + /* + * Recurse to input subexpression. + */ + if (!foreign_expr_walker((Node *) r->arg, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * RelabelType must not introduce a collation not derived from + * an input foreign Var (same logic as for a real function). + */ + collation = r->resultcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_BoolExpr: + { + BoolExpr *b = (BoolExpr *) node; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) b->args, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* Output is always boolean and so noncollatable. */ + collation = InvalidOid; + state = FDW_COLLATE_NONE; + } + break; + case T_NullTest: + { + NullTest *nt = (NullTest *) node; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) nt->arg, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* Output is always boolean and so noncollatable. */ + collation = InvalidOid; + state = FDW_COLLATE_NONE; + } + break; + case T_CaseExpr: + { + CaseExpr *ce = (CaseExpr *) node; + foreign_loc_cxt arg_cxt; + foreign_loc_cxt tmp_cxt; + ListCell *lc; + + /* + * Recurse to CASE's arg expression, if any. Its collation + * has to be saved aside for use while examining CaseTestExprs + * within the WHEN expressions. + */ + arg_cxt.collation = InvalidOid; + arg_cxt.state = FDW_COLLATE_NONE; + if (ce->arg) + { + if (!foreign_expr_walker((Node *) ce->arg, + glob_cxt, &arg_cxt, case_arg_cxt)) + return false; + } + + /* Examine the CaseWhen subexpressions. */ + foreach(lc, ce->args) + { + CaseWhen *cw = lfirst_node(CaseWhen, lc); + + if (ce->arg) + { + /* + * In a CASE-with-arg, the parser should have produced + * WHEN clauses of the form "CaseTestExpr = RHS", + * possibly with an implicit coercion inserted above + * the CaseTestExpr. However in an expression that's + * been through the optimizer, the WHEN clause could + * be almost anything (since the equality operator + * could have been expanded into an inline function). + * In such cases forbid pushdown, because + * deparseCaseExpr can't handle it. + */ + Node *whenExpr = (Node *) cw->expr; + List *opArgs; + + if (!IsA(whenExpr, OpExpr)) + return false; + + opArgs = ((OpExpr *) whenExpr)->args; + if (list_length(opArgs) != 2 || + !IsA(strip_implicit_coercions(linitial(opArgs)), + CaseTestExpr)) + return false; + } + + /* + * Recurse to WHEN expression, passing down the arg info. + * Its collation doesn't affect the result (really, it + * should be boolean and thus not have a collation). + */ + tmp_cxt.collation = InvalidOid; + tmp_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) cw->expr, + glob_cxt, &tmp_cxt, &arg_cxt)) + return false; + + /* Recurse to THEN expression. */ + if (!foreign_expr_walker((Node *) cw->result, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + } + + /* Recurse to ELSE expression. */ + if (!foreign_expr_walker((Node *) ce->defresult, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * Detect whether node is introducing a collation not derived + * from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent + * node might not care.) This is the same as for function + * nodes, except that the input collation is derived from only + * the THEN and ELSE subexpressions. + */ + collation = ce->casecollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_CaseTestExpr: + { + CaseTestExpr *c = (CaseTestExpr *) node; + + /* Punt if we seem not to be inside a CASE arg WHEN. */ + if (!case_arg_cxt) + return false; + + /* + * Otherwise, any nondefault collation attached to the + * CaseTestExpr node must be derived from foreign Var(s) in + * the CASE arg. + */ + collation = c->collation; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (case_arg_cxt->state == FDW_COLLATE_SAFE && + collation == case_arg_cxt->collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_ArrayExpr: + { + ArrayExpr *a = (ArrayExpr *) node; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) a->elements, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * ArrayExpr must not introduce a collation not derived from + * an input foreign Var (same logic as for a function). + */ + collation = a->array_collid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_List: + { + List *l = (List *) node; + ListCell *lc; + + /* + * Recurse to component subexpressions. + */ + foreach(lc, l) + { + if (!foreign_expr_walker((Node *) lfirst(lc), + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + } + + /* + * When processing a list, collation state just bubbles up + * from the list elements. + */ + collation = inner_cxt.collation; + state = inner_cxt.state; + + /* Don't apply exprType() to the list. */ + check_type = false; + } + break; + case T_Aggref: + { + Aggref *agg = (Aggref *) node; + ListCell *lc; + + /* Not safe to pushdown when not in grouping context */ + if (!IS_UPPER_REL(glob_cxt->foreignrel)) + return false; + + /* Only non-split aggregates are pushable. */ + if (agg->aggsplit != AGGSPLIT_SIMPLE) + return false; + + /* As usual, it must be shippable. */ + if (!is_shippable(agg->aggfnoid, ProcedureRelationId, fpinfo)) + return false; + + /* + * Recurse to input args. aggdirectargs, aggorder and + * aggdistinct are all present in args, so no need to check + * their shippability explicitly. + */ + foreach(lc, agg->args) + { + Node *n = (Node *) lfirst(lc); + + /* If TargetEntry, extract the expression from it */ + if (IsA(n, TargetEntry)) + { + TargetEntry *tle = (TargetEntry *) n; + + n = (Node *) tle->expr; + } + + if (!foreign_expr_walker(n, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + } + + /* + * For aggorder elements, check whether the sort operator, if + * specified, is shippable or not. + */ + if (agg->aggorder) + { + foreach(lc, agg->aggorder) + { + SortGroupClause *srt = (SortGroupClause *) lfirst(lc); + Oid sortcoltype; + TypeCacheEntry *typentry; + TargetEntry *tle; + + tle = get_sortgroupref_tle(srt->tleSortGroupRef, + agg->args); + sortcoltype = exprType((Node *) tle->expr); + typentry = lookup_type_cache(sortcoltype, + TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); + /* Check shippability of non-default sort operator. */ + if (srt->sortop != typentry->lt_opr && + srt->sortop != typentry->gt_opr && + !is_shippable(srt->sortop, OperatorRelationId, + fpinfo)) + return false; + } + } + + /* Check aggregate filter */ + if (!foreign_expr_walker((Node *) agg->aggfilter, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * If aggregate's input collation is not derived from a + * foreign Var, it can't be sent to remote. + */ + if (agg->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || + agg->inputcollid != inner_cxt.collation) + return false; + + /* + * Detect whether node is introducing a collation not derived + * from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent + * node might not care.) + */ + collation = agg->aggcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + default: + + /* + * If it's anything else, assume it's unsafe. This list can be + * expanded later, but don't forget to add deparse support below. + */ + return false; + } + + /* + * If result type of given expression is not shippable, it can't be sent + * to remote because it might have incompatible semantics on remote side. + */ + if (check_type && !is_shippable(exprType(node), TypeRelationId, fpinfo)) + return false; + + /* + * Now, merge my collation information into my parent's state. + */ + if (state > outer_cxt->state) + { + /* Override previous parent state */ + outer_cxt->collation = collation; + outer_cxt->state = state; + } + else if (state == outer_cxt->state) + { + /* Merge, or detect error if there's a collation conflict */ + switch (state) + { + case FDW_COLLATE_NONE: + /* Nothing + nothing is still nothing */ + break; + case FDW_COLLATE_SAFE: + if (collation != outer_cxt->collation) + { + /* + * Non-default collation always beats default. + */ + if (outer_cxt->collation == DEFAULT_COLLATION_OID) + { + /* Override previous parent state */ + outer_cxt->collation = collation; + } + else if (collation != DEFAULT_COLLATION_OID) + { + /* + * Conflict; show state as indeterminate. We don't + * want to "return false" right away, since parent + * node might not care about collation. + */ + outer_cxt->state = FDW_COLLATE_UNSAFE; + } + } + break; + case FDW_COLLATE_UNSAFE: + /* We're still conflicted ... */ + break; + } + } + + /* It looks OK */ + return true; +} + +/* + * Returns true if given expr is something we'd have to send the value of + * to the foreign server. + * + * This should return true when the expression is a shippable node that + * deparseExpr would add to context->params_list. Note that we don't care + * if the expression *contains* such a node, only whether one appears at top + * level. We need this to detect cases where setrefs.c would recognize a + * false match between an fdw_exprs item (which came from the params_list) + * and an entry in fdw_scan_tlist (which we're considering putting the given + * expression into). + */ +bool +is_foreign_param(PlannerInfo *root, + RelOptInfo *baserel, + Expr *expr) +{ + if (expr == NULL) + return false; + + switch (nodeTag(expr)) + { + case T_Var: + { + /* It would have to be sent unless it's a foreign Var */ + Var *var = (Var *) expr; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) (baserel->fdw_private); + Relids relids; + + if (IS_UPPER_REL(baserel)) + relids = fpinfo->outerrel->relids; + else + relids = baserel->relids; + + if (bms_is_member(var->varno, relids) && var->varlevelsup == 0) + return false; /* foreign Var, so not a param */ + else + return true; /* it'd have to be a param */ + break; + } + case T_Param: + /* Params always have to be sent to the foreign server */ + return true; + default: + break; + } + return false; +} + +/* + * Returns true if it's safe to push down the sort expression described by + * 'pathkey' to the foreign server. + */ +bool +is_foreign_pathkey(PlannerInfo *root, + RelOptInfo *baserel, + PathKey *pathkey) +{ + EquivalenceClass *pathkey_ec = pathkey->pk_eclass; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private; + + /* + * is_foreign_expr would detect volatile expressions as well, but checking + * ec_has_volatile here saves some cycles. + */ + if (pathkey_ec->ec_has_volatile) + return false; + + /* can't push down the sort if the pathkey's opfamily is not shippable */ + if (!is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, fpinfo)) + return false; + + /* can push if a suitable EC member exists */ + return (find_em_for_rel(root, pathkey_ec, baserel) != NULL); +} + +/* + * Convert type OID + typmod info into a type name we can ship to the remote + * server. Someplace else had better have verified that this type name is + * expected to be known on the remote end. + * + * This is almost just format_type_with_typemod(), except that if left to its + * own devices, that function will make schema-qualification decisions based + * on the local search_path, which is wrong. We must schema-qualify all + * type names that are not in pg_catalog. We assume here that built-in types + * are all in pg_catalog and need not be qualified; otherwise, qualify. + */ +static char * +deparse_type_name(Oid type_oid, int32 typemod) +{ + bits16 flags = FORMAT_TYPE_TYPEMOD_GIVEN; + + if (!is_builtin(type_oid)) + flags |= FORMAT_TYPE_FORCE_QUALIFY; + + return format_type_extended(type_oid, typemod, flags); +} + +/* + * Build the targetlist for given relation to be deparsed as SELECT clause. + * + * The output targetlist contains the columns that need to be fetched from the + * foreign server for the given relation. If foreignrel is an upper relation, + * then the output targetlist can also contain expressions to be evaluated on + * foreign server. + */ +List * +build_tlist_to_deparse(RelOptInfo *foreignrel) +{ + List *tlist = NIL; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; + ListCell *lc; + + /* + * For an upper relation, we have already built the target list while + * checking shippability, so just return that. + */ + if (IS_UPPER_REL(foreignrel)) + return fpinfo->grouped_tlist; + + /* + * We require columns specified in foreignrel->reltarget->exprs and those + * required for evaluating the local conditions. + */ + tlist = add_to_flat_tlist(tlist, + pull_var_clause((Node *) foreignrel->reltarget->exprs, + PVC_RECURSE_PLACEHOLDERS)); + foreach(lc, fpinfo->local_conds) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + tlist = add_to_flat_tlist(tlist, + pull_var_clause((Node *) rinfo->clause, + PVC_RECURSE_PLACEHOLDERS)); + } + + return tlist; +} + +/* + * Deparse SELECT statement for given relation into buf. + * + * tlist contains the list of desired columns to be fetched from foreign server. + * For a base relation fpinfo->attrs_used is used to construct SELECT clause, + * hence the tlist is ignored for a base relation. + * + * remote_conds is the list of conditions to be deparsed into the WHERE clause + * (or, in the case of upper relations, into the HAVING clause). + * + * If params_list is not NULL, it receives a list of Params and other-relation + * Vars used in the clauses; these values must be transmitted to the remote + * server as parameter values. + * + * If params_list is NULL, we're generating the query for EXPLAIN purposes, + * so Params and other-relation Vars should be replaced by dummy values. + * + * pathkeys is the list of pathkeys to order the result by. + * + * is_subquery is the flag to indicate whether to deparse the specified + * relation as a subquery. + * + * List of columns selected is returned in retrieved_attrs. + */ +void +deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, + List *tlist, List *remote_conds, List *pathkeys, + bool has_final_sort, bool has_limit, bool is_subquery, + List **retrieved_attrs, List **params_list) +{ + deparse_expr_cxt context; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private; + List *quals; + + /* + * We handle relations for foreign tables, joins between those and upper + * relations. + */ + Assert(IS_JOIN_REL(rel) || IS_SIMPLE_REL(rel) || IS_UPPER_REL(rel)); + + /* Fill portions of context common to upper, join and base relation */ + context.buf = buf; + context.root = root; + context.foreignrel = rel; + context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel; + context.params_list = params_list; + + /* Construct SELECT clause */ + deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context); + + /* + * For upper relations, the WHERE clause is built from the remote + * conditions of the underlying scan relation; otherwise, we can use the + * supplied list of remote conditions directly. + */ + if (IS_UPPER_REL(rel)) + { + PgFdwRelationInfo *ofpinfo; + + ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private; + quals = ofpinfo->remote_conds; + } + else + quals = remote_conds; + + /* Construct FROM and WHERE clauses */ + deparseFromExpr(quals, &context); + + if (IS_UPPER_REL(rel)) + { + /* Append GROUP BY clause */ + appendGroupByClause(tlist, &context); + + /* Append HAVING clause */ + if (remote_conds) + { + appendStringInfoString(buf, " HAVING "); + appendConditions(remote_conds, &context); + } + } + + /* Add ORDER BY clause if we found any useful pathkeys */ + if (pathkeys) + appendOrderByClause(pathkeys, has_final_sort, &context); + + /* Add LIMIT clause if necessary */ + if (has_limit) + appendLimitClause(&context); + + /* Add any necessary FOR UPDATE/SHARE. */ + deparseLockingClause(&context); +} + +/* + * Construct a simple SELECT statement that retrieves desired columns + * of the specified foreign table, and append it to "buf". The output + * contains just "SELECT ... ". + * + * We also create an integer List of the columns being retrieved, which is + * returned to *retrieved_attrs, unless we deparse the specified relation + * as a subquery. + * + * tlist is the list of desired columns. is_subquery is the flag to + * indicate whether to deparse the specified relation as a subquery. + * Read prologue of deparseSelectStmtForRel() for details. + */ +static void +deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs, + deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + RelOptInfo *foreignrel = context->foreignrel; + PlannerInfo *root = context->root; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; + + /* + * Construct SELECT list + */ + appendStringInfoString(buf, "SELECT "); + + if (is_subquery) + { + /* + * For a relation that is deparsed as a subquery, emit expressions + * specified in the relation's reltarget. Note that since this is for + * the subquery, no need to care about *retrieved_attrs. + */ + deparseSubqueryTargetList(context); + } + else if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) + { + /* + * For a join or upper relation the input tlist gives the list of + * columns required to be fetched from the foreign server. + */ + deparseExplicitTargetList(tlist, false, retrieved_attrs, context); + } + else + { + /* + * For a base relation fpinfo->attrs_used gives the list of columns + * required to be fetched from the foreign server. + */ + RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); + + /* + * Core code already has some lock on each rel being planned, so we + * can use NoLock here. + */ + Relation rel = table_open(rte->relid, NoLock); + + deparseTargetList(buf, rte, foreignrel->relid, rel, false, + fpinfo->attrs_used, false, retrieved_attrs); + table_close(rel, NoLock); + } +} + +/* + * Construct a FROM clause and, if needed, a WHERE clause, and append those to + * "buf". + * + * quals is the list of clauses to be included in the WHERE clause. + * (These may or may not include RestrictInfo decoration.) + */ +static void +deparseFromExpr(List *quals, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + RelOptInfo *scanrel = context->scanrel; + + /* For upper relations, scanrel must be either a joinrel or a baserel */ + Assert(!IS_UPPER_REL(context->foreignrel) || + IS_JOIN_REL(scanrel) || IS_SIMPLE_REL(scanrel)); + + /* Construct FROM clause */ + appendStringInfoString(buf, " FROM "); + deparseFromExprForRel(buf, context->root, scanrel, + (bms_membership(scanrel->relids) == BMS_MULTIPLE), + (Index) 0, NULL, context->params_list); + + /* Construct WHERE clause */ + if (quals != NIL) + { + appendStringInfoString(buf, " WHERE "); + appendConditions(quals, context); + } +} + +/* + * Emit a target list that retrieves the columns specified in attrs_used. + * This is used for both SELECT and RETURNING targetlists; the is_returning + * parameter is true only for a RETURNING targetlist. + * + * The tlist text is appended to buf, and we also create an integer List + * of the columns being retrieved, which is returned to *retrieved_attrs. + * + * If qualify_col is true, add relation alias before the column name. + */ +static void +deparseTargetList(StringInfo buf, + RangeTblEntry *rte, + Index rtindex, + Relation rel, + bool is_returning, + Bitmapset *attrs_used, + bool qualify_col, + List **retrieved_attrs) +{ + TupleDesc tupdesc = RelationGetDescr(rel); + bool have_wholerow; + bool first; + int i; + + *retrieved_attrs = NIL; + + /* If there's a whole-row reference, we'll need all the columns. */ + have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, + attrs_used); + + first = true; + for (i = 1; i <= tupdesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1); + + /* Ignore dropped attributes. */ + if (attr->attisdropped) + continue; + + if (have_wholerow || + bms_is_member(i - FirstLowInvalidHeapAttributeNumber, + attrs_used)) + { + if (!first) + appendStringInfoString(buf, ", "); + else if (is_returning) + appendStringInfoString(buf, " RETURNING "); + first = false; + + deparseColumnRef(buf, rtindex, i, rte, qualify_col); + + *retrieved_attrs = lappend_int(*retrieved_attrs, i); + } + } + + /* + * Add ctid if needed. We currently don't support retrieving any other + * system columns. + */ + if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber, + attrs_used)) + { + if (!first) + appendStringInfoString(buf, ", "); + else if (is_returning) + appendStringInfoString(buf, " RETURNING "); + first = false; + + if (qualify_col) + ADD_REL_QUALIFIER(buf, rtindex); + appendStringInfoString(buf, "ctid"); + + *retrieved_attrs = lappend_int(*retrieved_attrs, + SelfItemPointerAttributeNumber); + } + + /* Don't generate bad syntax if no undropped columns */ + if (first && !is_returning) + appendStringInfoString(buf, "NULL"); +} + +/* + * Deparse the appropriate locking clause (FOR UPDATE or FOR SHARE) for a + * given relation (context->scanrel). + */ +static void +deparseLockingClause(deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + PlannerInfo *root = context->root; + RelOptInfo *rel = context->scanrel; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private; + int relid = -1; + + while ((relid = bms_next_member(rel->relids, relid)) >= 0) + { + /* + * Ignore relation if it appears in a lower subquery. Locking clause + * for such a relation is included in the subquery if necessary. + */ + if (bms_is_member(relid, fpinfo->lower_subquery_rels)) + continue; + + /* + * Add FOR UPDATE/SHARE if appropriate. We apply locking during the + * initial row fetch, rather than later on as is done for local + * tables. The extra roundtrips involved in trying to duplicate the + * local semantics exactly don't seem worthwhile (see also comments + * for RowMarkType). + * + * Note: because we actually run the query as a cursor, this assumes + * that DECLARE CURSOR ... FOR UPDATE is supported, which it isn't + * before 8.3. + */ + if (bms_is_member(relid, root->all_result_relids) && + (root->parse->commandType == CMD_UPDATE || + root->parse->commandType == CMD_DELETE)) + { + /* Relation is UPDATE/DELETE target, so use FOR UPDATE */ + appendStringInfoString(buf, " FOR UPDATE"); + + /* Add the relation alias if we are here for a join relation */ + if (IS_JOIN_REL(rel)) + appendStringInfo(buf, " OF %s%d", REL_ALIAS_PREFIX, relid); + } + else + { + PlanRowMark *rc = get_plan_rowmark(root->rowMarks, relid); + + if (rc) + { + /* + * Relation is specified as a FOR UPDATE/SHARE target, so + * handle that. (But we could also see LCS_NONE, meaning this + * isn't a target relation after all.) + * + * For now, just ignore any [NO] KEY specification, since (a) + * it's not clear what that means for a remote table that we + * don't have complete information about, and (b) it wouldn't + * work anyway on older remote servers. Likewise, we don't + * worry about NOWAIT. + */ + switch (rc->strength) + { + case LCS_NONE: + /* No locking needed */ + break; + case LCS_FORKEYSHARE: + case LCS_FORSHARE: + appendStringInfoString(buf, " FOR SHARE"); + break; + case LCS_FORNOKEYUPDATE: + case LCS_FORUPDATE: + appendStringInfoString(buf, " FOR UPDATE"); + break; + } + + /* Add the relation alias if we are here for a join relation */ + if (bms_membership(rel->relids) == BMS_MULTIPLE && + rc->strength != LCS_NONE) + appendStringInfo(buf, " OF %s%d", REL_ALIAS_PREFIX, relid); + } + } + } +} + +/* + * Deparse conditions from the provided list and append them to buf. + * + * The conditions in the list are assumed to be ANDed. This function is used to + * deparse WHERE clauses, JOIN .. ON clauses and HAVING clauses. + * + * Depending on the caller, the list elements might be either RestrictInfos + * or bare clauses. + */ +static void +appendConditions(List *exprs, deparse_expr_cxt *context) +{ + int nestlevel; + ListCell *lc; + bool is_first = true; + StringInfo buf = context->buf; + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + foreach(lc, exprs) + { + Expr *expr = (Expr *) lfirst(lc); + + /* Extract clause from RestrictInfo, if required */ + if (IsA(expr, RestrictInfo)) + expr = ((RestrictInfo *) expr)->clause; + + /* Connect expressions with "AND" and parenthesize each condition. */ + if (!is_first) + appendStringInfoString(buf, " AND "); + + appendStringInfoChar(buf, '('); + deparseExpr(expr, context); + appendStringInfoChar(buf, ')'); + + is_first = false; + } + + reset_transmission_modes(nestlevel); +} + +/* Output join name for given join type */ +const char * +get_jointype_name(JoinType jointype) +{ + switch (jointype) + { + case JOIN_INNER: + return "INNER"; + + case JOIN_LEFT: + return "LEFT"; + + case JOIN_RIGHT: + return "RIGHT"; + + case JOIN_FULL: + return "FULL"; + + default: + /* Shouldn't come here, but protect from buggy code. */ + elog(ERROR, "unsupported join type %d", jointype); + } + + /* Keep compiler happy */ + return NULL; +} + +/* + * Deparse given targetlist and append it to context->buf. + * + * tlist is list of TargetEntry's which in turn contain Var nodes. + * + * retrieved_attrs is the list of continuously increasing integers starting + * from 1. It has same number of entries as tlist. + * + * This is used for both SELECT and RETURNING targetlists; the is_returning + * parameter is true only for a RETURNING targetlist. + */ +static void +deparseExplicitTargetList(List *tlist, + bool is_returning, + List **retrieved_attrs, + deparse_expr_cxt *context) +{ + ListCell *lc; + StringInfo buf = context->buf; + int i = 0; + + *retrieved_attrs = NIL; + + foreach(lc, tlist) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + + if (i > 0) + appendStringInfoString(buf, ", "); + else if (is_returning) + appendStringInfoString(buf, " RETURNING "); + + deparseExpr((Expr *) tle->expr, context); + + *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); + i++; + } + + if (i == 0 && !is_returning) + appendStringInfoString(buf, "NULL"); +} + +/* + * Emit expressions specified in the given relation's reltarget. + * + * This is used for deparsing the given relation as a subquery. + */ +static void +deparseSubqueryTargetList(deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + RelOptInfo *foreignrel = context->foreignrel; + bool first; + ListCell *lc; + + /* Should only be called in these cases. */ + Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); + + first = true; + foreach(lc, foreignrel->reltarget->exprs) + { + Node *node = (Node *) lfirst(lc); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + deparseExpr((Expr *) node, context); + } + + /* Don't generate bad syntax if no expressions */ + if (first) + appendStringInfoString(buf, "NULL"); +} + +/* + * Construct FROM clause for given relation + * + * The function constructs ... JOIN ... ON ... for join relation. For a base + * relation it just returns schema-qualified tablename, with the appropriate + * alias if so requested. + * + * 'ignore_rel' is either zero or the RT index of a target relation. In the + * latter case the function constructs FROM clause of UPDATE or USING clause + * of DELETE; it deparses the join relation as if the relation never contained + * the target relation, and creates a List of conditions to be deparsed into + * the top-level WHERE clause, which is returned to *ignore_conds. + */ +static void +deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, + bool use_alias, Index ignore_rel, List **ignore_conds, + List **params_list) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; + + if (IS_JOIN_REL(foreignrel)) + { + StringInfoData join_sql_o; + StringInfoData join_sql_i; + RelOptInfo *outerrel = fpinfo->outerrel; + RelOptInfo *innerrel = fpinfo->innerrel; + bool outerrel_is_target = false; + bool innerrel_is_target = false; + + if (ignore_rel > 0 && bms_is_member(ignore_rel, foreignrel->relids)) + { + /* + * If this is an inner join, add joinclauses to *ignore_conds and + * set it to empty so that those can be deparsed into the WHERE + * clause. Note that since the target relation can never be + * within the nullable side of an outer join, those could safely + * be pulled up into the WHERE clause (see foreign_join_ok()). + * Note also that since the target relation is only inner-joined + * to any other relation in the query, all conditions in the join + * tree mentioning the target relation could be deparsed into the + * WHERE clause by doing this recursively. + */ + if (fpinfo->jointype == JOIN_INNER) + { + *ignore_conds = list_concat(*ignore_conds, + fpinfo->joinclauses); + fpinfo->joinclauses = NIL; + } + + /* + * Check if either of the input relations is the target relation. + */ + if (outerrel->relid == ignore_rel) + outerrel_is_target = true; + else if (innerrel->relid == ignore_rel) + innerrel_is_target = true; + } + + /* Deparse outer relation if not the target relation. */ + if (!outerrel_is_target) + { + initStringInfo(&join_sql_o); + deparseRangeTblRef(&join_sql_o, root, outerrel, + fpinfo->make_outerrel_subquery, + ignore_rel, ignore_conds, params_list); + + /* + * If inner relation is the target relation, skip deparsing it. + * Note that since the join of the target relation with any other + * relation in the query is an inner join and can never be within + * the nullable side of an outer join, the join could be + * interchanged with higher-level joins (cf. identity 1 on outer + * join reordering shown in src/backend/optimizer/README), which + * means it's safe to skip the target-relation deparsing here. + */ + if (innerrel_is_target) + { + Assert(fpinfo->jointype == JOIN_INNER); + Assert(fpinfo->joinclauses == NIL); + appendBinaryStringInfo(buf, join_sql_o.data, join_sql_o.len); + return; + } + } + + /* Deparse inner relation if not the target relation. */ + if (!innerrel_is_target) + { + initStringInfo(&join_sql_i); + deparseRangeTblRef(&join_sql_i, root, innerrel, + fpinfo->make_innerrel_subquery, + ignore_rel, ignore_conds, params_list); + + /* + * If outer relation is the target relation, skip deparsing it. + * See the above note about safety. + */ + if (outerrel_is_target) + { + Assert(fpinfo->jointype == JOIN_INNER); + Assert(fpinfo->joinclauses == NIL); + appendBinaryStringInfo(buf, join_sql_i.data, join_sql_i.len); + return; + } + } + + /* Neither of the relations is the target relation. */ + Assert(!outerrel_is_target && !innerrel_is_target); + + /* + * For a join relation FROM clause entry is deparsed as + * + * ((outer relation) (inner relation) ON (joinclauses)) + */ + appendStringInfo(buf, "(%s %s JOIN %s ON ", join_sql_o.data, + get_jointype_name(fpinfo->jointype), join_sql_i.data); + + /* Append join clause; (TRUE) if no join clause */ + if (fpinfo->joinclauses) + { + deparse_expr_cxt context; + + context.buf = buf; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.root = root; + context.params_list = params_list; + + appendStringInfoChar(buf, '('); + appendConditions(fpinfo->joinclauses, &context); + appendStringInfoChar(buf, ')'); + } + else + appendStringInfoString(buf, "(TRUE)"); + + /* End the FROM clause entry. */ + appendStringInfoChar(buf, ')'); + } + else + { + RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); + + /* + * Core code already has some lock on each rel being planned, so we + * can use NoLock here. + */ + Relation rel = table_open(rte->relid, NoLock); + + deparseRelation(buf, rel); + + /* + * Add a unique alias to avoid any conflict in relation names due to + * pulled up subqueries in the query being built for a pushed down + * join. + */ + if (use_alias) + appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); + + table_close(rel, NoLock); + } +} + +/* + * Append FROM clause entry for the given relation into buf. + */ +static void +deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, + bool make_subquery, Index ignore_rel, List **ignore_conds, + List **params_list) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; + + /* Should only be called in these cases. */ + Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); + + Assert(fpinfo->local_conds == NIL); + + /* If make_subquery is true, deparse the relation as a subquery. */ + if (make_subquery) + { + List *retrieved_attrs; + int ncols; + + /* + * The given relation shouldn't contain the target relation, because + * this should only happen for input relations for a full join, and + * such relations can never contain an UPDATE/DELETE target. + */ + Assert(ignore_rel == 0 || + !bms_is_member(ignore_rel, foreignrel->relids)); + + /* Deparse the subquery representing the relation. */ + appendStringInfoChar(buf, '('); + deparseSelectStmtForRel(buf, root, foreignrel, NIL, + fpinfo->remote_conds, NIL, + false, false, true, + &retrieved_attrs, params_list); + appendStringInfoChar(buf, ')'); + + /* Append the relation alias. */ + appendStringInfo(buf, " %s%d", SUBQUERY_REL_ALIAS_PREFIX, + fpinfo->relation_index); + + /* + * Append the column aliases if needed. Note that the subquery emits + * expressions specified in the relation's reltarget (see + * deparseSubqueryTargetList). + */ + ncols = list_length(foreignrel->reltarget->exprs); + if (ncols > 0) + { + int i; + + appendStringInfoChar(buf, '('); + for (i = 1; i <= ncols; i++) + { + if (i > 1) + appendStringInfoString(buf, ", "); + + appendStringInfo(buf, "%s%d", SUBQUERY_COL_ALIAS_PREFIX, i); + } + appendStringInfoChar(buf, ')'); + } + } + else + deparseFromExprForRel(buf, root, foreignrel, true, ignore_rel, + ignore_conds, params_list); +} + +/* + * deparse remote INSERT statement + * + * The statement text is appended to buf, and we also create an integer List + * of the columns being retrieved by WITH CHECK OPTION or RETURNING (if any), + * which is returned to *retrieved_attrs. + * + * This also stores end position of the VALUES clause, so that we can rebuild + * an INSERT for a batch of rows later. + */ +void +deparseInsertSql(StringInfo buf, RangeTblEntry *rte, + Index rtindex, Relation rel, + List *targetAttrs, bool doNothing, + List *withCheckOptionList, List *returningList, + List **retrieved_attrs, int *values_end_len) +{ + TupleDesc tupdesc = RelationGetDescr(rel); + AttrNumber pindex; + bool first; + ListCell *lc; + + appendStringInfoString(buf, "INSERT INTO "); + deparseRelation(buf, rel); + + if (targetAttrs) + { + appendStringInfoChar(buf, '('); + + first = true; + foreach(lc, targetAttrs) + { + int attnum = lfirst_int(lc); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + deparseColumnRef(buf, rtindex, attnum, rte, false); + } + + appendStringInfoString(buf, ") VALUES ("); + + pindex = 1; + first = true; + foreach(lc, targetAttrs) + { + int attnum = lfirst_int(lc); + Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + if (attr->attgenerated) + appendStringInfoString(buf, "DEFAULT"); + else + { + appendStringInfo(buf, "$%d", pindex); + pindex++; + } + } + + appendStringInfoChar(buf, ')'); + } + else + appendStringInfoString(buf, " DEFAULT VALUES"); + *values_end_len = buf->len; + + if (doNothing) + appendStringInfoString(buf, " ON CONFLICT DO NOTHING"); + + deparseReturningList(buf, rte, rtindex, rel, + rel->trigdesc && rel->trigdesc->trig_insert_after_row, + withCheckOptionList, returningList, retrieved_attrs); +} + +/* + * rebuild remote INSERT statement + * + * Provided a number of rows in a batch, builds INSERT statement with the + * right number of parameters. + */ +void +rebuildInsertSql(StringInfo buf, Relation rel, + char *orig_query, List *target_attrs, + int values_end_len, int num_params, + int num_rows) +{ + TupleDesc tupdesc = RelationGetDescr(rel); + int i; + int pindex; + bool first; + ListCell *lc; + + /* Make sure the values_end_len is sensible */ + Assert((values_end_len > 0) && (values_end_len <= strlen(orig_query))); + + /* Copy up to the end of the first record from the original query */ + appendBinaryStringInfo(buf, orig_query, values_end_len); + + /* + * Add records to VALUES clause (we already have parameters for the first + * row, so start at the right offset). + */ + pindex = num_params + 1; + for (i = 0; i < num_rows; i++) + { + appendStringInfoString(buf, ", ("); + + first = true; + foreach(lc, target_attrs) + { + int attnum = lfirst_int(lc); + Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + if (attr->attgenerated) + appendStringInfoString(buf, "DEFAULT"); + else + { + appendStringInfo(buf, "$%d", pindex); + pindex++; + } + } + + appendStringInfoChar(buf, ')'); + } + + /* Copy stuff after VALUES clause from the original query */ + appendStringInfoString(buf, orig_query + values_end_len); +} + +/* + * deparse remote UPDATE statement + * + * The statement text is appended to buf, and we also create an integer List + * of the columns being retrieved by WITH CHECK OPTION or RETURNING (if any), + * which is returned to *retrieved_attrs. + */ +void +deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, + Index rtindex, Relation rel, + List *targetAttrs, + List *withCheckOptionList, List *returningList, + List **retrieved_attrs) +{ + TupleDesc tupdesc = RelationGetDescr(rel); + AttrNumber pindex; + bool first; + ListCell *lc; + + appendStringInfoString(buf, "UPDATE "); + deparseRelation(buf, rel); + appendStringInfoString(buf, " SET "); + + pindex = 2; /* ctid is always the first param */ + first = true; + foreach(lc, targetAttrs) + { + int attnum = lfirst_int(lc); + Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + deparseColumnRef(buf, rtindex, attnum, rte, false); + if (attr->attgenerated) + appendStringInfoString(buf, " = DEFAULT"); + else + { + appendStringInfo(buf, " = $%d", pindex); + pindex++; + } + } + appendStringInfoString(buf, " WHERE ctid = $1"); + + deparseReturningList(buf, rte, rtindex, rel, + rel->trigdesc && rel->trigdesc->trig_update_after_row, + withCheckOptionList, returningList, retrieved_attrs); +} + +/* + * deparse remote UPDATE statement + * + * 'buf' is the output buffer to append the statement to + * 'rtindex' is the RT index of the associated target relation + * 'rel' is the relation descriptor for the target relation + * 'foreignrel' is the RelOptInfo for the target relation or the join relation + * containing all base relations in the query + * 'targetlist' is the tlist of the underlying foreign-scan plan node + * (note that this only contains new-value expressions and junk attrs) + * 'targetAttrs' is the target columns of the UPDATE + * 'remote_conds' is the qual clauses that must be evaluated remotely + * '*params_list' is an output list of exprs that will become remote Params + * 'returningList' is the RETURNING targetlist + * '*retrieved_attrs' is an output list of integers of columns being retrieved + * by RETURNING (if any) + */ +void +deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, + Index rtindex, Relation rel, + RelOptInfo *foreignrel, + List *targetlist, + List *targetAttrs, + List *remote_conds, + List **params_list, + List *returningList, + List **retrieved_attrs) +{ + deparse_expr_cxt context; + int nestlevel; + bool first; + RangeTblEntry *rte = planner_rt_fetch(rtindex, root); + ListCell *lc, + *lc2; + + /* Set up context struct for recursion */ + context.root = root; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.buf = buf; + context.params_list = params_list; + + appendStringInfoString(buf, "UPDATE "); + deparseRelation(buf, rel); + if (foreignrel->reloptkind == RELOPT_JOINREL) + appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex); + appendStringInfoString(buf, " SET "); + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + first = true; + forboth(lc, targetlist, lc2, targetAttrs) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + int attnum = lfirst_int(lc2); + + /* update's new-value expressions shouldn't be resjunk */ + Assert(!tle->resjunk); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + deparseColumnRef(buf, rtindex, attnum, rte, false); + appendStringInfoString(buf, " = "); + deparseExpr((Expr *) tle->expr, &context); + } + + reset_transmission_modes(nestlevel); + + if (foreignrel->reloptkind == RELOPT_JOINREL) + { + List *ignore_conds = NIL; + + appendStringInfoString(buf, " FROM "); + deparseFromExprForRel(buf, root, foreignrel, true, rtindex, + &ignore_conds, params_list); + remote_conds = list_concat(remote_conds, ignore_conds); + } + + if (remote_conds) + { + appendStringInfoString(buf, " WHERE "); + appendConditions(remote_conds, &context); + } + + if (foreignrel->reloptkind == RELOPT_JOINREL) + deparseExplicitTargetList(returningList, true, retrieved_attrs, + &context); + else + deparseReturningList(buf, rte, rtindex, rel, false, + NIL, returningList, retrieved_attrs); +} + +/* + * deparse remote DELETE statement + * + * The statement text is appended to buf, and we also create an integer List + * of the columns being retrieved by RETURNING (if any), which is returned + * to *retrieved_attrs. + */ +void +deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, + Index rtindex, Relation rel, + List *returningList, + List **retrieved_attrs) +{ + appendStringInfoString(buf, "DELETE FROM "); + deparseRelation(buf, rel); + appendStringInfoString(buf, " WHERE ctid = $1"); + + deparseReturningList(buf, rte, rtindex, rel, + rel->trigdesc && rel->trigdesc->trig_delete_after_row, + NIL, returningList, retrieved_attrs); +} + +/* + * deparse remote DELETE statement + * + * 'buf' is the output buffer to append the statement to + * 'rtindex' is the RT index of the associated target relation + * 'rel' is the relation descriptor for the target relation + * 'foreignrel' is the RelOptInfo for the target relation or the join relation + * containing all base relations in the query + * 'remote_conds' is the qual clauses that must be evaluated remotely + * '*params_list' is an output list of exprs that will become remote Params + * 'returningList' is the RETURNING targetlist + * '*retrieved_attrs' is an output list of integers of columns being retrieved + * by RETURNING (if any) + */ +void +deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, + Index rtindex, Relation rel, + RelOptInfo *foreignrel, + List *remote_conds, + List **params_list, + List *returningList, + List **retrieved_attrs) +{ + deparse_expr_cxt context; + + /* Set up context struct for recursion */ + context.root = root; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.buf = buf; + context.params_list = params_list; + + appendStringInfoString(buf, "DELETE FROM "); + deparseRelation(buf, rel); + if (foreignrel->reloptkind == RELOPT_JOINREL) + appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex); + + if (foreignrel->reloptkind == RELOPT_JOINREL) + { + List *ignore_conds = NIL; + + appendStringInfoString(buf, " USING "); + deparseFromExprForRel(buf, root, foreignrel, true, rtindex, + &ignore_conds, params_list); + remote_conds = list_concat(remote_conds, ignore_conds); + } + + if (remote_conds) + { + appendStringInfoString(buf, " WHERE "); + appendConditions(remote_conds, &context); + } + + if (foreignrel->reloptkind == RELOPT_JOINREL) + deparseExplicitTargetList(returningList, true, retrieved_attrs, + &context); + else + deparseReturningList(buf, planner_rt_fetch(rtindex, root), + rtindex, rel, false, + NIL, returningList, retrieved_attrs); +} + +/* + * Add a RETURNING clause, if needed, to an INSERT/UPDATE/DELETE. + */ +static void +deparseReturningList(StringInfo buf, RangeTblEntry *rte, + Index rtindex, Relation rel, + bool trig_after_row, + List *withCheckOptionList, + List *returningList, + List **retrieved_attrs) +{ + Bitmapset *attrs_used = NULL; + + if (trig_after_row) + { + /* whole-row reference acquires all non-system columns */ + attrs_used = + bms_make_singleton(0 - FirstLowInvalidHeapAttributeNumber); + } + + if (withCheckOptionList != NIL) + { + /* + * We need the attrs, non-system and system, mentioned in the local + * query's WITH CHECK OPTION list. + * + * Note: we do this to ensure that WCO constraints will be evaluated + * on the data actually inserted/updated on the remote side, which + * might differ from the data supplied by the core code, for example + * as a result of remote triggers. + */ + pull_varattnos((Node *) withCheckOptionList, rtindex, + &attrs_used); + } + + if (returningList != NIL) + { + /* + * We need the attrs, non-system and system, mentioned in the local + * query's RETURNING list. + */ + pull_varattnos((Node *) returningList, rtindex, + &attrs_used); + } + + if (attrs_used != NULL) + deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false, + retrieved_attrs); + else + *retrieved_attrs = NIL; +} + +/* + * Construct SELECT statement to acquire size in blocks of given relation. + * + * Note: we use local definition of block size, not remote definition. + * This is perhaps debatable. + * + * Note: pg_relation_size() exists in 8.1 and later. + */ +void +deparseAnalyzeSizeSql(StringInfo buf, Relation rel) +{ + StringInfoData relname; + + /* We'll need the remote relation name as a literal. */ + initStringInfo(&relname); + deparseRelation(&relname, rel); + + appendStringInfoString(buf, "SELECT pg_catalog.pg_relation_size("); + deparseStringLiteral(buf, relname.data); + appendStringInfo(buf, "::pg_catalog.regclass) / %d", BLCKSZ); +} + +/* + * Construct SELECT statement to acquire the number of rows and the relkind of + * a relation. + * + * Note: we just return the remote server's reltuples value, which might + * be off a good deal, but it doesn't seem worth working harder. See + * comments in postgresAcquireSampleRowsFunc. + */ +void +deparseAnalyzeInfoSql(StringInfo buf, Relation rel) +{ + StringInfoData relname; + + /* We'll need the remote relation name as a literal. */ + initStringInfo(&relname); + deparseRelation(&relname, rel); + + appendStringInfoString(buf, "SELECT reltuples, relkind FROM pg_catalog.pg_class WHERE oid = "); + deparseStringLiteral(buf, relname.data); + appendStringInfoString(buf, "::pg_catalog.regclass"); +} + +/* + * Construct SELECT statement to acquire sample rows of given relation. + * + * SELECT command is appended to buf, and list of columns retrieved + * is returned to *retrieved_attrs. + * + * We only support sampling methods we can decide based on server version. + * Allowing custom TSM modules (like tsm_system_rows) might be useful, but it + * would require detecting which extensions are installed, to allow automatic + * fall-back. Moreover, the methods may use different parameters like number + * of rows (and not sampling rate). So we leave this for future improvements. + * + * Using random() to sample rows on the remote server has the advantage that + * this works on all PostgreSQL versions (unlike TABLESAMPLE), and that it + * does the sampling on the remote side (without transferring everything and + * then discarding most rows). + * + * The disadvantage is that we still have to read all rows and evaluate the + * random(), while TABLESAMPLE (at least with the "system" method) may skip. + * It's not that different from the "bernoulli" method, though. + * + * We could also do "ORDER BY random() LIMIT x", which would always pick + * the expected number of rows, but it requires sorting so it may be much + * more expensive (particularly on large tables, which is what the + * remote sampling is meant to improve). + */ +void +deparseAnalyzeSql(StringInfo buf, Relation rel, + PgFdwSamplingMethod sample_method, double sample_frac, + List **retrieved_attrs) +{ + Oid relid = RelationGetRelid(rel); + TupleDesc tupdesc = RelationGetDescr(rel); + int i; + char *colname; + List *options; + ListCell *lc; + bool first = true; + + *retrieved_attrs = NIL; + + appendStringInfoString(buf, "SELECT "); + for (i = 0; i < tupdesc->natts; i++) + { + /* Ignore dropped columns. */ + if (TupleDescAttr(tupdesc, i)->attisdropped) + continue; + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + /* Use attribute name or column_name option. */ + colname = NameStr(TupleDescAttr(tupdesc, i)->attname); + options = GetForeignColumnOptions(relid, i + 1); + + foreach(lc, options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "column_name") == 0) + { + colname = defGetString(def); + break; + } + } + + appendStringInfoString(buf, quote_identifier(colname)); + + *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); + } + + /* Don't generate bad syntax for zero-column relation. */ + if (first) + appendStringInfoString(buf, "NULL"); + + /* + * Construct FROM clause, and perhaps WHERE clause too, depending on the + * selected sampling method. + */ + appendStringInfoString(buf, " FROM "); + deparseRelation(buf, rel); + + switch (sample_method) + { + case ANALYZE_SAMPLE_OFF: + /* nothing to do here */ + break; + + case ANALYZE_SAMPLE_RANDOM: + appendStringInfo(buf, " WHERE pg_catalog.random() < %f", sample_frac); + break; + + case ANALYZE_SAMPLE_SYSTEM: + appendStringInfo(buf, " TABLESAMPLE SYSTEM(%f)", (100.0 * sample_frac)); + break; + + case ANALYZE_SAMPLE_BERNOULLI: + appendStringInfo(buf, " TABLESAMPLE BERNOULLI(%f)", (100.0 * sample_frac)); + break; + + case ANALYZE_SAMPLE_AUTO: + /* should have been resolved into actual method */ + elog(ERROR, "unexpected sampling method"); + break; + } +} + +/* + * Construct a simple "TRUNCATE rel" statement + */ +void +deparseTruncateSql(StringInfo buf, + List *rels, + DropBehavior behavior, + bool restart_seqs) +{ + ListCell *cell; + + appendStringInfoString(buf, "TRUNCATE "); + + foreach(cell, rels) + { + Relation rel = lfirst(cell); + + if (cell != list_head(rels)) + appendStringInfoString(buf, ", "); + + deparseRelation(buf, rel); + } + + appendStringInfo(buf, " %s IDENTITY", + restart_seqs ? "RESTART" : "CONTINUE"); + + if (behavior == DROP_RESTRICT) + appendStringInfoString(buf, " RESTRICT"); + else if (behavior == DROP_CASCADE) + appendStringInfoString(buf, " CASCADE"); +} + +/* + * Construct name to use for given column, and emit it into buf. + * If it has a column_name FDW option, use that instead of attribute name. + * + * If qualify_col is true, qualify column name with the alias of relation. + */ +static void +deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, + bool qualify_col) +{ + /* We support fetching the remote side's CTID and OID. */ + if (varattno == SelfItemPointerAttributeNumber) + { + if (qualify_col) + ADD_REL_QUALIFIER(buf, varno); + appendStringInfoString(buf, "ctid"); + } + else if (varattno < 0) + { + /* + * All other system attributes are fetched as 0, except for table OID, + * which is fetched as the local table OID. However, we must be + * careful; the table could be beneath an outer join, in which case it + * must go to NULL whenever the rest of the row does. + */ + Oid fetchval = 0; + + if (varattno == TableOidAttributeNumber) + fetchval = rte->relid; + + if (qualify_col) + { + appendStringInfoString(buf, "CASE WHEN ("); + ADD_REL_QUALIFIER(buf, varno); + appendStringInfo(buf, "*)::text IS NOT NULL THEN %u END", fetchval); + } + else + appendStringInfo(buf, "%u", fetchval); + } + else if (varattno == 0) + { + /* Whole row reference */ + Relation rel; + Bitmapset *attrs_used; + + /* Required only to be passed down to deparseTargetList(). */ + List *retrieved_attrs; + + /* + * The lock on the relation will be held by upper callers, so it's + * fine to open it with no lock here. + */ + rel = table_open(rte->relid, NoLock); + + /* + * The local name of the foreign table can not be recognized by the + * foreign server and the table it references on foreign server might + * have different column ordering or different columns than those + * declared locally. Hence we have to deparse whole-row reference as + * ROW(columns referenced locally). Construct this by deparsing a + * "whole row" attribute. + */ + attrs_used = bms_add_member(NULL, + 0 - FirstLowInvalidHeapAttributeNumber); + + /* + * In case the whole-row reference is under an outer join then it has + * to go NULL whenever the rest of the row goes NULL. Deparsing a join + * query would always involve multiple relations, thus qualify_col + * would be true. + */ + if (qualify_col) + { + appendStringInfoString(buf, "CASE WHEN ("); + ADD_REL_QUALIFIER(buf, varno); + appendStringInfoString(buf, "*)::text IS NOT NULL THEN "); + } + + appendStringInfoString(buf, "ROW("); + deparseTargetList(buf, rte, varno, rel, false, attrs_used, qualify_col, + &retrieved_attrs); + appendStringInfoChar(buf, ')'); + + /* Complete the CASE WHEN statement started above. */ + if (qualify_col) + appendStringInfoString(buf, " END"); + + table_close(rel, NoLock); + bms_free(attrs_used); + } + else + { + char *colname = NULL; + List *options; + ListCell *lc; + + /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ + Assert(!IS_SPECIAL_VARNO(varno)); + + /* + * If it's a column of a foreign table, and it has the column_name FDW + * option, use that value. + */ + options = GetForeignColumnOptions(rte->relid, varattno); + foreach(lc, options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "column_name") == 0) + { + colname = defGetString(def); + break; + } + } + + /* + * If it's a column of a regular table or it doesn't have column_name + * FDW option, use attribute name. + */ + if (colname == NULL) + colname = get_attname(rte->relid, varattno, false); + + if (qualify_col) + ADD_REL_QUALIFIER(buf, varno); + + appendStringInfoString(buf, quote_identifier(colname)); + } +} + +/* + * Append remote name of specified foreign table to buf. + * Use value of table_name FDW option (if any) instead of relation's name. + * Similarly, schema_name FDW option overrides schema name. + */ +static void +deparseRelation(StringInfo buf, Relation rel) +{ + ForeignTable *table; + const char *nspname = NULL; + const char *relname = NULL; + ListCell *lc; + + /* obtain additional catalog information. */ + table = GetForeignTable(RelationGetRelid(rel)); + + /* + * Use value of FDW options if any, instead of the name of object itself. + */ + foreach(lc, table->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "schema_name") == 0) + nspname = defGetString(def); + else if (strcmp(def->defname, "table_name") == 0) + relname = defGetString(def); + } + + /* + * Note: we could skip printing the schema name if it's pg_catalog, but + * that doesn't seem worth the trouble. + */ + if (nspname == NULL) + nspname = get_namespace_name(RelationGetNamespace(rel)); + if (relname == NULL) + relname = RelationGetRelationName(rel); + + appendStringInfo(buf, "%s.%s", + quote_identifier(nspname), quote_identifier(relname)); +} + +/* + * Append a SQL string literal representing "val" to buf. + */ +void +deparseStringLiteral(StringInfo buf, const char *val) +{ + const char *valptr; + + /* + * Rather than making assumptions about the remote server's value of + * standard_conforming_strings, always use E'foo' syntax if there are any + * backslashes. This will fail on remote servers before 8.1, but those + * are long out of support. + */ + if (strchr(val, '\\') != NULL) + appendStringInfoChar(buf, ESCAPE_STRING_SYNTAX); + appendStringInfoChar(buf, '\''); + for (valptr = val; *valptr; valptr++) + { + char ch = *valptr; + + if (SQL_STR_DOUBLE(ch, true)) + appendStringInfoChar(buf, ch); + appendStringInfoChar(buf, ch); + } + appendStringInfoChar(buf, '\''); +} + +/* + * Deparse given expression into context->buf. + * + * This function must support all the same node types that foreign_expr_walker + * accepts. + * + * Note: unlike ruleutils.c, we just use a simple hard-wired parenthesization + * scheme: anything more complex than a Var, Const, function call or cast + * should be self-parenthesized. + */ +static void +deparseExpr(Expr *node, deparse_expr_cxt *context) +{ + if (node == NULL) + return; + + switch (nodeTag(node)) + { + case T_Var: + deparseVar((Var *) node, context); + break; + case T_Const: + deparseConst((Const *) node, context, 0); + break; + case T_Param: + deparseParam((Param *) node, context); + break; + case T_SubscriptingRef: + deparseSubscriptingRef((SubscriptingRef *) node, context); + break; + case T_FuncExpr: + deparseFuncExpr((FuncExpr *) node, context); + break; + case T_OpExpr: + deparseOpExpr((OpExpr *) node, context); + break; + case T_DistinctExpr: + deparseDistinctExpr((DistinctExpr *) node, context); + break; + case T_ScalarArrayOpExpr: + deparseScalarArrayOpExpr((ScalarArrayOpExpr *) node, context); + break; + case T_RelabelType: + deparseRelabelType((RelabelType *) node, context); + break; + case T_BoolExpr: + deparseBoolExpr((BoolExpr *) node, context); + break; + case T_NullTest: + deparseNullTest((NullTest *) node, context); + break; + case T_CaseExpr: + deparseCaseExpr((CaseExpr *) node, context); + break; + case T_ArrayExpr: + deparseArrayExpr((ArrayExpr *) node, context); + break; + case T_Aggref: + deparseAggref((Aggref *) node, context); + break; + default: + elog(ERROR, "unsupported expression type for deparse: %d", + (int) nodeTag(node)); + break; + } +} + +/* + * Deparse given Var node into context->buf. + * + * If the Var belongs to the foreign relation, just print its remote name. + * Otherwise, it's effectively a Param (and will in fact be a Param at + * run time). Handle it the same way we handle plain Params --- see + * deparseParam for comments. + */ +static void +deparseVar(Var *node, deparse_expr_cxt *context) +{ + Relids relids = context->scanrel->relids; + int relno; + int colno; + + /* Qualify columns when multiple relations are involved. */ + bool qualify_col = (bms_membership(relids) == BMS_MULTIPLE); + + /* + * If the Var belongs to the foreign relation that is deparsed as a + * subquery, use the relation and column alias to the Var provided by the + * subquery, instead of the remote name. + */ + if (is_subquery_var(node, context->scanrel, &relno, &colno)) + { + appendStringInfo(context->buf, "%s%d.%s%d", + SUBQUERY_REL_ALIAS_PREFIX, relno, + SUBQUERY_COL_ALIAS_PREFIX, colno); + return; + } + + if (bms_is_member(node->varno, relids) && node->varlevelsup == 0) + deparseColumnRef(context->buf, node->varno, node->varattno, + planner_rt_fetch(node->varno, context->root), + qualify_col); + else + { + /* Treat like a Param */ + if (context->params_list) + { + int pindex = 0; + ListCell *lc; + + /* find its index in params_list */ + foreach(lc, *context->params_list) + { + pindex++; + if (equal(node, (Node *) lfirst(lc))) + break; + } + if (lc == NULL) + { + /* not in list, so add it */ + pindex++; + *context->params_list = lappend(*context->params_list, node); + } + + printRemoteParam(pindex, node->vartype, node->vartypmod, context); + } + else + { + printRemotePlaceholder(node->vartype, node->vartypmod, context); + } + } +} + +/* + * Deparse given constant value into context->buf. + * + * This function has to be kept in sync with ruleutils.c's get_const_expr. + * + * As in that function, showtype can be -1 to never show "::typename" + * decoration, +1 to always show it, or 0 to show it only if the constant + * wouldn't be assumed to be the right type by default. + * + * In addition, this code allows showtype to be -2 to indicate that we should + * not show "::typename" decoration if the constant is printed as an untyped + * literal or NULL (while in other cases, behaving as for showtype == 0). + */ +static void +deparseConst(Const *node, deparse_expr_cxt *context, int showtype) +{ + StringInfo buf = context->buf; + Oid typoutput; + bool typIsVarlena; + char *extval; + bool isfloat = false; + bool isstring = false; + bool needlabel; + + if (node->constisnull) + { + appendStringInfoString(buf, "NULL"); + if (showtype >= 0) + appendStringInfo(buf, "::%s", + deparse_type_name(node->consttype, + node->consttypmod)); + return; + } + + getTypeOutputInfo(node->consttype, + &typoutput, &typIsVarlena); + extval = OidOutputFunctionCall(typoutput, node->constvalue); + + switch (node->consttype) + { + case INT2OID: + case INT4OID: + case INT8OID: + case OIDOID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + { + /* + * No need to quote unless it's a special value such as 'NaN'. + * See comments in get_const_expr(). + */ + if (strspn(extval, "0123456789+-eE.") == strlen(extval)) + { + if (extval[0] == '+' || extval[0] == '-') + appendStringInfo(buf, "(%s)", extval); + else + appendStringInfoString(buf, extval); + if (strcspn(extval, "eE.") != strlen(extval)) + isfloat = true; /* it looks like a float */ + } + else + appendStringInfo(buf, "'%s'", extval); + } + break; + case BITOID: + case VARBITOID: + appendStringInfo(buf, "B'%s'", extval); + break; + case BOOLOID: + if (strcmp(extval, "t") == 0) + appendStringInfoString(buf, "true"); + else + appendStringInfoString(buf, "false"); + break; + default: + deparseStringLiteral(buf, extval); + isstring = true; + break; + } + + pfree(extval); + + if (showtype == -1) + return; /* never print type label */ + + /* + * For showtype == 0, append ::typename unless the constant will be + * implicitly typed as the right type when it is read in. + * + * XXX this code has to be kept in sync with the behavior of the parser, + * especially make_const. + */ + switch (node->consttype) + { + case BOOLOID: + case INT4OID: + case UNKNOWNOID: + needlabel = false; + break; + case NUMERICOID: + needlabel = !isfloat || (node->consttypmod >= 0); + break; + default: + if (showtype == -2) + { + /* label unless we printed it as an untyped string */ + needlabel = !isstring; + } + else + needlabel = true; + break; + } + if (needlabel || showtype > 0) + appendStringInfo(buf, "::%s", + deparse_type_name(node->consttype, + node->consttypmod)); +} + +/* + * Deparse given Param node. + * + * If we're generating the query "for real", add the Param to + * context->params_list if it's not already present, and then use its index + * in that list as the remote parameter number. During EXPLAIN, there's + * no need to identify a parameter number. + */ +static void +deparseParam(Param *node, deparse_expr_cxt *context) +{ + if (context->params_list) + { + int pindex = 0; + ListCell *lc; + + /* find its index in params_list */ + foreach(lc, *context->params_list) + { + pindex++; + if (equal(node, (Node *) lfirst(lc))) + break; + } + if (lc == NULL) + { + /* not in list, so add it */ + pindex++; + *context->params_list = lappend(*context->params_list, node); + } + + printRemoteParam(pindex, node->paramtype, node->paramtypmod, context); + } + else + { + printRemotePlaceholder(node->paramtype, node->paramtypmod, context); + } +} + +/* + * Deparse a container subscript expression. + */ +static void +deparseSubscriptingRef(SubscriptingRef *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + ListCell *lowlist_item; + ListCell *uplist_item; + + /* Always parenthesize the expression. */ + appendStringInfoChar(buf, '('); + + /* + * Deparse referenced array expression first. If that expression includes + * a cast, we have to parenthesize to prevent the array subscript from + * being taken as typename decoration. We can avoid that in the typical + * case of subscripting a Var, but otherwise do it. + */ + if (IsA(node->refexpr, Var)) + deparseExpr(node->refexpr, context); + else + { + appendStringInfoChar(buf, '('); + deparseExpr(node->refexpr, context); + appendStringInfoChar(buf, ')'); + } + + /* Deparse subscript expressions. */ + lowlist_item = list_head(node->reflowerindexpr); /* could be NULL */ + foreach(uplist_item, node->refupperindexpr) + { + appendStringInfoChar(buf, '['); + if (lowlist_item) + { + deparseExpr(lfirst(lowlist_item), context); + appendStringInfoChar(buf, ':'); + lowlist_item = lnext(node->reflowerindexpr, lowlist_item); + } + deparseExpr(lfirst(uplist_item), context); + appendStringInfoChar(buf, ']'); + } + + appendStringInfoChar(buf, ')'); +} + +/* + * Deparse a function call. + */ +static void +deparseFuncExpr(FuncExpr *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + bool use_variadic; + bool first; + ListCell *arg; + + /* + * If the function call came from an implicit coercion, then just show the + * first argument. + */ + if (node->funcformat == COERCE_IMPLICIT_CAST) + { + deparseExpr((Expr *) linitial(node->args), context); + return; + } + + /* + * If the function call came from a cast, then show the first argument + * plus an explicit cast operation. + */ + if (node->funcformat == COERCE_EXPLICIT_CAST) + { + Oid rettype = node->funcresulttype; + int32 coercedTypmod; + + /* Get the typmod if this is a length-coercion function */ + (void) exprIsLengthCoercion((Node *) node, &coercedTypmod); + + deparseExpr((Expr *) linitial(node->args), context); + appendStringInfo(buf, "::%s", + deparse_type_name(rettype, coercedTypmod)); + return; + } + + /* Check if need to print VARIADIC (cf. ruleutils.c) */ + use_variadic = node->funcvariadic; + + /* + * Normal function: display as proname(args). + */ + appendFunctionName(node->funcid, context); + appendStringInfoChar(buf, '('); + + /* ... and all the arguments */ + first = true; + foreach(arg, node->args) + { + if (!first) + appendStringInfoString(buf, ", "); + if (use_variadic && lnext(node->args, arg) == NULL) + appendStringInfoString(buf, "VARIADIC "); + deparseExpr((Expr *) lfirst(arg), context); + first = false; + } + appendStringInfoChar(buf, ')'); +} + +/* + * Deparse given operator expression. To avoid problems around + * priority of operations, we always parenthesize the arguments. + */ +static void +deparseOpExpr(OpExpr *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + HeapTuple tuple; + Form_pg_operator form; + Expr *right; + bool canSuppressRightConstCast = false; + char oprkind; + + /* Retrieve information about the operator from system catalog. */ + tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for operator %u", node->opno); + form = (Form_pg_operator) GETSTRUCT(tuple); + oprkind = form->oprkind; + + /* Sanity check. */ + Assert((oprkind == 'l' && list_length(node->args) == 1) || + (oprkind == 'b' && list_length(node->args) == 2)); + + right = llast(node->args); + + /* Always parenthesize the expression. */ + appendStringInfoChar(buf, '('); + + /* Deparse left operand, if any. */ + if (oprkind == 'b') + { + Expr *left = linitial(node->args); + Oid leftType = exprType((Node *) left); + Oid rightType = exprType((Node *) right); + bool canSuppressLeftConstCast = false; + + /* + * When considering a binary operator, if one operand is a Const that + * can be printed as a bare string literal or NULL (i.e., it will look + * like type UNKNOWN to the remote parser), the Const normally + * receives an explicit cast to the operator's input type. However, + * in Const-to-Var comparisons where both operands are of the same + * type, we prefer to suppress the explicit cast, leaving the Const's + * type resolution up to the remote parser. The remote's resolution + * heuristic will assume that an unknown input type being compared to + * a known input type is of that known type as well. + * + * This hack allows some cases to succeed where a remote column is + * declared with a different type in the local (foreign) table. By + * emitting "foreigncol = 'foo'" not "foreigncol = 'foo'::text" or the + * like, we allow the remote parser to pick an "=" operator that's + * compatible with whatever type the remote column really is, such as + * an enum. + * + * We allow cast suppression to happen only when the other operand is + * a plain foreign Var. Although the remote's unknown-type heuristic + * would apply to other cases just as well, we would be taking a + * bigger risk that the inferred type is something unexpected. With + * this restriction, if anything goes wrong it's the user's fault for + * not declaring the local column with the same type as the remote + * column. + */ + if (leftType == rightType) + { + if (IsA(left, Const)) + canSuppressLeftConstCast = isPlainForeignVar(right, context); + else if (IsA(right, Const)) + canSuppressRightConstCast = isPlainForeignVar(left, context); + } + + if (canSuppressLeftConstCast) + deparseConst((Const *) left, context, -2); + else + deparseExpr(left, context); + + appendStringInfoChar(buf, ' '); + } + + /* Deparse operator name. */ + deparseOperatorName(buf, form); + + /* Deparse right operand. */ + appendStringInfoChar(buf, ' '); + + if (canSuppressRightConstCast) + deparseConst((Const *) right, context, -2); + else + deparseExpr(right, context); + + appendStringInfoChar(buf, ')'); + + ReleaseSysCache(tuple); +} + +/* + * Will "node" deparse as a plain foreign Var? + */ +static bool +isPlainForeignVar(Expr *node, deparse_expr_cxt *context) +{ + /* + * We allow the foreign Var to have an implicit RelabelType, mainly so + * that this'll work with varchar columns. Note that deparseRelabelType + * will not print such a cast, so we're not breaking the restriction that + * the expression print as a plain Var. We won't risk it for an implicit + * cast that requires a function, nor for non-implicit RelabelType; such + * cases seem too likely to involve semantics changes compared to what + * would happen on the remote side. + */ + if (IsA(node, RelabelType) && + ((RelabelType *) node)->relabelformat == COERCE_IMPLICIT_CAST) + node = ((RelabelType *) node)->arg; + + if (IsA(node, Var)) + { + /* + * The Var must be one that'll deparse as a foreign column reference + * (cf. deparseVar). + */ + Var *var = (Var *) node; + Relids relids = context->scanrel->relids; + + if (bms_is_member(var->varno, relids) && var->varlevelsup == 0) + return true; + } + + return false; +} + +/* + * Print the name of an operator. + */ +static void +deparseOperatorName(StringInfo buf, Form_pg_operator opform) +{ + char *opname; + + /* opname is not a SQL identifier, so we should not quote it. */ + opname = NameStr(opform->oprname); + + /* Print schema name only if it's not pg_catalog */ + if (opform->oprnamespace != PG_CATALOG_NAMESPACE) + { + const char *opnspname; + + opnspname = get_namespace_name(opform->oprnamespace); + /* Print fully qualified operator name. */ + appendStringInfo(buf, "OPERATOR(%s.%s)", + quote_identifier(opnspname), opname); + } + else + { + /* Just print operator name. */ + appendStringInfoString(buf, opname); + } +} + +/* + * Deparse IS DISTINCT FROM. + */ +static void +deparseDistinctExpr(DistinctExpr *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + + Assert(list_length(node->args) == 2); + + appendStringInfoChar(buf, '('); + deparseExpr(linitial(node->args), context); + appendStringInfoString(buf, " IS DISTINCT FROM "); + deparseExpr(lsecond(node->args), context); + appendStringInfoChar(buf, ')'); +} + +/* + * Deparse given ScalarArrayOpExpr expression. To avoid problems + * around priority of operations, we always parenthesize the arguments. + */ +static void +deparseScalarArrayOpExpr(ScalarArrayOpExpr *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + HeapTuple tuple; + Form_pg_operator form; + Expr *arg1; + Expr *arg2; + + /* Retrieve information about the operator from system catalog. */ + tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for operator %u", node->opno); + form = (Form_pg_operator) GETSTRUCT(tuple); + + /* Sanity check. */ + Assert(list_length(node->args) == 2); + + /* Always parenthesize the expression. */ + appendStringInfoChar(buf, '('); + + /* Deparse left operand. */ + arg1 = linitial(node->args); + deparseExpr(arg1, context); + appendStringInfoChar(buf, ' '); + + /* Deparse operator name plus decoration. */ + deparseOperatorName(buf, form); + appendStringInfo(buf, " %s (", node->useOr ? "ANY" : "ALL"); + + /* Deparse right operand. */ + arg2 = lsecond(node->args); + deparseExpr(arg2, context); + + appendStringInfoChar(buf, ')'); + + /* Always parenthesize the expression. */ + appendStringInfoChar(buf, ')'); + + ReleaseSysCache(tuple); +} + +/* + * Deparse a RelabelType (binary-compatible cast) node. + */ +static void +deparseRelabelType(RelabelType *node, deparse_expr_cxt *context) +{ + deparseExpr(node->arg, context); + if (node->relabelformat != COERCE_IMPLICIT_CAST) + appendStringInfo(context->buf, "::%s", + deparse_type_name(node->resulttype, + node->resulttypmod)); +} + +/* + * Deparse a BoolExpr node. + */ +static void +deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + const char *op = NULL; /* keep compiler quiet */ + bool first; + ListCell *lc; + + switch (node->boolop) + { + case AND_EXPR: + op = "AND"; + break; + case OR_EXPR: + op = "OR"; + break; + case NOT_EXPR: + appendStringInfoString(buf, "(NOT "); + deparseExpr(linitial(node->args), context); + appendStringInfoChar(buf, ')'); + return; + } + + appendStringInfoChar(buf, '('); + first = true; + foreach(lc, node->args) + { + if (!first) + appendStringInfo(buf, " %s ", op); + deparseExpr((Expr *) lfirst(lc), context); + first = false; + } + appendStringInfoChar(buf, ')'); +} + +/* + * Deparse IS [NOT] NULL expression. + */ +static void +deparseNullTest(NullTest *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + + appendStringInfoChar(buf, '('); + deparseExpr(node->arg, context); + + /* + * For scalar inputs, we prefer to print as IS [NOT] NULL, which is + * shorter and traditional. If it's a rowtype input but we're applying a + * scalar test, must print IS [NOT] DISTINCT FROM NULL to be semantically + * correct. + */ + if (node->argisrow || !type_is_rowtype(exprType((Node *) node->arg))) + { + if (node->nulltesttype == IS_NULL) + appendStringInfoString(buf, " IS NULL)"); + else + appendStringInfoString(buf, " IS NOT NULL)"); + } + else + { + if (node->nulltesttype == IS_NULL) + appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL)"); + else + appendStringInfoString(buf, " IS DISTINCT FROM NULL)"); + } +} + +/* + * Deparse CASE expression + */ +static void +deparseCaseExpr(CaseExpr *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + ListCell *lc; + + appendStringInfoString(buf, "(CASE"); + + /* If this is a CASE arg WHEN then emit the arg expression */ + if (node->arg != NULL) + { + appendStringInfoChar(buf, ' '); + deparseExpr(node->arg, context); + } + + /* Add each condition/result of the CASE clause */ + foreach(lc, node->args) + { + CaseWhen *whenclause = (CaseWhen *) lfirst(lc); + + /* WHEN */ + appendStringInfoString(buf, " WHEN "); + if (node->arg == NULL) /* CASE WHEN */ + deparseExpr(whenclause->expr, context); + else /* CASE arg WHEN */ + { + /* Ignore the CaseTestExpr and equality operator. */ + deparseExpr(lsecond(castNode(OpExpr, whenclause->expr)->args), + context); + } + + /* THEN */ + appendStringInfoString(buf, " THEN "); + deparseExpr(whenclause->result, context); + } + + /* add ELSE if present */ + if (node->defresult != NULL) + { + appendStringInfoString(buf, " ELSE "); + deparseExpr(node->defresult, context); + } + + /* append END */ + appendStringInfoString(buf, " END)"); +} + +/* + * Deparse ARRAY[...] construct. + */ +static void +deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + bool first = true; + ListCell *lc; + + appendStringInfoString(buf, "ARRAY["); + foreach(lc, node->elements) + { + if (!first) + appendStringInfoString(buf, ", "); + deparseExpr(lfirst(lc), context); + first = false; + } + appendStringInfoChar(buf, ']'); + + /* If the array is empty, we need an explicit cast to the array type. */ + if (node->elements == NIL) + appendStringInfo(buf, "::%s", + deparse_type_name(node->array_typeid, -1)); +} + +/* + * Deparse an Aggref node. + */ +static void +deparseAggref(Aggref *node, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + bool use_variadic; + + /* Only basic, non-split aggregation accepted. */ + Assert(node->aggsplit == AGGSPLIT_SIMPLE); + + /* Check if need to print VARIADIC (cf. ruleutils.c) */ + use_variadic = node->aggvariadic; + + /* Find aggregate name from aggfnoid which is a pg_proc entry */ + appendFunctionName(node->aggfnoid, context); + appendStringInfoChar(buf, '('); + + /* Add DISTINCT */ + appendStringInfoString(buf, (node->aggdistinct != NIL) ? "DISTINCT " : ""); + + if (AGGKIND_IS_ORDERED_SET(node->aggkind)) + { + /* Add WITHIN GROUP (ORDER BY ..) */ + ListCell *arg; + bool first = true; + + Assert(!node->aggvariadic); + Assert(node->aggorder != NIL); + + foreach(arg, node->aggdirectargs) + { + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + deparseExpr((Expr *) lfirst(arg), context); + } + + appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY "); + appendAggOrderBy(node->aggorder, node->args, context); + } + else + { + /* aggstar can be set only in zero-argument aggregates */ + if (node->aggstar) + appendStringInfoChar(buf, '*'); + else + { + ListCell *arg; + bool first = true; + + /* Add all the arguments */ + foreach(arg, node->args) + { + TargetEntry *tle = (TargetEntry *) lfirst(arg); + Node *n = (Node *) tle->expr; + + if (tle->resjunk) + continue; + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + /* Add VARIADIC */ + if (use_variadic && lnext(node->args, arg) == NULL) + appendStringInfoString(buf, "VARIADIC "); + + deparseExpr((Expr *) n, context); + } + } + + /* Add ORDER BY */ + if (node->aggorder != NIL) + { + appendStringInfoString(buf, " ORDER BY "); + appendAggOrderBy(node->aggorder, node->args, context); + } + } + + /* Add FILTER (WHERE ..) */ + if (node->aggfilter != NULL) + { + appendStringInfoString(buf, ") FILTER (WHERE "); + deparseExpr((Expr *) node->aggfilter, context); + } + + appendStringInfoChar(buf, ')'); +} + +/* + * Append ORDER BY within aggregate function. + */ +static void +appendAggOrderBy(List *orderList, List *targetList, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + ListCell *lc; + bool first = true; + + foreach(lc, orderList) + { + SortGroupClause *srt = (SortGroupClause *) lfirst(lc); + Node *sortexpr; + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + /* Deparse the sort expression proper. */ + sortexpr = deparseSortGroupClause(srt->tleSortGroupRef, targetList, + false, context); + /* Add decoration as needed. */ + appendOrderBySuffix(srt->sortop, exprType(sortexpr), srt->nulls_first, + context); + } +} + +/* + * Append the ASC, DESC, USING and NULLS FIRST / NULLS LAST parts + * of an ORDER BY clause. + */ +static void +appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, + deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + TypeCacheEntry *typentry; + + /* See whether operator is default < or > for sort expr's datatype. */ + typentry = lookup_type_cache(sortcoltype, + TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); + + if (sortop == typentry->lt_opr) + appendStringInfoString(buf, " ASC"); + else if (sortop == typentry->gt_opr) + appendStringInfoString(buf, " DESC"); + else + { + HeapTuple opertup; + Form_pg_operator operform; + + appendStringInfoString(buf, " USING "); + + /* Append operator name. */ + opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(sortop)); + if (!HeapTupleIsValid(opertup)) + elog(ERROR, "cache lookup failed for operator %u", sortop); + operform = (Form_pg_operator) GETSTRUCT(opertup); + deparseOperatorName(buf, operform); + ReleaseSysCache(opertup); + } + + if (nulls_first) + appendStringInfoString(buf, " NULLS FIRST"); + else + appendStringInfoString(buf, " NULLS LAST"); +} + +/* + * Print the representation of a parameter to be sent to the remote side. + * + * Note: we always label the Param's type explicitly rather than relying on + * transmitting a numeric type OID in PQexecParams(). This allows us to + * avoid assuming that types have the same OIDs on the remote side as they + * do locally --- they need only have the same names. + */ +static void +printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, + deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + char *ptypename = deparse_type_name(paramtype, paramtypmod); + + appendStringInfo(buf, "$%d::%s", paramindex, ptypename); +} + +/* + * Print the representation of a placeholder for a parameter that will be + * sent to the remote side at execution time. + * + * This is used when we're just trying to EXPLAIN the remote query. + * We don't have the actual value of the runtime parameter yet, and we don't + * want the remote planner to generate a plan that depends on such a value + * anyway. Thus, we can't do something simple like "$1::paramtype". + * Instead, we emit "((SELECT null::paramtype)::paramtype)". + * In all extant versions of Postgres, the planner will see that as an unknown + * constant value, which is what we want. This might need adjustment if we + * ever make the planner flatten scalar subqueries. Note: the reason for the + * apparently useless outer cast is to ensure that the representation as a + * whole will be parsed as an a_expr and not a select_with_parens; the latter + * would do the wrong thing in the context "x = ANY(...)". + */ +static void +printRemotePlaceholder(Oid paramtype, int32 paramtypmod, + deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + char *ptypename = deparse_type_name(paramtype, paramtypmod); + + appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename); +} + +/* + * Deparse GROUP BY clause. + */ +static void +appendGroupByClause(List *tlist, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + Query *query = context->root->parse; + ListCell *lc; + bool first = true; + + /* Nothing to be done, if there's no GROUP BY clause in the query. */ + if (!query->groupClause) + return; + + appendStringInfoString(buf, " GROUP BY "); + + /* + * Queries with grouping sets are not pushed down, so we don't expect + * grouping sets here. + */ + Assert(!query->groupingSets); + + /* + * We intentionally print query->groupClause not processed_groupClause, + * leaving it to the remote planner to get rid of any redundant GROUP BY + * items again. This is necessary in case processed_groupClause reduced + * to empty, and in any case the redundancy situation on the remote might + * be different than what we think here. + */ + foreach(lc, query->groupClause) + { + SortGroupClause *grp = (SortGroupClause *) lfirst(lc); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + deparseSortGroupClause(grp->tleSortGroupRef, tlist, true, context); + } +} + +/* + * Deparse ORDER BY clause defined by the given pathkeys. + * + * The clause should use Vars from context->scanrel if !has_final_sort, + * or from context->foreignrel's targetlist if has_final_sort. + * + * We find a suitable pathkey expression (some earlier step + * should have verified that there is one) and deparse it. + */ +static void +appendOrderByClause(List *pathkeys, bool has_final_sort, + deparse_expr_cxt *context) +{ + ListCell *lcell; + int nestlevel; + const char *delim = " "; + StringInfo buf = context->buf; + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + appendStringInfoString(buf, " ORDER BY"); + foreach(lcell, pathkeys) + { + PathKey *pathkey = lfirst(lcell); + EquivalenceMember *em; + Expr *em_expr; + Oid oprid; + + if (has_final_sort) + { + /* + * By construction, context->foreignrel is the input relation to + * the final sort. + */ + em = find_em_for_rel_target(context->root, + pathkey->pk_eclass, + context->foreignrel); + } + else + em = find_em_for_rel(context->root, + pathkey->pk_eclass, + context->scanrel); + + /* + * We don't expect any error here; it would mean that shippability + * wasn't verified earlier. For the same reason, we don't recheck + * shippability of the sort operator. + */ + if (em == NULL) + elog(ERROR, "could not find pathkey item to sort"); + + em_expr = em->em_expr; + + /* + * Lookup the operator corresponding to the strategy in the opclass. + * The datatype used by the opfamily is not necessarily the same as + * the expression type (for array types for example). + */ + oprid = get_opfamily_member(pathkey->pk_opfamily, + em->em_datatype, + em->em_datatype, + pathkey->pk_strategy); + if (!OidIsValid(oprid)) + elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", + pathkey->pk_strategy, em->em_datatype, em->em_datatype, + pathkey->pk_opfamily); + + appendStringInfoString(buf, delim); + deparseExpr(em_expr, context); + + /* + * Here we need to use the expression's actual type to discover + * whether the desired operator will be the default or not. + */ + appendOrderBySuffix(oprid, exprType((Node *) em_expr), + pathkey->pk_nulls_first, context); + + delim = ", "; + } + reset_transmission_modes(nestlevel); +} + +/* + * Deparse LIMIT/OFFSET clause. + */ +static void +appendLimitClause(deparse_expr_cxt *context) +{ + PlannerInfo *root = context->root; + StringInfo buf = context->buf; + int nestlevel; + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + if (root->parse->limitCount) + { + appendStringInfoString(buf, " LIMIT "); + deparseExpr((Expr *) root->parse->limitCount, context); + } + if (root->parse->limitOffset) + { + appendStringInfoString(buf, " OFFSET "); + deparseExpr((Expr *) root->parse->limitOffset, context); + } + + reset_transmission_modes(nestlevel); +} + +/* + * appendFunctionName + * Deparses function name from given function oid. + */ +static void +appendFunctionName(Oid funcid, deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + HeapTuple proctup; + Form_pg_proc procform; + const char *proname; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for function %u", funcid); + procform = (Form_pg_proc) GETSTRUCT(proctup); + + /* Print schema name only if it's not pg_catalog */ + if (procform->pronamespace != PG_CATALOG_NAMESPACE) + { + const char *schemaname; + + schemaname = get_namespace_name(procform->pronamespace); + appendStringInfo(buf, "%s.", quote_identifier(schemaname)); + } + + /* Always print the function name */ + proname = NameStr(procform->proname); + appendStringInfoString(buf, quote_identifier(proname)); + + ReleaseSysCache(proctup); +} + +/* + * Appends a sort or group clause. + * + * Like get_rule_sortgroupclause(), returns the expression tree, so caller + * need not find it again. + */ +static Node * +deparseSortGroupClause(Index ref, List *tlist, bool force_colno, + deparse_expr_cxt *context) +{ + StringInfo buf = context->buf; + TargetEntry *tle; + Expr *expr; + + tle = get_sortgroupref_tle(ref, tlist); + expr = tle->expr; + + if (force_colno) + { + /* Use column-number form when requested by caller. */ + Assert(!tle->resjunk); + appendStringInfo(buf, "%d", tle->resno); + } + else if (expr && IsA(expr, Const)) + { + /* + * Force a typecast here so that we don't emit something like "GROUP + * BY 2", which will be misconstrued as a column position rather than + * a constant. + */ + deparseConst((Const *) expr, context, 1); + } + else if (!expr || IsA(expr, Var)) + deparseExpr(expr, context); + else + { + /* Always parenthesize the expression. */ + appendStringInfoChar(buf, '('); + deparseExpr(expr, context); + appendStringInfoChar(buf, ')'); + } + + return (Node *) expr; +} + + +/* + * Returns true if given Var is deparsed as a subquery output column, in + * which case, *relno and *colno are set to the IDs for the relation and + * column alias to the Var provided by the subquery. + */ +static bool +is_subquery_var(Var *node, RelOptInfo *foreignrel, int *relno, int *colno) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; + RelOptInfo *outerrel = fpinfo->outerrel; + RelOptInfo *innerrel = fpinfo->innerrel; + + /* Should only be called in these cases. */ + Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); + + /* + * If the given relation isn't a join relation, it doesn't have any lower + * subqueries, so the Var isn't a subquery output column. + */ + if (!IS_JOIN_REL(foreignrel)) + return false; + + /* + * If the Var doesn't belong to any lower subqueries, it isn't a subquery + * output column. + */ + if (!bms_is_member(node->varno, fpinfo->lower_subquery_rels)) + return false; + + if (bms_is_member(node->varno, outerrel->relids)) + { + /* + * If outer relation is deparsed as a subquery, the Var is an output + * column of the subquery; get the IDs for the relation/column alias. + */ + if (fpinfo->make_outerrel_subquery) + { + get_relation_column_alias_ids(node, outerrel, relno, colno); + return true; + } + + /* Otherwise, recurse into the outer relation. */ + return is_subquery_var(node, outerrel, relno, colno); + } + else + { + Assert(bms_is_member(node->varno, innerrel->relids)); + + /* + * If inner relation is deparsed as a subquery, the Var is an output + * column of the subquery; get the IDs for the relation/column alias. + */ + if (fpinfo->make_innerrel_subquery) + { + get_relation_column_alias_ids(node, innerrel, relno, colno); + return true; + } + + /* Otherwise, recurse into the inner relation. */ + return is_subquery_var(node, innerrel, relno, colno); + } +} + +/* + * Get the IDs for the relation and column alias to given Var belonging to + * given relation, which are returned into *relno and *colno. + */ +static void +get_relation_column_alias_ids(Var *node, RelOptInfo *foreignrel, + int *relno, int *colno) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; + int i; + ListCell *lc; + + /* Get the relation alias ID */ + *relno = fpinfo->relation_index; + + /* Get the column alias ID */ + i = 1; + foreach(lc, foreignrel->reltarget->exprs) + { + Var *tlvar = (Var *) lfirst(lc); + + /* + * Match reltarget entries only on varno/varattno. Ideally there + * would be some cross-check on varnullingrels, but it's unclear what + * to do exactly; we don't have enough context to know what that value + * should be. + */ + if (IsA(tlvar, Var) && + tlvar->varno == node->varno && + tlvar->varattno == node->varattno) + { + *colno = i; + return; + } + i++; + } + + /* Shouldn't get here */ + elog(ERROR, "unexpected expression in subquery output"); +} diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out new file mode 100644 index 0000000..08fab73 --- /dev/null +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -0,0 +1,11923 @@ +-- =================================================================== +-- create FDW objects +-- =================================================================== +CREATE EXTENSION postgres_fdw; +CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; +CREATE USER MAPPING FOR public SERVER testserver1 + OPTIONS (user 'value', password 'value'); +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; +CREATE USER MAPPING FOR public SERVER loopback3; +-- =================================================================== +-- create objects used through FDW loopback server +-- =================================================================== +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); +CREATE SCHEMA "S 1"; +CREATE TABLE "S 1"."T 1" ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10), + c8 user_enum, + CONSTRAINT t1_pkey PRIMARY KEY ("C 1") +); +CREATE TABLE "S 1"."T 2" ( + c1 int NOT NULL, + c2 text, + CONSTRAINT t2_pkey PRIMARY KEY (c1) +); +CREATE TABLE "S 1"."T 3" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t3_pkey PRIMARY KEY (c1) +); +CREATE TABLE "S 1"."T 4" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t4_pkey PRIMARY KEY (c1) +); +-- Disable autovacuum for these tables to avoid unexpected effects of that +ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false'); +INSERT INTO "S 1"."T 1" + SELECT id, + id % 10, + to_char(id, 'FM00000'), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, + '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, + id % 10, + id % 10, + 'foo'::user_enum + FROM generate_series(1, 1000) id; +INSERT INTO "S 1"."T 2" + SELECT id, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +INSERT INTO "S 1"."T 3" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0; -- delete for outer join tests +INSERT INTO "S 1"."T 4" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests +ANALYZE "S 1"."T 1"; +ANALYZE "S 1"."T 2"; +ANALYZE "S 1"."T 3"; +ANALYZE "S 1"."T 4"; +-- =================================================================== +-- create foreign tables +-- =================================================================== +CREATE FOREIGN TABLE ft1 ( + c0 int, + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft1 DROP COLUMN c0; +CREATE FOREIGN TABLE ft2 ( + c1 int NOT NULL, + c2 int NOT NULL, + cx int, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft2', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft2 DROP COLUMN cx; +CREATE FOREIGN TABLE ft4 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3'); +CREATE FOREIGN TABLE ft5 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); +CREATE FOREIGN TABLE ft6 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4'); +CREATE FOREIGN TABLE ft7 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4'); +-- =================================================================== +-- tests for validator +-- =================================================================== +-- requiressl and some other parameters are omitted because +-- valid values for them depend on configure options +ALTER SERVER testserver1 OPTIONS ( + use_remote_estimate 'false', + updatable 'true', + fdw_startup_cost '123.456', + fdw_tuple_cost '0.123', + service 'value', + connect_timeout 'value', + dbname 'value', + host 'value', + hostaddr 'value', + port 'value', + --client_encoding 'value', + application_name 'value', + --fallback_application_name 'value', + keepalives 'value', + keepalives_idle 'value', + keepalives_interval 'value', + tcp_user_timeout 'value', + -- requiressl 'value', + sslcompression 'value', + sslmode 'value', + sslcert 'value', + sslkey 'value', + sslrootcert 'value', + sslcrl 'value', + --requirepeer 'value', + krbsrvname 'value', + gsslib 'value', + gssdelegation 'value' + --replication 'value' +); +-- Error, invalid list syntax +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo; bar'); +ERROR: parameter "extensions" must be a list of extension names +-- OK but gets a warning +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo, bar'); +WARNING: extension "foo" is not installed +WARNING: extension "bar" is not installed +ALTER SERVER testserver1 OPTIONS (DROP extensions); +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (DROP user, DROP password); +-- Attempt to add a valid option that's not allowed in a user mapping +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslmode 'require'); +ERROR: invalid option "sslmode" +-- But we can add valid ones fine +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslpassword 'dummy'); +-- Ensure valid options we haven't used in a user mapping yet are +-- permitted to check validation. +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslkey 'value', ADD sslcert 'value'); +ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +\det+ + List of foreign tables + Schema | Table | Server | FDW options | Description +--------+-------+-----------+---------------------------------------+------------- + public | ft1 | loopback | (schema_name 'S 1', table_name 'T 1') | + public | ft2 | loopback | (schema_name 'S 1', table_name 'T 1') | + public | ft4 | loopback | (schema_name 'S 1', table_name 'T 3') | + public | ft5 | loopback | (schema_name 'S 1', table_name 'T 4') | + public | ft6 | loopback2 | (schema_name 'S 1', table_name 'T 4') | + public | ft7 | loopback3 | (schema_name 'S 1', table_name 'T 4') | +(6 rows) + +-- Test that alteration of server options causes reconnection +-- Remote's errors might be non-English, so hide them to ensure stable results +\set VERBOSITY terse +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work + c3 | c4 +-------+------------------------------ + 00001 | Fri Jan 02 00:00:00 1970 PST +(1 row) + +ALTER SERVER loopback OPTIONS (SET dbname 'no such database'); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail +ERROR: could not connect to server "loopback" +DO $d$ + BEGIN + EXECUTE $$ALTER SERVER loopback + OPTIONS (SET dbname '$$||current_database()||$$')$$; + END; +$d$; +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again + c3 | c4 +-------+------------------------------ + 00001 | Fri Jan 02 00:00:00 1970 PST +(1 row) + +-- Test that alteration of user mapping options causes reconnection +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback + OPTIONS (ADD user 'no such user'); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail +ERROR: could not connect to server "loopback" +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback + OPTIONS (DROP user); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again + c3 | c4 +-------+------------------------------ + 00001 | Fri Jan 02 00:00:00 1970 PST +(1 row) + +\set VERBOSITY default +-- Now we should be able to run ANALYZE. +-- To exercise multiple code paths, we use local stats on ft1 +-- and remote-estimate mode on ft2. +ANALYZE ft1; +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); +-- =================================================================== +-- test error case for create publication on foreign table +-- =================================================================== +CREATE PUBLICATION testpub_ftbl FOR TABLE ft1; -- should fail +ERROR: cannot add relation "ft1" to publication +DETAIL: This operation is not supported for foreign tables. +-- =================================================================== +-- simple queries +-- =================================================================== +-- single table without alias +EXPLAIN (COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------- + Foreign Scan on ft1 +(1 row) + +SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 103 | 3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 107 | 7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 109 | 9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo + 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- single table with alias - also test that tableoid sort is not pushed to remote side +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid + -> Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid + Sort Key: t1.c3, t1.c1, t1.tableoid + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8, tableoid + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(8 rows) + +SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 103 | 3 | 00103 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 107 | 7 | 00107 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 109 | 9 | 00109 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo + 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- whole-row reference +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: t1.*, c3, c1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c3 ASC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint +(3 rows) + +SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + t1 +-------------------------------------------------------------------------------------------- + (101,1,00101,"Fri Jan 02 00:00:00 1970 PST","Fri Jan 02 00:00:00 1970",1,"1 ",foo) + (102,2,00102,"Sat Jan 03 00:00:00 1970 PST","Sat Jan 03 00:00:00 1970",2,"2 ",foo) + (103,3,00103,"Sun Jan 04 00:00:00 1970 PST","Sun Jan 04 00:00:00 1970",3,"3 ",foo) + (104,4,00104,"Mon Jan 05 00:00:00 1970 PST","Mon Jan 05 00:00:00 1970",4,"4 ",foo) + (105,5,00105,"Tue Jan 06 00:00:00 1970 PST","Tue Jan 06 00:00:00 1970",5,"5 ",foo) + (106,6,00106,"Wed Jan 07 00:00:00 1970 PST","Wed Jan 07 00:00:00 1970",6,"6 ",foo) + (107,7,00107,"Thu Jan 08 00:00:00 1970 PST","Thu Jan 08 00:00:00 1970",7,"7 ",foo) + (108,8,00108,"Fri Jan 09 00:00:00 1970 PST","Fri Jan 09 00:00:00 1970",8,"8 ",foo) + (109,9,00109,"Sat Jan 10 00:00:00 1970 PST","Sat Jan 10 00:00:00 1970",9,"9 ",foo) + (110,0,00110,"Sun Jan 11 00:00:00 1970 PST","Sun Jan 11 00:00:00 1970",0,"0 ",foo) +(10 rows) + +-- empty result +SELECT * FROM ft1 WHERE false; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+----+----+----+----+----+---- +(0 rows) + +-- with WHERE clause +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c7 >= '1')) AND (("C 1" = 101)) AND ((c6 = '1')) +(3 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- with FOR UPDATE/SHARE +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 101)) FOR UPDATE +(3 rows) + +SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8, t1.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 102)) FOR SHARE +(3 rows) + +SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 102 | 2 | 00102 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo +(1 row) + +-- aggregate +SELECT COUNT(*) FROM ft1 t1; + count +------- + 1000 +(1 row) + +-- subquery +SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- subquery+MAX +SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+----+-------+------------------------------+--------------------------+----+------------+----- + 1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0 | 0 | foo +(1 row) + +-- used in CTE +WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1; + c1 | c2 | c3 | c4 +----+----+-------+------------------------------ + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST +(10 rows) + +-- fixed values +SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1; + ?column? | ?column? +----------+---------- + fixed | +(1 row) + +-- Test forcing the remote server to produce sorted data for a merge join. +SET enable_hashjoin TO false; +SET enable_nestloop TO false; +-- inner join; expressions in the clauses appear in the equivalence class list +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2."C 1" + -> Merge Join + Output: t1.c1, t2."C 1" + Inner Unique: true + Merge Cond: (t1.c1 = t2."C 1") + -> Foreign Scan on public.ft2 t1 + Output: t1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Index Only Scan using t1_pkey on "S 1"."T 1" t2 + Output: t2."C 1" +(11 rows) + +SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + c1 | C 1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2."C 1" + -> Merge Left Join + Output: t1.c1, t2."C 1" + Inner Unique: true + Merge Cond: (t1.c1 = t2."C 1") + -> Foreign Scan on public.ft2 t1 + Output: t1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Index Only Scan using t1_pkey on "S 1"."T 1" t2 + Output: t2."C 1" +(11 rows) + +SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; + c1 | C 1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- A join between local table and foreign join. ORDER BY clause is added to the +-- foreign join so that the local table can be joined using merge join strategy. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1."C 1" + -> Merge Right Join + Output: t1."C 1" + Inner Unique: true + Merge Cond: (t3.c1 = t1."C 1") + -> Foreign Scan + Output: t3.c1 + Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) + Remote SQL: SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (((r3."C 1" = r2."C 1")))) ORDER BY r2."C 1" ASC NULLS LAST + -> Index Only Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" +(12 rows) + +SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 +----- + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 +(10 rows) + +-- Test similar to above, except that the full join prevents any equivalence +-- classes from being merged. This produces single relation equivalence classes +-- included in join restrictions. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1."C 1", t2.c1, t3.c1 + -> Merge Right Join + Output: t1."C 1", t2.c1, t3.c1 + Inner Unique: true + Merge Cond: (t3.c1 = t1."C 1") + -> Foreign Scan + Output: t3.c1, t2.c1 + Relations: (public.ft2 t3) LEFT JOIN (public.ft1 t2) + Remote SQL: SELECT r3."C 1", r2."C 1" FROM ("S 1"."T 1" r3 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + -> Index Only Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" +(12 rows) + +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 | c1 | c1 +-----+-----+----- + 101 | 101 | 101 + 102 | 102 | 102 + 103 | 103 | 103 + 104 | 104 | 104 + 105 | 105 | 105 + 106 | 106 | 106 + 107 | 107 | 107 + 108 | 108 | 108 + 109 | 109 | 109 + 110 | 110 | 110 +(10 rows) + +-- Test similar to above with all full outer joins +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1."C 1", t2.c1, t3.c1 + -> Merge Full Join + Output: t1."C 1", t2.c1, t3.c1 + Inner Unique: true + Merge Cond: (t3.c1 = t1."C 1") + -> Foreign Scan + Output: t2.c1, t3.c1 + Relations: (public.ft1 t2) FULL JOIN (public.ft2 t3) + Remote SQL: SELECT r2."C 1", r3."C 1" FROM ("S 1"."T 1" r2 FULL JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r3."C 1" ASC NULLS LAST + -> Index Only Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1" +(12 rows) + +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; + C 1 | c1 | c1 +-----+-----+----- + 101 | 101 | 101 + 102 | 102 | 102 + 103 | 103 | 103 + 104 | 104 | 104 + 105 | 105 | 105 + 106 | 106 | 106 + 107 | 107 | 107 + 108 | 108 | 108 + 109 | 109 | 109 + 110 | 110 | 110 +(10 rows) + +RESET enable_hashjoin; +RESET enable_nestloop; +-- Test executing assertion in estimate_path_cost_size() that makes sure that +-- retrieved_rows for foreign rel re-used to cost pre-sorted foreign paths is +-- a sensible value even when the rel has tuples=0 +CREATE TABLE loct_empty (c1 int NOT NULL, c2 text); +CREATE FOREIGN TABLE ft_empty (c1 int NOT NULL, c2 text) + SERVER loopback OPTIONS (table_name 'loct_empty'); +INSERT INTO loct_empty + SELECT id, 'AAA' || to_char(id, 'FM000') FROM generate_series(1, 100) id; +DELETE FROM loct_empty; +ANALYZE ft_empty; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; + QUERY PLAN +------------------------------------------------------------------------------- + Foreign Scan on public.ft_empty + Output: c1, c2 + Remote SQL: SELECT c1, c2 FROM public.loct_empty ORDER BY c1 ASC NULLS LAST +(3 rows) + +-- =================================================================== +-- WHERE with remotely-executable conditions +-- =================================================================== +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 100)) AND ((c2 = 0)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest + QUERY PLAN +------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NULL)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" IS NOT NULL)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((round(abs("C 1"), 0) = 1::numeric)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1"))) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL))) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ANY (ARRAY[c2, 1, ("C 1" + 0)]))) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = ((ARRAY["C 1", c2, 3])[1]))) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c6 = E'foo''s\\bar')) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote + QUERY PLAN +------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(4 rows) + +-- parameterized remote path for foreign table +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + -> Index Scan using t1_pkey on "S 1"."T 1" a + Output: a."C 1", a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + Index Cond: (a."C 1" = 47) + -> Foreign Scan on public.ft2 b + Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) +(8 rows) + +SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; + C 1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+----- + 47 | 7 | 00047 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo | 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo +(1 row) + +-- check both safe and unsafe join conditions +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 a, ft2 b + WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); + QUERY PLAN +------------------------------------------------------------------------------------------------------------- + Nested Loop + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8, b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + -> Foreign Scan on public.ft2 a + Output: a.c1, a.c2, a.c3, a.c4, a.c5, a.c6, a.c7, a.c8 + Filter: (a.c8 = 'foo'::user_enum) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c2 = 6)) + -> Foreign Scan on public.ft2 b + Output: b.c1, b.c2, b.c3, b.c4, b.c5, b.c6, b.c7, b.c8 + Filter: ((b.c7)::text = upper((a.c7)::text)) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) +(10 rows) + +SELECT * FROM ft2 a, ft2 b +WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+-----+-----+----+-------+------------------------------+--------------------------+----+------------+----- + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo + 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo + 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo + 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo + 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo + 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo + 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo + 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo + 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo + 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo +(100 rows) + +-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters +SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5)); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo +(4 rows) + +SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo +(4 rows) + +-- we should not push order by clause with volatile expressions or unsafe +-- collations +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, random(); + QUERY PLAN +------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, (random()) + Sort Key: ft2.c1, (random()) + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8, random() + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(6 rows) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C"; + QUERY PLAN +------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8, ((c3)::text) + Sort Key: ft2.c1, ft2.c3 COLLATE "C" + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8, c3 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(6 rows) + +-- user-defined operator/function +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ +BEGIN +RETURN abs($1); +END +$$ LANGUAGE plpgsql IMMUTABLE; +CREATE OPERATOR === ( + LEFTARG = int, + RIGHTARG = int, + PROCEDURE = int4eq, + COMMUTATOR = === +); +-- built-in operators and functions can be shipped for remote execution +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); + QUERY PLAN +--------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = abs(c2))) +(4 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + QUERY PLAN +---------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = c2)) +(4 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + count +------- + 9 +(1 row) + +-- by default, user-defined ones cannot +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + QUERY PLAN +----------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 = postgres_fdw_abs(t1.c2)) + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" +(6 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + QUERY PLAN +----------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" +(6 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + count +------- + 9 +(1 row) + +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c1 === t1.c2) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST +(6 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- but let's put them in an extension ... +ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); +ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- ... now they can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + QUERY PLAN +----------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" = public.postgres_fdw_abs(c2))) +(4 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); + count +------- + 9 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + QUERY PLAN +----------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2)) +(4 rows) + +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + count +------- + 9 +(1 row) + +-- and both ORDER BY and LIMIT can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2)) ORDER BY c2 ASC NULLS LAST LIMIT 1::bigint +(3 rows) + +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- Test CASE pushdown +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1,c2,c3 FROM ft2 WHERE CASE WHEN c1 > 990 THEN c1 END < 1000 ORDER BY c1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft2 + Output: c1, c2, c3 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" WHERE (((CASE WHEN ("C 1" > 990) THEN "C 1" ELSE NULL::integer END) < 1000)) ORDER BY "C 1" ASC NULLS LAST +(3 rows) + +SELECT c1,c2,c3 FROM ft2 WHERE CASE WHEN c1 > 990 THEN c1 END < 1000 ORDER BY c1; + c1 | c2 | c3 +-----+----+------- + 991 | 1 | 00991 + 992 | 2 | 00992 + 993 | 3 | 00993 + 994 | 4 | 00994 + 995 | 5 | 00995 + 996 | 6 | 00996 + 997 | 7 | 00997 + 998 | 8 | 00998 + 999 | 9 | 00999 +(9 rows) + +-- Nested CASE +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1,c2,c3 FROM ft2 WHERE CASE CASE WHEN c2 > 0 THEN c2 END WHEN 100 THEN 601 WHEN c2 THEN c2 ELSE 0 END > 600 ORDER BY c1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft2 + Output: c1, c2, c3 + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" WHERE (((CASE (CASE WHEN (c2 > 0) THEN c2 ELSE NULL::integer END) WHEN 100 THEN 601 WHEN c2 THEN c2 ELSE 0 END) > 600)) ORDER BY "C 1" ASC NULLS LAST +(3 rows) + +SELECT c1,c2,c3 FROM ft2 WHERE CASE CASE WHEN c2 > 0 THEN c2 END WHEN 100 THEN 601 WHEN c2 THEN c2 ELSE 0 END > 600 ORDER BY c1; + c1 | c2 | c3 +----+----+---- +(0 rows) + +-- CASE arg WHEN +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c1 > (CASE mod(c1, 4) WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" > (CASE mod("C 1", 4) WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END))) +(3 rows) + +-- CASE cannot be pushed down because of unshippable arg clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c1 > (CASE random()::integer WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END); + QUERY PLAN +----------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (ft1.c1 > CASE (random())::integer WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(4 rows) + +-- these are shippable +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c6 WHEN 'foo' THEN true ELSE c3 < 'bar' END; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((CASE c6 WHEN 'foo'::text THEN true ELSE (c3 < 'bar') END)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c3 WHEN c6 THEN true ELSE c3 < 'bar' END; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((CASE c3 WHEN c6 THEN true ELSE (c3 < 'bar') END)) +(3 rows) + +-- but this is not because of collation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c3 COLLATE "C" WHEN c6 THEN true ELSE c3 < 'bar' END; + QUERY PLAN +------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: CASE (ft1.c3)::text WHEN ft1.c6 THEN true ELSE (ft1.c3 < 'bar'::text) END + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(4 rows) + +-- a regconfig constant referring to this text search configuration +-- is initially unshippable +CREATE TEXT SEARCH CONFIGURATION public.custom_search + (COPY = pg_catalog.english); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; + QUERY PLAN +------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, to_tsvector('custom_search'::regconfig, c3) + Filter: (length(to_tsvector('custom_search'::regconfig, ft1.c3)) > 0) + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" WHERE (("C 1" = 642)) +(4 rows) + +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; + c1 | to_tsvector +-----+------------- + 642 | '00642':1 +(1 row) + +-- but if it's in a shippable extension, it can be shipped +ALTER EXTENSION postgres_fdw ADD TEXT SEARCH CONFIGURATION public.custom_search; +-- however, that doesn't flush the shippability cache, so do a quick reconnect +\c - +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, to_tsvector('custom_search'::regconfig, c3) + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" WHERE (("C 1" = 642)) AND ((length(to_tsvector('public.custom_search'::regconfig, c3)) > 0)) +(3 rows) + +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; + c1 | to_tsvector +-----+------------- + 642 | '00642':1 +(1 row) + +-- =================================================================== +-- JOIN queries +-- =================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; +-- join two tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c1, t1.c3 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3, t1.c3 + Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + 22 | + 24 | 24 + 26 | + 28 | + 30 | 30 + 32 | + 34 | + 36 | 36 + 38 | + 40 | +(10 rows) + +-- left outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + placement of clauses. +-- clauses within the nullable side are not pulled up, but top level clause on +-- non-nullable side is pushed into non-nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t1.c2, ft5.c1, ft5.c2 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5) + Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE ((r1.c1 < 10)) +(4 rows) + +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; + c1 | c2 | c1 | c2 +----+----+----+---- + 2 | 3 | | + 4 | 5 | | + 6 | 7 | 6 | 7 + 8 | 9 | | +(4 rows) + +-- clauses within the nullable side are not pulled up, but the top level clause +-- on nullable side is not pushed down into nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t1.c2, ft5.c1, ft5.c2 + Relations: (public.ft4 t1) LEFT JOIN (public.ft5) + Remote SQL: SELECT r1.c1, r1.c2, r4.c1, r4.c2 FROM ("S 1"."T 3" r1 LEFT JOIN "S 1"."T 4" r4 ON (((r1.c1 = r4.c1)) AND ((r4.c1 < 10)))) WHERE (((r4.c1 < 10) OR (r4.c1 IS NULL))) AND ((r1.c1 < 10)) +(4 rows) + +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; + c1 | c2 | c1 | c2 +----+----+----+---- + 2 | 3 | | + 4 | 5 | | + 6 | 7 | 6 | 7 + 8 | 9 | | +(4 rows) + +-- right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft4 t2) LEFT JOIN (public.ft5 t1) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r1 ON (((r1.c1 = r2.c1)))) ORDER BY r2.c1 ASC NULLS LAST, r1.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + | 22 + 24 | 24 + | 26 + | 28 + 30 | 30 + | 32 + | 34 + 36 | 36 + | 38 + | 40 +(10 rows) + +-- right outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 45::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; + c1 | c1 +-----+---- + 92 | + 94 | + 96 | 96 + 98 | + 100 | + | 3 + | 9 + | 15 + | 21 + | 27 +(10 rows) + +-- full outer join with restrictions on the joining relations +-- a. the joining relations are both base relations +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ft5.c1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT s4.c1, s5.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s5.c1 ASC NULLS LAST +(4 rows) + +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; + c1 | c1 +----+---- + 50 | + 52 | + 54 | 54 + 56 | + 58 | + 60 | 60 + | 51 + | 57 +(8 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: 1 + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT NULL FROM ((SELECT NULL FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4 FULL JOIN (SELECT NULL FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5 ON (TRUE)) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; + ?column? +---------- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(10 rows) + +-- b. one of the joining relations is a base relation and the other is a join +-- relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, t2.c1, t3.c1 + Relations: (public.ft4) FULL JOIN ((public.ft4 t2) LEFT JOIN (public.ft5 t3)) + Remote SQL: SELECT s4.c1, s8.c1, s8.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT r5.c1, r6.c1 FROM ("S 1"."T 3" r5 LEFT JOIN "S 1"."T 4" r6 ON (((r5.c1 = r6.c1)))) WHERE ((r5.c1 >= 50)) AND ((r5.c1 <= 60))) s8(c1, c2) ON (((s4.c1 = s8.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s8.c1 ASC NULLS LAST, s8.c2 ASC NULLS LAST +(4 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + c1 | a | b +----+----+---- + 50 | 50 | + 52 | 52 | + 54 | 54 | 54 + 56 | 56 | + 58 | 58 | + 60 | 60 | 60 +(6 rows) + +-- c. test deparsing the remote query as nested subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ft4_1.c1, ft5.c1 + Relations: (public.ft4) FULL JOIN ((public.ft4 ft4_1) FULL JOIN (public.ft5)) + Remote SQL: SELECT s4.c1, s10.c1, s10.c2 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT s8.c1, s9.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL)))) s10(c1, c2) ON (((s4.c1 = s10.c1)))) ORDER BY s4.c1 ASC NULLS LAST, s10.c1 ASC NULLS LAST, s10.c2 ASC NULLS LAST +(4 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; + c1 | a | b +----+----+---- + 50 | 50 | + 52 | 52 | + 54 | 54 | 54 + 56 | 56 | + 58 | 58 | + 60 | 60 | 60 + | | 51 + | | 57 +(8 rows) + +-- d. test deparsing rowmarked relations as subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ctid, ft4.*, ft5.* + -> Nested Loop + Output: "T 3".c1, ft4.c1, ft5.c1, "T 3".ctid, ft4.*, ft5.* + -> Foreign Scan + Output: ft4.c1, ft4.*, ft5.c1, ft5.* + Relations: (public.ft4) FULL JOIN (public.ft5) + Remote SQL: SELECT s8.c1, s8.c2, s9.c1, s9.c2 FROM ((SELECT c1, ROW(c1, c2, c3) FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s8(c1, c2) FULL JOIN (SELECT c1, ROW(c1, c2, c3) FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s9(c1, c2) ON (((s8.c1 = s9.c1)))) WHERE (((s8.c1 IS NULL) OR (s8.c1 IS NOT NULL))) ORDER BY s8.c1 ASC NULLS LAST, s9.c1 ASC NULLS LAST + -> Sort + Output: ft4.c1, ft4.*, ft5.c1, ft5.* + Sort Key: ft4.c1, ft5.c1 + -> Hash Full Join + Output: ft4.c1, ft4.*, ft5.c1, ft5.* + Hash Cond: (ft4.c1 = ft5.c1) + Filter: ((ft4.c1 IS NULL) OR (ft4.c1 IS NOT NULL)) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + -> Hash + Output: ft5.c1, ft5.* + -> Foreign Scan on public.ft5 + Output: ft5.c1, ft5.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60)) + -> Materialize + Output: "T 3".c1, "T 3".ctid + -> Seq Scan on "S 1"."T 3" + Output: "T 3".c1, "T 3".ctid + Filter: ("T 3".c1 = 50) +(28 rows) + +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; + c1 | a | b +----+----+---- + 50 | 50 | + 50 | 52 | + 50 | 54 | 54 + 50 | 56 | + 50 | 58 | + 50 | 60 | 60 + 50 | | 51 + 50 | | 57 +(8 rows) + +-- full outer join + inner join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c1, t3.c1 + Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST LIMIT 10::bigint +(4 rows) + +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; + c1 | c1 | c1 +----+----+---- + 52 | 51 | + 58 | 57 | + | | 2 + | | 4 + | | 6 + | | 8 + | | 10 + | | 12 + | | 14 + | | 16 +(10 rows) + +-- full outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- full outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft4 t3) LEFT JOIN (public.ft2 t2)) LEFT JOIN (public.ft2 t1) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 3" r4 LEFT JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r4.c1)))) LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- right outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- full outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t1) FULL JOIN (public.ft2 t2)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +-- left outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t1) LEFT JOIN (public.ft2 t2)) FULL JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r1 LEFT JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FULL JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +SET enable_memoize TO off; +-- right outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: ((public.ft2 t2) LEFT JOIN (public.ft2 t1)) LEFT JOIN (public.ft4 t3) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM (("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) LEFT JOIN "S 1"."T 3" r4 ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 11 | 1 | + 12 | 2 | AAA012 + 13 | 3 | + 14 | 4 | AAA014 + 15 | 5 | + 16 | 6 | AAA016 + 17 | 7 | + 18 | 8 | AAA018 + 19 | 9 | + 20 | 0 | AAA020 +(10 rows) + +RESET enable_memoize; +-- left outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c2, t3.c3 + Relations: (public.ft4 t3) LEFT JOIN ((public.ft2 t1) INNER JOIN (public.ft2 t2)) + Remote SQL: SELECT r1."C 1", r2.c2, r4.c3 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1)))) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; + c1 | c2 | c3 +----+----+-------- + 22 | 2 | AAA022 + 24 | 4 | AAA024 + 26 | 6 | AAA026 + 28 | 8 | AAA028 + 30 | 0 | AAA030 + 32 | 2 | AAA032 + 34 | 4 | AAA034 + 36 | 6 | AAA036 + 38 | 8 | AAA038 + 40 | 0 | AAA040 +(10 rows) + +-- full outer join + WHERE clause, only matched rows +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft4 t1) FULL JOIN (public.ft5 t2) + Remote SQL: SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 = r2.c1) OR (r1.c1 IS NULL))) +(9 rows) + +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c1 +----+---- + 66 | 66 + 72 | 72 + 78 | 78 + 84 | 84 + 90 | 90 + 96 | 96 + | 3 + | 9 + | 15 + | 21 +(10 rows) + +-- full outer join + WHERE clause with shippable extensions set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c1, t2.c2, t1.c3 + Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2.c2, r1.c3 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) WHERE ((public.postgres_fdw_abs(r1."C 1") > 0)) LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); +-- full outer join + WHERE clause with shippable extensions not set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c2, t1.c3 + -> Foreign Scan + Output: t1.c1, t2.c2, t1.c3 + Filter: (postgres_fdw_abs(t1.c1) > 0) + Relations: (public.ft1 t1) FULL JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2.c2, r1.c3 FROM ("S 1"."T 1" r1 FULL JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) +(7 rows) + +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- join two tables with FOR UPDATE clause +-- tests whole-row reference for row marks +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1 +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1 FOR UPDATE OF r2 +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join two tables with FOR SHARE clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1 +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1 FOR SHARE OF r2 +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join in CTE +EXPLAIN (VERBOSE, COSTS OFF) +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t.c1_1, t.c2_1, t.c1_3 + CTE t + -> Foreign Scan + Output: t1.c1, t1.c3, t2.c1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) + -> Sort + Output: t.c1_1, t.c2_1, t.c1_3 + Sort Key: t.c1_3, t.c1_1 + -> CTE Scan on t + Output: t.c1_1, t.c2_1, t.c1_3 +(12 rows) + +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; + c1_1 | c2_1 +------+------ + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- ctid with whole-row reference +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r1."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint +(4 rows) + +-- SEMI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1 + -> Merge Semi Join + Output: t1.c1 + Merge Cond: (t1.c1 = t2.c1) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST +(11 rows) + +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + c1 +----- + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 +(10 rows) + +-- ANTI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1 + -> Merge Anti Join + Output: t1.c1 + Merge Cond: (t1.c1 = t2.c2) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST + -> Foreign Scan on public.ft2 t2 + Output: t2.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST +(11 rows) + +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; + c1 +----- + 110 + 111 + 112 + 113 + 114 + 115 + 116 + 117 + 118 + 119 +(10 rows) + +-- CROSS JOIN can be pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint +(4 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+----- + 1 | 101 + 1 | 102 + 1 | 103 + 1 | 104 + 1 | 105 + 1 | 106 + 1 | 107 + 1 | 108 + 1 | 109 + 1 | 110 +(10 rows) + +-- different server, not pushed down. No result expected. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1 + -> Merge Join + Output: t1.c1, t2.c1 + Merge Cond: (t2.c1 = t1.c1) + -> Foreign Scan on public.ft6 t2 + Output: t2.c1, t2.c2, t2.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST + -> Materialize + Output: t1.c1, t1.c2, t1.c3 + -> Foreign Scan on public.ft5 t1 + Output: t1.c1, t1.c2, t1.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 4" ORDER BY c1 ASC NULLS LAST +(13 rows) + +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+---- +(0 rows) + +-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS +-- JOIN since c8 in both tables has same value. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1 + -> Sort + Output: t1.c1, t2.c1 + Sort Key: t1.c1, t2.c1 + -> Merge Left Join + Output: t1.c1, t2.c1 + Merge Cond: (t1.c8 = t2.c8) + -> Sort + Output: t1.c1, t1.c8 + Sort Key: t1.c8 + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c8 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" + -> Sort + Output: t2.c1, t2.c8 + Sort Key: t2.c8 + -> Foreign Scan on public.ft2 t2 + Output: t2.c1, t2.c8 + Remote SQL: SELECT "C 1", c8 FROM "S 1"."T 1" +(20 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; + c1 | c1 +----+----- + 1 | 101 + 1 | 102 + 1 | 103 + 1 | 104 + 1 | 105 + 1 | 106 + 1 | 107 + 1 | 108 + 1 | 109 + 1 | 110 +(10 rows) + +-- unsafe conditions on one side (c8 has a UDT), not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +----------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Hash Right Join + Output: t1.c1, t2.c1, t1.c3 + Hash Cond: (t2.c1 = t1.c1) + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: t1.c1, t1.c3 + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3 + Filter: (t1.c8 = 'foo'::user_enum) + Remote SQL: SELECT "C 1", c3, c8 FROM "S 1"."T 1" +(17 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- join where unsafe to pushdown condition in WHERE clause has a column not +-- in the SELECT clause. In this test unsafe clause needs to have column +-- references from both joining sides so that the clause is not pushed down +-- into one of the joining sides. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Foreign Scan + Output: t1.c1, t2.c1, t1.c3 + Filter: (t1.c8 = t2.c8) + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) +(10 rows) + +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + c1 | c1 +-----+----- + 101 | 101 + 102 | 102 + 103 | 103 + 104 | 104 + 105 | 105 + 106 | 106 + 107 | 107 + 108 | 108 + 109 | 109 + 110 | 110 +(10 rows) + +-- Aggregate after UNION, for testing setrefs +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1.c1, (avg((t1.c1 + t2.c1))) + -> Sort + Output: t1.c1, (avg((t1.c1 + t2.c1))) + Sort Key: t1.c1 + -> HashAggregate + Output: t1.c1, avg((t1.c1 + t2.c1)) + Group Key: t1.c1 + -> HashAggregate + Output: t1.c1, t2.c1 + Group Key: t1.c1, t2.c1 + -> Append + -> Foreign Scan + Output: t1.c1, t2.c1 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) + -> Foreign Scan + Output: t1_1.c1, t2_1.c1 + Relations: (public.ft1 t1_1) INNER JOIN (public.ft2 t2_1) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) +(20 rows) + +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; + t1c1 | avg +------+---------------------- + 101 | 202.0000000000000000 + 102 | 204.0000000000000000 + 103 | 206.0000000000000000 + 104 | 208.0000000000000000 + 105 | 210.0000000000000000 + 106 | 212.0000000000000000 + 107 | 214.0000000000000000 + 108 | 216.0000000000000000 + 109 | 218.0000000000000000 + 110 | 220.0000000000000000 +(10 rows) + +-- join with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Limit + Output: t1."C 1" + -> Nested Loop + Output: t1."C 1" + -> Index Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1", t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + -> Memoize + Cache Key: t1.c2 + Cache Mode: binary + -> Subquery Scan on q + -> HashAggregate + Output: t2.c1, t3.c1 + Group Key: t2.c1 + -> Foreign Scan + Output: t2.c1, t3.c1 + Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")) AND ((r1.c2 = $1::integer)))) +(17 rows) + +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; + C 1 +----- + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +(10 rows) + +-- join with pseudoconstant quals, not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1 AND CURRENT_USER = SESSION_USER) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + QUERY PLAN +------------------------------------------------------------------------------- + Limit + Output: t1.c1, t2.c1, t1.c3 + -> Sort + Output: t1.c1, t2.c1, t1.c3 + Sort Key: t1.c3, t1.c1 + -> Result + Output: t1.c1, t2.c1, t1.c3 + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Hash Join + Output: t1.c1, t1.c3, t2.c1 + Hash Cond: (t2.c1 = t1.c1) + -> Foreign Scan on public.ft2 t2 + Output: t2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" + -> Hash + Output: t1.c1, t1.c3 + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c3 + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" +(19 rows) + +-- non-Var items in targetlist of the nullable rel of a join preventing +-- push-down in some cases +-- unable to push {ft1, ft2} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: (13), ft2.c1 + Join Filter: (13 = ft2.c1) + -> Foreign Scan on public.ft2 + Output: ft2.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST + -> Materialize + Output: (13) + -> Foreign Scan on public.ft1 + Output: 13 + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13)) +(11 rows) + +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + a | c1 +----+---- + | 10 + | 11 + | 12 + 13 | 13 + | 14 + | 15 +(6 rows) + +-- ok to push {ft1, ft2} but not {ft1, ft2, ft4} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: ft4.c1, (13), ft1.c1, ft2.c1 + Join Filter: (ft4.c1 = ft1.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15)) + -> Materialize + Output: ft1.c1, ft2.c1, (13) + -> Foreign Scan + Output: ft1.c1, ft2.c1, 13 + Relations: (public.ft1) INNER JOIN (public.ft2) + Remote SQL: SELECT r4."C 1", r5."C 1" FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r5."C 1" = 12)) AND ((r4."C 1" = 12)))) ORDER BY r4."C 1" ASC NULLS LAST +(12 rows) + +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + c1 | a | b | c +----+----+----+---- + 10 | | | + 12 | 13 | 12 | 12 + 14 | | | +(3 rows) + +-- join with nullable side with some columns with null values +UPDATE ft5 SET c3 = null where c1 % 9 = 0; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 + Relations: (public.ft5) INNER JOIN (public.ft4) + Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST +(4 rows) + +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + ft5 | c1 | c2 | c3 | c1 | c2 +----------------+----+----+--------+----+---- + (12,13,AAA012) | 12 | 13 | AAA012 | 12 | 13 + (18,19,) | 18 | 19 | | 18 | 19 + (24,25,AAA024) | 24 | 25 | AAA024 | 24 | 25 + (30,31,AAA030) | 30 | 31 | AAA030 | 30 | 31 +(4 rows) + +-- multi-way join involving multiple merge joins +-- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)); +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; +SET enable_nestloop TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid + Inner Unique: true + Merge Cond: (ft1.c2 = local_tbl.c1) + -> Foreign Scan + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.* + Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5) + Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4 + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.* + Merge Cond: (ft1.c2 = ft5.c1) + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.* + Merge Cond: (ft1.c2 = ft4.c1) + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + Sort Key: ft1.c2 + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + Merge Cond: (ft1.c1 = ft2.c1) + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Sort Key: ft1.c1 + -> Foreign Scan on public.ft1 + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE + -> Materialize + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + -> Foreign Scan on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + -> Sort + Output: ft4.c1, ft4.c2, ft4.c3, ft4.* + Sort Key: ft4.c1 + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3, ft4.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE + -> Sort + Output: ft5.c1, ft5.c2, ft5.c3, ft5.* + Sort Key: ft5.c1 + -> Foreign Scan on public.ft5 + Output: ft5.c1, ft5.c2, ft5.c3, ft5.* + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE + -> Index Scan using local_tbl_pkey on public.local_tbl + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid +(47 rows) + +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 | c1 | c2 | c3 +----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+--------+----+----+--------+----+----+------ + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6 | 6 | foo | 6 | 7 | AAA006 | 6 | 7 | AAA006 | 6 | 6 | 0006 +(10 rows) + +RESET enable_nestloop; +RESET enable_hashjoin; +-- test that add_paths_with_pathkeys_for_rel() arranges for the epq_path to +-- return columns needed by the parent ForeignScan node +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.*, COALESCE(ft1.c3 || ft2.c3, 'foobar') FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100)) ss ON (local_tbl.c1 = ss.c1) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)), local_tbl.ctid, ft1.*, ft2.* + -> Merge Left Join + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)), local_tbl.ctid, ft1.*, ft2.* + Merge Cond: (local_tbl.c1 = ft1.c1) + -> Index Scan using local_tbl_pkey on public.local_tbl + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + -> Materialize + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)) + -> Foreign Scan + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, COALESCE((ft1.c3 || ft2.c3), 'foobar'::text) + Relations: (public.ft1) INNER JOIN (public.ft2) + Remote SQL: SELECT r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8) END, CASE WHEN (r5.*)::text IS NOT NULL THEN ROW(r5."C 1", r5.c2, r5.c3, r5.c4, r5.c5, r5.c6, r5.c7, r5.c8) END, r5.c3 FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r5."C 1" = r4."C 1")) AND ((r4."C 1" < 100)))) ORDER BY r4."C 1" ASC NULLS LAST + -> Result + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, ft2.c3 + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, (COALESCE((ft1.c3 || ft2.c3), 'foobar'::text)), ft2.c3 + Sort Key: ft1.c1 + -> Hash Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, COALESCE((ft1.c3 || ft2.c3), 'foobar'::text), ft2.c3 + Hash Cond: (ft1.c1 = ft2.c1) + -> Foreign Scan on public.ft1 + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) + -> Hash + Output: ft2.*, ft2.c1, ft2.c3 + -> Foreign Scan on public.ft2 + Output: ft2.*, ft2.c1, ft2.c3 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(29 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); +ALTER SERVER loopback OPTIONS (ADD fdw_startup_cost '10000.0'); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.* FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100 AND (ft1.c1 - postgres_fdw_abs(ft2.c2)) = 0)) ss ON (local_tbl.c3 = ss.c3) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + LockRows + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, local_tbl.ctid, ft1.*, ft2.* + -> Nested Loop Left Join + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, local_tbl.ctid, ft1.*, ft2.* + Join Filter: (local_tbl.c3 = ft1.c3) + -> Index Scan using local_tbl_pkey on public.local_tbl + Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid + -> Materialize + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.* + -> Foreign Scan + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.* + Filter: ((ft1.c1 - postgres_fdw_abs(ft2.c2)) = 0) + Relations: (public.ft1) INNER JOIN (public.ft2) + Remote SQL: SELECT r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4."C 1", r4.c2, r4.c3, r4.c4, r4.c5, r4.c6, r4.c7, r4.c8) END, CASE WHEN (r5.*)::text IS NOT NULL THEN ROW(r5."C 1", r5.c2, r5.c3, r5.c4, r5.c5, r5.c6, r5.c7, r5.c8) END, r5.c2 FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r5."C 1" = r4."C 1")) AND ((r4."C 1" < 100)))) ORDER BY r4.c3 ASC NULLS LAST + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, ft2.c2 + Sort Key: ft1.c3 + -> Merge Join + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.*, ft2.c2 + Merge Cond: (ft1.c1 = ft2.c1) + Join Filter: ((ft1.c1 - postgres_fdw_abs(ft2.c2)) = 0) + -> Sort + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Sort Key: ft1.c1 + -> Foreign Scan on public.ft1 + Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) + -> Materialize + Output: ft2.*, ft2.c1, ft2.c2 + -> Foreign Scan on public.ft2 + Output: ft2.*, ft2.c1, ft2.c2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY "C 1" ASC NULLS LAST +(32 rows) + +ALTER SERVER loopback OPTIONS (DROP fdw_startup_cost); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +DROP TABLE local_tbl; +-- check join pushdown in situations where multiple userids are involved +CREATE ROLE regress_view_owner SUPERUSER; +CREATE USER MAPPING FOR regress_view_owner SERVER loopback; +GRANT SELECT ON ft4 TO regress_view_owner; +GRANT SELECT ON ft5 TO regress_view_owner; +CREATE VIEW v4 AS SELECT * FROM ft4; +CREATE VIEW v5 AS SELECT * FROM ft5; +ALTER VIEW v5 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, different view owners + QUERY PLAN +---------------------------------------------------------------------- + Limit + Output: ft4.c1, ft5.c2, ft5.c1 + -> Sort + Output: ft4.c1, ft5.c2, ft5.c1 + Sort Key: ft4.c1, ft5.c1 + -> Hash Left Join + Output: ft4.c1, ft5.c2, ft5.c1 + Hash Cond: (ft4.c1 = ft5.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Hash + Output: ft5.c2, ft5.c1 + -> Foreign Scan on public.ft5 + Output: ft5.c2, ft5.c1 + Remote SQL: SELECT c1, c2 FROM "S 1"."T 4" +(16 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ft5.c2, ft5.c1 + Relations: (public.ft4) LEFT JOIN (public.ft5) + Remote SQL: SELECT r4.c1, r5.c2, r5.c1 FROM ("S 1"."T 3" r4 LEFT JOIN "S 1"."T 4" r5 ON (((r4.c1 = r5.c1)))) ORDER BY r4.c1 ASC NULLS LAST, r5.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, view owner not current user + QUERY PLAN +---------------------------------------------------------------------- + Limit + Output: ft4.c1, t2.c2, t2.c1 + -> Sort + Output: ft4.c1, t2.c2, t2.c1 + Sort Key: ft4.c1, t2.c1 + -> Hash Left Join + Output: ft4.c1, t2.c2, t2.c1 + Hash Cond: (ft4.c1 = t2.c1) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Hash + Output: t2.c2, t2.c1 + -> Foreign Scan on public.ft5 t2 + Output: t2.c2, t2.c1 + Remote SQL: SELECT c1, c2 FROM "S 1"."T 4" +(16 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO CURRENT_USER; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, t2.c2, t2.c1 + Relations: (public.ft4) LEFT JOIN (public.ft5 t2) + Remote SQL: SELECT r4.c1, r2.c2, r2.c1 FROM ("S 1"."T 3" r4 LEFT JOIN "S 1"."T 4" r2 ON (((r4.c1 = r2.c1)))) ORDER BY r4.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint +(4 rows) + +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + c1 | c2 +----+---- + 22 | + 24 | 25 + 26 | + 28 | + 30 | 31 + 32 | + 34 | + 36 | 37 + 38 | + 40 | +(10 rows) + +ALTER VIEW v4 OWNER TO regress_view_owner; +-- ==================================================================== +-- Check that userid to use when querying the remote table is correctly +-- propagated into foreign rels present in subqueries under an UNION ALL +-- ==================================================================== +CREATE ROLE regress_view_owner_another; +ALTER VIEW v4 OWNER TO regress_view_owner_another; +GRANT SELECT ON ft4 TO regress_view_owner_another; +ALTER FOREIGN TABLE ft4 OPTIONS (ADD use_remote_estimate 'true'); +-- The following should query the remote backing table of ft4 as user +-- regress_view_owner_another, the view owner, though it fails as expected +-- due to the lack of a user mapping for that user. +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM v4; +ERROR: user mapping not found for "regress_view_owner_another" +-- Likewise, but with the query under an UNION ALL +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM (SELECT * FROM v4 UNION ALL SELECT * FROM v4); +ERROR: user mapping not found for "regress_view_owner_another" +-- Should not get that error once a user mapping is created +CREATE USER MAPPING FOR regress_view_owner_another SERVER loopback OPTIONS (password_required 'false'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM v4; + QUERY PLAN +-------------------------------------------------- + Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM (SELECT * FROM v4 UNION ALL SELECT * FROM v4); + QUERY PLAN +-------------------------------------------------------- + Append + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Foreign Scan on public.ft4 ft4_1 + Output: ft4_1.c1, ft4_1.c2, ft4_1.c3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" +(7 rows) + +DROP USER MAPPING FOR regress_view_owner_another SERVER loopback; +DROP OWNED BY regress_view_owner_another; +DROP ROLE regress_view_owner_another; +ALTER FOREIGN TABLE ft4 OPTIONS (SET use_remote_estimate 'false'); +-- cleanup +DROP OWNED BY regress_view_owner; +DROP ROLE regress_view_owner; +-- =================================================================== +-- Aggregate and grouping queries +-- =================================================================== +-- Simple aggregates +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST +(4 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+------+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 + 100 | 49700 | 497.0000000000000000 | 2 | 992 | 0 | 49700 + 100 | 49800 | 498.0000000000000000 | 3 | 993 | 0 | 49800 + 100 | 49900 | 499.0000000000000000 | 4 | 994 | 0 | 49900 + 100 | 50500 | 505.0000000000000000 | 0 | 1000 | 0 | 50500 +(5 rows) + +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint +(4 rows) + +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + count | sum | avg | min | max | stddev | sum2 +-------+-------+----------------------+-----+-----+--------+------- + 100 | 49600 | 496.0000000000000000 | 1 | 991 | 0 | 49600 +(1 row) + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; + QUERY PLAN +------------------------------------------------------------------------------- + Aggregate + Output: sum((c1 * ((random() <= '1'::double precision))::integer)), avg(c1) + -> Foreign Scan on public.ft1 + Output: c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" +(5 rows) + +-- Aggregate over join query +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)), (sum(t1.c1)), (avg(t2.c1)) + Relations: Aggregate on ((public.ft1 t1) INNER JOIN (public.ft1 t2)) + Remote SQL: SELECT count(*), sum(r1."C 1"), avg(r2."C 1") FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2.c2 = 6)) AND ((r1.c2 = 6)))) +(4 rows) + +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + count | sum | avg +-------+---------+---------------------- + 10000 | 5010000 | 501.0000000000000000 +(1 row) + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) +select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: sum(t1.c1), count(t2.c1) + -> Foreign Scan + Output: t1.c1, t2.c1 + Filter: (((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)))::double precision * random()) <= '1'::double precision) + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = r1."C 1")))) +(7 rows) + +-- GROUP BY clause having expressions +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: ((c2 / 2)), ((sum(c2) * (c2 / 2))) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT (c2 / 2), (sum(c2) * (c2 / 2)) FROM "S 1"."T 1" GROUP BY 1 ORDER BY (c2 / 2) ASC NULLS LAST +(4 rows) + +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + ?column? | ?column? +----------+---------- + 0 | 0 + 1 | 500 + 2 | 1800 + 3 | 3900 + 4 | 6800 +(5 rows) + +-- Aggregates in subquery are pushed down. +set enable_incremental_sort = off; +explain (verbose, costs off) +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: count(ft1.c2), sum(ft1.c2) + -> Foreign Scan + Output: ft1.c2, (sum(ft1.c1)), (sqrt((ft1.c1)::double precision)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, sum("C 1"), sqrt("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 ORDER BY c2 ASC NULLS LAST, sum("C 1") ASC NULLS LAST +(6 rows) + +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; + count | sum +-------+------ + 1000 | 4500 +(1 row) + +reset enable_incremental_sort; +-- Aggregate is still pushed down by taking unshippable expression out +explain (verbose, costs off) +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Sort + Output: ((c2 * ((random() <= '1'::double precision))::integer)), ((sum(c1) * c2)), c2 + Sort Key: ((ft1.c2 * ((random() <= '1'::double precision))::integer)), ((sum(ft1.c1) * ft1.c2)) + -> Foreign Scan + Output: (c2 * ((random() <= '1'::double precision))::integer), ((sum(c1) * c2)), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT (sum("C 1") * c2), c2 FROM "S 1"."T 1" GROUP BY 2 +(7 rows) + +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + sum1 | sum2 +------+-------- + 0 | 0 + 1 | 49600 + 2 | 99400 + 3 | 149400 + 4 | 199600 + 5 | 250000 + 6 | 300600 + 7 | 351400 + 8 | 402400 + 9 | 453600 +(10 rows) + +-- Aggregate with unshippable GROUP BY clause are not pushed +explain (verbose, costs off) +select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Output: ((c2 * ((random() <= '1'::double precision))::integer)) + Sort Key: ((ft2.c2 * ((random() <= '1'::double precision))::integer)) + -> HashAggregate + Output: ((c2 * ((random() <= '1'::double precision))::integer)) + Group Key: (ft2.c2 * ((random() <= '1'::double precision))::integer) + -> Foreign Scan on public.ft2 + Output: (c2 * ((random() <= '1'::double precision))::integer) + Remote SQL: SELECT c2 FROM "S 1"."T 1" +(9 rows) + +-- GROUP BY clause in various forms, cardinal, alias and constant expression +explain (verbose, costs off) +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (count(c2)), c2, 5, 7.0, 9 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST +(4 rows) + +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + w | x | y | z +-----+---+---+----- + 100 | 0 | 5 | 7.0 + 100 | 1 | 5 | 7.0 + 100 | 2 | 5 | 7.0 + 100 | 3 | 5 | 7.0 + 100 | 4 | 5 | 7.0 + 100 | 5 | 5 | 7.0 + 100 | 6 | 5 | 7.0 + 100 | 7 | 5 | 7.0 + 100 | 8 | 5 | 7.0 + 100 | 9 | 5 | 7.0 +(10 rows) + +-- GROUP BY clause referring to same column multiple times +-- Also, ORDER BY contains an aggregate function +explain (verbose, costs off) +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c2, c2, (sum(c1)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, c2, sum("C 1") FROM "S 1"."T 1" WHERE ((c2 > 6)) GROUP BY 1, 2 ORDER BY sum("C 1") ASC NULLS LAST +(4 rows) + +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + c2 | c2 +----+---- + 7 | 7 + 8 | 8 + 9 | 9 +(3 rows) + +-- Testing HAVING clause shippability +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: c2, (sum(c1)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 HAVING ((avg("C 1") < 500::numeric)) AND ((sum("C 1") < 49800)) ORDER BY c2 ASC NULLS LAST +(4 rows) + +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + c2 | sum +----+------- + 1 | 49600 + 2 | 49700 +(2 rows) + +-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: count(*) + -> Foreign Scan + Output: ft1.c5, NULL::bigint, (sqrt((ft1.c2)::double precision)) + Filter: (((((avg(ft1.c1)) / (avg(ft1.c1))))::double precision * random()) <= '1'::double precision) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c5, NULL::bigint, sqrt(c2), avg("C 1") FROM "S 1"."T 1" GROUP BY 1, 3 HAVING ((avg("C 1") < 500::numeric)) +(7 rows) + +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + count +------- + 49 +(1 row) + +-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Sort + Output: (sum(c1)), c2 + Sort Key: (sum(ft1.c1)) + -> HashAggregate + Output: sum(c1), c2 + Group Key: ft1.c2 + Filter: (avg((ft1.c1 * ((random() <= '1'::double precision))::integer)) > '100'::numeric) + -> Foreign Scan on public.ft1 + Output: c1, c2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" +(10 rows) + +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; + QUERY PLAN +-------------------------------------------------- + Foreign Scan + Output: $0, (sum(ft1.c1)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1") FROM "S 1"."T 1" + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum +(6 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1; + exists | sum +--------+-------- + t | 500500 +(1 row) + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + QUERY PLAN +--------------------------------------------------- + GroupAggregate + Output: $0, sum(ft1.c1) + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_enum + -> Foreign Scan on public.ft1 + Output: ft1.c1 + Remote SQL: SELECT "C 1" FROM "S 1"."T 1" +(7 rows) + +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + exists | sum +--------+-------- + t | 500500 +(1 row) + +-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates +-- ORDER BY within aggregate, same column used to order +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c1 ORDER BY c1)), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) GROUP BY 2 ORDER BY array_agg("C 1" ORDER BY "C 1" ASC NULLS LAST) ASC NULLS LAST +(4 rows) + +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + array_agg +-------------------------------- + {1,11,21,31,41,51,61,71,81,91} + {2,12,22,32,42,52,62,72,82,92} + {3,13,23,33,43,53,63,73,83,93} + {4,14,24,34,44,54,64,74,84,94} + {5,15,25,35,45,55,65,75,85,95} + {6,16,26,36,46,56,66,76,86,96} + {7,17,27,37,47,57,67,77,87,97} + {8,18,28,38,48,58,68,78,88,98} + {9,19,29,39,49,59,69,79,89,99} + {10,20,30,40,50,60,70,80,90} +(10 rows) + +-- ORDER BY within aggregate, different column used to order also using DESC +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c5 ORDER BY c1 DESC)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT array_agg(c5 ORDER BY "C 1" DESC NULLS FIRST) FROM "S 1"."T 1" WHERE (("C 1" < 50)) AND ((c2 = 6)) +(4 rows) + +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + array_agg +------------------------------------------------------------------------------------------------------------------------------------------ + {"Mon Feb 16 00:00:00 1970","Fri Feb 06 00:00:00 1970","Tue Jan 27 00:00:00 1970","Sat Jan 17 00:00:00 1970","Wed Jan 07 00:00:00 1970"} +(1 row) + +-- DISTINCT within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5)), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5)) ASC NULLS LAST +(4 rows) + +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +-- DISTINCT combined with ORDER BY within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) ASC NULLS LAST) ASC NULLS LAST +(4 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {0,1,2,3,4} + {1,2,3,NULL} +(2 rows) + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5) DESC NULLS LAST)), ((t2.c1 % 3)) + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST), (r2.c1 % 3) FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) WHERE (((r1.c1 < 20) OR ((r1.c1 IS NULL) AND (r2.c1 < 5)))) GROUP BY 2 ORDER BY array_agg(DISTINCT (r1.c1 % 5) ORDER BY ((r1.c1 % 5)) DESC NULLS LAST) ASC NULLS LAST +(4 rows) + +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + array_agg +-------------- + {3,2,1,NULL} + {4,3,2,1,0} +(2 rows) + +-- FILTER within aggregate +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5)))), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum("C 1") FILTER (WHERE (("C 1" < 100) AND (c2 > 5))), c2 FROM "S 1"."T 1" GROUP BY 2 ORDER BY sum("C 1") FILTER (WHERE (("C 1" < 100) AND (c2 > 5))) ASC NULLS LAST +(4 rows) + +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; + sum +----- + 510 + 520 + 530 + 540 + + + + + + +(10 rows) + +-- DISTINCT, ORDER BY and FILTER within aggregate +explain (verbose, costs off) +select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: (sum((c1 % 3))), (sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3)) FILTER (WHERE ((c1 % 3) < 2))), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum(("C 1" % 3)), sum(DISTINCT ("C 1" % 3) ORDER BY (("C 1" % 3)) ASC NULLS LAST) FILTER (WHERE (("C 1" % 3) < 2)), c2 FROM "S 1"."T 1" WHERE ((c2 = 6)) GROUP BY 3 +(4 rows) + +select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; + sum | sum | c2 +-----+-----+---- + 99 | 1 | 6 +(1 row) + +-- Outer query is aggregation query +explain (verbose, costs off) +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------ + Unique + Output: ((SubPlan 1)) + -> Sort + Output: ((SubPlan 1)) + Sort Key: ((SubPlan 1)) + -> Foreign Scan + Output: (SubPlan 1) + Relations: Aggregate on (public.ft2 t2) + Remote SQL: SELECT count(*) FILTER (WHERE ((c2 = 6) AND ("C 1" < 10))) FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + SubPlan 1 + -> Foreign Scan on public.ft1 t1 + Output: (count(*) FILTER (WHERE ((t2.c2 = 6) AND (t2.c1 < 10)))) + Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 6)) +(13 rows) + +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + count +------- + 1 +(1 row) + +-- Inner query is aggregation query +explain (verbose, costs off) +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------ + Unique + Output: ((SubPlan 1)) + -> Sort + Output: ((SubPlan 1)) + Sort Key: ((SubPlan 1)) + -> Foreign Scan on public.ft2 t2 + Output: (SubPlan 1) + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (((c2 % 6) = 0)) + SubPlan 1 + -> Foreign Scan + Output: (count(t1.c1) FILTER (WHERE ((t2.c2 = 6) AND (t2.c1 < 10)))) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count("C 1") FILTER (WHERE (($1::integer = 6) AND ($2::integer < 10))) FROM "S 1"."T 1" WHERE (("C 1" = 6)) +(13 rows) + +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + count +------- + 0 + 1 +(2 rows) + +-- Aggregate not pushed down as FILTER condition is not pushable +explain (verbose, costs off) +select sum(c1) filter (where (c1 / c1) * random() <= 1) from ft1 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------ + Sort + Output: (sum(c1) FILTER (WHERE ((((c1 / c1))::double precision * random()) <= '1'::double precision))), c2 + Sort Key: (sum(ft1.c1) FILTER (WHERE ((((ft1.c1 / ft1.c1))::double precision * random()) <= '1'::double precision))) + -> HashAggregate + Output: sum(c1) FILTER (WHERE ((((c1 / c1))::double precision * random()) <= '1'::double precision)), c2 + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c1, c2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" +(9 rows) + +explain (verbose, costs off) +select sum(c2) filter (where c2 in (select c2 from ft1 where c2 < 5)) from ft1; + QUERY PLAN +------------------------------------------------------------------- + Aggregate + Output: sum(ft1.c2) FILTER (WHERE (hashed SubPlan 1)) + -> Foreign Scan on public.ft1 + Output: ft1.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + SubPlan 1 + -> Foreign Scan on public.ft1 ft1_1 + Output: ft1_1.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) +(9 rows) + +-- Ordered-sets within aggregate +explain (verbose, costs off) +select c2, rank('10'::varchar) within group (order by c6), percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (rank('10'::character varying) WITHIN GROUP (ORDER BY c6)), (percentile_cont((((c2)::numeric / '10'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision))) + Sort Key: ft1.c2 + -> Foreign Scan + Output: c2, (rank('10'::character varying) WITHIN GROUP (ORDER BY c6)), (percentile_cont((((c2)::numeric / '10'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision))) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, rank('10'::character varying) WITHIN GROUP (ORDER BY c6 ASC NULLS LAST), percentile_cont((c2 / 10::numeric)) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1 HAVING ((percentile_cont((c2 / 10::numeric)) WITHIN GROUP (ORDER BY ("C 1") ASC NULLS LAST) < 500::double precision)) +(7 rows) + +select c2, rank('10'::varchar) within group (order by c6), percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; + c2 | rank | percentile_cont +----+------+----------------- + 0 | 101 | 10 + 1 | 101 | 100 + 2 | 1 | 200 + 3 | 1 | 300 + 4 | 1 | 400 +(5 rows) + +-- Using multiple arguments within aggregates +explain (verbose, costs off) +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: c1, (rank(c1, c2) WITHIN GROUP (ORDER BY c1, c2)), c2 + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT "C 1", rank("C 1", c2) WITHIN GROUP (ORDER BY "C 1" ASC NULLS LAST, c2 ASC NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" = 6)) GROUP BY 1, 3 +(4 rows) + +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; + c1 | rank +----+------ + 6 | 1 +(1 row) + +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); +-- Disable hash aggregation for plan stability. +set enable_hashagg to false; +-- Not pushed down due to user defined aggregate +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + QUERY PLAN +---------------------------------------------------------------------------------- + GroupAggregate + Output: c2, least_agg(VARIADIC ARRAY[c1]) + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST +(6 rows) + +-- Add function and aggregate into extension +alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw add aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); +-- Now aggregate will be pushed. Aggregate will display VARIADIC argument. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Sort + Output: c2, (least_agg(VARIADIC ARRAY[c1])) + Sort Key: ft1.c2 + -> Foreign Scan + Output: c2, (least_agg(VARIADIC ARRAY[c1])) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, public.least_agg(VARIADIC ARRAY["C 1"]) FROM "S 1"."T 1" WHERE ((c2 < 100)) GROUP BY 1 +(7 rows) + +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; + c2 | least_agg +----+----------- + 0 | 10 + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 + 9 | 9 +(10 rows) + +-- Remove function and aggregate from extension +alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); +-- Not pushed down as we have dropped objects from extension. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + QUERY PLAN +---------------------------------------------------------------------------------- + GroupAggregate + Output: c2, least_agg(VARIADIC ARRAY[c1]) + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" ORDER BY c2 ASC NULLS LAST +(6 rows) + +-- Cleanup +reset enable_hashagg; +drop aggregate least_agg(variadic items anyarray); +drop function least_accum(anyelement, variadic anyarray); +-- Testing USING OPERATOR() in ORDER BY within aggregate. +-- For this, we need user defined operators along with operator family and +-- operator class. Create those and then add them in extension. Note that +-- user defined objects are considered unshippable unless they are part of +-- the extension. +create operator public.<^ ( + leftarg = int4, + rightarg = int4, + procedure = int4eq +); +create operator public.=^ ( + leftarg = int4, + rightarg = int4, + procedure = int4lt +); +create operator public.>^ ( + leftarg = int4, + rightarg = int4, + procedure = int4gt +); +create operator family my_op_family using btree; +create function my_op_cmp(a int, b int) returns int as + $$begin return btint4cmp(a, b); end $$ language plpgsql; +create operator class my_op_class for type int using btree family my_op_family as + operator 1 public.<^, + operator 3 public.=^, + operator 5 public.>^, + function 1 my_op_cmp(int, int); +-- This will not be pushed as user defined sort operator is not part of the +-- extension yet. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + GroupAggregate + Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2 + -> Sort + Output: c1, c2 + Sort Key: ft2.c1 USING <^ + -> Foreign Scan on public.ft2 + Output: c1, c2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) +(8 rows) + +-- This should not be pushed either. +explain (verbose, costs off) +select * from ft2 order by c1 using operator(public.<^); + QUERY PLAN +------------------------------------------------------------------------------- + Sort + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Sort Key: ft2.c1 USING <^ + -> Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(6 rows) + +-- Update local stats on ft2 +ANALYZE ft2; +-- Add into extension +alter extension postgres_fdw add operator class my_op_class using btree; +alter extension postgres_fdw add function my_op_cmp(a int, b int); +alter extension postgres_fdw add operator family my_op_family using btree; +alter extension postgres_fdw add operator public.<^(int, int); +alter extension postgres_fdw add operator public.=^(int, int); +alter extension postgres_fdw add operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); +-- Now this will be pushed as sort operator is part of the extension. +alter server loopback options (add fdw_tuple_cost '0.5'); +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (array_agg(c1 ORDER BY c1 USING <^ NULLS LAST)), c2 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT array_agg("C 1" ORDER BY "C 1" USING OPERATOR(public.<^) NULLS LAST), c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) GROUP BY 2 +(4 rows) + +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + array_agg +-------------------------------- + {6,16,26,36,46,56,66,76,86,96} +(1 row) + +alter server loopback options (drop fdw_tuple_cost); +-- This should be pushed too. +explain (verbose, costs off) +select * from ft2 order by c1 using operator(public.<^); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY "C 1" USING OPERATOR(public.<^) NULLS LAST +(3 rows) + +-- Remove from extension +alter extension postgres_fdw drop operator class my_op_class using btree; +alter extension postgres_fdw drop function my_op_cmp(a int, b int); +alter extension postgres_fdw drop operator family my_op_family using btree; +alter extension postgres_fdw drop operator public.<^(int, int); +alter extension postgres_fdw drop operator public.=^(int, int); +alter extension postgres_fdw drop operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); +-- This will not be pushed as sort operator is now removed from the extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + GroupAggregate + Output: array_agg(c1 ORDER BY c1 USING <^ NULLS LAST), c2 + -> Sort + Output: c1, c2 + Sort Key: ft2.c1 USING <^ + -> Foreign Scan on public.ft2 + Output: c1, c2 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE (("C 1" < 100)) AND ((c2 = 6)) +(8 rows) + +-- Cleanup +drop operator class my_op_class using btree; +drop function my_op_cmp(a int, b int); +drop operator family my_op_family using btree; +drop operator public.>^(int, int); +drop operator public.=^(int, int); +drop operator public.<^(int, int); +-- Input relation to aggregate push down hook is not safe to pushdown and thus +-- the aggregate cannot be pushed down to foreign server. +explain (verbose, costs off) +select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2); + QUERY PLAN +------------------------------------------------------------------------------------------- + Aggregate + Output: count(t1.c3) + -> Nested Loop Left Join + Output: t1.c3 + Join Filter: ((t1.c1)::double precision = (random() * (t2.c2)::double precision)) + -> Foreign Scan on public.ft2 t1 + Output: t1.c3, t1.c1 + Remote SQL: SELECT "C 1", c3 FROM "S 1"."T 1" + -> Materialize + Output: t2.c2 + -> Foreign Scan on public.ft2 t2 + Output: t2.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" +(13 rows) + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Sort + Output: (count(*)), x.b + Sort Key: (count(*)), x.b + -> HashAggregate + Output: count(*), x.b + Group Key: x.b + -> Hash Join + Output: x.b + Inner Unique: true + Hash Cond: (ft1.c2 = x.a) + -> Foreign Scan on public.ft1 + Output: ft1.c2 + Remote SQL: SELECT c2 FROM "S 1"."T 1" + -> Hash + Output: x.b, x.a + -> Subquery Scan on x + Output: x.b, x.a + -> Foreign Scan + Output: ft1_1.c2, (sum(ft1_1.c1)) + Relations: Aggregate on (public.ft1 ft1_1) + Remote SQL: SELECT c2, sum("C 1") FROM "S 1"."T 1" GROUP BY 1 +(21 rows) + +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + count | b +-------+------- + 100 | 49600 + 100 | 49700 + 100 | 49800 + 100 | 49900 + 100 | 50000 + 100 | 50100 + 100 | 50200 + 100 | 50300 + 100 | 50400 + 100 | 50500 +(10 rows) + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 + Relations: Aggregate on ((public.ft4 t1) FULL JOIN (public.ft5 t2)) + Remote SQL: SELECT avg(r1.c1), sum(r2.c1), r2.c1 FROM ("S 1"."T 3" r1 FULL JOIN "S 1"."T 4" r2 ON (((r1.c1 = r2.c1)))) GROUP BY 3 HAVING ((((avg(r1.c1) IS NULL) AND (sum(r2.c1) < 10)) OR (sum(r2.c1) IS NULL))) ORDER BY avg(r1.c1) ASC NULLS LAST, sum(r2.c1) ASC NULLS LAST +(4 rows) + +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + avg | sum +---------------------+----- + 51.0000000000000000 | + | 3 + | 9 +(3 rows) + +-- Aggregate over FULL join needing to deparse the joining relations as +-- subqueries. +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(*)), (sum(ft4.c1)), (avg(ft5.c1)) + Relations: Aggregate on ((public.ft4) FULL JOIN (public.ft5)) + Remote SQL: SELECT count(*), sum(s4.c1), avg(s5.c1) FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s4(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) s5(c1) ON (((s4.c1 = s5.c1)))) +(4 rows) + +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + count | sum | avg +-------+-----+--------------------- + 8 | 330 | 55.5000000000000000 +(1 row) + +-- ORDER BY expression is part of the target list but not pushed down to +-- foreign server. +explain (verbose, costs off) +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + QUERY PLAN +-------------------------------------------------------------------------------- + Sort + Output: (((sum(c2)) * ((random() <= '1'::double precision))::integer)) + Sort Key: (((sum(ft1.c2)) * ((random() <= '1'::double precision))::integer)) + -> Foreign Scan + Output: ((sum(c2)) * ((random() <= '1'::double precision))::integer) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT sum(c2) FROM "S 1"."T 1" +(7 rows) + +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + sum +------ + 4500 +(1 row) + +-- LATERAL join, with parameterization +set enable_hashagg to false; +explain (verbose, costs off) +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------ + Sort + Output: t1.c2, qry.sum + Sort Key: t1.c2 + -> Nested Loop + Output: t1.c2, qry.sum + -> Index Scan using t1_pkey on "S 1"."T 1" t1 + Output: t1."C 1", t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Index Cond: (t1."C 1" < 100) + Filter: (t1.c2 < 3) + -> Subquery Scan on qry + Output: qry.sum, t2.c1 + Filter: ((t1.c2 * 2) = qry.sum) + -> Foreign Scan + Output: (sum((t2.c1 + t1."C 1"))), t2.c1 + Relations: Aggregate on (public.ft2 t2) + Remote SQL: SELECT sum(("C 1" + $1::integer)), "C 1" FROM "S 1"."T 1" GROUP BY 2 +(16 rows) + +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1; + c2 | sum +----+----- + 1 | 2 + 2 | 4 +(2 rows) + +reset enable_hashagg; +-- bug #15613: bad plan for foreign table scan with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T 1" AS ref_0, + LATERAL ( + SELECT ref_0."C 1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C 1"; + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Nested Loop + Output: ref_0.c2, ref_0."C 1", (ref_0.c2), ref_1.c3, ref_0."C 1" + -> Nested Loop + Output: ref_0.c2, ref_0."C 1", ref_1.c3, (ref_0.c2) + -> Index Scan using t1_pkey on "S 1"."T 1" ref_0 + Output: ref_0."C 1", ref_0.c2, ref_0.c3, ref_0.c4, ref_0.c5, ref_0.c6, ref_0.c7, ref_0.c8 + Index Cond: (ref_0."C 1" < 10) + -> Foreign Scan on public.ft1 ref_1 + Output: ref_1.c3, ref_0.c2 + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001')) + -> Materialize + Output: ref_3.c3 + -> Foreign Scan on public.ft2 ref_3 + Output: ref_3.c3 + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001')) +(15 rows) + +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T 1" AS ref_0, + LATERAL ( + SELECT ref_0."C 1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C 1"; + c2 | c1 | c2 | c3 +----+----+----+------- + 1 | 1 | 1 | 00001 + 2 | 2 | 2 | 00001 + 3 | 3 | 3 | 00001 + 4 | 4 | 4 | 00001 + 5 | 5 | 5 | 00001 + 6 | 6 | 6 | 00001 + 7 | 7 | 7 | 00001 + 8 | 8 | 8 | 00001 + 9 | 9 | 9 | 00001 +(9 rows) + +-- Check with placeHolderVars +explain (verbose, costs off) +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------- + Aggregate + Output: sum(q.a), count(q.b) + -> Nested Loop Left Join + Output: q.a, q.b + Inner Unique: true + Join Filter: ((ft4.c1)::numeric <= q.b) + -> Foreign Scan on public.ft4 + Output: ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1 FROM "S 1"."T 3" + -> Materialize + Output: q.a, q.b + -> Subquery Scan on q + Output: q.a, q.b + -> Foreign Scan + Output: 13, (avg(ft1.c1)), NULL::bigint + Relations: Aggregate on ((public.ft2) LEFT JOIN (public.ft1)) + Remote SQL: SELECT 13, avg(r1."C 1"), NULL::bigint FROM ("S 1"."T 1" r2 LEFT JOIN "S 1"."T 1" r1 ON (((r1."C 1" = r2."C 1")))) +(17 rows) + +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + sum | count +-----+------- + 650 | 50 +(1 row) + +-- Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> MixedAggregate + Output: c2, sum(c1) + Hash Key: ft1.c2 + Group Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) +(10 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; + c2 | sum +----+-------- + 0 | 50500 + 1 | 49600 + 2 | 49700 + | 149800 +(4 rows) + +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)) + Sort Key: ft1.c2 + -> MixedAggregate + Output: c2, sum(c1) + Hash Key: ft1.c2 + Group Key: () + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) +(10 rows) + +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; + c2 | sum +----+-------- + 0 | 50500 + 1 | 49600 + 2 | 49700 + | 149800 +(4 rows) + +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + QUERY PLAN +---------------------------------------------------------------------------------- + Sort + Output: c2, c6, (sum(c1)) + Sort Key: ft1.c2, ft1.c6 + -> HashAggregate + Output: c2, c6, sum(c1) + Hash Key: ft1.c2 + Hash Key: ft1.c6 + -> Foreign Scan on public.ft1 + Output: c2, c6, c1 + Remote SQL: SELECT "C 1", c2, c6 FROM "S 1"."T 1" WHERE ((c2 < 3)) +(10 rows) + +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; + c2 | c6 | sum +----+----+------- + 0 | | 50500 + 1 | | 49600 + 2 | | 49700 + | 0 | 50500 + | 1 | 49600 + | 2 | 49700 +(6 rows) + +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + QUERY PLAN +------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c1)), (GROUPING(c2)) + Sort Key: ft1.c2 + -> HashAggregate + Output: c2, sum(c1), GROUPING(c2) + Group Key: ft1.c2 + -> Foreign Scan on public.ft1 + Output: c2, c1 + Remote SQL: SELECT "C 1", c2 FROM "S 1"."T 1" WHERE ((c2 < 3)) +(9 rows) + +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + c2 | sum | grouping +----+-------+---------- + 0 | 50500 | 0 + 1 | 49600 | 0 + 2 | 49700 | 0 +(3 rows) + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Unique + Output: ((sum(c1) / 1000)), c2 + -> Sort + Output: ((sum(c1) / 1000)), c2 + Sort Key: ((sum(ft2.c1) / 1000)) + -> Foreign Scan + Output: ((sum(c1) / 1000)), c2 + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT (sum("C 1") / 1000), c2 FROM "S 1"."T 1" WHERE ((c2 < 6)) GROUP BY 2 +(9 rows) + +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + s +---- + 49 + 50 +(2 rows) + +-- WindowAgg +explain (verbose, costs off) +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + QUERY PLAN +------------------------------------------------------------------------------------------------------------ + Sort + Output: c2, (sum(c2)), (count(c2) OVER (?)), ((c2 % 2)) + Sort Key: ft2.c2 + -> WindowAgg + Output: c2, (sum(c2)), count(c2) OVER (?), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)), (sum(c2)) + Sort Key: ((ft2.c2 % 2)) + -> Foreign Scan + Output: c2, ((c2 % 2)), (sum(c2)) + Relations: Aggregate on (public.ft2) + Remote SQL: SELECT c2, (c2 % 2), sum(c2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1 +(12 rows) + +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; + c2 | sum | count +----+-----+------- + 0 | 0 | 5 + 1 | 100 | 5 + 2 | 200 | 5 + 3 | 300 | 5 + 4 | 400 | 5 + 5 | 500 | 5 + 6 | 600 | 5 + 7 | 700 | 5 + 8 | 800 | 5 + 9 | 900 | 5 +(10 rows) + +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Sort + Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 DESC + -> Foreign Scan + Output: c2, ((c2 % 2)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, (c2 % 2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1 +(12 rows) + +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; + c2 | array_agg +----+------------- + 0 | {8,6,4,2,0} + 1 | {9,7,5,3,1} + 2 | {8,6,4,2} + 3 | {9,7,5,3} + 4 | {8,6,4} + 5 | {9,7,5} + 6 | {8,6} + 7 | {9,7} + 8 | {8} + 9 | {9} +(10 rows) + +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Sort + Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) + Sort Key: ft1.c2 + -> WindowAgg + Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) + -> Sort + Output: c2, ((c2 % 2)) + Sort Key: ((ft1.c2 % 2)), ft1.c2 + -> Foreign Scan + Output: c2, ((c2 % 2)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT c2, (c2 % 2) FROM "S 1"."T 1" WHERE ((c2 < 10)) GROUP BY 1 +(12 rows) + +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + c2 | array_agg +----+------------- + 0 | {0,2,4,6,8} + 1 | {1,3,5,7,9} + 2 | {2,4,6,8} + 3 | {3,5,7,9} + 4 | {4,6,8} + 5 | {5,7,9} + 6 | {6,8} + 7 | {7,9} + 8 | {8} + 9 | {9} +(10 rows) + +-- =================================================================== +-- parameterized queries +-- =================================================================== +-- simple join +PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan + Output: t1.c3, t2.c3 + Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) + Remote SQL: SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = 2)) AND ((r1."C 1" = 1)))) +(4 rows) + +EXECUTE st1(1, 1); + c3 | c3 +-------+------- + 00001 | 00001 +(1 row) + +EXECUTE st1(101, 101); + c3 | c3 +-------+------- + 00101 | 00101 +(1 row) + +-- subquery using stable function (can't be sent to remote) +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20); + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Sort + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Sort Key: t1.c1 + -> Nested Loop Semi Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Join Filter: (t2.c3 = t1.c3) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 20)) + -> Materialize + Output: t2.c3 + -> Foreign Scan on public.ft2 t2 + Output: t2.c3 + Filter: (date(t2.c4) = '01-17-1970'::date) + Remote SQL: SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > 10)) +(15 rows) + +EXECUTE st2(10, 20); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +EXECUTE st2(101, 121); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-----+----+-------+------------------------------+--------------------------+----+------------+----- + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +-- subquery using immutable function (can be sent to remote) +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Sort + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Sort Key: t1.c1 + -> Nested Loop Semi Join + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Join Filter: (t2.c3 = t1.c3) + -> Foreign Scan on public.ft1 t1 + Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 20)) + -> Materialize + Output: t2.c3 + -> Foreign Scan on public.ft2 t2 + Output: t2.c3 + Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date(c5) = '1970-01-17'::date)) +(14 rows) + +EXECUTE st3(10, 20); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6 | 6 | foo +(1 row) + +EXECUTE st3(20, 30); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+----+----+----+----+----+---- +(0 rows) + +-- custom plan should be chosen initially +PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(3 rows) + +-- once we try it enough times, should switch to generic plan +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) +(3 rows) + +-- value of $1 should not be sent to remote +PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +--------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = 'foo'::user_enum) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = 1)) +(4 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.c8 = $1) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer)) +(4 rows) + +EXECUTE st5('foo', 1); + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- altering FDW options requires replanning +PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = c2)) +(3 rows) + +PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft1 + Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + Batch Size: 1 + -> Result + Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1 '::character(10), NULL::user_enum +(5 rows) + +ALTER TABLE "S 1"."T 1" RENAME TO "T 0"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; + QUERY PLAN +---------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 0" WHERE (("C 1" = c2)) +(3 rows) + +EXECUTE st6; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST | Mon Jan 05 00:00:00 1970 | 4 | 4 | foo + 5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6 | 6 | foo + 7 | 7 | 00007 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 9 | 9 | 00009 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | 9 | foo +(9 rows) + +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft1 + Remote SQL: INSERT INTO "S 1"."T 0"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + Batch Size: 1 + -> Result + Output: NULL::integer, 1001, 101, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft1 '::character(10), NULL::user_enum +(5 rows) + +ALTER TABLE "S 1"."T 0" RENAME TO "T 1"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1'); +PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN +----------------------------------------------------------------------------------------- + Foreign Scan + Output: (count(c3)) + Relations: Aggregate on (public.ft1 t1) + Remote SQL: SELECT count(c3) FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(public.===) c2)) +(4 rows) + +ALTER SERVER loopback OPTIONS (DROP extensions); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; + QUERY PLAN +----------------------------------------------------------- + Aggregate + Output: count(c3) + -> Foreign Scan on public.ft1 t1 + Output: c3 + Filter: (t1.c1 === t1.c2) + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" +(6 rows) + +EXECUTE st8; + count +------- + 9 +(1 row) + +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- cleanup +DEALLOCATE st1; +DEALLOCATE st2; +DEALLOCATE st3; +DEALLOCATE st4; +DEALLOCATE st5; +DEALLOCATE st6; +DEALLOCATE st7; +DEALLOCATE st8; +-- System columns, except ctid and oid, should not be sent to remote +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1; + QUERY PLAN +------------------------------------------------------------------------------- + Limit + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Filter: (t1.tableoid = '1259'::oid) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(6 rows) + +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; + QUERY PLAN +----------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: (tableoid)::regclass, c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" LIMIT 1::bigint +(3 rows) + +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; + tableoid | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----------+----+----+-------+------------------------------+--------------------------+----+------------+----- + ft1 | 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((ctid = '(0,2)')) +(3 rows) + +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ctid, * FROM ft1 t1 LIMIT 1; + QUERY PLAN +----------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 t1 + Output: ctid, c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" LIMIT 1::bigint +(3 rows) + +SELECT ctid, * FROM ft1 t1 LIMIT 1; + ctid | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +-------+----+----+-------+------------------------------+--------------------------+----+------------+----- + (0,1) | 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- =================================================================== +-- used in PL/pgSQL function +-- =================================================================== +CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$ +DECLARE + v_c1 int; +BEGIN + SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1; + PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1; + RETURN v_c1; +END; +$$ LANGUAGE plpgsql; +SELECT f_test(100); + f_test +-------- + 100 +(1 row) + +DROP FUNCTION f_test(int); +-- =================================================================== +-- REINDEX +-- =================================================================== +-- remote table is not created here +CREATE FOREIGN TABLE reindex_foreign (c1 int, c2 int) + SERVER loopback2 OPTIONS (table_name 'reindex_local'); +REINDEX TABLE reindex_foreign; -- error +ERROR: "reindex_foreign" is not a table or materialized view +REINDEX TABLE CONCURRENTLY reindex_foreign; -- error +ERROR: "reindex_foreign" is not a table or materialized view +DROP FOREIGN TABLE reindex_foreign; +-- partitions and foreign tables +CREATE TABLE reind_fdw_parent (c1 int) PARTITION BY RANGE (c1); +CREATE TABLE reind_fdw_0_10 PARTITION OF reind_fdw_parent + FOR VALUES FROM (0) TO (10); +CREATE FOREIGN TABLE reind_fdw_10_20 PARTITION OF reind_fdw_parent + FOR VALUES FROM (10) TO (20) + SERVER loopback OPTIONS (table_name 'reind_local_10_20'); +REINDEX TABLE reind_fdw_parent; -- ok +REINDEX TABLE CONCURRENTLY reind_fdw_parent; -- ok +DROP TABLE reind_fdw_parent; +-- =================================================================== +-- conversion error +-- =================================================================== +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int; +SELECT * FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8) WHERE x1 = 1; -- ERROR +ERROR: invalid input syntax for type integer: "foo" +CONTEXT: column "x8" of foreign table "ftx" +SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +ERROR: invalid input syntax for type integer: "foo" +CONTEXT: column "x8" of foreign table "ftx" +SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +ERROR: invalid input syntax for type integer: "foo" +CONTEXT: whole-row reference to foreign table "ftx" +SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR +ERROR: invalid input syntax for type integer: "foo" +CONTEXT: processing expression at position 2 in select list +ANALYZE ft1; -- ERROR +ERROR: invalid input syntax for type integer: "foo" +CONTEXT: column "c8" of foreign table "ft1" +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; +-- =================================================================== +-- local type can be different from remote type in some cases, +-- in particular if similarly-named operators do equivalent things +-- =================================================================== +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE text; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c8 = 'foo')) LIMIT 1::bigint +(3 rows) + +SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE 'foo' = c8 LIMIT 1; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (('foo' = c8)) LIMIT 1::bigint +(3 rows) + +SELECT * FROM ft1 WHERE 'foo' = c8 LIMIT 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +-- we declared c8 to be text locally, but it's still the same type on +-- the remote which will balk if we try to do anything incompatible +-- with that remote type +SELECT * FROM ft1 WHERE c8 LIKE 'foo' LIMIT 1; -- ERROR +ERROR: operator does not exist: public.user_enum ~~ unknown +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c8 ~~ 'foo')) LIMIT 1::bigint +SELECT * FROM ft1 WHERE c8::text LIKE 'foo' LIMIT 1; -- ERROR; cast not pushed down +ERROR: operator does not exist: public.user_enum ~~ unknown +HINT: No operator matches the given name and argument types. You might need to add explicit type casts. +CONTEXT: remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c8 ~~ 'foo')) LIMIT 1::bigint +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; +-- =================================================================== +-- subtransaction +-- + local/remote error doesn't break cursor +-- =================================================================== +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +FETCH c; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +SAVEPOINT s; +ERROR OUT; -- ERROR +ERROR: syntax error at or near "ERROR" +LINE 1: ERROR OUT; + ^ +ROLLBACK TO s; +FETCH c; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 2 | 2 | 00002 | Sat Jan 03 00:00:00 1970 PST | Sat Jan 03 00:00:00 1970 | 2 | 2 | foo +(1 row) + +SAVEPOINT s; +SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR +ERROR: division by zero +CONTEXT: remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (((1 / ("C 1" - 1)) > 0)) +ROLLBACK TO s; +FETCH c; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 3 | 3 | 00003 | Sun Jan 04 00:00:00 1970 PST | Sun Jan 04 00:00:00 1970 | 3 | 3 | foo +(1 row) + +SELECT * FROM ft1 ORDER BY c1 LIMIT 1; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +----+----+-------+------------------------------+--------------------------+----+------------+----- + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 | foo +(1 row) + +COMMIT; +-- =================================================================== +-- test handling of collations +-- =================================================================== +create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique); +create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10)) + server loopback options (table_name 'loct3', use_remote_estimate 'true'); +-- can be sent to remote +explain (verbose, costs off) select * from ft3 where f1 = 'foo'; + QUERY PLAN +------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo')) +(3 rows) + +explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo'; + QUERY PLAN +------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo')) +(3 rows) + +explain (verbose, costs off) select * from ft3 where f2 = 'foo'; + QUERY PLAN +------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f2 = 'foo')) +(3 rows) + +explain (verbose, costs off) select * from ft3 where f3 = 'foo'; + QUERY PLAN +------------------------------------------------------------------------ + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = 'foo')) +(3 rows) + +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 and l.f1 = 'foo'; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Nested Loop + Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3 + -> Index Scan using loct3_f1_key on public.loct3 l + Output: l.f1, l.f2, l.f3 + Index Cond: (l.f1 = 'foo'::text) + -> Foreign Scan on public.ft3 f + Output: f.f1, f.f2, f.f3 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = $1::character varying(10))) +(8 rows) + +-- can't be sent to remote +explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo'; + QUERY PLAN +--------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: ((ft3.f1)::text = 'foo'::text) + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 +(4 rows) + +explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C"; + QUERY PLAN +--------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: (ft3.f1 = 'foo'::text COLLATE "C") + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 +(4 rows) + +explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo'; + QUERY PLAN +--------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: ((ft3.f2)::text = 'foo'::text) + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 +(4 rows) + +explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C"; + QUERY PLAN +--------------------------------------------------- + Foreign Scan on public.ft3 + Output: f1, f2, f3 + Filter: (ft3.f2 = 'foo'::text COLLATE "C") + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 +(4 rows) + +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo'; + QUERY PLAN +------------------------------------------------------------- + Hash Join + Output: f.f1, f.f2, f.f3, l.f1, l.f2, l.f3 + Inner Unique: true + Hash Cond: ((f.f3)::text = (l.f3)::text) + -> Foreign Scan on public.ft3 f + Output: f.f1, f.f2, f.f3 + Remote SQL: SELECT f1, f2, f3 FROM public.loct3 + -> Hash + Output: l.f1, l.f2, l.f3 + -> Index Scan using loct3_f1_key on public.loct3 l + Output: l.f1, l.f2, l.f3 + Index Cond: (l.f1 = 'foo'::text) +(12 rows) + +-- =================================================================== +-- test writable foreign table stuff +-- =================================================================== +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft2 + Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + Batch Size: 1 + -> Subquery Scan on "*SELECT*" + Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1", NULL::integer, "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum + -> Foreign Scan on public.ft2 ft2_1 + Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3) + Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint +(8 rows) + +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) + VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----+----+----+----+------------+---- + 1101 | 201 | aaa | | | | ft2 | + 1102 | 202 | bbb | | | | ft2 | + 1103 | 203 | ccc | | | | ft2 | +(3 rows) + +INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + -> Foreign Update on public.ft2 + Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 300), c3 = (c3 || '_update3') WHERE ((("C 1" % 10) = 3)) +(3 rows) + +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------ + Update on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + -> Foreign Update on public.ft2 + Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 400), c3 = (c3 || '_update7') WHERE ((("C 1" % 10) = 7)) RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8 +(4 rows) + +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+--------------------+------------------------------+--------------------------+----+------------+----- + 7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 47 | 407 | 00047_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 57 | 407 | 00057_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 67 | 407 | 00067_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 77 | 407 | 00077_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 87 | 407 | 00087_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 97 | 407 | 00097_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 107 | 407 | 00107_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 117 | 407 | 00117_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 127 | 407 | 00127_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 137 | 407 | 00137_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 147 | 407 | 00147_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 157 | 407 | 00157_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 167 | 407 | 00167_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 177 | 407 | 00177_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 187 | 407 | 00187_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 197 | 407 | 00197_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 207 | 407 | 00207_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 217 | 407 | 00217_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 227 | 407 | 00227_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 237 | 407 | 00237_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 247 | 407 | 00247_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 257 | 407 | 00257_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 267 | 407 | 00267_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 277 | 407 | 00277_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 287 | 407 | 00287_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 297 | 407 | 00297_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 307 | 407 | 00307_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 317 | 407 | 00317_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 327 | 407 | 00327_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 337 | 407 | 00337_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 347 | 407 | 00347_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 357 | 407 | 00357_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 367 | 407 | 00367_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 377 | 407 | 00377_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 387 | 407 | 00387_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 397 | 407 | 00397_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 407 | 407 | 00407_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 417 | 407 | 00417_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 427 | 407 | 00427_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 437 | 407 | 00437_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 447 | 407 | 00447_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 457 | 407 | 00457_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 467 | 407 | 00467_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 477 | 407 | 00477_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 487 | 407 | 00487_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 497 | 407 | 00497_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 507 | 407 | 00507_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 517 | 407 | 00517_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 527 | 407 | 00527_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 537 | 407 | 00537_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 547 | 407 | 00547_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 557 | 407 | 00557_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 567 | 407 | 00567_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 577 | 407 | 00577_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 587 | 407 | 00587_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 597 | 407 | 00597_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 607 | 407 | 00607_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 617 | 407 | 00617_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 627 | 407 | 00627_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 637 | 407 | 00637_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 647 | 407 | 00647_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 657 | 407 | 00657_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 667 | 407 | 00667_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 677 | 407 | 00677_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 687 | 407 | 00687_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 697 | 407 | 00697_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 707 | 407 | 00707_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 717 | 407 | 00717_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 727 | 407 | 00727_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 737 | 407 | 00737_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 747 | 407 | 00747_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 757 | 407 | 00757_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 767 | 407 | 00767_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 777 | 407 | 00777_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 787 | 407 | 00787_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 797 | 407 | 00797_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 807 | 407 | 00807_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 817 | 407 | 00817_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 827 | 407 | 00827_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 837 | 407 | 00837_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 847 | 407 | 00847_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 857 | 407 | 00857_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 867 | 407 | 00867_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 877 | 407 | 00877_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 887 | 407 | 00887_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 897 | 407 | 00897_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 907 | 407 | 00907_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo + 917 | 407 | 00917_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo + 927 | 407 | 00927_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo + 937 | 407 | 00937_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo + 947 | 407 | 00947_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo + 957 | 407 | 00957_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo + 967 | 407 | 00967_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo + 977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo + 987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo + 997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo + 1007 | 507 | 0000700007_update7 | | | | ft2 | + 1017 | 507 | 0001700017_update7 | | | | ft2 | +(102 rows) + +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + -> Foreign Update + Remote SQL: UPDATE "S 1"."T 1" r1 SET c2 = (r1.c2 + 500), c3 = (r1.c3 || '_update9'), c7 = 'ft2 '::character(10) FROM "S 1"."T 1" r2 WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 9)) +(3 rows) + +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +EXPLAIN (verbose, costs off) + DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down + QUERY PLAN +-------------------------------------------------------------------------------------------- + Delete on public.ft2 + Output: c1, c4 + -> Foreign Delete on public.ft2 + Remote SQL: DELETE FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) RETURNING "C 1", c4 +(4 rows) + +DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; + c1 | c4 +------+------------------------------ + 5 | Tue Jan 06 00:00:00 1970 PST + 15 | Fri Jan 16 00:00:00 1970 PST + 25 | Mon Jan 26 00:00:00 1970 PST + 35 | Thu Feb 05 00:00:00 1970 PST + 45 | Sun Feb 15 00:00:00 1970 PST + 55 | Wed Feb 25 00:00:00 1970 PST + 65 | Sat Mar 07 00:00:00 1970 PST + 75 | Tue Mar 17 00:00:00 1970 PST + 85 | Fri Mar 27 00:00:00 1970 PST + 95 | Mon Apr 06 00:00:00 1970 PST + 105 | Tue Jan 06 00:00:00 1970 PST + 115 | Fri Jan 16 00:00:00 1970 PST + 125 | Mon Jan 26 00:00:00 1970 PST + 135 | Thu Feb 05 00:00:00 1970 PST + 145 | Sun Feb 15 00:00:00 1970 PST + 155 | Wed Feb 25 00:00:00 1970 PST + 165 | Sat Mar 07 00:00:00 1970 PST + 175 | Tue Mar 17 00:00:00 1970 PST + 185 | Fri Mar 27 00:00:00 1970 PST + 195 | Mon Apr 06 00:00:00 1970 PST + 205 | Tue Jan 06 00:00:00 1970 PST + 215 | Fri Jan 16 00:00:00 1970 PST + 225 | Mon Jan 26 00:00:00 1970 PST + 235 | Thu Feb 05 00:00:00 1970 PST + 245 | Sun Feb 15 00:00:00 1970 PST + 255 | Wed Feb 25 00:00:00 1970 PST + 265 | Sat Mar 07 00:00:00 1970 PST + 275 | Tue Mar 17 00:00:00 1970 PST + 285 | Fri Mar 27 00:00:00 1970 PST + 295 | Mon Apr 06 00:00:00 1970 PST + 305 | Tue Jan 06 00:00:00 1970 PST + 315 | Fri Jan 16 00:00:00 1970 PST + 325 | Mon Jan 26 00:00:00 1970 PST + 335 | Thu Feb 05 00:00:00 1970 PST + 345 | Sun Feb 15 00:00:00 1970 PST + 355 | Wed Feb 25 00:00:00 1970 PST + 365 | Sat Mar 07 00:00:00 1970 PST + 375 | Tue Mar 17 00:00:00 1970 PST + 385 | Fri Mar 27 00:00:00 1970 PST + 395 | Mon Apr 06 00:00:00 1970 PST + 405 | Tue Jan 06 00:00:00 1970 PST + 415 | Fri Jan 16 00:00:00 1970 PST + 425 | Mon Jan 26 00:00:00 1970 PST + 435 | Thu Feb 05 00:00:00 1970 PST + 445 | Sun Feb 15 00:00:00 1970 PST + 455 | Wed Feb 25 00:00:00 1970 PST + 465 | Sat Mar 07 00:00:00 1970 PST + 475 | Tue Mar 17 00:00:00 1970 PST + 485 | Fri Mar 27 00:00:00 1970 PST + 495 | Mon Apr 06 00:00:00 1970 PST + 505 | Tue Jan 06 00:00:00 1970 PST + 515 | Fri Jan 16 00:00:00 1970 PST + 525 | Mon Jan 26 00:00:00 1970 PST + 535 | Thu Feb 05 00:00:00 1970 PST + 545 | Sun Feb 15 00:00:00 1970 PST + 555 | Wed Feb 25 00:00:00 1970 PST + 565 | Sat Mar 07 00:00:00 1970 PST + 575 | Tue Mar 17 00:00:00 1970 PST + 585 | Fri Mar 27 00:00:00 1970 PST + 595 | Mon Apr 06 00:00:00 1970 PST + 605 | Tue Jan 06 00:00:00 1970 PST + 615 | Fri Jan 16 00:00:00 1970 PST + 625 | Mon Jan 26 00:00:00 1970 PST + 635 | Thu Feb 05 00:00:00 1970 PST + 645 | Sun Feb 15 00:00:00 1970 PST + 655 | Wed Feb 25 00:00:00 1970 PST + 665 | Sat Mar 07 00:00:00 1970 PST + 675 | Tue Mar 17 00:00:00 1970 PST + 685 | Fri Mar 27 00:00:00 1970 PST + 695 | Mon Apr 06 00:00:00 1970 PST + 705 | Tue Jan 06 00:00:00 1970 PST + 715 | Fri Jan 16 00:00:00 1970 PST + 725 | Mon Jan 26 00:00:00 1970 PST + 735 | Thu Feb 05 00:00:00 1970 PST + 745 | Sun Feb 15 00:00:00 1970 PST + 755 | Wed Feb 25 00:00:00 1970 PST + 765 | Sat Mar 07 00:00:00 1970 PST + 775 | Tue Mar 17 00:00:00 1970 PST + 785 | Fri Mar 27 00:00:00 1970 PST + 795 | Mon Apr 06 00:00:00 1970 PST + 805 | Tue Jan 06 00:00:00 1970 PST + 815 | Fri Jan 16 00:00:00 1970 PST + 825 | Mon Jan 26 00:00:00 1970 PST + 835 | Thu Feb 05 00:00:00 1970 PST + 845 | Sun Feb 15 00:00:00 1970 PST + 855 | Wed Feb 25 00:00:00 1970 PST + 865 | Sat Mar 07 00:00:00 1970 PST + 875 | Tue Mar 17 00:00:00 1970 PST + 885 | Fri Mar 27 00:00:00 1970 PST + 895 | Mon Apr 06 00:00:00 1970 PST + 905 | Tue Jan 06 00:00:00 1970 PST + 915 | Fri Jan 16 00:00:00 1970 PST + 925 | Mon Jan 26 00:00:00 1970 PST + 935 | Thu Feb 05 00:00:00 1970 PST + 945 | Sun Feb 15 00:00:00 1970 PST + 955 | Wed Feb 25 00:00:00 1970 PST + 965 | Sat Mar 07 00:00:00 1970 PST + 975 | Tue Mar 17 00:00:00 1970 PST + 985 | Fri Mar 27 00:00:00 1970 PST + 995 | Mon Apr 06 00:00:00 1970 PST + 1005 | + 1015 | + 1105 | +(103 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------- + Delete on public.ft2 + -> Foreign Delete + Remote SQL: DELETE FROM "S 1"."T 1" r1 USING "S 1"."T 1" r2 WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 2)) +(3 rows) + +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; + c1 | c2 | c3 | c4 +------+-----+--------------------+------------------------------ + 1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST + 3 | 303 | 00003_update3 | Sun Jan 04 00:00:00 1970 PST + 4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST + 6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST + 7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST + 8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST + 9 | 509 | 00009_update9 | Sat Jan 10 00:00:00 1970 PST + 10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST + 11 | 1 | 00011 | Mon Jan 12 00:00:00 1970 PST + 13 | 303 | 00013_update3 | Wed Jan 14 00:00:00 1970 PST + 14 | 4 | 00014 | Thu Jan 15 00:00:00 1970 PST + 16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST + 17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST + 18 | 8 | 00018 | Mon Jan 19 00:00:00 1970 PST + 19 | 509 | 00019_update9 | Tue Jan 20 00:00:00 1970 PST + 20 | 0 | 00020 | Wed Jan 21 00:00:00 1970 PST + 21 | 1 | 00021 | Thu Jan 22 00:00:00 1970 PST + 23 | 303 | 00023_update3 | Sat Jan 24 00:00:00 1970 PST + 24 | 4 | 00024 | Sun Jan 25 00:00:00 1970 PST + 26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST + 27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST + 28 | 8 | 00028 | Thu Jan 29 00:00:00 1970 PST + 29 | 509 | 00029_update9 | Fri Jan 30 00:00:00 1970 PST + 30 | 0 | 00030 | Sat Jan 31 00:00:00 1970 PST + 31 | 1 | 00031 | Sun Feb 01 00:00:00 1970 PST + 33 | 303 | 00033_update3 | Tue Feb 03 00:00:00 1970 PST + 34 | 4 | 00034 | Wed Feb 04 00:00:00 1970 PST + 36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST + 37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST + 38 | 8 | 00038 | Sun Feb 08 00:00:00 1970 PST + 39 | 509 | 00039_update9 | Mon Feb 09 00:00:00 1970 PST + 40 | 0 | 00040 | Tue Feb 10 00:00:00 1970 PST + 41 | 1 | 00041 | Wed Feb 11 00:00:00 1970 PST + 43 | 303 | 00043_update3 | Fri Feb 13 00:00:00 1970 PST + 44 | 4 | 00044 | Sat Feb 14 00:00:00 1970 PST + 46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST + 47 | 407 | 00047_update7 | Tue Feb 17 00:00:00 1970 PST + 48 | 8 | 00048 | Wed Feb 18 00:00:00 1970 PST + 49 | 509 | 00049_update9 | Thu Feb 19 00:00:00 1970 PST + 50 | 0 | 00050 | Fri Feb 20 00:00:00 1970 PST + 51 | 1 | 00051 | Sat Feb 21 00:00:00 1970 PST + 53 | 303 | 00053_update3 | Mon Feb 23 00:00:00 1970 PST + 54 | 4 | 00054 | Tue Feb 24 00:00:00 1970 PST + 56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST + 57 | 407 | 00057_update7 | Fri Feb 27 00:00:00 1970 PST + 58 | 8 | 00058 | Sat Feb 28 00:00:00 1970 PST + 59 | 509 | 00059_update9 | Sun Mar 01 00:00:00 1970 PST + 60 | 0 | 00060 | Mon Mar 02 00:00:00 1970 PST + 61 | 1 | 00061 | Tue Mar 03 00:00:00 1970 PST + 63 | 303 | 00063_update3 | Thu Mar 05 00:00:00 1970 PST + 64 | 4 | 00064 | Fri Mar 06 00:00:00 1970 PST + 66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST + 67 | 407 | 00067_update7 | Mon Mar 09 00:00:00 1970 PST + 68 | 8 | 00068 | Tue Mar 10 00:00:00 1970 PST + 69 | 509 | 00069_update9 | Wed Mar 11 00:00:00 1970 PST + 70 | 0 | 00070 | Thu Mar 12 00:00:00 1970 PST + 71 | 1 | 00071 | Fri Mar 13 00:00:00 1970 PST + 73 | 303 | 00073_update3 | Sun Mar 15 00:00:00 1970 PST + 74 | 4 | 00074 | Mon Mar 16 00:00:00 1970 PST + 76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST + 77 | 407 | 00077_update7 | Thu Mar 19 00:00:00 1970 PST + 78 | 8 | 00078 | Fri Mar 20 00:00:00 1970 PST + 79 | 509 | 00079_update9 | Sat Mar 21 00:00:00 1970 PST + 80 | 0 | 00080 | Sun Mar 22 00:00:00 1970 PST + 81 | 1 | 00081 | Mon Mar 23 00:00:00 1970 PST + 83 | 303 | 00083_update3 | Wed Mar 25 00:00:00 1970 PST + 84 | 4 | 00084 | Thu Mar 26 00:00:00 1970 PST + 86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST + 87 | 407 | 00087_update7 | Sun Mar 29 00:00:00 1970 PST + 88 | 8 | 00088 | Mon Mar 30 00:00:00 1970 PST + 89 | 509 | 00089_update9 | Tue Mar 31 00:00:00 1970 PST + 90 | 0 | 00090 | Wed Apr 01 00:00:00 1970 PST + 91 | 1 | 00091 | Thu Apr 02 00:00:00 1970 PST + 93 | 303 | 00093_update3 | Sat Apr 04 00:00:00 1970 PST + 94 | 4 | 00094 | Sun Apr 05 00:00:00 1970 PST + 96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST + 97 | 407 | 00097_update7 | Wed Apr 08 00:00:00 1970 PST + 98 | 8 | 00098 | Thu Apr 09 00:00:00 1970 PST + 99 | 509 | 00099_update9 | Fri Apr 10 00:00:00 1970 PST + 100 | 0 | 00100 | Thu Jan 01 00:00:00 1970 PST + 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST + 103 | 303 | 00103_update3 | Sun Jan 04 00:00:00 1970 PST + 104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST + 106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST + 107 | 407 | 00107_update7 | Thu Jan 08 00:00:00 1970 PST + 108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST + 109 | 509 | 00109_update9 | Sat Jan 10 00:00:00 1970 PST + 110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST + 111 | 1 | 00111 | Mon Jan 12 00:00:00 1970 PST + 113 | 303 | 00113_update3 | Wed Jan 14 00:00:00 1970 PST + 114 | 4 | 00114 | Thu Jan 15 00:00:00 1970 PST + 116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST + 117 | 407 | 00117_update7 | Sun Jan 18 00:00:00 1970 PST + 118 | 8 | 00118 | Mon Jan 19 00:00:00 1970 PST + 119 | 509 | 00119_update9 | Tue Jan 20 00:00:00 1970 PST + 120 | 0 | 00120 | Wed Jan 21 00:00:00 1970 PST + 121 | 1 | 00121 | Thu Jan 22 00:00:00 1970 PST + 123 | 303 | 00123_update3 | Sat Jan 24 00:00:00 1970 PST + 124 | 4 | 00124 | Sun Jan 25 00:00:00 1970 PST + 126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST + 127 | 407 | 00127_update7 | Wed Jan 28 00:00:00 1970 PST + 128 | 8 | 00128 | Thu Jan 29 00:00:00 1970 PST + 129 | 509 | 00129_update9 | Fri Jan 30 00:00:00 1970 PST + 130 | 0 | 00130 | Sat Jan 31 00:00:00 1970 PST + 131 | 1 | 00131 | Sun Feb 01 00:00:00 1970 PST + 133 | 303 | 00133_update3 | Tue Feb 03 00:00:00 1970 PST + 134 | 4 | 00134 | Wed Feb 04 00:00:00 1970 PST + 136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST + 137 | 407 | 00137_update7 | Sat Feb 07 00:00:00 1970 PST + 138 | 8 | 00138 | Sun Feb 08 00:00:00 1970 PST + 139 | 509 | 00139_update9 | Mon Feb 09 00:00:00 1970 PST + 140 | 0 | 00140 | Tue Feb 10 00:00:00 1970 PST + 141 | 1 | 00141 | Wed Feb 11 00:00:00 1970 PST + 143 | 303 | 00143_update3 | Fri Feb 13 00:00:00 1970 PST + 144 | 4 | 00144 | Sat Feb 14 00:00:00 1970 PST + 146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST + 147 | 407 | 00147_update7 | Tue Feb 17 00:00:00 1970 PST + 148 | 8 | 00148 | Wed Feb 18 00:00:00 1970 PST + 149 | 509 | 00149_update9 | Thu Feb 19 00:00:00 1970 PST + 150 | 0 | 00150 | Fri Feb 20 00:00:00 1970 PST + 151 | 1 | 00151 | Sat Feb 21 00:00:00 1970 PST + 153 | 303 | 00153_update3 | Mon Feb 23 00:00:00 1970 PST + 154 | 4 | 00154 | Tue Feb 24 00:00:00 1970 PST + 156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST + 157 | 407 | 00157_update7 | Fri Feb 27 00:00:00 1970 PST + 158 | 8 | 00158 | Sat Feb 28 00:00:00 1970 PST + 159 | 509 | 00159_update9 | Sun Mar 01 00:00:00 1970 PST + 160 | 0 | 00160 | Mon Mar 02 00:00:00 1970 PST + 161 | 1 | 00161 | Tue Mar 03 00:00:00 1970 PST + 163 | 303 | 00163_update3 | Thu Mar 05 00:00:00 1970 PST + 164 | 4 | 00164 | Fri Mar 06 00:00:00 1970 PST + 166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST + 167 | 407 | 00167_update7 | Mon Mar 09 00:00:00 1970 PST + 168 | 8 | 00168 | Tue Mar 10 00:00:00 1970 PST + 169 | 509 | 00169_update9 | Wed Mar 11 00:00:00 1970 PST + 170 | 0 | 00170 | Thu Mar 12 00:00:00 1970 PST + 171 | 1 | 00171 | Fri Mar 13 00:00:00 1970 PST + 173 | 303 | 00173_update3 | Sun Mar 15 00:00:00 1970 PST + 174 | 4 | 00174 | Mon Mar 16 00:00:00 1970 PST + 176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST + 177 | 407 | 00177_update7 | Thu Mar 19 00:00:00 1970 PST + 178 | 8 | 00178 | Fri Mar 20 00:00:00 1970 PST + 179 | 509 | 00179_update9 | Sat Mar 21 00:00:00 1970 PST + 180 | 0 | 00180 | Sun Mar 22 00:00:00 1970 PST + 181 | 1 | 00181 | Mon Mar 23 00:00:00 1970 PST + 183 | 303 | 00183_update3 | Wed Mar 25 00:00:00 1970 PST + 184 | 4 | 00184 | Thu Mar 26 00:00:00 1970 PST + 186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST + 187 | 407 | 00187_update7 | Sun Mar 29 00:00:00 1970 PST + 188 | 8 | 00188 | Mon Mar 30 00:00:00 1970 PST + 189 | 509 | 00189_update9 | Tue Mar 31 00:00:00 1970 PST + 190 | 0 | 00190 | Wed Apr 01 00:00:00 1970 PST + 191 | 1 | 00191 | Thu Apr 02 00:00:00 1970 PST + 193 | 303 | 00193_update3 | Sat Apr 04 00:00:00 1970 PST + 194 | 4 | 00194 | Sun Apr 05 00:00:00 1970 PST + 196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST + 197 | 407 | 00197_update7 | Wed Apr 08 00:00:00 1970 PST + 198 | 8 | 00198 | Thu Apr 09 00:00:00 1970 PST + 199 | 509 | 00199_update9 | Fri Apr 10 00:00:00 1970 PST + 200 | 0 | 00200 | Thu Jan 01 00:00:00 1970 PST + 201 | 1 | 00201 | Fri Jan 02 00:00:00 1970 PST + 203 | 303 | 00203_update3 | Sun Jan 04 00:00:00 1970 PST + 204 | 4 | 00204 | Mon Jan 05 00:00:00 1970 PST + 206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST + 207 | 407 | 00207_update7 | Thu Jan 08 00:00:00 1970 PST + 208 | 8 | 00208 | Fri Jan 09 00:00:00 1970 PST + 209 | 509 | 00209_update9 | Sat Jan 10 00:00:00 1970 PST + 210 | 0 | 00210 | Sun Jan 11 00:00:00 1970 PST + 211 | 1 | 00211 | Mon Jan 12 00:00:00 1970 PST + 213 | 303 | 00213_update3 | Wed Jan 14 00:00:00 1970 PST + 214 | 4 | 00214 | Thu Jan 15 00:00:00 1970 PST + 216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST + 217 | 407 | 00217_update7 | Sun Jan 18 00:00:00 1970 PST + 218 | 8 | 00218 | Mon Jan 19 00:00:00 1970 PST + 219 | 509 | 00219_update9 | Tue Jan 20 00:00:00 1970 PST + 220 | 0 | 00220 | Wed Jan 21 00:00:00 1970 PST + 221 | 1 | 00221 | Thu Jan 22 00:00:00 1970 PST + 223 | 303 | 00223_update3 | Sat Jan 24 00:00:00 1970 PST + 224 | 4 | 00224 | Sun Jan 25 00:00:00 1970 PST + 226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST + 227 | 407 | 00227_update7 | Wed Jan 28 00:00:00 1970 PST + 228 | 8 | 00228 | Thu Jan 29 00:00:00 1970 PST + 229 | 509 | 00229_update9 | Fri Jan 30 00:00:00 1970 PST + 230 | 0 | 00230 | Sat Jan 31 00:00:00 1970 PST + 231 | 1 | 00231 | Sun Feb 01 00:00:00 1970 PST + 233 | 303 | 00233_update3 | Tue Feb 03 00:00:00 1970 PST + 234 | 4 | 00234 | Wed Feb 04 00:00:00 1970 PST + 236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST + 237 | 407 | 00237_update7 | Sat Feb 07 00:00:00 1970 PST + 238 | 8 | 00238 | Sun Feb 08 00:00:00 1970 PST + 239 | 509 | 00239_update9 | Mon Feb 09 00:00:00 1970 PST + 240 | 0 | 00240 | Tue Feb 10 00:00:00 1970 PST + 241 | 1 | 00241 | Wed Feb 11 00:00:00 1970 PST + 243 | 303 | 00243_update3 | Fri Feb 13 00:00:00 1970 PST + 244 | 4 | 00244 | Sat Feb 14 00:00:00 1970 PST + 246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST + 247 | 407 | 00247_update7 | Tue Feb 17 00:00:00 1970 PST + 248 | 8 | 00248 | Wed Feb 18 00:00:00 1970 PST + 249 | 509 | 00249_update9 | Thu Feb 19 00:00:00 1970 PST + 250 | 0 | 00250 | Fri Feb 20 00:00:00 1970 PST + 251 | 1 | 00251 | Sat Feb 21 00:00:00 1970 PST + 253 | 303 | 00253_update3 | Mon Feb 23 00:00:00 1970 PST + 254 | 4 | 00254 | Tue Feb 24 00:00:00 1970 PST + 256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST + 257 | 407 | 00257_update7 | Fri Feb 27 00:00:00 1970 PST + 258 | 8 | 00258 | Sat Feb 28 00:00:00 1970 PST + 259 | 509 | 00259_update9 | Sun Mar 01 00:00:00 1970 PST + 260 | 0 | 00260 | Mon Mar 02 00:00:00 1970 PST + 261 | 1 | 00261 | Tue Mar 03 00:00:00 1970 PST + 263 | 303 | 00263_update3 | Thu Mar 05 00:00:00 1970 PST + 264 | 4 | 00264 | Fri Mar 06 00:00:00 1970 PST + 266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST + 267 | 407 | 00267_update7 | Mon Mar 09 00:00:00 1970 PST + 268 | 8 | 00268 | Tue Mar 10 00:00:00 1970 PST + 269 | 509 | 00269_update9 | Wed Mar 11 00:00:00 1970 PST + 270 | 0 | 00270 | Thu Mar 12 00:00:00 1970 PST + 271 | 1 | 00271 | Fri Mar 13 00:00:00 1970 PST + 273 | 303 | 00273_update3 | Sun Mar 15 00:00:00 1970 PST + 274 | 4 | 00274 | Mon Mar 16 00:00:00 1970 PST + 276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST + 277 | 407 | 00277_update7 | Thu Mar 19 00:00:00 1970 PST + 278 | 8 | 00278 | Fri Mar 20 00:00:00 1970 PST + 279 | 509 | 00279_update9 | Sat Mar 21 00:00:00 1970 PST + 280 | 0 | 00280 | Sun Mar 22 00:00:00 1970 PST + 281 | 1 | 00281 | Mon Mar 23 00:00:00 1970 PST + 283 | 303 | 00283_update3 | Wed Mar 25 00:00:00 1970 PST + 284 | 4 | 00284 | Thu Mar 26 00:00:00 1970 PST + 286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST + 287 | 407 | 00287_update7 | Sun Mar 29 00:00:00 1970 PST + 288 | 8 | 00288 | Mon Mar 30 00:00:00 1970 PST + 289 | 509 | 00289_update9 | Tue Mar 31 00:00:00 1970 PST + 290 | 0 | 00290 | Wed Apr 01 00:00:00 1970 PST + 291 | 1 | 00291 | Thu Apr 02 00:00:00 1970 PST + 293 | 303 | 00293_update3 | Sat Apr 04 00:00:00 1970 PST + 294 | 4 | 00294 | Sun Apr 05 00:00:00 1970 PST + 296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST + 297 | 407 | 00297_update7 | Wed Apr 08 00:00:00 1970 PST + 298 | 8 | 00298 | Thu Apr 09 00:00:00 1970 PST + 299 | 509 | 00299_update9 | Fri Apr 10 00:00:00 1970 PST + 300 | 0 | 00300 | Thu Jan 01 00:00:00 1970 PST + 301 | 1 | 00301 | Fri Jan 02 00:00:00 1970 PST + 303 | 303 | 00303_update3 | Sun Jan 04 00:00:00 1970 PST + 304 | 4 | 00304 | Mon Jan 05 00:00:00 1970 PST + 306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST + 307 | 407 | 00307_update7 | Thu Jan 08 00:00:00 1970 PST + 308 | 8 | 00308 | Fri Jan 09 00:00:00 1970 PST + 309 | 509 | 00309_update9 | Sat Jan 10 00:00:00 1970 PST + 310 | 0 | 00310 | Sun Jan 11 00:00:00 1970 PST + 311 | 1 | 00311 | Mon Jan 12 00:00:00 1970 PST + 313 | 303 | 00313_update3 | Wed Jan 14 00:00:00 1970 PST + 314 | 4 | 00314 | Thu Jan 15 00:00:00 1970 PST + 316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST + 317 | 407 | 00317_update7 | Sun Jan 18 00:00:00 1970 PST + 318 | 8 | 00318 | Mon Jan 19 00:00:00 1970 PST + 319 | 509 | 00319_update9 | Tue Jan 20 00:00:00 1970 PST + 320 | 0 | 00320 | Wed Jan 21 00:00:00 1970 PST + 321 | 1 | 00321 | Thu Jan 22 00:00:00 1970 PST + 323 | 303 | 00323_update3 | Sat Jan 24 00:00:00 1970 PST + 324 | 4 | 00324 | Sun Jan 25 00:00:00 1970 PST + 326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST + 327 | 407 | 00327_update7 | Wed Jan 28 00:00:00 1970 PST + 328 | 8 | 00328 | Thu Jan 29 00:00:00 1970 PST + 329 | 509 | 00329_update9 | Fri Jan 30 00:00:00 1970 PST + 330 | 0 | 00330 | Sat Jan 31 00:00:00 1970 PST + 331 | 1 | 00331 | Sun Feb 01 00:00:00 1970 PST + 333 | 303 | 00333_update3 | Tue Feb 03 00:00:00 1970 PST + 334 | 4 | 00334 | Wed Feb 04 00:00:00 1970 PST + 336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST + 337 | 407 | 00337_update7 | Sat Feb 07 00:00:00 1970 PST + 338 | 8 | 00338 | Sun Feb 08 00:00:00 1970 PST + 339 | 509 | 00339_update9 | Mon Feb 09 00:00:00 1970 PST + 340 | 0 | 00340 | Tue Feb 10 00:00:00 1970 PST + 341 | 1 | 00341 | Wed Feb 11 00:00:00 1970 PST + 343 | 303 | 00343_update3 | Fri Feb 13 00:00:00 1970 PST + 344 | 4 | 00344 | Sat Feb 14 00:00:00 1970 PST + 346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST + 347 | 407 | 00347_update7 | Tue Feb 17 00:00:00 1970 PST + 348 | 8 | 00348 | Wed Feb 18 00:00:00 1970 PST + 349 | 509 | 00349_update9 | Thu Feb 19 00:00:00 1970 PST + 350 | 0 | 00350 | Fri Feb 20 00:00:00 1970 PST + 351 | 1 | 00351 | Sat Feb 21 00:00:00 1970 PST + 353 | 303 | 00353_update3 | Mon Feb 23 00:00:00 1970 PST + 354 | 4 | 00354 | Tue Feb 24 00:00:00 1970 PST + 356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST + 357 | 407 | 00357_update7 | Fri Feb 27 00:00:00 1970 PST + 358 | 8 | 00358 | Sat Feb 28 00:00:00 1970 PST + 359 | 509 | 00359_update9 | Sun Mar 01 00:00:00 1970 PST + 360 | 0 | 00360 | Mon Mar 02 00:00:00 1970 PST + 361 | 1 | 00361 | Tue Mar 03 00:00:00 1970 PST + 363 | 303 | 00363_update3 | Thu Mar 05 00:00:00 1970 PST + 364 | 4 | 00364 | Fri Mar 06 00:00:00 1970 PST + 366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST + 367 | 407 | 00367_update7 | Mon Mar 09 00:00:00 1970 PST + 368 | 8 | 00368 | Tue Mar 10 00:00:00 1970 PST + 369 | 509 | 00369_update9 | Wed Mar 11 00:00:00 1970 PST + 370 | 0 | 00370 | Thu Mar 12 00:00:00 1970 PST + 371 | 1 | 00371 | Fri Mar 13 00:00:00 1970 PST + 373 | 303 | 00373_update3 | Sun Mar 15 00:00:00 1970 PST + 374 | 4 | 00374 | Mon Mar 16 00:00:00 1970 PST + 376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST + 377 | 407 | 00377_update7 | Thu Mar 19 00:00:00 1970 PST + 378 | 8 | 00378 | Fri Mar 20 00:00:00 1970 PST + 379 | 509 | 00379_update9 | Sat Mar 21 00:00:00 1970 PST + 380 | 0 | 00380 | Sun Mar 22 00:00:00 1970 PST + 381 | 1 | 00381 | Mon Mar 23 00:00:00 1970 PST + 383 | 303 | 00383_update3 | Wed Mar 25 00:00:00 1970 PST + 384 | 4 | 00384 | Thu Mar 26 00:00:00 1970 PST + 386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST + 387 | 407 | 00387_update7 | Sun Mar 29 00:00:00 1970 PST + 388 | 8 | 00388 | Mon Mar 30 00:00:00 1970 PST + 389 | 509 | 00389_update9 | Tue Mar 31 00:00:00 1970 PST + 390 | 0 | 00390 | Wed Apr 01 00:00:00 1970 PST + 391 | 1 | 00391 | Thu Apr 02 00:00:00 1970 PST + 393 | 303 | 00393_update3 | Sat Apr 04 00:00:00 1970 PST + 394 | 4 | 00394 | Sun Apr 05 00:00:00 1970 PST + 396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST + 397 | 407 | 00397_update7 | Wed Apr 08 00:00:00 1970 PST + 398 | 8 | 00398 | Thu Apr 09 00:00:00 1970 PST + 399 | 509 | 00399_update9 | Fri Apr 10 00:00:00 1970 PST + 400 | 0 | 00400 | Thu Jan 01 00:00:00 1970 PST + 401 | 1 | 00401 | Fri Jan 02 00:00:00 1970 PST + 403 | 303 | 00403_update3 | Sun Jan 04 00:00:00 1970 PST + 404 | 4 | 00404 | Mon Jan 05 00:00:00 1970 PST + 406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST + 407 | 407 | 00407_update7 | Thu Jan 08 00:00:00 1970 PST + 408 | 8 | 00408 | Fri Jan 09 00:00:00 1970 PST + 409 | 509 | 00409_update9 | Sat Jan 10 00:00:00 1970 PST + 410 | 0 | 00410 | Sun Jan 11 00:00:00 1970 PST + 411 | 1 | 00411 | Mon Jan 12 00:00:00 1970 PST + 413 | 303 | 00413_update3 | Wed Jan 14 00:00:00 1970 PST + 414 | 4 | 00414 | Thu Jan 15 00:00:00 1970 PST + 416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST + 417 | 407 | 00417_update7 | Sun Jan 18 00:00:00 1970 PST + 418 | 8 | 00418 | Mon Jan 19 00:00:00 1970 PST + 419 | 509 | 00419_update9 | Tue Jan 20 00:00:00 1970 PST + 420 | 0 | 00420 | Wed Jan 21 00:00:00 1970 PST + 421 | 1 | 00421 | Thu Jan 22 00:00:00 1970 PST + 423 | 303 | 00423_update3 | Sat Jan 24 00:00:00 1970 PST + 424 | 4 | 00424 | Sun Jan 25 00:00:00 1970 PST + 426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST + 427 | 407 | 00427_update7 | Wed Jan 28 00:00:00 1970 PST + 428 | 8 | 00428 | Thu Jan 29 00:00:00 1970 PST + 429 | 509 | 00429_update9 | Fri Jan 30 00:00:00 1970 PST + 430 | 0 | 00430 | Sat Jan 31 00:00:00 1970 PST + 431 | 1 | 00431 | Sun Feb 01 00:00:00 1970 PST + 433 | 303 | 00433_update3 | Tue Feb 03 00:00:00 1970 PST + 434 | 4 | 00434 | Wed Feb 04 00:00:00 1970 PST + 436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST + 437 | 407 | 00437_update7 | Sat Feb 07 00:00:00 1970 PST + 438 | 8 | 00438 | Sun Feb 08 00:00:00 1970 PST + 439 | 509 | 00439_update9 | Mon Feb 09 00:00:00 1970 PST + 440 | 0 | 00440 | Tue Feb 10 00:00:00 1970 PST + 441 | 1 | 00441 | Wed Feb 11 00:00:00 1970 PST + 443 | 303 | 00443_update3 | Fri Feb 13 00:00:00 1970 PST + 444 | 4 | 00444 | Sat Feb 14 00:00:00 1970 PST + 446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST + 447 | 407 | 00447_update7 | Tue Feb 17 00:00:00 1970 PST + 448 | 8 | 00448 | Wed Feb 18 00:00:00 1970 PST + 449 | 509 | 00449_update9 | Thu Feb 19 00:00:00 1970 PST + 450 | 0 | 00450 | Fri Feb 20 00:00:00 1970 PST + 451 | 1 | 00451 | Sat Feb 21 00:00:00 1970 PST + 453 | 303 | 00453_update3 | Mon Feb 23 00:00:00 1970 PST + 454 | 4 | 00454 | Tue Feb 24 00:00:00 1970 PST + 456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST + 457 | 407 | 00457_update7 | Fri Feb 27 00:00:00 1970 PST + 458 | 8 | 00458 | Sat Feb 28 00:00:00 1970 PST + 459 | 509 | 00459_update9 | Sun Mar 01 00:00:00 1970 PST + 460 | 0 | 00460 | Mon Mar 02 00:00:00 1970 PST + 461 | 1 | 00461 | Tue Mar 03 00:00:00 1970 PST + 463 | 303 | 00463_update3 | Thu Mar 05 00:00:00 1970 PST + 464 | 4 | 00464 | Fri Mar 06 00:00:00 1970 PST + 466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST + 467 | 407 | 00467_update7 | Mon Mar 09 00:00:00 1970 PST + 468 | 8 | 00468 | Tue Mar 10 00:00:00 1970 PST + 469 | 509 | 00469_update9 | Wed Mar 11 00:00:00 1970 PST + 470 | 0 | 00470 | Thu Mar 12 00:00:00 1970 PST + 471 | 1 | 00471 | Fri Mar 13 00:00:00 1970 PST + 473 | 303 | 00473_update3 | Sun Mar 15 00:00:00 1970 PST + 474 | 4 | 00474 | Mon Mar 16 00:00:00 1970 PST + 476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST + 477 | 407 | 00477_update7 | Thu Mar 19 00:00:00 1970 PST + 478 | 8 | 00478 | Fri Mar 20 00:00:00 1970 PST + 479 | 509 | 00479_update9 | Sat Mar 21 00:00:00 1970 PST + 480 | 0 | 00480 | Sun Mar 22 00:00:00 1970 PST + 481 | 1 | 00481 | Mon Mar 23 00:00:00 1970 PST + 483 | 303 | 00483_update3 | Wed Mar 25 00:00:00 1970 PST + 484 | 4 | 00484 | Thu Mar 26 00:00:00 1970 PST + 486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST + 487 | 407 | 00487_update7 | Sun Mar 29 00:00:00 1970 PST + 488 | 8 | 00488 | Mon Mar 30 00:00:00 1970 PST + 489 | 509 | 00489_update9 | Tue Mar 31 00:00:00 1970 PST + 490 | 0 | 00490 | Wed Apr 01 00:00:00 1970 PST + 491 | 1 | 00491 | Thu Apr 02 00:00:00 1970 PST + 493 | 303 | 00493_update3 | Sat Apr 04 00:00:00 1970 PST + 494 | 4 | 00494 | Sun Apr 05 00:00:00 1970 PST + 496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST + 497 | 407 | 00497_update7 | Wed Apr 08 00:00:00 1970 PST + 498 | 8 | 00498 | Thu Apr 09 00:00:00 1970 PST + 499 | 509 | 00499_update9 | Fri Apr 10 00:00:00 1970 PST + 500 | 0 | 00500 | Thu Jan 01 00:00:00 1970 PST + 501 | 1 | 00501 | Fri Jan 02 00:00:00 1970 PST + 503 | 303 | 00503_update3 | Sun Jan 04 00:00:00 1970 PST + 504 | 4 | 00504 | Mon Jan 05 00:00:00 1970 PST + 506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST + 507 | 407 | 00507_update7 | Thu Jan 08 00:00:00 1970 PST + 508 | 8 | 00508 | Fri Jan 09 00:00:00 1970 PST + 509 | 509 | 00509_update9 | Sat Jan 10 00:00:00 1970 PST + 510 | 0 | 00510 | Sun Jan 11 00:00:00 1970 PST + 511 | 1 | 00511 | Mon Jan 12 00:00:00 1970 PST + 513 | 303 | 00513_update3 | Wed Jan 14 00:00:00 1970 PST + 514 | 4 | 00514 | Thu Jan 15 00:00:00 1970 PST + 516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST + 517 | 407 | 00517_update7 | Sun Jan 18 00:00:00 1970 PST + 518 | 8 | 00518 | Mon Jan 19 00:00:00 1970 PST + 519 | 509 | 00519_update9 | Tue Jan 20 00:00:00 1970 PST + 520 | 0 | 00520 | Wed Jan 21 00:00:00 1970 PST + 521 | 1 | 00521 | Thu Jan 22 00:00:00 1970 PST + 523 | 303 | 00523_update3 | Sat Jan 24 00:00:00 1970 PST + 524 | 4 | 00524 | Sun Jan 25 00:00:00 1970 PST + 526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST + 527 | 407 | 00527_update7 | Wed Jan 28 00:00:00 1970 PST + 528 | 8 | 00528 | Thu Jan 29 00:00:00 1970 PST + 529 | 509 | 00529_update9 | Fri Jan 30 00:00:00 1970 PST + 530 | 0 | 00530 | Sat Jan 31 00:00:00 1970 PST + 531 | 1 | 00531 | Sun Feb 01 00:00:00 1970 PST + 533 | 303 | 00533_update3 | Tue Feb 03 00:00:00 1970 PST + 534 | 4 | 00534 | Wed Feb 04 00:00:00 1970 PST + 536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST + 537 | 407 | 00537_update7 | Sat Feb 07 00:00:00 1970 PST + 538 | 8 | 00538 | Sun Feb 08 00:00:00 1970 PST + 539 | 509 | 00539_update9 | Mon Feb 09 00:00:00 1970 PST + 540 | 0 | 00540 | Tue Feb 10 00:00:00 1970 PST + 541 | 1 | 00541 | Wed Feb 11 00:00:00 1970 PST + 543 | 303 | 00543_update3 | Fri Feb 13 00:00:00 1970 PST + 544 | 4 | 00544 | Sat Feb 14 00:00:00 1970 PST + 546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST + 547 | 407 | 00547_update7 | Tue Feb 17 00:00:00 1970 PST + 548 | 8 | 00548 | Wed Feb 18 00:00:00 1970 PST + 549 | 509 | 00549_update9 | Thu Feb 19 00:00:00 1970 PST + 550 | 0 | 00550 | Fri Feb 20 00:00:00 1970 PST + 551 | 1 | 00551 | Sat Feb 21 00:00:00 1970 PST + 553 | 303 | 00553_update3 | Mon Feb 23 00:00:00 1970 PST + 554 | 4 | 00554 | Tue Feb 24 00:00:00 1970 PST + 556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST + 557 | 407 | 00557_update7 | Fri Feb 27 00:00:00 1970 PST + 558 | 8 | 00558 | Sat Feb 28 00:00:00 1970 PST + 559 | 509 | 00559_update9 | Sun Mar 01 00:00:00 1970 PST + 560 | 0 | 00560 | Mon Mar 02 00:00:00 1970 PST + 561 | 1 | 00561 | Tue Mar 03 00:00:00 1970 PST + 563 | 303 | 00563_update3 | Thu Mar 05 00:00:00 1970 PST + 564 | 4 | 00564 | Fri Mar 06 00:00:00 1970 PST + 566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST + 567 | 407 | 00567_update7 | Mon Mar 09 00:00:00 1970 PST + 568 | 8 | 00568 | Tue Mar 10 00:00:00 1970 PST + 569 | 509 | 00569_update9 | Wed Mar 11 00:00:00 1970 PST + 570 | 0 | 00570 | Thu Mar 12 00:00:00 1970 PST + 571 | 1 | 00571 | Fri Mar 13 00:00:00 1970 PST + 573 | 303 | 00573_update3 | Sun Mar 15 00:00:00 1970 PST + 574 | 4 | 00574 | Mon Mar 16 00:00:00 1970 PST + 576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST + 577 | 407 | 00577_update7 | Thu Mar 19 00:00:00 1970 PST + 578 | 8 | 00578 | Fri Mar 20 00:00:00 1970 PST + 579 | 509 | 00579_update9 | Sat Mar 21 00:00:00 1970 PST + 580 | 0 | 00580 | Sun Mar 22 00:00:00 1970 PST + 581 | 1 | 00581 | Mon Mar 23 00:00:00 1970 PST + 583 | 303 | 00583_update3 | Wed Mar 25 00:00:00 1970 PST + 584 | 4 | 00584 | Thu Mar 26 00:00:00 1970 PST + 586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST + 587 | 407 | 00587_update7 | Sun Mar 29 00:00:00 1970 PST + 588 | 8 | 00588 | Mon Mar 30 00:00:00 1970 PST + 589 | 509 | 00589_update9 | Tue Mar 31 00:00:00 1970 PST + 590 | 0 | 00590 | Wed Apr 01 00:00:00 1970 PST + 591 | 1 | 00591 | Thu Apr 02 00:00:00 1970 PST + 593 | 303 | 00593_update3 | Sat Apr 04 00:00:00 1970 PST + 594 | 4 | 00594 | Sun Apr 05 00:00:00 1970 PST + 596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST + 597 | 407 | 00597_update7 | Wed Apr 08 00:00:00 1970 PST + 598 | 8 | 00598 | Thu Apr 09 00:00:00 1970 PST + 599 | 509 | 00599_update9 | Fri Apr 10 00:00:00 1970 PST + 600 | 0 | 00600 | Thu Jan 01 00:00:00 1970 PST + 601 | 1 | 00601 | Fri Jan 02 00:00:00 1970 PST + 603 | 303 | 00603_update3 | Sun Jan 04 00:00:00 1970 PST + 604 | 4 | 00604 | Mon Jan 05 00:00:00 1970 PST + 606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST + 607 | 407 | 00607_update7 | Thu Jan 08 00:00:00 1970 PST + 608 | 8 | 00608 | Fri Jan 09 00:00:00 1970 PST + 609 | 509 | 00609_update9 | Sat Jan 10 00:00:00 1970 PST + 610 | 0 | 00610 | Sun Jan 11 00:00:00 1970 PST + 611 | 1 | 00611 | Mon Jan 12 00:00:00 1970 PST + 613 | 303 | 00613_update3 | Wed Jan 14 00:00:00 1970 PST + 614 | 4 | 00614 | Thu Jan 15 00:00:00 1970 PST + 616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST + 617 | 407 | 00617_update7 | Sun Jan 18 00:00:00 1970 PST + 618 | 8 | 00618 | Mon Jan 19 00:00:00 1970 PST + 619 | 509 | 00619_update9 | Tue Jan 20 00:00:00 1970 PST + 620 | 0 | 00620 | Wed Jan 21 00:00:00 1970 PST + 621 | 1 | 00621 | Thu Jan 22 00:00:00 1970 PST + 623 | 303 | 00623_update3 | Sat Jan 24 00:00:00 1970 PST + 624 | 4 | 00624 | Sun Jan 25 00:00:00 1970 PST + 626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST + 627 | 407 | 00627_update7 | Wed Jan 28 00:00:00 1970 PST + 628 | 8 | 00628 | Thu Jan 29 00:00:00 1970 PST + 629 | 509 | 00629_update9 | Fri Jan 30 00:00:00 1970 PST + 630 | 0 | 00630 | Sat Jan 31 00:00:00 1970 PST + 631 | 1 | 00631 | Sun Feb 01 00:00:00 1970 PST + 633 | 303 | 00633_update3 | Tue Feb 03 00:00:00 1970 PST + 634 | 4 | 00634 | Wed Feb 04 00:00:00 1970 PST + 636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST + 637 | 407 | 00637_update7 | Sat Feb 07 00:00:00 1970 PST + 638 | 8 | 00638 | Sun Feb 08 00:00:00 1970 PST + 639 | 509 | 00639_update9 | Mon Feb 09 00:00:00 1970 PST + 640 | 0 | 00640 | Tue Feb 10 00:00:00 1970 PST + 641 | 1 | 00641 | Wed Feb 11 00:00:00 1970 PST + 643 | 303 | 00643_update3 | Fri Feb 13 00:00:00 1970 PST + 644 | 4 | 00644 | Sat Feb 14 00:00:00 1970 PST + 646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST + 647 | 407 | 00647_update7 | Tue Feb 17 00:00:00 1970 PST + 648 | 8 | 00648 | Wed Feb 18 00:00:00 1970 PST + 649 | 509 | 00649_update9 | Thu Feb 19 00:00:00 1970 PST + 650 | 0 | 00650 | Fri Feb 20 00:00:00 1970 PST + 651 | 1 | 00651 | Sat Feb 21 00:00:00 1970 PST + 653 | 303 | 00653_update3 | Mon Feb 23 00:00:00 1970 PST + 654 | 4 | 00654 | Tue Feb 24 00:00:00 1970 PST + 656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST + 657 | 407 | 00657_update7 | Fri Feb 27 00:00:00 1970 PST + 658 | 8 | 00658 | Sat Feb 28 00:00:00 1970 PST + 659 | 509 | 00659_update9 | Sun Mar 01 00:00:00 1970 PST + 660 | 0 | 00660 | Mon Mar 02 00:00:00 1970 PST + 661 | 1 | 00661 | Tue Mar 03 00:00:00 1970 PST + 663 | 303 | 00663_update3 | Thu Mar 05 00:00:00 1970 PST + 664 | 4 | 00664 | Fri Mar 06 00:00:00 1970 PST + 666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST + 667 | 407 | 00667_update7 | Mon Mar 09 00:00:00 1970 PST + 668 | 8 | 00668 | Tue Mar 10 00:00:00 1970 PST + 669 | 509 | 00669_update9 | Wed Mar 11 00:00:00 1970 PST + 670 | 0 | 00670 | Thu Mar 12 00:00:00 1970 PST + 671 | 1 | 00671 | Fri Mar 13 00:00:00 1970 PST + 673 | 303 | 00673_update3 | Sun Mar 15 00:00:00 1970 PST + 674 | 4 | 00674 | Mon Mar 16 00:00:00 1970 PST + 676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST + 677 | 407 | 00677_update7 | Thu Mar 19 00:00:00 1970 PST + 678 | 8 | 00678 | Fri Mar 20 00:00:00 1970 PST + 679 | 509 | 00679_update9 | Sat Mar 21 00:00:00 1970 PST + 680 | 0 | 00680 | Sun Mar 22 00:00:00 1970 PST + 681 | 1 | 00681 | Mon Mar 23 00:00:00 1970 PST + 683 | 303 | 00683_update3 | Wed Mar 25 00:00:00 1970 PST + 684 | 4 | 00684 | Thu Mar 26 00:00:00 1970 PST + 686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST + 687 | 407 | 00687_update7 | Sun Mar 29 00:00:00 1970 PST + 688 | 8 | 00688 | Mon Mar 30 00:00:00 1970 PST + 689 | 509 | 00689_update9 | Tue Mar 31 00:00:00 1970 PST + 690 | 0 | 00690 | Wed Apr 01 00:00:00 1970 PST + 691 | 1 | 00691 | Thu Apr 02 00:00:00 1970 PST + 693 | 303 | 00693_update3 | Sat Apr 04 00:00:00 1970 PST + 694 | 4 | 00694 | Sun Apr 05 00:00:00 1970 PST + 696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST + 697 | 407 | 00697_update7 | Wed Apr 08 00:00:00 1970 PST + 698 | 8 | 00698 | Thu Apr 09 00:00:00 1970 PST + 699 | 509 | 00699_update9 | Fri Apr 10 00:00:00 1970 PST + 700 | 0 | 00700 | Thu Jan 01 00:00:00 1970 PST + 701 | 1 | 00701 | Fri Jan 02 00:00:00 1970 PST + 703 | 303 | 00703_update3 | Sun Jan 04 00:00:00 1970 PST + 704 | 4 | 00704 | Mon Jan 05 00:00:00 1970 PST + 706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST + 707 | 407 | 00707_update7 | Thu Jan 08 00:00:00 1970 PST + 708 | 8 | 00708 | Fri Jan 09 00:00:00 1970 PST + 709 | 509 | 00709_update9 | Sat Jan 10 00:00:00 1970 PST + 710 | 0 | 00710 | Sun Jan 11 00:00:00 1970 PST + 711 | 1 | 00711 | Mon Jan 12 00:00:00 1970 PST + 713 | 303 | 00713_update3 | Wed Jan 14 00:00:00 1970 PST + 714 | 4 | 00714 | Thu Jan 15 00:00:00 1970 PST + 716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST + 717 | 407 | 00717_update7 | Sun Jan 18 00:00:00 1970 PST + 718 | 8 | 00718 | Mon Jan 19 00:00:00 1970 PST + 719 | 509 | 00719_update9 | Tue Jan 20 00:00:00 1970 PST + 720 | 0 | 00720 | Wed Jan 21 00:00:00 1970 PST + 721 | 1 | 00721 | Thu Jan 22 00:00:00 1970 PST + 723 | 303 | 00723_update3 | Sat Jan 24 00:00:00 1970 PST + 724 | 4 | 00724 | Sun Jan 25 00:00:00 1970 PST + 726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST + 727 | 407 | 00727_update7 | Wed Jan 28 00:00:00 1970 PST + 728 | 8 | 00728 | Thu Jan 29 00:00:00 1970 PST + 729 | 509 | 00729_update9 | Fri Jan 30 00:00:00 1970 PST + 730 | 0 | 00730 | Sat Jan 31 00:00:00 1970 PST + 731 | 1 | 00731 | Sun Feb 01 00:00:00 1970 PST + 733 | 303 | 00733_update3 | Tue Feb 03 00:00:00 1970 PST + 734 | 4 | 00734 | Wed Feb 04 00:00:00 1970 PST + 736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST + 737 | 407 | 00737_update7 | Sat Feb 07 00:00:00 1970 PST + 738 | 8 | 00738 | Sun Feb 08 00:00:00 1970 PST + 739 | 509 | 00739_update9 | Mon Feb 09 00:00:00 1970 PST + 740 | 0 | 00740 | Tue Feb 10 00:00:00 1970 PST + 741 | 1 | 00741 | Wed Feb 11 00:00:00 1970 PST + 743 | 303 | 00743_update3 | Fri Feb 13 00:00:00 1970 PST + 744 | 4 | 00744 | Sat Feb 14 00:00:00 1970 PST + 746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST + 747 | 407 | 00747_update7 | Tue Feb 17 00:00:00 1970 PST + 748 | 8 | 00748 | Wed Feb 18 00:00:00 1970 PST + 749 | 509 | 00749_update9 | Thu Feb 19 00:00:00 1970 PST + 750 | 0 | 00750 | Fri Feb 20 00:00:00 1970 PST + 751 | 1 | 00751 | Sat Feb 21 00:00:00 1970 PST + 753 | 303 | 00753_update3 | Mon Feb 23 00:00:00 1970 PST + 754 | 4 | 00754 | Tue Feb 24 00:00:00 1970 PST + 756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST + 757 | 407 | 00757_update7 | Fri Feb 27 00:00:00 1970 PST + 758 | 8 | 00758 | Sat Feb 28 00:00:00 1970 PST + 759 | 509 | 00759_update9 | Sun Mar 01 00:00:00 1970 PST + 760 | 0 | 00760 | Mon Mar 02 00:00:00 1970 PST + 761 | 1 | 00761 | Tue Mar 03 00:00:00 1970 PST + 763 | 303 | 00763_update3 | Thu Mar 05 00:00:00 1970 PST + 764 | 4 | 00764 | Fri Mar 06 00:00:00 1970 PST + 766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST + 767 | 407 | 00767_update7 | Mon Mar 09 00:00:00 1970 PST + 768 | 8 | 00768 | Tue Mar 10 00:00:00 1970 PST + 769 | 509 | 00769_update9 | Wed Mar 11 00:00:00 1970 PST + 770 | 0 | 00770 | Thu Mar 12 00:00:00 1970 PST + 771 | 1 | 00771 | Fri Mar 13 00:00:00 1970 PST + 773 | 303 | 00773_update3 | Sun Mar 15 00:00:00 1970 PST + 774 | 4 | 00774 | Mon Mar 16 00:00:00 1970 PST + 776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST + 777 | 407 | 00777_update7 | Thu Mar 19 00:00:00 1970 PST + 778 | 8 | 00778 | Fri Mar 20 00:00:00 1970 PST + 779 | 509 | 00779_update9 | Sat Mar 21 00:00:00 1970 PST + 780 | 0 | 00780 | Sun Mar 22 00:00:00 1970 PST + 781 | 1 | 00781 | Mon Mar 23 00:00:00 1970 PST + 783 | 303 | 00783_update3 | Wed Mar 25 00:00:00 1970 PST + 784 | 4 | 00784 | Thu Mar 26 00:00:00 1970 PST + 786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST + 787 | 407 | 00787_update7 | Sun Mar 29 00:00:00 1970 PST + 788 | 8 | 00788 | Mon Mar 30 00:00:00 1970 PST + 789 | 509 | 00789_update9 | Tue Mar 31 00:00:00 1970 PST + 790 | 0 | 00790 | Wed Apr 01 00:00:00 1970 PST + 791 | 1 | 00791 | Thu Apr 02 00:00:00 1970 PST + 793 | 303 | 00793_update3 | Sat Apr 04 00:00:00 1970 PST + 794 | 4 | 00794 | Sun Apr 05 00:00:00 1970 PST + 796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST + 797 | 407 | 00797_update7 | Wed Apr 08 00:00:00 1970 PST + 798 | 8 | 00798 | Thu Apr 09 00:00:00 1970 PST + 799 | 509 | 00799_update9 | Fri Apr 10 00:00:00 1970 PST + 800 | 0 | 00800 | Thu Jan 01 00:00:00 1970 PST + 801 | 1 | 00801 | Fri Jan 02 00:00:00 1970 PST + 803 | 303 | 00803_update3 | Sun Jan 04 00:00:00 1970 PST + 804 | 4 | 00804 | Mon Jan 05 00:00:00 1970 PST + 806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST + 807 | 407 | 00807_update7 | Thu Jan 08 00:00:00 1970 PST + 808 | 8 | 00808 | Fri Jan 09 00:00:00 1970 PST + 809 | 509 | 00809_update9 | Sat Jan 10 00:00:00 1970 PST + 810 | 0 | 00810 | Sun Jan 11 00:00:00 1970 PST + 811 | 1 | 00811 | Mon Jan 12 00:00:00 1970 PST + 813 | 303 | 00813_update3 | Wed Jan 14 00:00:00 1970 PST + 814 | 4 | 00814 | Thu Jan 15 00:00:00 1970 PST + 816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST + 817 | 407 | 00817_update7 | Sun Jan 18 00:00:00 1970 PST + 818 | 8 | 00818 | Mon Jan 19 00:00:00 1970 PST + 819 | 509 | 00819_update9 | Tue Jan 20 00:00:00 1970 PST + 820 | 0 | 00820 | Wed Jan 21 00:00:00 1970 PST + 821 | 1 | 00821 | Thu Jan 22 00:00:00 1970 PST + 823 | 303 | 00823_update3 | Sat Jan 24 00:00:00 1970 PST + 824 | 4 | 00824 | Sun Jan 25 00:00:00 1970 PST + 826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST + 827 | 407 | 00827_update7 | Wed Jan 28 00:00:00 1970 PST + 828 | 8 | 00828 | Thu Jan 29 00:00:00 1970 PST + 829 | 509 | 00829_update9 | Fri Jan 30 00:00:00 1970 PST + 830 | 0 | 00830 | Sat Jan 31 00:00:00 1970 PST + 831 | 1 | 00831 | Sun Feb 01 00:00:00 1970 PST + 833 | 303 | 00833_update3 | Tue Feb 03 00:00:00 1970 PST + 834 | 4 | 00834 | Wed Feb 04 00:00:00 1970 PST + 836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST + 837 | 407 | 00837_update7 | Sat Feb 07 00:00:00 1970 PST + 838 | 8 | 00838 | Sun Feb 08 00:00:00 1970 PST + 839 | 509 | 00839_update9 | Mon Feb 09 00:00:00 1970 PST + 840 | 0 | 00840 | Tue Feb 10 00:00:00 1970 PST + 841 | 1 | 00841 | Wed Feb 11 00:00:00 1970 PST + 843 | 303 | 00843_update3 | Fri Feb 13 00:00:00 1970 PST + 844 | 4 | 00844 | Sat Feb 14 00:00:00 1970 PST + 846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST + 847 | 407 | 00847_update7 | Tue Feb 17 00:00:00 1970 PST + 848 | 8 | 00848 | Wed Feb 18 00:00:00 1970 PST + 849 | 509 | 00849_update9 | Thu Feb 19 00:00:00 1970 PST + 850 | 0 | 00850 | Fri Feb 20 00:00:00 1970 PST + 851 | 1 | 00851 | Sat Feb 21 00:00:00 1970 PST + 853 | 303 | 00853_update3 | Mon Feb 23 00:00:00 1970 PST + 854 | 4 | 00854 | Tue Feb 24 00:00:00 1970 PST + 856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST + 857 | 407 | 00857_update7 | Fri Feb 27 00:00:00 1970 PST + 858 | 8 | 00858 | Sat Feb 28 00:00:00 1970 PST + 859 | 509 | 00859_update9 | Sun Mar 01 00:00:00 1970 PST + 860 | 0 | 00860 | Mon Mar 02 00:00:00 1970 PST + 861 | 1 | 00861 | Tue Mar 03 00:00:00 1970 PST + 863 | 303 | 00863_update3 | Thu Mar 05 00:00:00 1970 PST + 864 | 4 | 00864 | Fri Mar 06 00:00:00 1970 PST + 866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST + 867 | 407 | 00867_update7 | Mon Mar 09 00:00:00 1970 PST + 868 | 8 | 00868 | Tue Mar 10 00:00:00 1970 PST + 869 | 509 | 00869_update9 | Wed Mar 11 00:00:00 1970 PST + 870 | 0 | 00870 | Thu Mar 12 00:00:00 1970 PST + 871 | 1 | 00871 | Fri Mar 13 00:00:00 1970 PST + 873 | 303 | 00873_update3 | Sun Mar 15 00:00:00 1970 PST + 874 | 4 | 00874 | Mon Mar 16 00:00:00 1970 PST + 876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST + 877 | 407 | 00877_update7 | Thu Mar 19 00:00:00 1970 PST + 878 | 8 | 00878 | Fri Mar 20 00:00:00 1970 PST + 879 | 509 | 00879_update9 | Sat Mar 21 00:00:00 1970 PST + 880 | 0 | 00880 | Sun Mar 22 00:00:00 1970 PST + 881 | 1 | 00881 | Mon Mar 23 00:00:00 1970 PST + 883 | 303 | 00883_update3 | Wed Mar 25 00:00:00 1970 PST + 884 | 4 | 00884 | Thu Mar 26 00:00:00 1970 PST + 886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST + 887 | 407 | 00887_update7 | Sun Mar 29 00:00:00 1970 PST + 888 | 8 | 00888 | Mon Mar 30 00:00:00 1970 PST + 889 | 509 | 00889_update9 | Tue Mar 31 00:00:00 1970 PST + 890 | 0 | 00890 | Wed Apr 01 00:00:00 1970 PST + 891 | 1 | 00891 | Thu Apr 02 00:00:00 1970 PST + 893 | 303 | 00893_update3 | Sat Apr 04 00:00:00 1970 PST + 894 | 4 | 00894 | Sun Apr 05 00:00:00 1970 PST + 896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST + 897 | 407 | 00897_update7 | Wed Apr 08 00:00:00 1970 PST + 898 | 8 | 00898 | Thu Apr 09 00:00:00 1970 PST + 899 | 509 | 00899_update9 | Fri Apr 10 00:00:00 1970 PST + 900 | 0 | 00900 | Thu Jan 01 00:00:00 1970 PST + 901 | 1 | 00901 | Fri Jan 02 00:00:00 1970 PST + 903 | 303 | 00903_update3 | Sun Jan 04 00:00:00 1970 PST + 904 | 4 | 00904 | Mon Jan 05 00:00:00 1970 PST + 906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST + 907 | 407 | 00907_update7 | Thu Jan 08 00:00:00 1970 PST + 908 | 8 | 00908 | Fri Jan 09 00:00:00 1970 PST + 909 | 509 | 00909_update9 | Sat Jan 10 00:00:00 1970 PST + 910 | 0 | 00910 | Sun Jan 11 00:00:00 1970 PST + 911 | 1 | 00911 | Mon Jan 12 00:00:00 1970 PST + 913 | 303 | 00913_update3 | Wed Jan 14 00:00:00 1970 PST + 914 | 4 | 00914 | Thu Jan 15 00:00:00 1970 PST + 916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST + 917 | 407 | 00917_update7 | Sun Jan 18 00:00:00 1970 PST + 918 | 8 | 00918 | Mon Jan 19 00:00:00 1970 PST + 919 | 509 | 00919_update9 | Tue Jan 20 00:00:00 1970 PST + 920 | 0 | 00920 | Wed Jan 21 00:00:00 1970 PST + 921 | 1 | 00921 | Thu Jan 22 00:00:00 1970 PST + 923 | 303 | 00923_update3 | Sat Jan 24 00:00:00 1970 PST + 924 | 4 | 00924 | Sun Jan 25 00:00:00 1970 PST + 926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST + 927 | 407 | 00927_update7 | Wed Jan 28 00:00:00 1970 PST + 928 | 8 | 00928 | Thu Jan 29 00:00:00 1970 PST + 929 | 509 | 00929_update9 | Fri Jan 30 00:00:00 1970 PST + 930 | 0 | 00930 | Sat Jan 31 00:00:00 1970 PST + 931 | 1 | 00931 | Sun Feb 01 00:00:00 1970 PST + 933 | 303 | 00933_update3 | Tue Feb 03 00:00:00 1970 PST + 934 | 4 | 00934 | Wed Feb 04 00:00:00 1970 PST + 936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST + 937 | 407 | 00937_update7 | Sat Feb 07 00:00:00 1970 PST + 938 | 8 | 00938 | Sun Feb 08 00:00:00 1970 PST + 939 | 509 | 00939_update9 | Mon Feb 09 00:00:00 1970 PST + 940 | 0 | 00940 | Tue Feb 10 00:00:00 1970 PST + 941 | 1 | 00941 | Wed Feb 11 00:00:00 1970 PST + 943 | 303 | 00943_update3 | Fri Feb 13 00:00:00 1970 PST + 944 | 4 | 00944 | Sat Feb 14 00:00:00 1970 PST + 946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST + 947 | 407 | 00947_update7 | Tue Feb 17 00:00:00 1970 PST + 948 | 8 | 00948 | Wed Feb 18 00:00:00 1970 PST + 949 | 509 | 00949_update9 | Thu Feb 19 00:00:00 1970 PST + 950 | 0 | 00950 | Fri Feb 20 00:00:00 1970 PST + 951 | 1 | 00951 | Sat Feb 21 00:00:00 1970 PST + 953 | 303 | 00953_update3 | Mon Feb 23 00:00:00 1970 PST + 954 | 4 | 00954 | Tue Feb 24 00:00:00 1970 PST + 956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST + 957 | 407 | 00957_update7 | Fri Feb 27 00:00:00 1970 PST + 958 | 8 | 00958 | Sat Feb 28 00:00:00 1970 PST + 959 | 509 | 00959_update9 | Sun Mar 01 00:00:00 1970 PST + 960 | 0 | 00960 | Mon Mar 02 00:00:00 1970 PST + 961 | 1 | 00961 | Tue Mar 03 00:00:00 1970 PST + 963 | 303 | 00963_update3 | Thu Mar 05 00:00:00 1970 PST + 964 | 4 | 00964 | Fri Mar 06 00:00:00 1970 PST + 966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST + 967 | 407 | 00967_update7 | Mon Mar 09 00:00:00 1970 PST + 968 | 8 | 00968 | Tue Mar 10 00:00:00 1970 PST + 969 | 509 | 00969_update9 | Wed Mar 11 00:00:00 1970 PST + 970 | 0 | 00970 | Thu Mar 12 00:00:00 1970 PST + 971 | 1 | 00971 | Fri Mar 13 00:00:00 1970 PST + 973 | 303 | 00973_update3 | Sun Mar 15 00:00:00 1970 PST + 974 | 4 | 00974 | Mon Mar 16 00:00:00 1970 PST + 976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST + 977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST + 978 | 8 | 00978 | Fri Mar 20 00:00:00 1970 PST + 979 | 509 | 00979_update9 | Sat Mar 21 00:00:00 1970 PST + 980 | 0 | 00980 | Sun Mar 22 00:00:00 1970 PST + 981 | 1 | 00981 | Mon Mar 23 00:00:00 1970 PST + 983 | 303 | 00983_update3 | Wed Mar 25 00:00:00 1970 PST + 984 | 4 | 00984 | Thu Mar 26 00:00:00 1970 PST + 986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST + 987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST + 988 | 8 | 00988 | Mon Mar 30 00:00:00 1970 PST + 989 | 509 | 00989_update9 | Tue Mar 31 00:00:00 1970 PST + 990 | 0 | 00990 | Wed Apr 01 00:00:00 1970 PST + 991 | 1 | 00991 | Thu Apr 02 00:00:00 1970 PST + 993 | 303 | 00993_update3 | Sat Apr 04 00:00:00 1970 PST + 994 | 4 | 00994 | Sun Apr 05 00:00:00 1970 PST + 996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST + 997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST + 998 | 8 | 00998 | Thu Apr 09 00:00:00 1970 PST + 999 | 509 | 00999_update9 | Fri Apr 10 00:00:00 1970 PST + 1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST + 1001 | 101 | 0000100001 | + 1003 | 403 | 0000300003_update3 | + 1004 | 104 | 0000400004 | + 1006 | 106 | 0000600006 | + 1007 | 507 | 0000700007_update7 | + 1008 | 108 | 0000800008 | + 1009 | 609 | 0000900009_update9 | + 1010 | 100 | 0001000010 | + 1011 | 101 | 0001100011 | + 1013 | 403 | 0001300013_update3 | + 1014 | 104 | 0001400014 | + 1016 | 106 | 0001600016 | + 1017 | 507 | 0001700017_update7 | + 1018 | 108 | 0001800018 | + 1019 | 609 | 0001900019_update9 | + 1020 | 100 | 0002000020 | + 1101 | 201 | aaa | + 1103 | 503 | ccc_update3 | + 1104 | 204 | ddd | +(819 rows) + +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.ft2 + Output: (ft2.tableoid)::regclass + Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + Batch Size: 1 + -> Result + Output: 1200, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2 '::character(10), NULL::user_enum +(6 rows) + +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; + tableoid +---------- + ft2 +(1 row) + +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down + QUERY PLAN +------------------------------------------------------------------------------------ + Update on public.ft2 + Output: (tableoid)::regclass + -> Foreign Update on public.ft2 + Remote SQL: UPDATE "S 1"."T 1" SET c3 = 'bar'::text WHERE (("C 1" = 1200)) +(4 rows) + +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; + tableoid +---------- + ft2 +(1 row) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down + QUERY PLAN +-------------------------------------------------------------------- + Delete on public.ft2 + Output: (tableoid)::regclass + -> Foreign Delete on public.ft2 + Remote SQL: DELETE FROM "S 1"."T 1" WHERE (("C 1" = 1200)) +(4 rows) + +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; + tableoid +---------- + ft2 +(1 row) + +-- Test UPDATE/DELETE with RETURNING on a three-table join +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; -- can be pushed down + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: ft2.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.*, ft4.c1, ft4.c2, ft4.c3 + -> Foreign Update + Remote SQL: UPDATE "S 1"."T 1" r1 SET c3 = 'foo'::text FROM ("S 1"."T 3" r2 INNER JOIN "S 1"."T 4" r3 ON (TRUE)) WHERE ((r2.c1 = r3.c1)) AND ((r1.c2 = r2.c1)) AND ((r1."C 1" > 1200)) RETURNING r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r2.c1, r2.c2, r2.c3 +(4 rows) + +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; + ft2 | c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | ft4 | c1 | c2 | c3 +--------------------------------+------+----+-----+----+----+----+------------+----+----------------+----+----+-------- + (1206,6,foo,,,,"ft2 ",) | 1206 | 6 | foo | | | | ft2 | | (6,7,AAA006) | 6 | 7 | AAA006 + (1212,12,foo,,,,"ft2 ",) | 1212 | 12 | foo | | | | ft2 | | (12,13,AAA012) | 12 | 13 | AAA012 + (1218,18,foo,,,,"ft2 ",) | 1218 | 18 | foo | | | | ft2 | | (18,19,AAA018) | 18 | 19 | AAA018 + (1224,24,foo,,,,"ft2 ",) | 1224 | 24 | foo | | | | ft2 | | (24,25,AAA024) | 24 | 25 | AAA024 + (1230,30,foo,,,,"ft2 ",) | 1230 | 30 | foo | | | | ft2 | | (30,31,AAA030) | 30 | 31 | AAA030 + (1236,36,foo,,,,"ft2 ",) | 1236 | 36 | foo | | | | ft2 | | (36,37,AAA036) | 36 | 37 | AAA036 + (1242,42,foo,,,,"ft2 ",) | 1242 | 42 | foo | | | | ft2 | | (42,43,AAA042) | 42 | 43 | AAA042 + (1248,48,foo,,,,"ft2 ",) | 1248 | 48 | foo | | | | ft2 | | (48,49,AAA048) | 48 | 49 | AAA048 + (1254,54,foo,,,,"ft2 ",) | 1254 | 54 | foo | | | | ft2 | | (54,55,AAA054) | 54 | 55 | AAA054 + (1260,60,foo,,,,"ft2 ",) | 1260 | 60 | foo | | | | ft2 | | (60,61,AAA060) | 60 | 61 | AAA060 + (1266,66,foo,,,,"ft2 ",) | 1266 | 66 | foo | | | | ft2 | | (66,67,AAA066) | 66 | 67 | AAA066 + (1272,72,foo,,,,"ft2 ",) | 1272 | 72 | foo | | | | ft2 | | (72,73,AAA072) | 72 | 73 | AAA072 + (1278,78,foo,,,,"ft2 ",) | 1278 | 78 | foo | | | | ft2 | | (78,79,AAA078) | 78 | 79 | AAA078 + (1284,84,foo,,,,"ft2 ",) | 1284 | 84 | foo | | | | ft2 | | (84,85,AAA084) | 84 | 85 | AAA084 + (1290,90,foo,,,,"ft2 ",) | 1290 | 90 | foo | | | | ft2 | | (90,91,AAA090) | 90 | 91 | AAA090 + (1296,96,foo,,,,"ft2 ",) | 1296 | 96 | foo | | | | ft2 | | (96,97,AAA096) | 96 | 97 | AAA096 +(16 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; -- can be pushed down + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Delete on public.ft2 + Output: 100 + -> Foreign Delete + Remote SQL: DELETE FROM "S 1"."T 1" r1 USING ("S 1"."T 3" r2 LEFT JOIN "S 1"."T 4" r3 ON (((r2.c1 = r3.c1)))) WHERE ((r1.c2 = r2.c1)) AND ((r1."C 1" > 1200)) AND (((r1."C 1" % 10) = 0)) +(4 rows) + +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; + ?column? +---------- + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 + 100 +(10 rows) + +DELETE FROM ft2 WHERE ft2.c1 > 1200; +-- Test UPDATE with a MULTIEXPR sub-select +-- (maybe someday this'll be remotely executable, but not today) +EXPLAIN (verbose, costs off) +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 target + Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c7 = $3 WHERE ctid = $1 + -> Foreign Scan on public.ft2 target + Output: $1, $2, (SubPlan 1 (returns $1,$2)), target.ctid, target.* + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1100)) FOR UPDATE + SubPlan 1 (returns $1,$2) + -> Foreign Scan on public.ft2 src + Output: (src.c2 * 10), src.c7 + Remote SQL: SELECT c2, c7 FROM "S 1"."T 1" WHERE (($1::integer = "C 1")) +(9 rows) + +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +UPDATE ft2 AS target SET (c2) = ( + SELECT c2 / 10 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +-- Test UPDATE involving a join that can be pushed down, +-- but a SET clause that can't be +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 d + Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1 + -> Foreign Scan + Output: CASE WHEN (random() >= '0'::double precision) THEN d.c2 ELSE 0 END, d.ctid, d.*, t.* + Relations: (public.ft2 d) INNER JOIN (public.ft2 t) + Remote SQL: SELECT r1.c2, r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1."C 1" > 1000)))) FOR UPDATE OF r1 + -> Hash Join + Output: d.c2, d.ctid, d.*, t.* + Hash Cond: (d.c1 = t.c1) + -> Foreign Scan on public.ft2 d + Output: d.c2, d.ctid, d.*, d.c1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 1000)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE + -> Hash + Output: t.*, t.c1 + -> Foreign Scan on public.ft2 t + Output: t.*, t.c1 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" +(17 rows) + +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; +-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing +-- user-defined operators/functions +ALTER SERVER loopback OPTIONS (DROP extensions); +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Foreign Scan on public.ft2 + Output: 'bar'::text, ctid, ft2.* + Filter: (postgres_fdw_abs(ft2.c1) > 2000) + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE +(7 rows) + +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+----+-----+----+----+----+------------+---- + 2001 | 1 | bar | | | | ft2 | + 2002 | 2 | bar | | | | ft2 | + 2003 | 3 | bar | | | | ft2 | + 2004 | 4 | bar | | | | ft2 | + 2005 | 5 | bar | | | | ft2 | + 2006 | 6 | bar | | | | ft2 | + 2007 | 7 | bar | | | | ft2 | + 2008 | 8 | bar | | | | ft2 | + 2009 | 9 | bar | | | | ft2 | + 2010 | 0 | bar | | | | ft2 | +(10 rows) + +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; -- can't be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Update on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 + Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8 + -> Nested Loop + Output: 'baz'::text, ft2.ctid, ft2.*, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3 + Join Filter: (ft2.c2 === ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.ctid, ft2.*, ft2.c2 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + -> Foreign Scan + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3 + Relations: (public.ft4) INNER JOIN (public.ft5) + Remote SQL: SELECT CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, r2.c1, r2.c2, r2.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r3.c1, r3.c2, r3.c3 FROM ("S 1"."T 3" r2 INNER JOIN "S 1"."T 4" r3 ON (((r2.c1 = r3.c1)))) + -> Hash Join + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3 + Hash Cond: (ft4.c1 = ft5.c1) + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1, ft4.c2, ft4.c3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Hash + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3 + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1, ft5.c2, ft5.c3 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" +(24 rows) + +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c1 | c2 | c3 | c1 | c2 | c3 +------+----+-----+----+----+----+------------+----+----+----+--------+----+----+-------- + 2006 | 6 | baz | | | | ft2 | | 6 | 7 | AAA006 | 6 | 7 | AAA006 +(1 row) + +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Delete on public.ft2 + Output: ft2.c1, ft2.c2, ft2.c3 + Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c2, c3 + -> Foreign Scan + Output: ft2.ctid, ft4.*, ft5.* + Filter: (ft4.c1 === ft5.c1) + Relations: ((public.ft2) INNER JOIN (public.ft4)) INNER JOIN (public.ft5) + Remote SQL: SELECT r1.ctid, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r2.c1, r3.c1 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c2 = r2.c1)) AND ((r1."C 1" > 2000)))) INNER JOIN "S 1"."T 4" r3 ON (TRUE)) FOR UPDATE OF r1 + -> Nested Loop + Output: ft2.ctid, ft4.*, ft5.*, ft4.c1, ft5.c1 + -> Nested Loop + Output: ft2.ctid, ft4.*, ft4.c1 + Join Filter: (ft2.c2 = ft4.c1) + -> Foreign Scan on public.ft2 + Output: ft2.ctid, ft2.c2 + Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE + -> Foreign Scan on public.ft4 + Output: ft4.*, ft4.c1 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" + -> Foreign Scan on public.ft5 + Output: ft5.*, ft5.c1 + Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" +(22 rows) + +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; + c1 | c2 | c3 +------+----+----- + 2006 | 6 | baz +(1 row) + +DELETE FROM ft2 WHERE ft2.c1 > 2000; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- Test that trigger on remote table works as expected +CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ +BEGIN + NEW.c3 = NEW.c3 || '_trig_update'; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE + ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG(); +INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----------------+----+----+----+------------+---- + 1208 | 818 | fff_trig_update | | | | ft2 | +(1 row) + +INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----------------+----+----+------+------------+---- + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | +(1 row) + +UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+------------------------+------------------------------+--------------------------+----+------------+----- + 8 | 608 | 00008_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 18 | 608 | 00018_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 28 | 608 | 00028_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 38 | 608 | 00038_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 48 | 608 | 00048_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 58 | 608 | 00058_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 68 | 608 | 00068_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 78 | 608 | 00078_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 88 | 608 | 00088_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 98 | 608 | 00098_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 108 | 608 | 00108_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 118 | 608 | 00118_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 128 | 608 | 00128_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 138 | 608 | 00138_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 148 | 608 | 00148_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 158 | 608 | 00158_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 168 | 608 | 00168_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 178 | 608 | 00178_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 188 | 608 | 00188_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 198 | 608 | 00198_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 208 | 608 | 00208_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 218 | 608 | 00218_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 228 | 608 | 00228_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 238 | 608 | 00238_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 248 | 608 | 00248_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 258 | 608 | 00258_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 268 | 608 | 00268_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 278 | 608 | 00278_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 288 | 608 | 00288_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 298 | 608 | 00298_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 308 | 608 | 00308_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 318 | 608 | 00318_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 328 | 608 | 00328_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 338 | 608 | 00338_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 348 | 608 | 00348_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 358 | 608 | 00358_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 368 | 608 | 00368_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 378 | 608 | 00378_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 388 | 608 | 00388_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 398 | 608 | 00398_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 408 | 608 | 00408_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 418 | 608 | 00418_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 428 | 608 | 00428_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 438 | 608 | 00438_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 448 | 608 | 00448_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 458 | 608 | 00458_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 468 | 608 | 00468_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 478 | 608 | 00478_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 488 | 608 | 00488_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 498 | 608 | 00498_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 508 | 608 | 00508_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 518 | 608 | 00518_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 528 | 608 | 00528_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 538 | 608 | 00538_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 548 | 608 | 00548_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 558 | 608 | 00558_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 568 | 608 | 00568_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 578 | 608 | 00578_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 588 | 608 | 00588_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 598 | 608 | 00598_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 608 | 608 | 00608_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 618 | 608 | 00618_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 628 | 608 | 00628_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 638 | 608 | 00638_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 648 | 608 | 00648_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 658 | 608 | 00658_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 668 | 608 | 00668_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 678 | 608 | 00678_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 688 | 608 | 00688_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 698 | 608 | 00698_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 708 | 608 | 00708_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 718 | 608 | 00718_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 728 | 608 | 00728_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 738 | 608 | 00738_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 748 | 608 | 00748_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 758 | 608 | 00758_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 768 | 608 | 00768_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 778 | 608 | 00778_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 788 | 608 | 00788_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 798 | 608 | 00798_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 808 | 608 | 00808_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 818 | 608 | 00818_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 828 | 608 | 00828_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 838 | 608 | 00838_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 848 | 608 | 00848_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 858 | 608 | 00858_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 868 | 608 | 00868_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 878 | 608 | 00878_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 888 | 608 | 00888_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 898 | 608 | 00898_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 908 | 608 | 00908_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo + 918 | 608 | 00918_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo + 928 | 608 | 00928_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo + 938 | 608 | 00938_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo + 948 | 608 | 00948_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo + 958 | 608 | 00958_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo + 968 | 608 | 00968_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo + 978 | 608 | 00978_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo + 988 | 608 | 00988_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo + 998 | 608 | 00998_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo + 1008 | 708 | 0000800008_trig_update | | | | ft2 | + 1018 | 708 | 0001800018_trig_update | | | | ft2 | +(102 rows) + +-- Test errors thrown on remote side during update +ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0); +INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key +ERROR: duplicate key value violates unique constraint "t1_pkey" +DETAIL: Key ("C 1")=(11) already exists. +CONTEXT: remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT DO NOTHING; -- works +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO NOTHING; -- unsupported +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO UPDATE SET c3 = 'ffg'; -- unsupported +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: Failing row contains (1111, -2, null, null, null, null, ft1 , null). +CONTEXT: remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo). +CONTEXT: remote SQL command: UPDATE "S 1"."T 1" SET c2 = (- c2) WHERE (("C 1" = 1)) +-- Test savepoint/rollback behavior +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 0 | 100 + 1 | 100 + 4 | 100 + 6 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 0 | 100 + 1 | 100 + 4 | 100 + 6 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +begin; +update ft2 set c2 = 42 where c2 = 0; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 4 | 100 + 6 | 100 + 42 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +savepoint s1; +update ft2 set c2 = 44 where c2 = 4; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +release savepoint s1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +savepoint s2; +update ft2 set c2 = 46 where c2 = 6; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 42 | 100 + 44 | 100 + 46 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +rollback to savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +release savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +savepoint s3; +update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: Failing row contains (10, -2, 00010_trig_update_trig_update, 1970-01-11 08:00:00+00, 1970-01-11 00:00:00, 0, 0 , foo). +CONTEXT: remote SQL command: UPDATE "S 1"."T 1" SET c2 = (-2) WHERE ((c2 = 42)) AND (("C 1" = 10)) +rollback to savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +release savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +-- none of the above is committed yet remotely +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 0 | 100 + 1 | 100 + 4 | 100 + 6 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +commit; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + c2 | count +-----+------- + 1 | 100 + 6 | 100 + 42 | 100 + 44 | 100 + 100 | 2 + 101 | 2 + 104 | 2 + 106 | 2 + 201 | 1 + 204 | 1 + 303 | 100 + 403 | 2 + 407 | 100 +(13 rows) + +VACUUM ANALYZE "S 1"."T 1"; +-- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs +-- FIRST behavior here. +-- ORDER BY DESC NULLS LAST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 DESC NULLS LAST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 795::bigint +(3 rows) + +SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+--------------------+------------------------------+--------------------------+------+------------+----- + 960 | 42 | 00960_trig_update | Mon Mar 02 00:00:00 1970 PST | Mon Mar 02 00:00:00 1970 | 0 | 0 | foo + 970 | 42 | 00970_trig_update | Thu Mar 12 00:00:00 1970 PST | Thu Mar 12 00:00:00 1970 | 0 | 0 | foo + 980 | 42 | 00980_trig_update | Sun Mar 22 00:00:00 1970 PST | Sun Mar 22 00:00:00 1970 | 0 | 0 | foo + 990 | 42 | 00990_trig_update | Wed Apr 01 00:00:00 1970 PST | Wed Apr 01 00:00:00 1970 | 0 | 0 | foo + 1000 | 42 | 01000_trig_update | Thu Jan 01 00:00:00 1970 PST | Thu Jan 01 00:00:00 1970 | 0 | 0 | foo + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | + 1001 | 101 | 0000100001 | | | | ft2 | + 1003 | 403 | 0000300003_update3 | | | | ft2 | + 1004 | 104 | 0000400004 | | | | ft2 | + 1006 | 106 | 0000600006 | | | | ft2 | +(10 rows) + +-- ORDER BY DESC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 DESC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint +(3 rows) + +SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-----------------+------------------------------+--------------------------+----+------------+----- + 1020 | 100 | 0002000020 | | | | ft2 | + 1101 | 201 | aaa | | | | ft2 | + 1103 | 503 | ccc_update3 | | | | ft2 | + 1104 | 204 | ddd | | | | ft2 | + 1208 | 818 | fff_trig_update | | | | ft2 | + 9 | 509 | 00009_update9 | Sat Jan 10 00:00:00 1970 PST | Sat Jan 10 00:00:00 1970 | 9 | ft2 | foo + 19 | 509 | 00019_update9 | Tue Jan 20 00:00:00 1970 PST | Tue Jan 20 00:00:00 1970 | 9 | ft2 | foo + 29 | 509 | 00029_update9 | Fri Jan 30 00:00:00 1970 PST | Fri Jan 30 00:00:00 1970 | 9 | ft2 | foo + 39 | 509 | 00039_update9 | Mon Feb 09 00:00:00 1970 PST | Mon Feb 09 00:00:00 1970 | 9 | ft2 | foo + 49 | 509 | 00049_update9 | Thu Feb 19 00:00:00 1970 PST | Thu Feb 19 00:00:00 1970 | 9 | ft2 | foo +(10 rows) + +-- ORDER BY ASC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on public.ft1 + Output: c1, c2, c3, c4, c5, c6, c7, c8 + Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY c6 ASC NULLS FIRST, "C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 15::bigint +(3 rows) + +SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 +------+-----+-------------------+------------------------------+--------------------------+------+------------+----- + 1020 | 100 | 0002000020 | | | | ft2 | + 1101 | 201 | aaa | | | | ft2 | + 1103 | 503 | ccc_update3 | | | | ft2 | + 1104 | 204 | ddd | | | | ft2 | + 1208 | 818 | fff_trig_update | | | | ft2 | + 1218 | 818 | ggg_trig_update | | | (--; | ft2 | + 10 | 42 | 00010_trig_update | Sun Jan 11 00:00:00 1970 PST | Sun Jan 11 00:00:00 1970 | 0 | 0 | foo + 20 | 42 | 00020_trig_update | Wed Jan 21 00:00:00 1970 PST | Wed Jan 21 00:00:00 1970 | 0 | 0 | foo + 30 | 42 | 00030_trig_update | Sat Jan 31 00:00:00 1970 PST | Sat Jan 31 00:00:00 1970 | 0 | 0 | foo + 40 | 42 | 00040_trig_update | Tue Feb 10 00:00:00 1970 PST | Tue Feb 10 00:00:00 1970 | 0 | 0 | foo +(10 rows) + +-- =================================================================== +-- test check constraints +-- =================================================================== +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +----------------------------------------------------------------- + Foreign Scan + Output: (count(*)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 < 0)) +(4 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; + QUERY PLAN +-------------------------------- + Aggregate + Output: count(*) + -> Result + One-Time Filter: false +(4 rows) + +SELECT count(*) FROM ft1 WHERE c2 < 0; + count +------- + 0 +(1 row) + +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: Failing row contains (1111, -2, null, null, null, null, ft1 , null). +CONTEXT: remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ERROR: new row for relation "T 1" violates check constraint "c2positive" +DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo). +CONTEXT: remote SQL command: UPDATE "S 1"."T 1" SET c2 = (- c2) WHERE (("C 1" = 1)) +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +------------------------------------------------------------------ + Foreign Scan + Output: (count(*)) + Relations: Aggregate on (public.ft1) + Remote SQL: SELECT count(*) FROM "S 1"."T 1" WHERE ((c2 >= 0)) +(4 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 821 +(1 row) + +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; + QUERY PLAN +-------------------------------- + Aggregate + Output: count(*) + -> Result + One-Time Filter: false +(4 rows) + +SELECT count(*) FROM ft1 WHERE c2 >= 0; + count +------- + 0 +(1 row) + +RESET constraint_exclusion; +-- local check constraint is not actually enforced +INSERT INTO ft1(c1, c2) VALUES(1111, 2); +UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; +-- =================================================================== +-- test WITH CHECK OPTION constraints +-- =================================================================== +CREATE FUNCTION row_before_insupd_trigfunc() RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; RETURN NEW; END$$ LANGUAGE plpgsql; +CREATE TABLE base_tbl (a int, b int); +ALTER TABLE base_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON base_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +CREATE VIEW rw_view AS SELECT * FROM foreign_tbl + WHERE a < b WITH CHECK OPTION; +\d+ rw_view + View "public.rw_view" + Column | Type | Collation | Nullable | Default | Storage | Description +--------+---------+-----------+----------+---------+---------+------------- + a | integer | | | | plain | + b | integer | | | | plain | +View definition: + SELECT a, + b + FROM foreign_tbl + WHERE a < b; +Options: check_option=cascaded + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); + QUERY PLAN +-------------------------------------------------------------------------------- + Insert on public.foreign_tbl + Remote SQL: INSERT INTO public.base_tbl(a, b) VALUES ($1, $2) RETURNING a, b + Batch Size: 1 + -> Result + Output: 0, 5 +(5 rows) + +INSERT INTO rw_view VALUES (0, 5); -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (10, 5). +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); + QUERY PLAN +-------------------------------------------------------------------------------- + Insert on public.foreign_tbl + Remote SQL: INSERT INTO public.base_tbl(a, b) VALUES ($1, $2) RETURNING a, b + Batch Size: 1 + -> Result + Output: 0, 15 +(5 rows) + +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM foreign_tbl; + a | b +----+---- + 10 | 15 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; + QUERY PLAN +--------------------------------------------------------------------------------------- + Update on public.foreign_tbl + Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b + -> Foreign Scan on public.foreign_tbl + Output: (foreign_tbl.b + 5), foreign_tbl.ctid, foreign_tbl.* + Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE +(5 rows) + +UPDATE rw_view SET b = b + 5; -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (20, 20). +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; + QUERY PLAN +--------------------------------------------------------------------------------------- + Update on public.foreign_tbl + Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b + -> Foreign Scan on public.foreign_tbl + Output: (foreign_tbl.b + 15), foreign_tbl.ctid, foreign_tbl.* + Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE +(5 rows) + +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM foreign_tbl; + a | b +----+---- + 20 | 30 +(1 row) + +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); + QUERY PLAN +-------------------------------------------------------------------------------- + Insert on public.foreign_tbl + Remote SQL: INSERT INTO public.base_tbl(a, b) VALUES ($1, $2) RETURNING a, b + Batch Size: 1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2 +(5 rows) + +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (10, 5). +SELECT * FROM foreign_tbl; + a | b +----+---- + 20 | 30 +(1 row) + +ALTER SERVER loopback OPTIONS (DROP batch_size); +DROP FOREIGN TABLE foreign_tbl CASCADE; +NOTICE: drop cascades to view rw_view +DROP TRIGGER row_before_insupd_trigger ON base_tbl; +DROP TABLE base_tbl; +-- test WCO for partitions +CREATE TABLE child_tbl (a int, b int); +ALTER TABLE child_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON child_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'child_tbl'); +CREATE TABLE parent_tbl (a int, b int) PARTITION BY RANGE(a); +ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES FROM (0) TO (100); +-- Detach and re-attach once, to stress the concurrent detach case. +ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl CONCURRENTLY; +ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES FROM (0) TO (100); +CREATE VIEW rw_view AS SELECT * FROM parent_tbl + WHERE a < b WITH CHECK OPTION; +\d+ rw_view + View "public.rw_view" + Column | Type | Collation | Nullable | Default | Storage | Description +--------+---------+-----------+----------+---------+---------+------------- + a | integer | | | | plain | + b | integer | | | | plain | +View definition: + SELECT a, + b + FROM parent_tbl + WHERE a < b; +Options: check_option=cascaded + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); + QUERY PLAN +----------------------------- + Insert on public.parent_tbl + -> Result + Output: 0, 5 +(3 rows) + +INSERT INTO rw_view VALUES (0, 5); -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (10, 5). +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); + QUERY PLAN +----------------------------- + Insert on public.parent_tbl + -> Result + Output: 0, 15 +(3 rows) + +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM foreign_tbl; + a | b +----+---- + 10 | 15 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.parent_tbl + Foreign Update on public.foreign_tbl parent_tbl_1 + Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b + -> Foreign Scan on public.foreign_tbl parent_tbl_1 + Output: (parent_tbl_1.b + 5), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.* + Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE +(6 rows) + +UPDATE rw_view SET b = b + 5; -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (20, 20). +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Update on public.parent_tbl + Foreign Update on public.foreign_tbl parent_tbl_1 + Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b + -> Foreign Scan on public.foreign_tbl parent_tbl_1 + Output: (parent_tbl_1.b + 15), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.* + Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE +(6 rows) + +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM foreign_tbl; + a | b +----+---- + 20 | 30 +(1 row) + +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); + QUERY PLAN +-------------------------------------------------------- + Insert on public.parent_tbl + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, "*VALUES*".column2 +(3 rows) + +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +ERROR: new row violates check option for view "rw_view" +DETAIL: Failing row contains (10, 5). +SELECT * FROM foreign_tbl; + a | b +----+---- + 20 | 30 +(1 row) + +ALTER SERVER loopback OPTIONS (DROP batch_size); +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON child_tbl; +DROP TABLE parent_tbl CASCADE; +NOTICE: drop cascades to view rw_view +DROP FUNCTION row_before_insupd_trigfunc; +-- Try a more complex permutation of WCO where there are multiple levels of +-- partitioned tables with columns not all in the same order +CREATE TABLE parent_tbl (a int, b text, c numeric) PARTITION BY RANGE(a); +CREATE TABLE sub_parent (c numeric, a int, b text) PARTITION BY RANGE(a); +ALTER TABLE parent_tbl ATTACH PARTITION sub_parent FOR VALUES FROM (1) TO (10); +CREATE TABLE child_local (b text, c numeric, a int); +CREATE FOREIGN TABLE child_foreign (b text, c numeric, a int) + SERVER loopback OPTIONS (table_name 'child_local'); +ALTER TABLE sub_parent ATTACH PARTITION child_foreign FOR VALUES FROM (1) TO (10); +CREATE VIEW rw_view AS SELECT * FROM parent_tbl WHERE a < 5 WITH CHECK OPTION; +INSERT INTO parent_tbl (a) VALUES(1),(5); +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = 'text', c = 123.456; + QUERY PLAN +------------------------------------------------------------------------------------------------- + Update on public.parent_tbl + Foreign Update on public.child_foreign parent_tbl_1 + Remote SQL: UPDATE public.child_local SET b = $2, c = $3 WHERE ctid = $1 RETURNING a + -> Foreign Scan on public.child_foreign parent_tbl_1 + Output: 'text'::text, 123.456, parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.* + Remote SQL: SELECT b, c, a, ctid FROM public.child_local WHERE ((a < 5)) FOR UPDATE +(6 rows) + +UPDATE rw_view SET b = 'text', c = 123.456; +SELECT * FROM parent_tbl ORDER BY a; + a | b | c +---+------+--------- + 1 | text | 123.456 + 5 | | +(2 rows) + +DROP VIEW rw_view; +DROP TABLE child_local; +DROP FOREIGN TABLE child_foreign; +DROP TABLE sub_parent; +DROP TABLE parent_tbl; +-- =================================================================== +-- test serial columns (ie, sequence-based defaults) +-- =================================================================== +create table loc1 (f1 serial, f2 text); +alter table loc1 set (autovacuum_enabled = 'false'); +create foreign table rem1 (f1 serial, f2 text) + server loopback options(table_name 'loc1'); +select pg_catalog.setval('rem1_f1_seq', 10, false); + setval +-------- + 10 +(1 row) + +insert into loc1(f2) values('hi'); +insert into rem1(f2) values('hi remote'); +insert into loc1(f2) values('bye'); +insert into rem1(f2) values('bye remote'); +select * from loc1; + f1 | f2 +----+------------ + 1 | hi + 10 | hi remote + 2 | bye + 11 | bye remote +(4 rows) + +select * from rem1; + f1 | f2 +----+------------ + 1 | hi + 10 | hi remote + 2 | bye + 11 | bye remote +(4 rows) + +-- =================================================================== +-- test generated columns +-- =================================================================== +create table gloc1 ( + a int, + b int generated always as (a * 2) stored); +alter table gloc1 set (autovacuum_enabled = 'false'); +create foreign table grem1 ( + a int, + b int generated always as (a * 2) stored) + server loopback options(table_name 'gloc1'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); + QUERY PLAN +------------------------------------------------------------------- + Insert on public.grem1 + Remote SQL: INSERT INTO public.gloc1(a, b) VALUES ($1, DEFAULT) + Batch Size: 1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, NULL::integer +(5 rows) + +insert into grem1 (a) values (1), (2); +explain (verbose, costs off) +update grem1 set a = 22 where a = 2; + QUERY PLAN +------------------------------------------------------------------------------------ + Update on public.grem1 + Remote SQL: UPDATE public.gloc1 SET a = $2, b = DEFAULT WHERE ctid = $1 + -> Foreign Scan on public.grem1 + Output: 22, ctid, grem1.* + Remote SQL: SELECT a, b, ctid FROM public.gloc1 WHERE ((a = 2)) FOR UPDATE +(5 rows) + +update grem1 set a = 22 where a = 2; +select * from gloc1; + a | b +----+---- + 1 | 2 + 22 | 44 +(2 rows) + +select * from grem1; + a | b +----+---- + 1 | 2 + 22 | 44 +(2 rows) + +delete from grem1; +-- test copy from +copy grem1 from stdin; +select * from gloc1; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +select * from grem1; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +delete from grem1; +-- test batch insert +alter server loopback options (add batch_size '10'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); + QUERY PLAN +------------------------------------------------------------------- + Insert on public.grem1 + Remote SQL: INSERT INTO public.gloc1(a, b) VALUES ($1, DEFAULT) + Batch Size: 10 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1, NULL::integer +(5 rows) + +insert into grem1 (a) values (1), (2); +select * from gloc1; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +select * from grem1; + a | b +---+--- + 1 | 2 + 2 | 4 +(2 rows) + +delete from grem1; +-- batch insert with foreign partitions. +-- This schema uses two partitions, one local and one remote with a modulo +-- to loop across all of them in batches. +create table tab_batch_local (id int, data text); +insert into tab_batch_local select i, 'test'|| i from generate_series(1, 45) i; +create table tab_batch_sharded (id int, data text) partition by hash(id); +create table tab_batch_sharded_p0 partition of tab_batch_sharded + for values with (modulus 2, remainder 0); +create table tab_batch_sharded_p1_remote (id int, data text); +create foreign table tab_batch_sharded_p1 partition of tab_batch_sharded + for values with (modulus 2, remainder 1) + server loopback options (table_name 'tab_batch_sharded_p1_remote'); +insert into tab_batch_sharded select * from tab_batch_local; +select count(*) from tab_batch_sharded; + count +------- + 45 +(1 row) + +drop table tab_batch_local; +drop table tab_batch_sharded; +drop table tab_batch_sharded_p1_remote; +alter server loopback options (drop batch_size); +-- =================================================================== +-- test local triggers +-- =================================================================== +-- Trigger functions "borrowed" from triggers regress test. +CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %', + TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; +END;$$; +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE OR TRUNCATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE OR TRUNCATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger +LANGUAGE plpgsql AS $$ + +declare + oldnew text[]; + relid text; + argstr text; +begin + + relid := TG_relid::regclass; + argstr := ''; + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + + RAISE NOTICE '%(%) % % % ON %', + tg_name, argstr, TG_when, TG_level, TG_OP, relid; + oldnew := '{}'::text[]; + if TG_OP != 'INSERT' then + oldnew := array_append(oldnew, format('OLD: %s', OLD)); + end if; + + if TG_OP != 'DELETE' then + oldnew := array_append(oldnew, format('NEW: %s', NEW)); + end if; + + RAISE NOTICE '%', array_to_string(oldnew, ','); + + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end; +$$; +-- Test basic functionality +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +delete from rem1; +NOTICE: trigger_func() called: action = DELETE, when = BEFORE, level = STATEMENT +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 +NOTICE: OLD: (1,hi) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 +NOTICE: OLD: (10,"hi remote") +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 +NOTICE: OLD: (2,bye) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON rem1 +NOTICE: OLD: (11,"bye remote") +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (1,hi) +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (10,"hi remote") +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (2,bye) +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (11,"bye remote") +NOTICE: trigger_func() called: action = DELETE, when = AFTER, level = STATEMENT +insert into rem1 values(1,'insert'); +NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1 +NOTICE: NEW: (1,insert) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (1,insert) +NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT +update rem1 set f2 = 'update' where f1 = 1; +NOTICE: trigger_func() called: action = UPDATE, when = BEFORE, level = STATEMENT +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1 +NOTICE: OLD: (1,insert),NEW: (1,update) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,insert),NEW: (1,update) +NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT +update rem1 set f2 = f2 || f2; +NOTICE: trigger_func() called: action = UPDATE, when = BEFORE, level = STATEMENT +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1 +NOTICE: OLD: (1,update),NEW: (1,updateupdate) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,update),NEW: (1,updateupdate) +NOTICE: trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT +truncate rem1; +NOTICE: trigger_func() called: action = TRUNCATE, when = BEFORE, level = STATEMENT +NOTICE: trigger_func() called: action = TRUNCATE, when = AFTER, level = STATEMENT +-- cleanup +DROP TRIGGER trig_row_before ON rem1; +DROP TRIGGER trig_row_after ON rem1; +DROP TRIGGER trig_stmt_before ON rem1; +DROP TRIGGER trig_stmt_after ON rem1; +DELETE from rem1; +-- Test multiple AFTER ROW triggers on a foreign table +CREATE TRIGGER trig_row_after1 +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after2 +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +insert into rem1 values(1,'insert'); +NOTICE: trig_row_after1(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (1,insert) +NOTICE: trig_row_after2(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (1,insert) +update rem1 set f2 = 'update' where f1 = 1; +NOTICE: trig_row_after1(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,insert),NEW: (1,update) +NOTICE: trig_row_after2(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,insert),NEW: (1,update) +update rem1 set f2 = f2 || f2; +NOTICE: trig_row_after1(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,update),NEW: (1,updateupdate) +NOTICE: trig_row_after2(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (1,update),NEW: (1,updateupdate) +delete from rem1; +NOTICE: trig_row_after1(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (1,updateupdate) +NOTICE: trig_row_after2(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (1,updateupdate) +-- cleanup +DROP TRIGGER trig_row_after1 ON rem1; +DROP TRIGGER trig_row_after2 ON rem1; +-- Test WHEN conditions +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after_insupd +AFTER INSERT OR UPDATE ON rem1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +-- Insert or update not matching: nothing happens +INSERT INTO rem1 values(1, 'insert'); +UPDATE rem1 set f2 = 'test'; +-- Insert or update matching: triggers are fired +INSERT INTO rem1 values(2, 'update'); +NOTICE: trig_row_before_insupd(23, skidoo) BEFORE ROW INSERT ON rem1 +NOTICE: NEW: (2,update) +NOTICE: trig_row_after_insupd(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (2,update) +UPDATE rem1 set f2 = 'update update' where f1 = '2'; +NOTICE: trig_row_before_insupd(23, skidoo) BEFORE ROW UPDATE ON rem1 +NOTICE: OLD: (2,update),NEW: (2,"update update") +NOTICE: trig_row_after_insupd(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (2,update),NEW: (2,"update update") +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON rem1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON rem1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); +-- Trigger is fired for f1=2, not for f1=1 +DELETE FROM rem1; +NOTICE: trig_row_before_delete(23, skidoo) BEFORE ROW DELETE ON rem1 +NOTICE: OLD: (2,"update update") +NOTICE: trig_row_after_delete(23, skidoo) AFTER ROW DELETE ON rem1 +NOTICE: OLD: (2,"update update") +-- cleanup +DROP TRIGGER trig_row_before_insupd ON rem1; +DROP TRIGGER trig_row_after_insupd ON rem1; +DROP TRIGGER trig_row_before_delete ON rem1; +DROP TRIGGER trig_row_after_delete ON rem1; +-- Test various RETURN statements in BEFORE triggers. +CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$ + BEGIN + NEW.f2 := NEW.f2 || ' triggered !'; + RETURN NEW; + END +$$ language plpgsql; +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); +-- The new values should have 'triggered' appended +INSERT INTO rem1 values(1, 'insert'); +SELECT * from loc1; + f1 | f2 +----+-------------------- + 1 | insert triggered ! +(1 row) + +INSERT INTO rem1 values(2, 'insert') RETURNING f2; + f2 +-------------------- + insert triggered ! +(1 row) + +SELECT * from loc1; + f1 | f2 +----+-------------------- + 1 | insert triggered ! + 2 | insert triggered ! +(2 rows) + +UPDATE rem1 set f2 = ''; +SELECT * from loc1; + f1 | f2 +----+-------------- + 1 | triggered ! + 2 | triggered ! +(2 rows) + +UPDATE rem1 set f2 = 'skidoo' RETURNING f2; + f2 +-------------------- + skidoo triggered ! + skidoo triggered ! +(2 rows) + +SELECT * from loc1; + f1 | f2 +----+-------------------- + 1 | skidoo triggered ! + 2 | skidoo triggered ! +(2 rows) + +EXPLAIN (verbose, costs off) +UPDATE rem1 set f1 = 10; -- all columns should be transmitted + QUERY PLAN +----------------------------------------------------------------------- + Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1 + -> Foreign Scan on public.rem1 + Output: 10, ctid, rem1.* + Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE +(5 rows) + +UPDATE rem1 set f1 = 10; +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 10 | skidoo triggered ! triggered ! + 10 | skidoo triggered ! triggered ! +(2 rows) + +DELETE FROM rem1; +-- Add a second trigger, to check that the changes are propagated correctly +-- from trigger to trigger +CREATE TRIGGER trig_row_before_insupd2 +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); +INSERT INTO rem1 values(1, 'insert'); +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 1 | insert triggered ! triggered ! +(1 row) + +INSERT INTO rem1 values(2, 'insert') RETURNING f2; + f2 +-------------------------------- + insert triggered ! triggered ! +(1 row) + +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 1 | insert triggered ! triggered ! + 2 | insert triggered ! triggered ! +(2 rows) + +UPDATE rem1 set f2 = ''; +SELECT * from loc1; + f1 | f2 +----+-------------------------- + 1 | triggered ! triggered ! + 2 | triggered ! triggered ! +(2 rows) + +UPDATE rem1 set f2 = 'skidoo' RETURNING f2; + f2 +-------------------------------- + skidoo triggered ! triggered ! + skidoo triggered ! triggered ! +(2 rows) + +SELECT * from loc1; + f1 | f2 +----+-------------------------------- + 1 | skidoo triggered ! triggered ! + 2 | skidoo triggered ! triggered ! +(2 rows) + +DROP TRIGGER trig_row_before_insupd ON rem1; +DROP TRIGGER trig_row_before_insupd2 ON rem1; +DELETE from rem1; +INSERT INTO rem1 VALUES (1, 'test'); +-- Test with a trigger returning NULL +CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$ + BEGIN + RETURN NULL; + END +$$ language plpgsql; +CREATE TRIGGER trig_null +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_null(); +-- Nothing should have changed. +INSERT INTO rem1 VALUES (2, 'test2'); +SELECT * from loc1; + f1 | f2 +----+------ + 1 | test +(1 row) + +UPDATE rem1 SET f2 = 'test2'; +SELECT * from loc1; + f1 | f2 +----+------ + 1 | test +(1 row) + +DELETE from rem1; +SELECT * from loc1; + f1 | f2 +----+------ + 1 | test +(1 row) + +DROP TRIGGER trig_null ON rem1; +DELETE from rem1; +-- Test a combination of local and remote triggers +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); +INSERT INTO rem1(f2) VALUES ('test'); +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1 +NOTICE: NEW: (12,test) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (12,"test triggered !") +UPDATE rem1 SET f2 = 'testo'; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON rem1 +NOTICE: OLD: (12,"test triggered !"),NEW: (12,testo) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON rem1 +NOTICE: OLD: (12,"test triggered !"),NEW: (12,"testo triggered !") +-- Test returning a system attribute +INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem1 +NOTICE: NEW: (13,test) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem1 +NOTICE: NEW: (13,"test triggered !") + ctid +-------- + (0,25) +(1 row) + +-- cleanup +DROP TRIGGER trig_row_before ON rem1; +DROP TRIGGER trig_row_after ON rem1; +DROP TRIGGER trig_local_before ON loc1; +-- Test direct foreign table modification functionality +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1 WHERE false; -- currently can't be pushed down + QUERY PLAN +------------------------------------------------------- + Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1 + -> Result + Output: ctid + One-Time Filter: false +(5 rows) + +-- Test with statement-level triggers +CREATE TRIGGER trig_stmt_before + BEFORE DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_stmt_before ON rem1; +CREATE TRIGGER trig_stmt_after + AFTER DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_stmt_after ON rem1; +-- Test with row-level ON INSERT triggers +CREATE TRIGGER trig_row_before_insert +BEFORE INSERT ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_row_before_insert ON rem1; +CREATE TRIGGER trig_row_after_insert +AFTER INSERT ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_row_after_insert ON rem1; +-- Test with row-level ON UPDATE triggers +CREATE TRIGGER trig_row_before_update +BEFORE UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down + QUERY PLAN +----------------------------------------------------------------------- + Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f1 = $2, f2 = $3 WHERE ctid = $1 + -> Foreign Scan on public.rem1 + Output: ''::text, ctid, rem1.* + Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE +(5 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_row_before_update ON rem1; +CREATE TRIGGER trig_row_after_update +AFTER UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down + QUERY PLAN +------------------------------------------------------------------------------- + Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2 + -> Foreign Scan on public.rem1 + Output: ''::text, ctid, rem1.* + Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE +(5 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down + QUERY PLAN +--------------------------------------------- + Delete on public.rem1 + -> Foreign Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 +(3 rows) + +DROP TRIGGER trig_row_after_update ON rem1; +-- Test with row-level ON DELETE triggers +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down + QUERY PLAN +--------------------------------------------------------------------- + Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1 + -> Foreign Scan on public.rem1 + Output: ctid, rem1.* + Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE +(5 rows) + +DROP TRIGGER trig_row_before_delete ON rem1; +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down + QUERY PLAN +---------------------------------------------------------- + Update on public.rem1 + -> Foreign Update on public.rem1 + Remote SQL: UPDATE public.loc1 SET f2 = ''::text +(3 rows) + +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down + QUERY PLAN +------------------------------------------------------------------------ + Delete on public.rem1 + Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1 RETURNING f1, f2 + -> Foreign Scan on public.rem1 + Output: ctid, rem1.* + Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE +(5 rows) + +DROP TRIGGER trig_row_after_delete ON rem1; +-- =================================================================== +-- test inheritance features +-- =================================================================== +CREATE TABLE a (aa TEXT); +CREATE TABLE loct (aa TEXT, bb TEXT); +ALTER TABLE a SET (autovacuum_enabled = 'false'); +ALTER TABLE loct SET (autovacuum_enabled = 'false'); +CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a) + SERVER loopback OPTIONS (table_name 'loct'); +INSERT INTO a(aa) VALUES('aaa'); +INSERT INTO a(aa) VALUES('aaaa'); +INSERT INTO a(aa) VALUES('aaaaa'); +INSERT INTO b(aa) VALUES('bbb'); +INSERT INTO b(aa) VALUES('bbbb'); +INSERT INTO b(aa) VALUES('bbbbb'); +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+------- + a | aaa + a | aaaa + a | aaaaa + b | bbb + b | bbbb + b | bbbbb +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+-------+---- + b | bbb | + b | bbbb | + b | bbbbb | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+------- + a | aaa + a | aaaa + a | aaaaa +(3 rows) + +UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%'; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz + b | bbb + b | bbbb + b | bbbbb +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+-------+---- + b | bbb | + b | bbbb | + b | bbbbb | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz +(3 rows) + +UPDATE b SET aa = 'new'; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz + b | new + b | new + b | new +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+-----+---- + b | new | + b | new | + b | new | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+-------- + a | aaa + a | zzzzzz + a | zzzzzz +(3 rows) + +UPDATE a SET aa = 'newtoo'; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+-------- + a | newtoo + a | newtoo + a | newtoo + b | newtoo + b | newtoo + b | newtoo +(6 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+--------+---- + b | newtoo | + b | newtoo | + b | newtoo | +(3 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+-------- + a | newtoo + a | newtoo + a | newtoo +(3 rows) + +DELETE FROM a; +SELECT tableoid::regclass, * FROM a; + tableoid | aa +----------+---- +(0 rows) + +SELECT tableoid::regclass, * FROM b; + tableoid | aa | bb +----------+----+---- +(0 rows) + +SELECT tableoid::regclass, * FROM ONLY a; + tableoid | aa +----------+---- +(0 rows) + +DROP TABLE a CASCADE; +NOTICE: drop cascades to foreign table b +DROP TABLE loct; +-- Check SELECT FOR UPDATE/SHARE with an inherited source table +create table loct1 (f1 int, f2 int, f3 int); +create table loct2 (f1 int, f2 int, f3 int); +alter table loct1 set (autovacuum_enabled = 'false'); +alter table loct2 set (autovacuum_enabled = 'false'); +create table foo (f1 int, f2 int); +create foreign table foo2 (f3 int) inherits (foo) + server loopback options (table_name 'loct1'); +create table bar (f1 int, f2 int); +create foreign table bar2 (f3 int) inherits (bar) + server loopback options (table_name 'loct2'); +alter table foo set (autovacuum_enabled = 'false'); +alter table bar set (autovacuum_enabled = 'false'); +insert into foo values(1,1); +insert into foo values(3,3); +insert into foo2 values(2,2,2); +insert into foo2 values(4,4,4); +insert into bar values(1,11); +insert into bar values(2,22); +insert into bar values(6,66); +insert into bar2 values(3,33,33); +insert into bar2 values(4,44,44); +insert into bar2 values(7,77,77); +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo) for update; + QUERY PLAN +---------------------------------------------------------------------------------------------- + LockRows + Output: bar.f1, bar.f2, bar.ctid, foo.ctid, bar.*, bar.tableoid, foo.*, foo.tableoid + -> Hash Join + Output: bar.f1, bar.f2, bar.ctid, foo.ctid, bar.*, bar.tableoid, foo.*, foo.tableoid + Inner Unique: true + Hash Cond: (bar.f1 = foo.f1) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f1, bar_1.f2, bar_1.ctid, bar_1.*, bar_1.tableoid + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f1, bar_2.f2, bar_2.ctid, bar_2.*, bar_2.tableoid + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE + -> Hash + Output: foo.ctid, foo.f1, foo.*, foo.tableoid + -> HashAggregate + Output: foo.ctid, foo.f1, foo.*, foo.tableoid + Group Key: foo.f1 + -> Append + -> Seq Scan on public.foo foo_1 + Output: foo_1.ctid, foo_1.f1, foo_1.*, foo_1.tableoid + -> Foreign Scan on public.foo2 foo_2 + Output: foo_2.ctid, foo_2.f1, foo_2.*, foo_2.tableoid + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 +(23 rows) + +select * from bar where f1 in (select f1 from foo) for update; + f1 | f2 +----+---- + 1 | 11 + 2 | 22 + 3 | 33 + 4 | 44 +(4 rows) + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo) for share; + QUERY PLAN +---------------------------------------------------------------------------------------------- + LockRows + Output: bar.f1, bar.f2, bar.ctid, foo.ctid, bar.*, bar.tableoid, foo.*, foo.tableoid + -> Hash Join + Output: bar.f1, bar.f2, bar.ctid, foo.ctid, bar.*, bar.tableoid, foo.*, foo.tableoid + Inner Unique: true + Hash Cond: (bar.f1 = foo.f1) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f1, bar_1.f2, bar_1.ctid, bar_1.*, bar_1.tableoid + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f1, bar_2.f2, bar_2.ctid, bar_2.*, bar_2.tableoid + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE + -> Hash + Output: foo.ctid, foo.f1, foo.*, foo.tableoid + -> HashAggregate + Output: foo.ctid, foo.f1, foo.*, foo.tableoid + Group Key: foo.f1 + -> Append + -> Seq Scan on public.foo foo_1 + Output: foo_1.ctid, foo_1.f1, foo_1.*, foo_1.tableoid + -> Foreign Scan on public.foo2 foo_2 + Output: foo_2.ctid, foo_2.f1, foo_2.*, foo_2.tableoid + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 +(23 rows) + +select * from bar where f1 in (select f1 from foo) for share; + f1 | f2 +----+---- + 1 | 11 + 2 | 22 + 3 | 33 + 4 | 44 +(4 rows) + +-- Now check SELECT FOR UPDATE/SHARE with an inherited source table, +-- where the parent is itself a foreign table +create table loct4 (f1 int, f2 int, f3 int); +create foreign table foo2child (f3 int) inherits (foo2) + server loopback options (table_name 'loct4'); +NOTICE: moving and merging column "f3" with inherited definition +DETAIL: User-specified column moved to the position of the inherited column. +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo2) for share; + QUERY PLAN +-------------------------------------------------------------------------------------- + LockRows + Output: bar.f1, bar.f2, bar.ctid, foo2.*, bar.*, bar.tableoid, foo2.tableoid + -> Hash Join + Output: bar.f1, bar.f2, bar.ctid, foo2.*, bar.*, bar.tableoid, foo2.tableoid + Inner Unique: true + Hash Cond: (bar.f1 = foo2.f1) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f1, bar_1.f2, bar_1.ctid, bar_1.*, bar_1.tableoid + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f1, bar_2.f2, bar_2.ctid, bar_2.*, bar_2.tableoid + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE + -> Hash + Output: foo2.*, foo2.f1, foo2.tableoid + -> HashAggregate + Output: foo2.*, foo2.f1, foo2.tableoid + Group Key: foo2.f1 + -> Append + -> Foreign Scan on public.foo2 foo2_1 + Output: foo2_1.*, foo2_1.f1, foo2_1.tableoid + Remote SQL: SELECT f1, f2, f3 FROM public.loct1 + -> Foreign Scan on public.foo2child foo2_2 + Output: foo2_2.*, foo2_2.f1, foo2_2.tableoid + Remote SQL: SELECT f1, f2, f3 FROM public.loct4 +(24 rows) + +select * from bar where f1 in (select f1 from foo2) for share; + f1 | f2 +----+---- + 2 | 22 + 4 | 44 +(2 rows) + +drop foreign table foo2child; +-- And with a local child relation of the foreign table parent +create table foo2child (f3 int) inherits (foo2); +NOTICE: moving and merging column "f3" with inherited definition +DETAIL: User-specified column moved to the position of the inherited column. +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo2) for share; + QUERY PLAN +------------------------------------------------------------------------------------------------- + LockRows + Output: bar.f1, bar.f2, bar.ctid, foo2.*, bar.*, bar.tableoid, foo2.ctid, foo2.tableoid + -> Hash Join + Output: bar.f1, bar.f2, bar.ctid, foo2.*, bar.*, bar.tableoid, foo2.ctid, foo2.tableoid + Inner Unique: true + Hash Cond: (bar.f1 = foo2.f1) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f1, bar_1.f2, bar_1.ctid, bar_1.*, bar_1.tableoid + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f1, bar_2.f2, bar_2.ctid, bar_2.*, bar_2.tableoid + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR SHARE + -> Hash + Output: foo2.*, foo2.f1, foo2.ctid, foo2.tableoid + -> HashAggregate + Output: foo2.*, foo2.f1, foo2.ctid, foo2.tableoid + Group Key: foo2.f1 + -> Append + -> Foreign Scan on public.foo2 foo2_1 + Output: foo2_1.*, foo2_1.f1, foo2_1.ctid, foo2_1.tableoid + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 + -> Seq Scan on public.foo2child foo2_2 + Output: foo2_2.*, foo2_2.f1, foo2_2.ctid, foo2_2.tableoid +(23 rows) + +select * from bar where f1 in (select f1 from foo2) for share; + f1 | f2 +----+---- + 2 | 22 + 4 | 44 +(2 rows) + +drop table foo2child; +-- Check UPDATE with inherited target and an inherited source table +explain (verbose, costs off) +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); + QUERY PLAN +------------------------------------------------------------------------------------------------------- + Update on public.bar + Update on public.bar bar_1 + Foreign Update on public.bar2 bar_2 + Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1 + -> Hash Join + Output: (bar.f2 + 100), foo.ctid, bar.tableoid, bar.ctid, (NULL::record), foo.*, foo.tableoid + Inner Unique: true + Hash Cond: (bar.f1 = foo.f1) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f2, bar_1.f1, bar_1.tableoid, bar_1.ctid, NULL::record + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f2, bar_2.f1, bar_2.tableoid, bar_2.ctid, bar_2.* + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE + -> Hash + Output: foo.ctid, foo.f1, foo.*, foo.tableoid + -> HashAggregate + Output: foo.ctid, foo.f1, foo.*, foo.tableoid + Group Key: foo.f1 + -> Append + -> Seq Scan on public.foo foo_1 + Output: foo_1.ctid, foo_1.f1, foo_1.*, foo_1.tableoid + -> Foreign Scan on public.foo2 foo_2 + Output: foo_2.ctid, foo_2.f1, foo_2.*, foo_2.tableoid + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1 +(25 rows) + +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); +select tableoid::regclass, * from bar order by 1,2; + tableoid | f1 | f2 +----------+----+----- + bar | 1 | 111 + bar | 2 | 122 + bar | 6 | 66 + bar2 | 3 | 133 + bar2 | 4 | 144 + bar2 | 7 | 77 +(6 rows) + +-- Check UPDATE with inherited target and an appendrel subquery +explain (verbose, costs off) +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.bar + Update on public.bar bar_1 + Foreign Update on public.bar2 bar_2 + Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1 + -> Merge Join + Output: (bar.f2 + 100), (ROW(foo.f1)), bar.tableoid, bar.ctid, (NULL::record) + Merge Cond: (bar.f1 = foo.f1) + -> Sort + Output: bar.f2, bar.f1, bar.tableoid, bar.ctid, (NULL::record) + Sort Key: bar.f1 + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f2, bar_1.f1, bar_1.tableoid, bar_1.ctid, NULL::record + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f2, bar_2.f1, bar_2.tableoid, bar_2.ctid, bar_2.* + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE + -> Sort + Output: (ROW(foo.f1)), foo.f1 + Sort Key: foo.f1 + -> Append + -> Seq Scan on public.foo + Output: ROW(foo.f1), foo.f1 + -> Foreign Scan on public.foo2 foo_1 + Output: ROW(foo_1.f1), foo_1.f1 + Remote SQL: SELECT f1 FROM public.loct1 + -> Seq Scan on public.foo foo_2 + Output: ROW((foo_2.f1 + 3)), (foo_2.f1 + 3) + -> Foreign Scan on public.foo2 foo_3 + Output: ROW((foo_3.f1 + 3)), (foo_3.f1 + 3) + Remote SQL: SELECT f1 FROM public.loct1 +(30 rows) + +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; +select tableoid::regclass, * from bar order by 1,2; + tableoid | f1 | f2 +----------+----+----- + bar | 1 | 211 + bar | 2 | 222 + bar | 6 | 166 + bar2 | 3 | 233 + bar2 | 4 | 244 + bar2 | 7 | 177 +(6 rows) + +-- Test forcing the remote server to produce sorted data for a merge join, +-- but the foreign table is an inheritance child. +truncate table loct1; +truncate table only foo; +\set num_rows_foo 2000 +insert into loct1 select generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2); +insert into foo select generate_series(1, :num_rows_foo, 2), generate_series(1, :num_rows_foo, 2); +SET enable_hashjoin to false; +SET enable_nestloop to false; +alter foreign table foo2 options (use_remote_estimate 'true'); +create index i_loct1_f1 on loct1(f1); +create index i_foo_f1 on foo(f1); +analyze foo; +analyze loct1; +-- inner join; expressions in the clauses appear in the equivalence class list +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Limit + Output: foo.f1, loct1.f1, foo.f2 + -> Sort + Output: foo.f1, loct1.f1, foo.f2 + Sort Key: foo.f2 + -> Merge Join + Output: foo.f1, loct1.f1, foo.f2 + Merge Cond: (foo.f1 = loct1.f1) + -> Merge Append + Sort Key: foo.f1 + -> Index Scan using i_foo_f1 on public.foo foo_1 + Output: foo_1.f1, foo_1.f2 + -> Foreign Scan on public.foo2 foo_2 + Output: foo_2.f1, foo_2.f2 + Remote SQL: SELECT f1, f2 FROM public.loct1 ORDER BY f1 ASC NULLS LAST + -> Index Only Scan using i_loct1_f1 on public.loct1 + Output: loct1.f1 +(17 rows) + +select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; + f1 | f1 +----+---- + 20 | 20 + 22 | 22 + 24 | 24 + 26 | 26 + 28 | 28 + 30 | 30 + 32 | 32 + 34 | 34 + 36 | 36 + 38 | 38 +(10 rows) + +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; + QUERY PLAN +-------------------------------------------------------------------------------------------------- + Limit + Output: foo.f1, loct1.f1, foo.f2 + -> Sort + Output: foo.f1, loct1.f1, foo.f2 + Sort Key: foo.f2 + -> Merge Left Join + Output: foo.f1, loct1.f1, foo.f2 + Merge Cond: (foo.f1 = loct1.f1) + -> Merge Append + Sort Key: foo.f1 + -> Index Scan using i_foo_f1 on public.foo foo_1 + Output: foo_1.f1, foo_1.f2 + -> Foreign Scan on public.foo2 foo_2 + Output: foo_2.f1, foo_2.f2 + Remote SQL: SELECT f1, f2 FROM public.loct1 ORDER BY f1 ASC NULLS LAST + -> Index Only Scan using i_loct1_f1 on public.loct1 + Output: loct1.f1 +(17 rows) + +select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; + f1 | f1 +----+---- + 10 | 10 + 11 | + 12 | 12 + 13 | + 14 | 14 + 15 | + 16 | 16 + 17 | + 18 | 18 + 19 | +(10 rows) + +RESET enable_hashjoin; +RESET enable_nestloop; +-- Test that WHERE CURRENT OF is not supported +begin; +declare c cursor for select * from bar where f1 = 7; +fetch from c; + f1 | f2 +----+----- + 7 | 177 +(1 row) + +update bar set f2 = null where current of c; +ERROR: WHERE CURRENT OF is not supported for this table type +rollback; +explain (verbose, costs off) +delete from foo where f1 < 5 returning *; + QUERY PLAN +-------------------------------------------------------------------------------------- + Delete on public.foo + Output: foo_1.f1, foo_1.f2 + Delete on public.foo foo_1 + Foreign Delete on public.foo2 foo_2 + -> Append + -> Index Scan using i_foo_f1 on public.foo foo_1 + Output: foo_1.tableoid, foo_1.ctid + Index Cond: (foo_1.f1 < 5) + -> Foreign Delete on public.foo2 foo_2 + Remote SQL: DELETE FROM public.loct1 WHERE ((f1 < 5)) RETURNING f1, f2 +(10 rows) + +delete from foo where f1 < 5 returning *; + f1 | f2 +----+---- + 1 | 1 + 3 | 3 + 0 | 0 + 2 | 2 + 4 | 4 +(5 rows) + +explain (verbose, costs off) +update bar set f2 = f2 + 100 returning *; + QUERY PLAN +------------------------------------------------------------------------------------------ + Update on public.bar + Output: bar_1.f1, bar_1.f2 + Update on public.bar bar_1 + Foreign Update on public.bar2 bar_2 + -> Result + Output: (bar.f2 + 100), bar.tableoid, bar.ctid, (NULL::record) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f2, bar_1.tableoid, bar_1.ctid, NULL::record + -> Foreign Update on public.bar2 bar_2 + Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2 +(11 rows) + +update bar set f2 = f2 + 100 returning *; + f1 | f2 +----+----- + 1 | 311 + 2 | 322 + 6 | 266 + 3 | 333 + 4 | 344 + 7 | 277 +(6 rows) + +-- Test that UPDATE/DELETE with inherited target works with row-level triggers +CREATE TRIGGER trig_row_before +BEFORE UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER trig_row_after +AFTER UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +explain (verbose, costs off) +update bar set f2 = f2 + 100; + QUERY PLAN +-------------------------------------------------------------------------------------------------------- + Update on public.bar + Update on public.bar bar_1 + Foreign Update on public.bar2 bar_2 + Remote SQL: UPDATE public.loct2 SET f1 = $2, f2 = $3, f3 = $4 WHERE ctid = $1 RETURNING f1, f2, f3 + -> Result + Output: (bar.f2 + 100), bar.tableoid, bar.ctid, (NULL::record) + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.f2, bar_1.tableoid, bar_1.ctid, NULL::record + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.f2, bar_2.tableoid, bar_2.ctid, bar_2.* + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE +(12 rows) + +update bar set f2 = f2 + 100; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2 +NOTICE: OLD: (3,333,33),NEW: (3,433,33) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2 +NOTICE: OLD: (4,344,44),NEW: (4,444,44) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW UPDATE ON bar2 +NOTICE: OLD: (7,277,77),NEW: (7,377,77) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2 +NOTICE: OLD: (3,333,33),NEW: (3,433,33) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2 +NOTICE: OLD: (4,344,44),NEW: (4,444,44) +NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2 +NOTICE: OLD: (7,277,77),NEW: (7,377,77) +explain (verbose, costs off) +delete from bar where f2 < 400; + QUERY PLAN +--------------------------------------------------------------------------------------------------- + Delete on public.bar + Delete on public.bar bar_1 + Foreign Delete on public.bar2 bar_2 + Remote SQL: DELETE FROM public.loct2 WHERE ctid = $1 RETURNING f1, f2, f3 + -> Append + -> Seq Scan on public.bar bar_1 + Output: bar_1.tableoid, bar_1.ctid, NULL::record + Filter: (bar_1.f2 < 400) + -> Foreign Scan on public.bar2 bar_2 + Output: bar_2.tableoid, bar_2.ctid, bar_2.* + Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE +(11 rows) + +delete from bar where f2 < 400; +NOTICE: trig_row_before(23, skidoo) BEFORE ROW DELETE ON bar2 +NOTICE: OLD: (7,377,77) +NOTICE: trig_row_after(23, skidoo) AFTER ROW DELETE ON bar2 +NOTICE: OLD: (7,377,77) +-- cleanup +drop table foo cascade; +NOTICE: drop cascades to foreign table foo2 +drop table bar cascade; +NOTICE: drop cascades to foreign table bar2 +drop table loct1; +drop table loct2; +-- Test pushing down UPDATE/DELETE joins to the remote server +create table parent (a int, b text); +create table loct1 (a int, b text); +create table loct2 (a int, b text); +create foreign table remt1 (a int, b text) + server loopback options (table_name 'loct1'); +create foreign table remt2 (a int, b text) + server loopback options (table_name 'loct2'); +alter foreign table remt1 inherit parent; +insert into remt1 values (1, 'foo'); +insert into remt1 values (2, 'bar'); +insert into remt2 values (1, 'foo'); +insert into remt2 values (2, 'bar'); +analyze remt1; +analyze remt2; +explain (verbose, costs off) +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- + Update on public.parent + Output: parent_1.a, parent_1.b, remt2.a, remt2.b + Update on public.parent parent_1 + Foreign Update on public.remt1 parent_2 + Remote SQL: UPDATE public.loct1 SET b = $2 WHERE ctid = $1 RETURNING a, b + -> Nested Loop + Output: (parent.b || remt2.b), remt2.*, remt2.a, remt2.b, parent.tableoid, parent.ctid, (NULL::record) + Join Filter: (parent.a = remt2.a) + -> Append + -> Seq Scan on public.parent parent_1 + Output: parent_1.b, parent_1.a, parent_1.tableoid, parent_1.ctid, NULL::record + -> Foreign Scan on public.remt1 parent_2 + Output: parent_2.b, parent_2.a, parent_2.tableoid, parent_2.ctid, parent_2.* + Remote SQL: SELECT a, b, ctid FROM public.loct1 FOR UPDATE + -> Materialize + Output: remt2.b, remt2.*, remt2.a + -> Foreign Scan on public.remt2 + Output: remt2.b, remt2.*, remt2.a + Remote SQL: SELECT a, b FROM public.loct2 +(19 rows) + +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; + a | b | a | b +---+--------+---+----- + 1 | foofoo | 1 | foo + 2 | barbar | 2 | bar +(2 rows) + +explain (verbose, costs off) +delete from parent using remt2 where parent.a = remt2.a returning parent; + QUERY PLAN +----------------------------------------------------------------------------- + Delete on public.parent + Output: parent_1.* + Delete on public.parent parent_1 + Foreign Delete on public.remt1 parent_2 + Remote SQL: DELETE FROM public.loct1 WHERE ctid = $1 RETURNING a, b + -> Nested Loop + Output: remt2.*, parent.tableoid, parent.ctid + Join Filter: (parent.a = remt2.a) + -> Append + -> Seq Scan on public.parent parent_1 + Output: parent_1.a, parent_1.tableoid, parent_1.ctid + -> Foreign Scan on public.remt1 parent_2 + Output: parent_2.a, parent_2.tableoid, parent_2.ctid + Remote SQL: SELECT a, ctid FROM public.loct1 FOR UPDATE + -> Materialize + Output: remt2.*, remt2.a + -> Foreign Scan on public.remt2 + Output: remt2.*, remt2.a + Remote SQL: SELECT a, b FROM public.loct2 +(19 rows) + +delete from parent using remt2 where parent.a = remt2.a returning parent; + parent +------------ + (1,foofoo) + (2,barbar) +(2 rows) + +-- cleanup +drop foreign table remt1; +drop foreign table remt2; +drop table loct1; +drop table loct2; +drop table parent; +-- =================================================================== +-- test tuple routing for foreign-table partitions +-- =================================================================== +-- Test insert tuple routing +create table itrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table itrtest attach partition remp1 for values in (1); +alter table itrtest attach partition remp2 for values in (2); +insert into itrtest values (1, 'foo'); +insert into itrtest values (1, 'bar') returning *; + a | b +---+----- + 1 | bar +(1 row) + +insert into itrtest values (2, 'baz'); +insert into itrtest values (2, 'qux') returning *; + a | b +---+----- + 2 | qux +(1 row) + +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; + a | b +---+------- + 1 | test1 + 2 | test2 +(2 rows) + +select tableoid::regclass, * FROM itrtest; + tableoid | a | b +----------+---+------- + remp1 | 1 | foo + remp1 | 1 | bar + remp1 | 1 | test1 + remp2 | 2 | baz + remp2 | 2 | qux + remp2 | 2 | test2 +(6 rows) + +select tableoid::regclass, * FROM remp1; + tableoid | a | b +----------+---+------- + remp1 | 1 | foo + remp1 | 1 | bar + remp1 | 1 | test1 +(3 rows) + +select tableoid::regclass, * FROM remp2; + tableoid | b | a +----------+-------+--- + remp2 | baz | 2 + remp2 | qux | 2 + remp2 | test2 | 2 +(3 rows) + +delete from itrtest; +-- MERGE ought to fail cleanly +merge into itrtest using (select 1, 'foo') as source on (true) + when matched then do nothing; +ERROR: cannot execute MERGE on relation "remp1" +DETAIL: This operation is not supported for foreign tables. +create unique index loct1_idx on loct1 (a); +-- DO NOTHING without an inference specification is supported +insert into itrtest values (1, 'foo') on conflict do nothing returning *; + a | b +---+----- + 1 | foo +(1 row) + +insert into itrtest values (1, 'foo') on conflict do nothing returning *; + a | b +---+--- +(0 rows) + +-- But other cases are not supported +insert into itrtest values (1, 'bar') on conflict (a) do nothing; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b; +ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification +select tableoid::regclass, * FROM itrtest; + tableoid | a | b +----------+---+----- + remp1 | 1 | foo +(1 row) + +delete from itrtest; +drop index loct1_idx; +-- Test that remote triggers work with insert tuple routing +create function br_insert_trigfunc() returns trigger as $$ +begin + new.b := new.b || ' triggered !'; + return new; +end +$$ language plpgsql; +create trigger loct1_br_insert_trigger before insert on loct1 + for each row execute procedure br_insert_trigfunc(); +create trigger loct2_br_insert_trigger before insert on loct2 + for each row execute procedure br_insert_trigfunc(); +-- The new values are concatenated with ' triggered !' +insert into itrtest values (1, 'foo') returning *; + a | b +---+----------------- + 1 | foo triggered ! +(1 row) + +insert into itrtest values (2, 'qux') returning *; + a | b +---+----------------- + 2 | qux triggered ! +(1 row) + +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; + a | b +---+------------------- + 1 | test1 triggered ! + 2 | test2 triggered ! +(2 rows) + +with result as (insert into itrtest values (1, 'test1'), (2, 'test2') returning *) select * from result; + a | b +---+------------------- + 1 | test1 triggered ! + 2 | test2 triggered ! +(2 rows) + +drop trigger loct1_br_insert_trigger on loct1; +drop trigger loct2_br_insert_trigger on loct2; +drop table itrtest; +drop table loct1; +drop table loct2; +-- Test update tuple routing +create table utrtest (a int, b text) partition by list (a); +create table loct (a int check (a in (1)), b text); +create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct'); +create table locp (a int check (a in (2)), b text); +alter table utrtest attach partition remp for values in (1); +alter table utrtest attach partition locp for values in (2); +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); +select tableoid::regclass, * FROM utrtest; + tableoid | a | b +----------+---+----- + remp | 1 | foo + locp | 2 | qux +(2 rows) + +select tableoid::regclass, * FROM remp; + tableoid | a | b +----------+---+----- + remp | 1 | foo +(1 row) + +select tableoid::regclass, * FROM locp; + tableoid | a | b +----------+---+----- + locp | 2 | qux +(1 row) + +-- It's not allowed to move a row from a partition that is foreign to another +update utrtest set a = 2 where b = 'foo' returning *; +ERROR: new row for relation "loct" violates check constraint "loct_a_check" +DETAIL: Failing row contains (2, foo). +CONTEXT: remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo')) RETURNING a, b +-- But the reverse is allowed +update utrtest set a = 1 where b = 'qux' returning *; +ERROR: cannot route tuples into foreign table to be updated "remp" +select tableoid::regclass, * FROM utrtest; + tableoid | a | b +----------+---+----- + remp | 1 | foo + locp | 2 | qux +(2 rows) + +select tableoid::regclass, * FROM remp; + tableoid | a | b +----------+---+----- + remp | 1 | foo +(1 row) + +select tableoid::regclass, * FROM locp; + tableoid | a | b +----------+---+----- + locp | 2 | qux +(1 row) + +-- The executor should not let unexercised FDWs shut down +update utrtest set a = 1 where b = 'foo'; +-- Test that remote triggers work with update tuple routing +create trigger loct_br_insert_trigger before insert on loct + for each row execute procedure br_insert_trigfunc(); +delete from utrtest; +insert into utrtest values (2, 'qux'); +-- Check case where the foreign partition is a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 1 or a = 2 returning *; + QUERY PLAN +---------------------------------------------------------------------------------------------------- + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b + Foreign Update on public.remp utrtest_1 + Update on public.locp utrtest_2 + -> Append + -> Foreign Update on public.remp utrtest_1 + Remote SQL: UPDATE public.loct SET a = 1 WHERE (((a = 1) OR (a = 2))) RETURNING a, b + -> Seq Scan on public.locp utrtest_2 + Output: 1, utrtest_2.tableoid, utrtest_2.ctid, NULL::record + Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2)) +(10 rows) + +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 1 or a = 2 returning *; +ERROR: cannot route tuples into foreign table to be updated "remp" +delete from utrtest; +insert into utrtest values (2, 'qux'); +-- Check case where the foreign partition isn't a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 2 returning *; + QUERY PLAN +------------------------------------------------------- + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b + Update on public.locp utrtest_1 + -> Seq Scan on public.locp utrtest_1 + Output: 1, utrtest_1.tableoid, utrtest_1.ctid + Filter: (utrtest_1.a = 2) +(6 rows) + +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 2 returning *; + a | b +---+----------------- + 1 | qux triggered ! +(1 row) + +drop trigger loct_br_insert_trigger on loct; +-- We can move rows to a foreign partition that has been updated already, +-- but can't move rows to a foreign partition that hasn't been updated yet +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); +-- Test the former case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 returning *; + QUERY PLAN +--------------------------------------------------------------------------- + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b + Foreign Update on public.remp utrtest_1 + Update on public.locp utrtest_2 + -> Append + -> Foreign Update on public.remp utrtest_1 + Remote SQL: UPDATE public.loct SET a = 1 RETURNING a, b + -> Seq Scan on public.locp utrtest_2 + Output: 1, utrtest_2.tableoid, utrtest_2.ctid, NULL::record +(9 rows) + +update utrtest set a = 1 returning *; +ERROR: cannot route tuples into foreign table to be updated "remp" +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; + QUERY PLAN +------------------------------------------------------------------------------------------------ + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b, "*VALUES*".column1 + Foreign Update on public.remp utrtest_1 + Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b + Update on public.locp utrtest_2 + -> Hash Join + Output: 1, "*VALUES*".*, "*VALUES*".column1, utrtest.tableoid, utrtest.ctid, utrtest.* + Hash Cond: (utrtest.a = "*VALUES*".column1) + -> Append + -> Foreign Scan on public.remp utrtest_1 + Output: utrtest_1.a, utrtest_1.tableoid, utrtest_1.ctid, utrtest_1.* + Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE + -> Seq Scan on public.locp utrtest_2 + Output: utrtest_2.a, utrtest_2.tableoid, utrtest_2.ctid, NULL::record + -> Hash + Output: "*VALUES*".*, "*VALUES*".column1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".*, "*VALUES*".column1 +(18 rows) + +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; +ERROR: cannot route tuples into foreign table to be updated "remp" +-- Change the definition of utrtest so that the foreign partition get updated +-- after the local partition +delete from utrtest; +alter table utrtest detach partition remp; +drop foreign table remp; +alter table loct drop constraint loct_a_check; +alter table loct add check (a in (3)); +create foreign table remp (a int check (a in (3)), b text) server loopback options (table_name 'loct'); +alter table utrtest attach partition remp for values in (3); +insert into utrtest values (2, 'qux'); +insert into utrtest values (3, 'xyzzy'); +-- Test the latter case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 returning *; + QUERY PLAN +--------------------------------------------------------------------------- + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b + Update on public.locp utrtest_1 + Foreign Update on public.remp utrtest_2 + -> Append + -> Seq Scan on public.locp utrtest_1 + Output: 3, utrtest_1.tableoid, utrtest_1.ctid, NULL::record + -> Foreign Update on public.remp utrtest_2 + Remote SQL: UPDATE public.loct SET a = 3 RETURNING a, b +(9 rows) + +update utrtest set a = 3 returning *; -- ERROR +ERROR: cannot route tuples into foreign table to be updated "remp" +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Update on public.utrtest + Output: utrtest_1.a, utrtest_1.b, "*VALUES*".column1 + Update on public.locp utrtest_1 + Foreign Update on public.remp utrtest_2 + Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b + -> Hash Join + Output: 3, "*VALUES*".*, "*VALUES*".column1, utrtest.tableoid, utrtest.ctid, (NULL::record) + Hash Cond: (utrtest.a = "*VALUES*".column1) + -> Append + -> Seq Scan on public.locp utrtest_1 + Output: utrtest_1.a, utrtest_1.tableoid, utrtest_1.ctid, NULL::record + -> Foreign Scan on public.remp utrtest_2 + Output: utrtest_2.a, utrtest_2.tableoid, utrtest_2.ctid, utrtest_2.* + Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE + -> Hash + Output: "*VALUES*".*, "*VALUES*".column1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".*, "*VALUES*".column1 +(18 rows) + +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR +ERROR: cannot route tuples into foreign table to be updated "remp" +drop table utrtest; +drop table loct; +-- Test copy tuple routing +create table ctrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table ctrtest attach partition remp1 for values in (1); +alter table ctrtest attach partition remp2 for values in (2); +copy ctrtest from stdin; +select tableoid::regclass, * FROM ctrtest; + tableoid | a | b +----------+---+----- + remp1 | 1 | foo + remp2 | 2 | qux +(2 rows) + +select tableoid::regclass, * FROM remp1; + tableoid | a | b +----------+---+----- + remp1 | 1 | foo +(1 row) + +select tableoid::regclass, * FROM remp2; + tableoid | b | a +----------+-----+--- + remp2 | qux | 2 +(1 row) + +-- Copying into foreign partitions directly should work as well +copy remp1 from stdin; +select tableoid::regclass, * FROM remp1; + tableoid | a | b +----------+---+----- + remp1 | 1 | foo + remp1 | 1 | bar +(2 rows) + +delete from ctrtest; +-- Test copy tuple routing with the batch_size option enabled +alter server loopback options (add batch_size '2'); +copy ctrtest from stdin; +select tableoid::regclass, * FROM ctrtest; + tableoid | a | b +----------+---+------- + remp1 | 1 | foo + remp1 | 1 | bar + remp1 | 1 | test1 + remp2 | 2 | baz + remp2 | 2 | qux + remp2 | 2 | test2 +(6 rows) + +select tableoid::regclass, * FROM remp1; + tableoid | a | b +----------+---+------- + remp1 | 1 | foo + remp1 | 1 | bar + remp1 | 1 | test1 +(3 rows) + +select tableoid::regclass, * FROM remp2; + tableoid | b | a +----------+-------+--- + remp2 | baz | 2 + remp2 | qux | 2 + remp2 | test2 | 2 +(3 rows) + +delete from ctrtest; +alter server loopback options (drop batch_size); +drop table ctrtest; +drop table loct1; +drop table loct2; +-- =================================================================== +-- test COPY FROM +-- =================================================================== +create table loc2 (f1 int, f2 text); +alter table loc2 set (autovacuum_enabled = 'false'); +create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2'); +-- Test basic functionality +copy rem2 from stdin; +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +delete from rem2; +-- Test check constraints +alter table loc2 add constraint loc2_f1positive check (f1 >= 0); +alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0); +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +copy rem2 from stdin; -- ERROR +ERROR: new row for relation "loc2" violates check constraint "loc2_f1positive" +DETAIL: Failing row contains (-1, xyzzy). +CONTEXT: remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2) +COPY rem2, line 1: "-1 xyzzy" +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +alter foreign table rem2 drop constraint rem2_f1positive; +alter table loc2 drop constraint loc2_f1positive; +delete from rem2; +-- Test local triggers +create trigger trig_stmt_before before insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_stmt_after after insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +copy rem2 from stdin; +NOTICE: trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2 +NOTICE: NEW: (1,foo) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2 +NOTICE: NEW: (2,bar) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2 +NOTICE: NEW: (1,foo) +NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2 +NOTICE: NEW: (2,bar) +NOTICE: trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +drop trigger trig_row_before on rem2; +drop trigger trig_row_after on rem2; +drop trigger trig_stmt_before on rem2; +drop trigger trig_stmt_after on rem2; +delete from rem2; +create trigger trig_row_before_insert before insert on rem2 + for each row execute procedure trig_row_before_insupdate(); +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +select * from rem2; + f1 | f2 +----+----------------- + 1 | foo triggered ! + 2 | bar triggered ! +(2 rows) + +drop trigger trig_row_before_insert on rem2; +delete from rem2; +create trigger trig_null before insert on rem2 + for each row execute procedure trig_null(); +-- Nothing happens +copy rem2 from stdin; +select * from rem2; + f1 | f2 +----+---- +(0 rows) + +drop trigger trig_null on rem2; +delete from rem2; +-- Test remote triggers +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +select * from rem2; + f1 | f2 +----+----------------- + 1 | foo triggered ! + 2 | bar triggered ! +(2 rows) + +drop trigger trig_row_before_insert on loc2; +delete from rem2; +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); +-- Nothing happens +copy rem2 from stdin; +select * from rem2; + f1 | f2 +----+---- +(0 rows) + +drop trigger trig_null on loc2; +delete from rem2; +-- Test a combination of local and remote triggers +create trigger rem2_trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger rem2_trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger loc2_trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); +copy rem2 from stdin; +NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2 +NOTICE: NEW: (1,foo) +NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2 +NOTICE: NEW: (2,bar) +NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2 +NOTICE: NEW: (1,"foo triggered !") +NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2 +NOTICE: NEW: (2,"bar triggered !") +select * from rem2; + f1 | f2 +----+----------------- + 1 | foo triggered ! + 2 | bar triggered ! +(2 rows) + +drop trigger rem2_trig_row_before on rem2; +drop trigger rem2_trig_row_after on rem2; +drop trigger loc2_trig_row_before_insert on loc2; +delete from rem2; +-- test COPY FROM with foreign table created in the same transaction +create table loc3 (f1 int, f2 text); +begin; +create foreign table rem3 (f1 int, f2 text) + server loopback options(table_name 'loc3'); +copy rem3 from stdin; +commit; +select * from rem3; + f1 | f2 +----+----- + 1 | foo + 2 | bar +(2 rows) + +drop foreign table rem3; +drop table loc3; +-- Test COPY FROM with the batch_size option enabled +alter server loopback options (add batch_size '2'); +-- Test basic functionality +copy rem2 from stdin; +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar + 3 | baz +(3 rows) + +delete from rem2; +-- Test check constraints +alter table loc2 add constraint loc2_f1positive check (f1 >= 0); +alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0); +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +copy rem2 from stdin; -- ERROR +ERROR: new row for relation "loc2" violates check constraint "loc2_f1positive" +DETAIL: Failing row contains (-1, xyzzy). +CONTEXT: remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2) +COPY rem2 +select * from rem2; + f1 | f2 +----+----- + 1 | foo + 2 | bar + 3 | baz +(3 rows) + +alter foreign table rem2 drop constraint rem2_f1positive; +alter table loc2 drop constraint loc2_f1positive; +delete from rem2; +-- Test remote triggers +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +select * from rem2; + f1 | f2 +----+----------------- + 1 | foo triggered ! + 2 | bar triggered ! + 3 | baz triggered ! +(3 rows) + +drop trigger trig_row_before_insert on loc2; +delete from rem2; +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); +-- Nothing happens +copy rem2 from stdin; +select * from rem2; + f1 | f2 +----+---- +(0 rows) + +drop trigger trig_null on loc2; +delete from rem2; +-- Check with zero-column foreign table; batch insert will be disabled +alter table loc2 drop column f1; +alter table loc2 drop column f2; +alter table rem2 drop column f1; +alter table rem2 drop column f2; +copy rem2 from stdin; +select * from rem2; +-- +(3 rows) + +delete from rem2; +alter server loopback options (drop batch_size); +-- =================================================================== +-- test for TRUNCATE +-- =================================================================== +CREATE TABLE tru_rtable0 (id int primary key); +CREATE FOREIGN TABLE tru_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_rtable0'); +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(1,10) x); +CREATE TABLE tru_ptable (id int) PARTITION BY HASH(id); +CREATE TABLE tru_ptable__p0 PARTITION OF tru_ptable + FOR VALUES WITH (MODULUS 2, REMAINDER 0); +CREATE TABLE tru_rtable1 (id int primary key); +CREATE FOREIGN TABLE tru_ftable__p1 PARTITION OF tru_ptable + FOR VALUES WITH (MODULUS 2, REMAINDER 1) + SERVER loopback OPTIONS (table_name 'tru_rtable1'); +INSERT INTO tru_ptable (SELECT x FROM generate_series(11,20) x); +CREATE TABLE tru_pk_table(id int primary key); +CREATE TABLE tru_fk_table(fkey int references tru_pk_table(id)); +INSERT INTO tru_pk_table (SELECT x FROM generate_series(1,10) x); +INSERT INTO tru_fk_table (SELECT x % 10 + 1 FROM generate_series(5,25) x); +CREATE FOREIGN TABLE tru_pk_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_pk_table'); +CREATE TABLE tru_rtable_parent (id int); +CREATE TABLE tru_rtable_child (id int); +CREATE FOREIGN TABLE tru_ftable_parent (id int) + SERVER loopback OPTIONS (table_name 'tru_rtable_parent'); +CREATE FOREIGN TABLE tru_ftable_child () INHERITS (tru_ftable_parent) + SERVER loopback OPTIONS (table_name 'tru_rtable_child'); +INSERT INTO tru_rtable_parent (SELECT x FROM generate_series(1,8) x); +INSERT INTO tru_rtable_child (SELECT x FROM generate_series(10, 18) x); +-- normal truncate +SELECT sum(id) FROM tru_ftable; -- 55 + sum +----- + 55 +(1 row) + +TRUNCATE tru_ftable; +SELECT count(*) FROM tru_rtable0; -- 0 + count +------- + 0 +(1 row) + +SELECT count(*) FROM tru_ftable; -- 0 + count +------- + 0 +(1 row) + +-- 'truncatable' option +ALTER SERVER loopback OPTIONS (ADD truncatable 'false'); +TRUNCATE tru_ftable; -- error +ERROR: foreign table "tru_ftable" does not allow truncates +ALTER FOREIGN TABLE tru_ftable OPTIONS (ADD truncatable 'true'); +TRUNCATE tru_ftable; -- accepted +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false'); +TRUNCATE tru_ftable; -- error +ERROR: foreign table "tru_ftable" does not allow truncates +ALTER SERVER loopback OPTIONS (DROP truncatable); +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false'); +TRUNCATE tru_ftable; -- error +ERROR: foreign table "tru_ftable" does not allow truncates +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'true'); +TRUNCATE tru_ftable; -- accepted +-- partitioned table with both local and foreign tables as partitions +SELECT sum(id) FROM tru_ptable; -- 155 + sum +----- + 155 +(1 row) + +TRUNCATE tru_ptable; +SELECT count(*) FROM tru_ptable; -- 0 + count +------- + 0 +(1 row) + +SELECT count(*) FROM tru_ptable__p0; -- 0 + count +------- + 0 +(1 row) + +SELECT count(*) FROM tru_ftable__p1; -- 0 + count +------- + 0 +(1 row) + +SELECT count(*) FROM tru_rtable1; -- 0 + count +------- + 0 +(1 row) + +-- 'CASCADE' option +SELECT sum(id) FROM tru_pk_ftable; -- 55 + sum +----- + 55 +(1 row) + +TRUNCATE tru_pk_ftable; -- failed by FK reference +ERROR: cannot truncate a table referenced in a foreign key constraint +DETAIL: Table "tru_fk_table" references "tru_pk_table". +HINT: Truncate table "tru_fk_table" at the same time, or use TRUNCATE ... CASCADE. +CONTEXT: remote SQL command: TRUNCATE public.tru_pk_table CONTINUE IDENTITY RESTRICT +TRUNCATE tru_pk_ftable CASCADE; +SELECT count(*) FROM tru_pk_ftable; -- 0 + count +------- + 0 +(1 row) + +SELECT count(*) FROM tru_fk_table; -- also truncated,0 + count +------- + 0 +(1 row) + +-- truncate two tables at a command +INSERT INTO tru_ftable (SELECT x FROM generate_series(1,8) x); +INSERT INTO tru_pk_ftable (SELECT x FROM generate_series(3,10) x); +SELECT count(*) from tru_ftable; -- 8 + count +------- + 8 +(1 row) + +SELECT count(*) from tru_pk_ftable; -- 8 + count +------- + 8 +(1 row) + +TRUNCATE tru_ftable, tru_pk_ftable CASCADE; +SELECT count(*) from tru_ftable; -- 0 + count +------- + 0 +(1 row) + +SELECT count(*) from tru_pk_ftable; -- 0 + count +------- + 0 +(1 row) + +-- truncate with ONLY clause +-- Since ONLY is specified, the table tru_ftable_child that inherits +-- tru_ftable_parent locally is not truncated. +TRUNCATE ONLY tru_ftable_parent; +SELECT sum(id) FROM tru_ftable_parent; -- 126 + sum +----- + 126 +(1 row) + +TRUNCATE tru_ftable_parent; +SELECT count(*) FROM tru_ftable_parent; -- 0 + count +------- + 0 +(1 row) + +-- in case when remote table has inherited children +CREATE TABLE tru_rtable0_child () INHERITS (tru_rtable0); +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(5,9) x); +INSERT INTO tru_rtable0_child (SELECT x FROM generate_series(10,14) x); +SELECT sum(id) FROM tru_ftable; -- 95 + sum +----- + 95 +(1 row) + +-- Both parent and child tables in the foreign server are truncated +-- even though ONLY is specified because ONLY has no effect +-- when truncating a foreign table. +TRUNCATE ONLY tru_ftable; +SELECT count(*) FROM tru_ftable; -- 0 + count +------- + 0 +(1 row) + +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(21,25) x); +INSERT INTO tru_rtable0_child (SELECT x FROM generate_series(26,30) x); +SELECT sum(id) FROM tru_ftable; -- 255 + sum +----- + 255 +(1 row) + +TRUNCATE tru_ftable; -- truncate both of parent and child +SELECT count(*) FROM tru_ftable; -- 0 + count +------- + 0 +(1 row) + +-- cleanup +DROP FOREIGN TABLE tru_ftable_parent, tru_ftable_child, tru_pk_ftable,tru_ftable__p1,tru_ftable; +DROP TABLE tru_rtable0, tru_rtable1, tru_ptable, tru_ptable__p0, tru_pk_table, tru_fk_table, +tru_rtable_parent,tru_rtable_child, tru_rtable0_child; +-- =================================================================== +-- test IMPORT FOREIGN SCHEMA +-- =================================================================== +CREATE SCHEMA import_source; +CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL); +CREATE TABLE import_source.t2 (c1 int default 42, c2 varchar NULL, c3 text collate "POSIX"); +CREATE TYPE typ1 AS (m1 int, m2 varchar); +CREATE TABLE import_source.t3 (c1 timestamptz default now(), c2 typ1); +CREATE TABLE import_source."x 4" (c1 float8, "C 2" text, c3 varchar(42)); +CREATE TABLE import_source."x 5" (c1 float8); +ALTER TABLE import_source."x 5" DROP COLUMN c1; +CREATE TABLE import_source."x 6" (c1 int, c2 int generated always as (c1 * 2) stored); +CREATE TABLE import_source.t4 (c1 int) PARTITION BY RANGE (c1); +CREATE TABLE import_source.t4_part PARTITION OF import_source.t4 + FOR VALUES FROM (1) TO (100); +CREATE TABLE import_source.t4_part2 PARTITION OF import_source.t4 + FOR VALUES FROM (100) TO (200); +CREATE SCHEMA import_dest1; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1; +\det+ import_dest1.* + List of foreign tables + Schema | Table | Server | FDW options | Description +--------------+-------+----------+-------------------------------------------------+------------- + import_dest1 | t1 | loopback | (schema_name 'import_source', table_name 't1') | + import_dest1 | t2 | loopback | (schema_name 'import_source', table_name 't2') | + import_dest1 | t3 | loopback | (schema_name 'import_source', table_name 't3') | + import_dest1 | t4 | loopback | (schema_name 'import_source', table_name 't4') | + import_dest1 | x 4 | loopback | (schema_name 'import_source', table_name 'x 4') | + import_dest1 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') | + import_dest1 | x 6 | loopback | (schema_name 'import_source', table_name 'x 6') | +(7 rows) + +\d import_dest1.* + Foreign table "import_dest1.t1" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | character varying | | not null | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't1') + + Foreign table "import_dest1.t2" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | character varying | | | | (column_name 'c2') + c3 | text | POSIX | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't2') + + Foreign table "import_dest1.t3" + Column | Type | Collation | Nullable | Default | FDW options +--------+--------------------------+-----------+----------+---------+-------------------- + c1 | timestamp with time zone | | | | (column_name 'c1') + c2 | typ1 | | | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't3') + + Foreign table "import_dest1.t4" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't4') + + Foreign table "import_dest1.x 4" + Column | Type | Collation | Nullable | Default | FDW options +--------+-----------------------+-----------+----------+---------+--------------------- + c1 | double precision | | | | (column_name 'c1') + C 2 | text | | | | (column_name 'C 2') + c3 | character varying(42) | | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 4') + + Foreign table "import_dest1.x 5" + Column | Type | Collation | Nullable | Default | FDW options +--------+------+-----------+----------+---------+------------- +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 5') + + Foreign table "import_dest1.x 6" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+-------------------------------------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | integer | | | generated always as (c1 * 2) stored | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 6') + +-- Options +CREATE SCHEMA import_dest2; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest2 + OPTIONS (import_default 'true'); +\det+ import_dest2.* + List of foreign tables + Schema | Table | Server | FDW options | Description +--------------+-------+----------+-------------------------------------------------+------------- + import_dest2 | t1 | loopback | (schema_name 'import_source', table_name 't1') | + import_dest2 | t2 | loopback | (schema_name 'import_source', table_name 't2') | + import_dest2 | t3 | loopback | (schema_name 'import_source', table_name 't3') | + import_dest2 | t4 | loopback | (schema_name 'import_source', table_name 't4') | + import_dest2 | x 4 | loopback | (schema_name 'import_source', table_name 'x 4') | + import_dest2 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') | + import_dest2 | x 6 | loopback | (schema_name 'import_source', table_name 'x 6') | +(7 rows) + +\d import_dest2.* + Foreign table "import_dest2.t1" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | character varying | | not null | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't1') + + Foreign table "import_dest2.t2" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | 42 | (column_name 'c1') + c2 | character varying | | | | (column_name 'c2') + c3 | text | POSIX | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't2') + + Foreign table "import_dest2.t3" + Column | Type | Collation | Nullable | Default | FDW options +--------+--------------------------+-----------+----------+---------+-------------------- + c1 | timestamp with time zone | | | now() | (column_name 'c1') + c2 | typ1 | | | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't3') + + Foreign table "import_dest2.t4" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't4') + + Foreign table "import_dest2.x 4" + Column | Type | Collation | Nullable | Default | FDW options +--------+-----------------------+-----------+----------+---------+--------------------- + c1 | double precision | | | | (column_name 'c1') + C 2 | text | | | | (column_name 'C 2') + c3 | character varying(42) | | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 4') + + Foreign table "import_dest2.x 5" + Column | Type | Collation | Nullable | Default | FDW options +--------+------+-----------+----------+---------+------------- +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 5') + + Foreign table "import_dest2.x 6" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+-------------------------------------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | integer | | | generated always as (c1 * 2) stored | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 6') + +CREATE SCHEMA import_dest3; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest3 + OPTIONS (import_collate 'false', import_generated 'false', import_not_null 'false'); +\det+ import_dest3.* + List of foreign tables + Schema | Table | Server | FDW options | Description +--------------+-------+----------+-------------------------------------------------+------------- + import_dest3 | t1 | loopback | (schema_name 'import_source', table_name 't1') | + import_dest3 | t2 | loopback | (schema_name 'import_source', table_name 't2') | + import_dest3 | t3 | loopback | (schema_name 'import_source', table_name 't3') | + import_dest3 | t4 | loopback | (schema_name 'import_source', table_name 't4') | + import_dest3 | x 4 | loopback | (schema_name 'import_source', table_name 'x 4') | + import_dest3 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') | + import_dest3 | x 6 | loopback | (schema_name 'import_source', table_name 'x 6') | +(7 rows) + +\d import_dest3.* + Foreign table "import_dest3.t1" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | character varying | | | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't1') + + Foreign table "import_dest3.t2" + Column | Type | Collation | Nullable | Default | FDW options +--------+-------------------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | character varying | | | | (column_name 'c2') + c3 | text | | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't2') + + Foreign table "import_dest3.t3" + Column | Type | Collation | Nullable | Default | FDW options +--------+--------------------------+-----------+----------+---------+-------------------- + c1 | timestamp with time zone | | | | (column_name 'c1') + c2 | typ1 | | | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't3') + + Foreign table "import_dest3.t4" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') +Server: loopback +FDW options: (schema_name 'import_source', table_name 't4') + + Foreign table "import_dest3.x 4" + Column | Type | Collation | Nullable | Default | FDW options +--------+-----------------------+-----------+----------+---------+--------------------- + c1 | double precision | | | | (column_name 'c1') + C 2 | text | | | | (column_name 'C 2') + c3 | character varying(42) | | | | (column_name 'c3') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 4') + + Foreign table "import_dest3.x 5" + Column | Type | Collation | Nullable | Default | FDW options +--------+------+-----------+----------+---------+------------- +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 5') + + Foreign table "import_dest3.x 6" + Column | Type | Collation | Nullable | Default | FDW options +--------+---------+-----------+----------+---------+-------------------- + c1 | integer | | | | (column_name 'c1') + c2 | integer | | | | (column_name 'c2') +Server: loopback +FDW options: (schema_name 'import_source', table_name 'x 6') + +-- Check LIMIT TO and EXCEPT +CREATE SCHEMA import_dest4; +IMPORT FOREIGN SCHEMA import_source LIMIT TO (t1, nonesuch, t4_part) + FROM SERVER loopback INTO import_dest4; +\det+ import_dest4.* + List of foreign tables + Schema | Table | Server | FDW options | Description +--------------+---------+----------+-----------------------------------------------------+------------- + import_dest4 | t1 | loopback | (schema_name 'import_source', table_name 't1') | + import_dest4 | t4_part | loopback | (schema_name 'import_source', table_name 't4_part') | +(2 rows) + +IMPORT FOREIGN SCHEMA import_source EXCEPT (t1, "x 4", nonesuch, t4_part) + FROM SERVER loopback INTO import_dest4; +\det+ import_dest4.* + List of foreign tables + Schema | Table | Server | FDW options | Description +--------------+---------+----------+-----------------------------------------------------+------------- + import_dest4 | t1 | loopback | (schema_name 'import_source', table_name 't1') | + import_dest4 | t2 | loopback | (schema_name 'import_source', table_name 't2') | + import_dest4 | t3 | loopback | (schema_name 'import_source', table_name 't3') | + import_dest4 | t4 | loopback | (schema_name 'import_source', table_name 't4') | + import_dest4 | t4_part | loopback | (schema_name 'import_source', table_name 't4_part') | + import_dest4 | x 5 | loopback | (schema_name 'import_source', table_name 'x 5') | + import_dest4 | x 6 | loopback | (schema_name 'import_source', table_name 'x 6') | +(7 rows) + +-- Assorted error cases +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest4; +ERROR: relation "t1" already exists +CONTEXT: importing foreign table "t1" +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO import_dest4; +ERROR: schema "nonesuch" is not present on foreign server "loopback" +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO notthere; +ERROR: schema "notthere" does not exist +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere; +ERROR: server "nowhere" does not exist +-- Check case of a type present only on the remote server. +-- We can fake this by dropping the type locally in our transaction. +CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue'); +CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors"); +CREATE SCHEMA import_dest5; +BEGIN; +DROP TYPE "Colors" CASCADE; +NOTICE: drop cascades to column Col of table import_source.t5 +IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5) + FROM SERVER loopback INTO import_dest5; -- ERROR +ERROR: type "public.Colors" does not exist +LINE 4: "Col" public."Colors" OPTIONS (column_name 'Col') + ^ +QUERY: CREATE FOREIGN TABLE t5 ( + c1 integer OPTIONS (column_name 'c1'), + c2 text OPTIONS (column_name 'c2') COLLATE pg_catalog."C", + "Col" public."Colors" OPTIONS (column_name 'Col') +) SERVER loopback +OPTIONS (schema_name 'import_source', table_name 't5'); +CONTEXT: importing foreign table "t5" +ROLLBACK; +BEGIN; +CREATE SERVER fetch101 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( fetch_size '101' ); +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=101']; + count +------- + 1 +(1 row) + +ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' ); +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=101']; + count +------- + 0 +(1 row) + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=202']; + count +------- + 1 +(1 row) + +CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' ); +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=30000']; + count +------- + 1 +(1 row) + +ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000'); +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=30000']; + count +------- + 0 +(1 row) + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=60000']; + count +------- + 1 +(1 row) + +ROLLBACK; +-- =================================================================== +-- test partitionwise joins +-- =================================================================== +SET enable_partitionwise_join=on; +CREATE TABLE fprt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE fprt1_p1 (LIKE fprt1); +CREATE TABLE fprt1_p2 (LIKE fprt1); +ALTER TABLE fprt1_p1 SET (autovacuum_enabled = 'false'); +ALTER TABLE fprt1_p2 SET (autovacuum_enabled = 'false'); +INSERT INTO fprt1_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 2) i; +INSERT INTO fprt1_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 2) i; +CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (0) TO (250) + SERVER loopback OPTIONS (table_name 'fprt1_p1', use_remote_estimate 'true'); +CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (250) TO (500) + SERVER loopback OPTIONS (TABLE_NAME 'fprt1_p2'); +ANALYZE fprt1; +ANALYZE fprt1_p1; +ANALYZE fprt1_p2; +CREATE TABLE fprt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE fprt2_p1 (LIKE fprt2); +CREATE TABLE fprt2_p2 (LIKE fprt2); +ALTER TABLE fprt2_p1 SET (autovacuum_enabled = 'false'); +ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false'); +INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i; +INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i; +CREATE FOREIGN TABLE ftprt2_p1 (b int, c varchar, a int) + SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true'); +ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250); +CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500) + SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true'); +ANALYZE fprt2; +ANALYZE fprt2_p1; +ANALYZE fprt2_p2; +-- inner join three tables +EXPLAIN (COSTS OFF) +SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; + QUERY PLAN +----------------------------------------------------------------------------------------------------- + Sort + Sort Key: t1.a, t3.c + -> Append + -> Foreign Scan + Relations: ((ftprt1_p1 t1_1) INNER JOIN (ftprt2_p1 t2_1)) INNER JOIN (ftprt1_p1 t3_1) + -> Foreign Scan + Relations: ((ftprt1_p2 t1_2) INNER JOIN (ftprt2_p2 t2_2)) INNER JOIN (ftprt1_p2 t3_2) +(7 rows) + +SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; + a | b | c +-----+-----+------ + 0 | 0 | 0000 + 150 | 150 | 0003 + 250 | 250 | 0005 + 400 | 400 | 0008 +(4 rows) + +-- left outer join + nullable clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: t1.a, fprt2.b, fprt2.c + Relations: (public.ftprt1_p1 t1) LEFT JOIN (public.ftprt2_p1 fprt2) + Remote SQL: SELECT r5.a, r6.b, r6.c FROM (public.fprt1_p1 r5 LEFT JOIN public.fprt2_p1 r6 ON (((r5.a = r6.b)) AND ((r5.b = r6.a)) AND ((r6.a < 10)))) WHERE ((r5.a < 10)) ORDER BY r5.a ASC NULLS LAST, r6.b ASC NULLS LAST, r6.c ASC NULLS LAST +(4 rows) + +SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; + a | b | c +---+---+------ + 0 | 0 | 0000 + 2 | | + 4 | | + 6 | 6 | 0000 + 8 | | +(5 rows) + +-- with whole-row reference; partitionwise join does not apply +EXPLAIN (COSTS OFF) +SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; + QUERY PLAN +-------------------------------------------------------- + Sort + Sort Key: ((t1.*)::fprt1), ((t2.*)::fprt2) + -> Hash Full Join + Hash Cond: (t1.a = t2.b) + -> Append + -> Foreign Scan on ftprt1_p1 t1_1 + -> Foreign Scan on ftprt1_p2 t1_2 + -> Hash + -> Append + -> Foreign Scan on ftprt2_p1 t2_1 + -> Foreign Scan on ftprt2_p2 t2_2 +(11 rows) + +SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; + wr | wr +----------------+---------------- + (0,0,0000) | (0,0,0000) + (50,50,0001) | + (100,100,0002) | + (150,150,0003) | (150,150,0003) + (200,200,0004) | + (250,250,0005) | (250,250,0005) + (300,300,0006) | + (350,350,0007) | + (400,400,0008) | (400,400,0008) + (450,450,0009) | + | (75,75,0001) + | (225,225,0004) + | (325,325,0006) + | (475,475,0009) +(14 rows) + +-- join with lateral reference +EXPLAIN (COSTS OFF) +SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2; + QUERY PLAN +----------------------------------------------------------------------- + Sort + Sort Key: t1.a, t1.b + -> Append + -> Foreign Scan + Relations: (ftprt1_p1 t1_1) INNER JOIN (ftprt2_p1 t2_1) + -> Foreign Scan + Relations: (ftprt1_p2 t1_2) INNER JOIN (ftprt2_p2 t2_2) +(7 rows) + +SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2; + a | b +-----+----- + 0 | 0 + 150 | 150 + 250 | 250 + 400 | 400 +(4 rows) + +-- with PHVs, partitionwise join selected but no join pushdown +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; + QUERY PLAN +----------------------------------------------------------- + Sort + Sort Key: fprt1.a, fprt2.b + -> Append + -> Hash Full Join + Hash Cond: (fprt1_1.a = fprt2_1.b) + -> Foreign Scan on ftprt1_p1 fprt1_1 + -> Hash + -> Foreign Scan on ftprt2_p1 fprt2_1 + -> Hash Full Join + Hash Cond: (fprt1_2.a = fprt2_2.b) + -> Foreign Scan on ftprt1_p2 fprt1_2 + -> Hash + -> Foreign Scan on ftprt2_p2 fprt2_2 +(13 rows) + +SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; + a | phv | b | phv +-----+--------+-----+-------- + 0 | t1_phv | 0 | t2_phv + 50 | t1_phv | | + 100 | t1_phv | | + 150 | t1_phv | 150 | t2_phv + 200 | t1_phv | | + 250 | t1_phv | 250 | t2_phv + 300 | t1_phv | | + 350 | t1_phv | | + 400 | t1_phv | 400 | t2_phv + 450 | t1_phv | | + | | 75 | t2_phv + | | 225 | t2_phv + | | 325 | t2_phv + | | 475 | t2_phv +(14 rows) + +-- test FOR UPDATE; partitionwise join does not apply +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; + QUERY PLAN +-------------------------------------------------------------- + LockRows + -> Sort + Sort Key: t1.a + -> Hash Join + Hash Cond: (t2.b = t1.a) + -> Append + -> Foreign Scan on ftprt2_p1 t2_1 + -> Foreign Scan on ftprt2_p2 t2_2 + -> Hash + -> Append + -> Foreign Scan on ftprt1_p1 t1_1 + -> Foreign Scan on ftprt1_p2 t1_2 +(12 rows) + +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; + a | b +-----+----- + 0 | 0 + 150 | 150 + 250 | 250 + 400 | 400 +(4 rows) + +RESET enable_partitionwise_join; +-- =================================================================== +-- test partitionwise aggregates +-- =================================================================== +CREATE TABLE pagg_tab (a int, b int, c text) PARTITION BY RANGE(a); +CREATE TABLE pagg_tab_p1 (LIKE pagg_tab); +CREATE TABLE pagg_tab_p2 (LIKE pagg_tab); +CREATE TABLE pagg_tab_p3 (LIKE pagg_tab); +INSERT INTO pagg_tab_p1 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 10; +INSERT INTO pagg_tab_p2 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 20 and (i % 30) >= 10; +INSERT INTO pagg_tab_p3 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 30 and (i % 30) >= 20; +-- Create foreign partitions +CREATE FOREIGN TABLE fpagg_tab_p1 PARTITION OF pagg_tab FOR VALUES FROM (0) TO (10) SERVER loopback OPTIONS (table_name 'pagg_tab_p1'); +CREATE FOREIGN TABLE fpagg_tab_p2 PARTITION OF pagg_tab FOR VALUES FROM (10) TO (20) SERVER loopback OPTIONS (table_name 'pagg_tab_p2'); +CREATE FOREIGN TABLE fpagg_tab_p3 PARTITION OF pagg_tab FOR VALUES FROM (20) TO (30) SERVER loopback OPTIONS (table_name 'pagg_tab_p3'); +ANALYZE pagg_tab; +ANALYZE fpagg_tab_p1; +ANALYZE fpagg_tab_p2; +ANALYZE fpagg_tab_p3; +-- When GROUP BY clause matches with PARTITION KEY. +-- Plan with partitionwise aggregates is disabled +SET enable_partitionwise_aggregate TO false; +EXPLAIN (COSTS OFF) +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + QUERY PLAN +----------------------------------------------------------- + Sort + Sort Key: pagg_tab.a + -> HashAggregate + Group Key: pagg_tab.a + Filter: (avg(pagg_tab.b) < '22'::numeric) + -> Append + -> Foreign Scan on fpagg_tab_p1 pagg_tab_1 + -> Foreign Scan on fpagg_tab_p2 pagg_tab_2 + -> Foreign Scan on fpagg_tab_p3 pagg_tab_3 +(9 rows) + +-- Plan with partitionwise aggregates is enabled +SET enable_partitionwise_aggregate TO true; +EXPLAIN (COSTS OFF) +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + QUERY PLAN +----------------------------------------------------------------- + Sort + Sort Key: pagg_tab.a + -> Append + -> Foreign Scan + Relations: Aggregate on (fpagg_tab_p1 pagg_tab) + -> Foreign Scan + Relations: Aggregate on (fpagg_tab_p2 pagg_tab_1) + -> Foreign Scan + Relations: Aggregate on (fpagg_tab_p3 pagg_tab_2) +(9 rows) + +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + a | sum | min | count +----+------+-----+------- + 0 | 2000 | 0 | 100 + 1 | 2100 | 1 | 100 + 10 | 2000 | 0 | 100 + 11 | 2100 | 1 | 100 + 20 | 2000 | 0 | 100 + 21 | 2100 | 1 | 100 +(6 rows) + +-- Check with whole-row reference +-- Should have all the columns in the target list for the given relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a, count(t1) FROM pagg_tab t1 GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + QUERY PLAN +------------------------------------------------------------------------ + Sort + Output: t1.a, (count(((t1.*)::pagg_tab))) + Sort Key: t1.a + -> Append + -> HashAggregate + Output: t1.a, count(((t1.*)::pagg_tab)) + Group Key: t1.a + Filter: (avg(t1.b) < '22'::numeric) + -> Foreign Scan on public.fpagg_tab_p1 t1 + Output: t1.a, t1.*, t1.b + Remote SQL: SELECT a, b, c FROM public.pagg_tab_p1 + -> HashAggregate + Output: t1_1.a, count(((t1_1.*)::pagg_tab)) + Group Key: t1_1.a + Filter: (avg(t1_1.b) < '22'::numeric) + -> Foreign Scan on public.fpagg_tab_p2 t1_1 + Output: t1_1.a, t1_1.*, t1_1.b + Remote SQL: SELECT a, b, c FROM public.pagg_tab_p2 + -> HashAggregate + Output: t1_2.a, count(((t1_2.*)::pagg_tab)) + Group Key: t1_2.a + Filter: (avg(t1_2.b) < '22'::numeric) + -> Foreign Scan on public.fpagg_tab_p3 t1_2 + Output: t1_2.a, t1_2.*, t1_2.b + Remote SQL: SELECT a, b, c FROM public.pagg_tab_p3 +(25 rows) + +SELECT a, count(t1) FROM pagg_tab t1 GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + a | count +----+------- + 0 | 100 + 1 | 100 + 10 | 100 + 11 | 100 + 20 | 100 + 21 | 100 +(6 rows) + +-- When GROUP BY clause does not match with PARTITION KEY. +EXPLAIN (COSTS OFF) +SELECT b, avg(a), max(a), count(*) FROM pagg_tab GROUP BY b HAVING sum(a) < 700 ORDER BY 1; + QUERY PLAN +----------------------------------------------------------------- + Sort + Sort Key: pagg_tab.b + -> Finalize HashAggregate + Group Key: pagg_tab.b + Filter: (sum(pagg_tab.a) < 700) + -> Append + -> Partial HashAggregate + Group Key: pagg_tab.b + -> Foreign Scan on fpagg_tab_p1 pagg_tab + -> Partial HashAggregate + Group Key: pagg_tab_1.b + -> Foreign Scan on fpagg_tab_p2 pagg_tab_1 + -> Partial HashAggregate + Group Key: pagg_tab_2.b + -> Foreign Scan on fpagg_tab_p3 pagg_tab_2 +(15 rows) + +-- =================================================================== +-- access rights and superuser +-- =================================================================== +-- Non-superuser cannot create a FDW without a password in the connstr +CREATE ROLE regress_nosuper NOSUPERUSER; +GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO regress_nosuper; +SET ROLE regress_nosuper; +SHOW is_superuser; + is_superuser +-------------- + off +(1 row) + +-- This will be OK, we can create the FDW +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback_nopw FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; +-- But creation of user mappings for non-superusers should fail +CREATE USER MAPPING FOR public SERVER loopback_nopw; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; +CREATE FOREIGN TABLE pg_temp.ft1_nopw ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback_nopw OPTIONS (schema_name 'public', table_name 'ft1'); +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password or GSSAPI delegated credentials required +DETAIL: Non-superusers must delegate GSSAPI credentials or provide a password in the user mapping. +-- If we add a password to the connstr it'll fail, because we don't allow passwords +-- in connstrs only in user mappings. +ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw'); +ERROR: invalid option "password" +HINT: Perhaps you meant the option "passfile". +-- If we add a password for our user mapping instead, we should get a different +-- error because the password wasn't actually *used* when we run with trust auth. +-- +-- This won't work with installcheck, but neither will most of the FDW checks. +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password 'dummypw'); +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password or GSSAPI delegated credentials required +DETAIL: Non-superuser cannot connect if the server does not request a password or use GSSAPI with delegated credentials. +HINT: Target server's authentication method must be changed or password_required=false set in the user mapping attributes. +-- Unpriv user cannot make the mapping passwordless +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password_required 'false'); +ERROR: password_required=false is superuser-only +HINT: User mappings with the password_required option set to false may only be created or modified by the superuser. +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password or GSSAPI delegated credentials required +DETAIL: Non-superuser cannot connect if the server does not request a password or use GSSAPI with delegated credentials. +HINT: Target server's authentication method must be changed or password_required=false set in the user mapping attributes. +RESET ROLE; +-- But the superuser can +ALTER USER MAPPING FOR regress_nosuper SERVER loopback_nopw OPTIONS (ADD password_required 'false'); +SET ROLE regress_nosuper; +-- Should finally work now +SELECT 1 FROM ft1_nopw LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- unpriv user also cannot set sslcert / sslkey on the user mapping +-- first set password_required so we see the right error messages +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (SET password_required 'true'); +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslcert 'foo.crt'); +ERROR: sslcert and sslkey are superuser-only +HINT: User mappings with the sslcert or sslkey options set may only be created or modified by the superuser. +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslkey 'foo.key'); +ERROR: sslcert and sslkey are superuser-only +HINT: User mappings with the sslcert or sslkey options set may only be created or modified by the superuser. +-- We're done with the role named after a specific user and need to check the +-- changes to the public mapping. +DROP USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; +-- This will fail again as it'll resolve the user mapping for public, which +-- lacks password_required=false +SELECT 1 FROM ft1_nopw LIMIT 1; +ERROR: password or GSSAPI delegated credentials required +DETAIL: Non-superusers must delegate GSSAPI credentials or provide a password in the user mapping. +RESET ROLE; +-- The user mapping for public is passwordless and lacks the password_required=false +-- mapping option, but will work because the current user is a superuser. +SELECT 1 FROM ft1_nopw LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- cleanup +DROP USER MAPPING FOR public SERVER loopback_nopw; +DROP OWNED BY regress_nosuper; +DROP ROLE regress_nosuper; +-- Clean-up +RESET enable_partitionwise_aggregate; +-- Two-phase transactions are not supported. +BEGIN; +SELECT count(*) FROM ft1; + count +------- + 822 +(1 row) + +-- error here +PREPARE TRANSACTION 'fdw_tpc'; +ERROR: cannot PREPARE a transaction that has operated on postgres_fdw foreign tables +ROLLBACK; +WARNING: there is no transaction in progress +-- =================================================================== +-- reestablish new connection +-- =================================================================== +-- Change application_name of remote connection to special one +-- so that we can easily terminate the connection later. +ALTER SERVER loopback OPTIONS (application_name 'fdw_retry_check'); +-- Make sure we have a remote connection. +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- Terminate the remote connection and wait for the termination to complete. +-- (If a cache flush happens, the remote connection might have already been +-- dropped; so code this step in a way that doesn't fail if no connection.) +DO $$ BEGIN +PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity + WHERE application_name = 'fdw_retry_check'; +END $$; +-- This query should detect the broken connection when starting new remote +-- transaction, reestablish new connection, and then succeed. +BEGIN; +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- If we detect the broken connection when starting a new remote +-- subtransaction, we should fail instead of establishing a new connection. +-- Terminate the remote connection and wait for the termination to complete. +DO $$ BEGIN +PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity + WHERE application_name = 'fdw_retry_check'; +END $$; +SAVEPOINT s; +-- The text of the error might vary across platforms, so only show SQLSTATE. +\set VERBOSITY sqlstate +SELECT 1 FROM ft1 LIMIT 1; -- should fail +ERROR: 08006 +\set VERBOSITY default +COMMIT; +-- ============================================================================= +-- test connection invalidation cases and postgres_fdw_get_connections function +-- ============================================================================= +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); + ?column? +---------- + 1 +(1 row) + +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- +(0 rows) + +-- This test case is for closing the connection in pgfdw_xact_callback +BEGIN; +-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact. +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +SELECT 1 FROM ft7 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- List all the existing cached connections. loopback and loopback3 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- + loopback + loopback3 +(2 rows) + +-- Connections are not closed at the end of the alter and drop statements. +-- That's because the connections are in midst of this xact, +-- they are just marked as invalid in pgfdw_inval_callback. +ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off'); +DROP SERVER loopback3 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to user mapping for public on server loopback3 +drop cascades to foreign table ft7 +-- List all the existing cached connections. loopback and loopback3 +-- should be output as invalid connections. Also the server name for +-- loopback3 should be NULL because the server was dropped. +SELECT * FROM postgres_fdw_get_connections() ORDER BY 1; + server_name | valid +-------------+------- + loopback | f + | f +(2 rows) + +-- The invalid connections get closed in pgfdw_xact_callback during commit. +COMMIT; +-- All cached connections were closed while committing above xact, so no +-- records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- +(0 rows) + +-- ======================================================================= +-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions +-- ======================================================================= +BEGIN; +-- Ensure to cache loopback connection. +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- Ensure to cache loopback2 connection. +SELECT 1 FROM ft6 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- List all the existing cached connections. loopback and loopback2 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- + loopback + loopback2 +(2 rows) + +-- Issue a warning and return false as loopback connection is still in use and +-- can not be closed. +SELECT postgres_fdw_disconnect('loopback'); +WARNING: cannot close connection for server "loopback" because it is still in use + postgres_fdw_disconnect +------------------------- + f +(1 row) + +-- List all the existing cached connections. loopback and loopback2 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- + loopback + loopback2 +(2 rows) + +-- Return false as connections are still in use, warnings are issued. +-- But disable warnings temporarily because the order of them is not stable. +SET client_min_messages = 'ERROR'; +SELECT postgres_fdw_disconnect_all(); + postgres_fdw_disconnect_all +----------------------------- + f +(1 row) + +RESET client_min_messages; +COMMIT; +-- Ensure that loopback2 connection is closed. +SELECT 1 FROM postgres_fdw_disconnect('loopback2'); + ?column? +---------- + 1 +(1 row) + +SELECT server_name FROM postgres_fdw_get_connections() WHERE server_name = 'loopback2'; + server_name +------------- +(0 rows) + +-- Return false as loopback2 connection is closed already. +SELECT postgres_fdw_disconnect('loopback2'); + postgres_fdw_disconnect +------------------------- + f +(1 row) + +-- Return an error as there is no foreign server with given name. +SELECT postgres_fdw_disconnect('unknownserver'); +ERROR: server "unknownserver" does not exist +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); + ?column? +---------- + 1 +(1 row) + +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- +(0 rows) + +-- ============================================================================= +-- test case for having multiple cached connections for a foreign server +-- ============================================================================= +CREATE ROLE regress_multi_conn_user1 SUPERUSER; +CREATE ROLE regress_multi_conn_user2 SUPERUSER; +CREATE USER MAPPING FOR regress_multi_conn_user1 SERVER loopback; +CREATE USER MAPPING FOR regress_multi_conn_user2 SERVER loopback; +BEGIN; +-- Will cache loopback connection with user mapping for regress_multi_conn_user1 +SET ROLE regress_multi_conn_user1; +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +RESET ROLE; +-- Will cache loopback connection with user mapping for regress_multi_conn_user2 +SET ROLE regress_multi_conn_user2; +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +RESET ROLE; +-- Should output two connections for loopback server +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- + loopback + loopback +(2 rows) + +COMMIT; +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); + ?column? +---------- + 1 +(1 row) + +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- +(0 rows) + +-- Clean up +DROP USER MAPPING FOR regress_multi_conn_user1 SERVER loopback; +DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback; +DROP ROLE regress_multi_conn_user1; +DROP ROLE regress_multi_conn_user2; +-- =================================================================== +-- Test foreign server level option keep_connections +-- =================================================================== +-- By default, the connections associated with foreign server are cached i.e. +-- keep_connections option is on. Set it to off. +ALTER SERVER loopback OPTIONS (keep_connections 'off'); +-- connection to loopback server is closed at the end of xact +-- as keep_connections was set to off. +SELECT 1 FROM ft1 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + server_name +------------- +(0 rows) + +ALTER SERVER loopback OPTIONS (SET keep_connections 'on'); +-- =================================================================== +-- batch insert +-- =================================================================== +BEGIN; +CREATE SERVER batch10 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( batch_size '10' ); +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; + count +------- + 1 +(1 row) + +ALTER SERVER batch10 OPTIONS( SET batch_size '20' ); +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; + count +------- + 0 +(1 row) + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=20']; + count +------- + 1 +(1 row) + +CREATE FOREIGN TABLE table30 ( x int ) SERVER batch10 OPTIONS ( batch_size '30' ); +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; + count +------- + 1 +(1 row) + +ALTER FOREIGN TABLE table30 OPTIONS ( SET batch_size '40'); +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; + count +------- + 0 +(1 row) + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=40']; + count +------- + 1 +(1 row) + +ROLLBACK; +CREATE TABLE batch_table ( x int ); +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table', batch_size '10' ); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; + QUERY PLAN +------------------------------------------------------------- + Insert on public.ftable + Remote SQL: INSERT INTO public.batch_table(x) VALUES ($1) + Batch Size: 10 + -> Function Scan on pg_catalog.generate_series i + Output: i.i + Function Call: generate_series(1, 10) +(6 rows) + +INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; +INSERT INTO ftable SELECT * FROM generate_series(11, 31) i; +INSERT INTO ftable VALUES (32); +INSERT INTO ftable VALUES (33), (34); +SELECT COUNT(*) FROM ftable; + count +------- + 34 +(1 row) + +TRUNCATE batch_table; +DROP FOREIGN TABLE ftable; +-- Disable batch insert +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table', batch_size '1' ); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (1), (2); + QUERY PLAN +------------------------------------------------------------- + Insert on public.ftable + Remote SQL: INSERT INTO public.batch_table(x) VALUES ($1) + Batch Size: 1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1 +(5 rows) + +INSERT INTO ftable VALUES (1), (2); +SELECT COUNT(*) FROM ftable; + count +------- + 2 +(1 row) + +-- Disable batch inserting into foreign tables with BEFORE ROW INSERT triggers +-- even if the batch_size option is enabled. +ALTER FOREIGN TABLE ftable OPTIONS ( SET batch_size '10' ); +CREATE TRIGGER trig_row_before BEFORE INSERT ON ftable +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (3), (4); + QUERY PLAN +------------------------------------------------------------- + Insert on public.ftable + Remote SQL: INSERT INTO public.batch_table(x) VALUES ($1) + Batch Size: 1 + -> Values Scan on "*VALUES*" + Output: "*VALUES*".column1 +(5 rows) + +INSERT INTO ftable VALUES (3), (4); +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON ftable +NOTICE: NEW: (3) +NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON ftable +NOTICE: NEW: (4) +SELECT COUNT(*) FROM ftable; + count +------- + 4 +(1 row) + +-- Clean up +DROP TRIGGER trig_row_before ON ftable; +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +-- Use partitioning +CREATE TABLE batch_table ( x int ) PARTITION BY HASH (x); +CREATE TABLE batch_table_p0 (LIKE batch_table); +CREATE FOREIGN TABLE batch_table_p0f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 0) + SERVER loopback + OPTIONS (table_name 'batch_table_p0', batch_size '10'); +CREATE TABLE batch_table_p1 (LIKE batch_table); +CREATE FOREIGN TABLE batch_table_p1f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 1) + SERVER loopback + OPTIONS (table_name 'batch_table_p1', batch_size '1'); +CREATE TABLE batch_table_p2 + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 2); +INSERT INTO batch_table SELECT * FROM generate_series(1, 66) i; +SELECT COUNT(*) FROM batch_table; + count +------- + 66 +(1 row) + +-- Clean up +DROP TABLE batch_table; +DROP TABLE batch_table_p0; +DROP TABLE batch_table_p1; +-- Check that batched mode also works for some inserts made during +-- cross-partition updates +CREATE TABLE batch_cp_upd_test (a int) PARTITION BY LIST (a); +CREATE TABLE batch_cp_upd_test1 (LIKE batch_cp_upd_test); +CREATE FOREIGN TABLE batch_cp_upd_test1_f + PARTITION OF batch_cp_upd_test + FOR VALUES IN (1) + SERVER loopback + OPTIONS (table_name 'batch_cp_upd_test1', batch_size '10'); +CREATE TABLE batch_cp_upd_test2 PARTITION OF batch_cp_upd_test + FOR VALUES IN (2); +CREATE TABLE batch_cp_upd_test3 (LIKE batch_cp_upd_test); +CREATE FOREIGN TABLE batch_cp_upd_test3_f + PARTITION OF batch_cp_upd_test + FOR VALUES IN (3) + SERVER loopback + OPTIONS (table_name 'batch_cp_upd_test3', batch_size '1'); +-- Create statement triggers on remote tables that "log" any INSERTs +-- performed on them. +CREATE TABLE cmdlog (cmd text); +CREATE FUNCTION log_stmt() RETURNS TRIGGER LANGUAGE plpgsql AS $$ + BEGIN INSERT INTO public.cmdlog VALUES (TG_OP || ' on ' || TG_RELNAME); RETURN NULL; END; +$$; +CREATE TRIGGER stmt_trig AFTER INSERT ON batch_cp_upd_test1 + FOR EACH STATEMENT EXECUTE FUNCTION log_stmt(); +CREATE TRIGGER stmt_trig AFTER INSERT ON batch_cp_upd_test3 + FOR EACH STATEMENT EXECUTE FUNCTION log_stmt(); +-- This update moves rows from the local partition 'batch_cp_upd_test2' to the +-- foreign partition 'batch_cp_upd_test1', one that has insert batching +-- enabled, so a single INSERT for both rows. +INSERT INTO batch_cp_upd_test VALUES (2), (2); +UPDATE batch_cp_upd_test t SET a = 1 FROM (VALUES (1), (2)) s(a) WHERE t.a = s.a AND s.a = 2; +-- This one moves rows from the local partition 'batch_cp_upd_test2' to the +-- foreign partition 'batch_cp_upd_test2', one that has insert batching +-- disabled, so separate INSERTs for the two rows. +INSERT INTO batch_cp_upd_test VALUES (2), (2); +UPDATE batch_cp_upd_test t SET a = 3 FROM (VALUES (1), (2)) s(a) WHERE t.a = s.a AND s.a = 2; +SELECT tableoid::regclass, * FROM batch_cp_upd_test ORDER BY 1; + tableoid | a +----------------------+--- + batch_cp_upd_test1_f | 1 + batch_cp_upd_test1_f | 1 + batch_cp_upd_test3_f | 3 + batch_cp_upd_test3_f | 3 +(4 rows) + +-- Should see 1 INSERT on batch_cp_upd_test1 and 2 on batch_cp_upd_test3 as +-- described above. +SELECT * FROM cmdlog ORDER BY 1; + cmd +------------------------------ + INSERT on batch_cp_upd_test1 + INSERT on batch_cp_upd_test3 + INSERT on batch_cp_upd_test3 +(3 rows) + +-- Clean up +DROP TABLE batch_cp_upd_test; +DROP TABLE batch_cp_upd_test1; +DROP TABLE batch_cp_upd_test3; +DROP TABLE cmdlog; +DROP FUNCTION log_stmt(); +-- Use partitioning +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +CREATE TABLE batch_table ( x int, field1 text, field2 text) PARTITION BY HASH (x); +CREATE TABLE batch_table_p0 (LIKE batch_table); +ALTER TABLE batch_table_p0 ADD CONSTRAINT p0_pkey PRIMARY KEY (x); +CREATE FOREIGN TABLE batch_table_p0f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 2, REMAINDER 0) + SERVER loopback + OPTIONS (table_name 'batch_table_p0'); +CREATE TABLE batch_table_p1 (LIKE batch_table); +ALTER TABLE batch_table_p1 ADD CONSTRAINT p1_pkey PRIMARY KEY (x); +CREATE FOREIGN TABLE batch_table_p1f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 2, REMAINDER 1) + SERVER loopback + OPTIONS (table_name 'batch_table_p1'); +INSERT INTO batch_table SELECT i, 'test'||i, 'test'|| i FROM generate_series(1, 50) i; +SELECT COUNT(*) FROM batch_table; + count +------- + 50 +(1 row) + +SELECT * FROM batch_table ORDER BY x; + x | field1 | field2 +----+--------+-------- + 1 | test1 | test1 + 2 | test2 | test2 + 3 | test3 | test3 + 4 | test4 | test4 + 5 | test5 | test5 + 6 | test6 | test6 + 7 | test7 | test7 + 8 | test8 | test8 + 9 | test9 | test9 + 10 | test10 | test10 + 11 | test11 | test11 + 12 | test12 | test12 + 13 | test13 | test13 + 14 | test14 | test14 + 15 | test15 | test15 + 16 | test16 | test16 + 17 | test17 | test17 + 18 | test18 | test18 + 19 | test19 | test19 + 20 | test20 | test20 + 21 | test21 | test21 + 22 | test22 | test22 + 23 | test23 | test23 + 24 | test24 | test24 + 25 | test25 | test25 + 26 | test26 | test26 + 27 | test27 | test27 + 28 | test28 | test28 + 29 | test29 | test29 + 30 | test30 | test30 + 31 | test31 | test31 + 32 | test32 | test32 + 33 | test33 | test33 + 34 | test34 | test34 + 35 | test35 | test35 + 36 | test36 | test36 + 37 | test37 | test37 + 38 | test38 | test38 + 39 | test39 | test39 + 40 | test40 | test40 + 41 | test41 | test41 + 42 | test42 | test42 + 43 | test43 | test43 + 44 | test44 | test44 + 45 | test45 | test45 + 46 | test46 | test46 + 47 | test47 | test47 + 48 | test48 | test48 + 49 | test49 | test49 + 50 | test50 | test50 +(50 rows) + +-- Clean up +DROP TABLE batch_table; +DROP TABLE batch_table_p0; +DROP TABLE batch_table_p1; +ALTER SERVER loopback OPTIONS (DROP batch_size); +-- Test that pending inserts are handled properly when needed +CREATE TABLE batch_table (a text, b int); +CREATE FOREIGN TABLE ftable (a text, b int) + SERVER loopback + OPTIONS (table_name 'batch_table', batch_size '2'); +CREATE TABLE ltable (a text, b int); +CREATE FUNCTION ftable_rowcount_trigf() RETURNS trigger LANGUAGE plpgsql AS +$$ +begin + raise notice '%: there are % rows in ftable', + TG_NAME, (SELECT count(*) FROM ftable); + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end; +$$; +CREATE TRIGGER ftable_rowcount_trigger +BEFORE INSERT OR UPDATE OR DELETE ON ltable +FOR EACH ROW EXECUTE PROCEDURE ftable_rowcount_trigf(); +WITH t AS ( + INSERT INTO ltable VALUES ('AAA', 42), ('BBB', 42) RETURNING * +) +INSERT INTO ftable SELECT * FROM t; +NOTICE: ftable_rowcount_trigger: there are 0 rows in ftable +NOTICE: ftable_rowcount_trigger: there are 1 rows in ftable +SELECT * FROM ltable; + a | b +-----+---- + AAA | 42 + BBB | 42 +(2 rows) + +SELECT * FROM ftable; + a | b +-----+---- + AAA | 42 + BBB | 42 +(2 rows) + +DELETE FROM ftable; +WITH t AS ( + UPDATE ltable SET b = b + 100 RETURNING * +) +INSERT INTO ftable SELECT * FROM t; +NOTICE: ftable_rowcount_trigger: there are 0 rows in ftable +NOTICE: ftable_rowcount_trigger: there are 1 rows in ftable +SELECT * FROM ltable; + a | b +-----+----- + AAA | 142 + BBB | 142 +(2 rows) + +SELECT * FROM ftable; + a | b +-----+----- + AAA | 142 + BBB | 142 +(2 rows) + +DELETE FROM ftable; +WITH t AS ( + DELETE FROM ltable RETURNING * +) +INSERT INTO ftable SELECT * FROM t; +NOTICE: ftable_rowcount_trigger: there are 0 rows in ftable +NOTICE: ftable_rowcount_trigger: there are 1 rows in ftable +SELECT * FROM ltable; + a | b +---+--- +(0 rows) + +SELECT * FROM ftable; + a | b +-----+----- + AAA | 142 + BBB | 142 +(2 rows) + +DELETE FROM ftable; +-- Clean up +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +DROP TRIGGER ftable_rowcount_trigger ON ltable; +DROP TABLE ltable; +CREATE TABLE parent (a text, b int) PARTITION BY LIST (a); +CREATE TABLE batch_table (a text, b int); +CREATE FOREIGN TABLE ftable + PARTITION OF parent + FOR VALUES IN ('AAA') + SERVER loopback + OPTIONS (table_name 'batch_table', batch_size '2'); +CREATE TABLE ltable + PARTITION OF parent + FOR VALUES IN ('BBB'); +CREATE TRIGGER ftable_rowcount_trigger +BEFORE INSERT ON ltable +FOR EACH ROW EXECUTE PROCEDURE ftable_rowcount_trigf(); +INSERT INTO parent VALUES ('AAA', 42), ('BBB', 42), ('AAA', 42), ('BBB', 42); +NOTICE: ftable_rowcount_trigger: there are 1 rows in ftable +NOTICE: ftable_rowcount_trigger: there are 2 rows in ftable +SELECT tableoid::regclass, * FROM parent; + tableoid | a | b +----------+-----+---- + ftable | AAA | 42 + ftable | AAA | 42 + ltable | BBB | 42 + ltable | BBB | 42 +(4 rows) + +-- Clean up +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +DROP TRIGGER ftable_rowcount_trigger ON ltable; +DROP TABLE ltable; +DROP TABLE parent; +DROP FUNCTION ftable_rowcount_trigf; +-- =================================================================== +-- test asynchronous execution +-- =================================================================== +ALTER SERVER loopback OPTIONS (DROP extensions); +ALTER SERVER loopback OPTIONS (ADD async_capable 'true'); +ALTER SERVER loopback2 OPTIONS (ADD async_capable 'true'); +CREATE TABLE async_pt (a int, b int, c text) PARTITION BY RANGE (a); +CREATE TABLE base_tbl1 (a int, b int, c text); +CREATE TABLE base_tbl2 (a int, b int, c text); +CREATE FOREIGN TABLE async_p1 PARTITION OF async_pt FOR VALUES FROM (1000) TO (2000) + SERVER loopback OPTIONS (table_name 'base_tbl1'); +CREATE FOREIGN TABLE async_p2 PARTITION OF async_pt FOR VALUES FROM (2000) TO (3000) + SERVER loopback2 OPTIONS (table_name 'base_tbl2'); +INSERT INTO async_p1 SELECT 1000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +INSERT INTO async_p2 SELECT 2000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; +-- simple queries +CREATE TABLE result_tbl (a int, b int, c text); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b % 100 = 0; + QUERY PLAN +---------------------------------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE (((b % 100) = 0)) + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE (((b % 100) = 0)) +(8 rows) + +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b % 100 = 0; +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1000 | 0 | 0000 + 1100 | 100 | 0100 + 1200 | 200 | 0200 + 1300 | 300 | 0300 + 1400 | 400 | 0400 + 1500 | 500 | 0500 + 1600 | 600 | 0600 + 1700 | 700 | 0700 + 1800 | 800 | 0800 + 1900 | 900 | 0900 + 2000 | 0 | 0000 + 2100 | 100 | 0100 + 2200 | 200 | 0200 + 2300 | 300 | 0300 + 2400 | 400 | 0400 + 2500 | 500 | 0500 + 2600 | 600 | 0600 + 2700 | 700 | 0700 + 2800 | 800 | 0800 + 2900 | 900 | 0900 +(20 rows) + +DELETE FROM result_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + QUERY PLAN +---------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Filter: (async_pt_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 +(10 rows) + +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1505 | 505 | 0505 + 2505 | 505 | 0505 +(2 rows) + +DELETE FROM result_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT a, b, 'AAA' || c FROM async_pt WHERE b === 505; + QUERY PLAN +--------------------------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, ('AAA'::text || async_pt_1.c) + Filter: (async_pt_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, ('AAA'::text || async_pt_2.c) + Filter: (async_pt_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 +(10 rows) + +INSERT INTO result_tbl SELECT a, b, 'AAA' || c FROM async_pt WHERE b === 505; +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+--------- + 1505 | 505 | AAA0505 + 2505 | 505 | AAA0505 +(2 rows) + +DELETE FROM result_tbl; +-- Test error handling, if accessing one of the foreign partitions errors out +CREATE FOREIGN TABLE async_p_broken PARTITION OF async_pt FOR VALUES FROM (10000) TO (10001) + SERVER loopback OPTIONS (table_name 'non_existent_table'); +SELECT * FROM async_pt; +ERROR: relation "public.non_existent_table" does not exist +CONTEXT: remote SQL command: SELECT a, b, c FROM public.non_existent_table +DROP FOREIGN TABLE async_p_broken; +-- Check case where multiple partitions use the same connection +CREATE TABLE base_tbl3 (a int, b int, c text); +CREATE FOREIGN TABLE async_p3 PARTITION OF async_pt FOR VALUES FROM (3000) TO (4000) + SERVER loopback2 OPTIONS (table_name 'base_tbl3'); +INSERT INTO async_p3 SELECT 3000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + QUERY PLAN +---------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Filter: (async_pt_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Async Foreign Scan on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c + Filter: (async_pt_3.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl3 +(14 rows) + +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1505 | 505 | 0505 + 2505 | 505 | 0505 + 3505 | 505 | 0505 +(3 rows) + +DELETE FROM result_tbl; +DROP FOREIGN TABLE async_p3; +DROP TABLE base_tbl3; +-- Check case where the partitioned table has local/remote partitions +CREATE TABLE async_p3 PARTITION OF async_pt FOR VALUES FROM (3000) TO (4000); +INSERT INTO async_p3 SELECT 3000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + QUERY PLAN +---------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Filter: (async_pt_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Seq Scan on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c + Filter: (async_pt_3.b === 505) +(13 rows) + +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1505 | 505 | 0505 + 2505 | 505 | 0505 + 3505 | 505 | 0505 +(3 rows) + +DELETE FROM result_tbl; +-- partitionwise joins +SET enable_partitionwise_join TO true; +CREATE TABLE join_tbl (a1 int, b1 int, c1 text, a2 int, b2 int, c2 text); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.join_tbl + -> Append + -> Async Foreign Scan + Output: t1_1.a, t1_1.b, t1_1.c, t2_1.a, t2_1.b, t2_1.c + Relations: (public.async_p1 t1_1) INNER JOIN (public.async_p1 t2_1) + Remote SQL: SELECT r5.a, r5.b, r5.c, r8.a, r8.b, r8.c FROM (public.base_tbl1 r5 INNER JOIN public.base_tbl1 r8 ON (((r5.a = r8.a)) AND ((r5.b = r8.b)) AND (((r5.b % 100) = 0)))) + -> Async Foreign Scan + Output: t1_2.a, t1_2.b, t1_2.c, t2_2.a, t2_2.b, t2_2.c + Relations: (public.async_p2 t1_2) INNER JOIN (public.async_p2 t2_2) + Remote SQL: SELECT r6.a, r6.b, r6.c, r9.a, r9.b, r9.c FROM (public.base_tbl2 r6 INNER JOIN public.base_tbl2 r9 ON (((r6.a = r9.a)) AND ((r6.b = r9.b)) AND (((r6.b % 100) = 0)))) + -> Hash Join + Output: t1_3.a, t1_3.b, t1_3.c, t2_3.a, t2_3.b, t2_3.c + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.b = t1_3.b)) + -> Seq Scan on public.async_p3 t2_3 + Output: t2_3.a, t2_3.b, t2_3.c + -> Hash + Output: t1_3.a, t1_3.b, t1_3.c + -> Seq Scan on public.async_p3 t1_3 + Output: t1_3.a, t1_3.b, t1_3.c + Filter: ((t1_3.b % 100) = 0) +(20 rows) + +INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +SELECT * FROM join_tbl ORDER BY a1; + a1 | b1 | c1 | a2 | b2 | c2 +------+-----+------+------+-----+------ + 1000 | 0 | 0000 | 1000 | 0 | 0000 + 1100 | 100 | 0100 | 1100 | 100 | 0100 + 1200 | 200 | 0200 | 1200 | 200 | 0200 + 1300 | 300 | 0300 | 1300 | 300 | 0300 + 1400 | 400 | 0400 | 1400 | 400 | 0400 + 1500 | 500 | 0500 | 1500 | 500 | 0500 + 1600 | 600 | 0600 | 1600 | 600 | 0600 + 1700 | 700 | 0700 | 1700 | 700 | 0700 + 1800 | 800 | 0800 | 1800 | 800 | 0800 + 1900 | 900 | 0900 | 1900 | 900 | 0900 + 2000 | 0 | 0000 | 2000 | 0 | 0000 + 2100 | 100 | 0100 | 2100 | 100 | 0100 + 2200 | 200 | 0200 | 2200 | 200 | 0200 + 2300 | 300 | 0300 | 2300 | 300 | 0300 + 2400 | 400 | 0400 | 2400 | 400 | 0400 + 2500 | 500 | 0500 | 2500 | 500 | 0500 + 2600 | 600 | 0600 | 2600 | 600 | 0600 + 2700 | 700 | 0700 | 2700 | 700 | 0700 + 2800 | 800 | 0800 | 2800 | 800 | 0800 + 2900 | 900 | 0900 | 2900 | 900 | 0900 + 3000 | 0 | 0000 | 3000 | 0 | 0000 + 3100 | 100 | 0100 | 3100 | 100 | 0100 + 3200 | 200 | 0200 | 3200 | 200 | 0200 + 3300 | 300 | 0300 | 3300 | 300 | 0300 + 3400 | 400 | 0400 | 3400 | 400 | 0400 + 3500 | 500 | 0500 | 3500 | 500 | 0500 + 3600 | 600 | 0600 | 3600 | 600 | 0600 + 3700 | 700 | 0700 | 3700 | 700 | 0700 + 3800 | 800 | 0800 | 3800 | 800 | 0800 + 3900 | 900 | 0900 | 3900 | 900 | 0900 +(30 rows) + +DELETE FROM join_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Insert on public.join_tbl + -> Append + -> Async Foreign Scan + Output: t1_1.a, t1_1.b, ('AAA'::text || t1_1.c), t2_1.a, t2_1.b, ('AAA'::text || t2_1.c) + Relations: (public.async_p1 t1_1) INNER JOIN (public.async_p1 t2_1) + Remote SQL: SELECT r5.a, r5.b, r5.c, r8.a, r8.b, r8.c FROM (public.base_tbl1 r5 INNER JOIN public.base_tbl1 r8 ON (((r5.a = r8.a)) AND ((r5.b = r8.b)) AND (((r5.b % 100) = 0)))) + -> Async Foreign Scan + Output: t1_2.a, t1_2.b, ('AAA'::text || t1_2.c), t2_2.a, t2_2.b, ('AAA'::text || t2_2.c) + Relations: (public.async_p2 t1_2) INNER JOIN (public.async_p2 t2_2) + Remote SQL: SELECT r6.a, r6.b, r6.c, r9.a, r9.b, r9.c FROM (public.base_tbl2 r6 INNER JOIN public.base_tbl2 r9 ON (((r6.a = r9.a)) AND ((r6.b = r9.b)) AND (((r6.b % 100) = 0)))) + -> Hash Join + Output: t1_3.a, t1_3.b, ('AAA'::text || t1_3.c), t2_3.a, t2_3.b, ('AAA'::text || t2_3.c) + Hash Cond: ((t2_3.a = t1_3.a) AND (t2_3.b = t1_3.b)) + -> Seq Scan on public.async_p3 t2_3 + Output: t2_3.a, t2_3.b, t2_3.c + -> Hash + Output: t1_3.a, t1_3.b, t1_3.c + -> Seq Scan on public.async_p3 t1_3 + Output: t1_3.a, t1_3.b, t1_3.c + Filter: ((t1_3.b % 100) = 0) +(20 rows) + +INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +SELECT * FROM join_tbl ORDER BY a1; + a1 | b1 | c1 | a2 | b2 | c2 +------+-----+---------+------+-----+--------- + 1000 | 0 | AAA0000 | 1000 | 0 | AAA0000 + 1100 | 100 | AAA0100 | 1100 | 100 | AAA0100 + 1200 | 200 | AAA0200 | 1200 | 200 | AAA0200 + 1300 | 300 | AAA0300 | 1300 | 300 | AAA0300 + 1400 | 400 | AAA0400 | 1400 | 400 | AAA0400 + 1500 | 500 | AAA0500 | 1500 | 500 | AAA0500 + 1600 | 600 | AAA0600 | 1600 | 600 | AAA0600 + 1700 | 700 | AAA0700 | 1700 | 700 | AAA0700 + 1800 | 800 | AAA0800 | 1800 | 800 | AAA0800 + 1900 | 900 | AAA0900 | 1900 | 900 | AAA0900 + 2000 | 0 | AAA0000 | 2000 | 0 | AAA0000 + 2100 | 100 | AAA0100 | 2100 | 100 | AAA0100 + 2200 | 200 | AAA0200 | 2200 | 200 | AAA0200 + 2300 | 300 | AAA0300 | 2300 | 300 | AAA0300 + 2400 | 400 | AAA0400 | 2400 | 400 | AAA0400 + 2500 | 500 | AAA0500 | 2500 | 500 | AAA0500 + 2600 | 600 | AAA0600 | 2600 | 600 | AAA0600 + 2700 | 700 | AAA0700 | 2700 | 700 | AAA0700 + 2800 | 800 | AAA0800 | 2800 | 800 | AAA0800 + 2900 | 900 | AAA0900 | 2900 | 900 | AAA0900 + 3000 | 0 | AAA0000 | 3000 | 0 | AAA0000 + 3100 | 100 | AAA0100 | 3100 | 100 | AAA0100 + 3200 | 200 | AAA0200 | 3200 | 200 | AAA0200 + 3300 | 300 | AAA0300 | 3300 | 300 | AAA0300 + 3400 | 400 | AAA0400 | 3400 | 400 | AAA0400 + 3500 | 500 | AAA0500 | 3500 | 500 | AAA0500 + 3600 | 600 | AAA0600 | 3600 | 600 | AAA0600 + 3700 | 700 | AAA0700 | 3700 | 700 | AAA0700 + 3800 | 800 | AAA0800 | 3800 | 800 | AAA0800 + 3900 | 900 | AAA0900 | 3900 | 900 | AAA0900 +(30 rows) + +DELETE FROM join_tbl; +RESET enable_partitionwise_join; +-- Test rescan of an async Append node with do_exec_prune=false +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT * FROM async_p1 t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + QUERY PLAN +---------------------------------------------------------------------------------------- + Insert on public.join_tbl + -> Nested Loop + Output: t1.a, t1.b, t1.c, t2.a, t2.b, t2.c + Join Filter: ((t1.a = t2.a) AND (t1.b = t2.b)) + -> Foreign Scan on public.async_p1 t1 + Output: t1.a, t1.b, t1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE (((b % 100) = 0)) + -> Append + -> Async Foreign Scan on public.async_p1 t2_1 + Output: t2_1.a, t2_1.b, t2_1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 t2_2 + Output: t2_2.a, t2_2.b, t2_2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Seq Scan on public.async_p3 t2_3 + Output: t2_3.a, t2_3.b, t2_3.c +(16 rows) + +INSERT INTO join_tbl SELECT * FROM async_p1 t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +SELECT * FROM join_tbl ORDER BY a1; + a1 | b1 | c1 | a2 | b2 | c2 +------+-----+------+------+-----+------ + 1000 | 0 | 0000 | 1000 | 0 | 0000 + 1100 | 100 | 0100 | 1100 | 100 | 0100 + 1200 | 200 | 0200 | 1200 | 200 | 0200 + 1300 | 300 | 0300 | 1300 | 300 | 0300 + 1400 | 400 | 0400 | 1400 | 400 | 0400 + 1500 | 500 | 0500 | 1500 | 500 | 0500 + 1600 | 600 | 0600 | 1600 | 600 | 0600 + 1700 | 700 | 0700 | 1700 | 700 | 0700 + 1800 | 800 | 0800 | 1800 | 800 | 0800 + 1900 | 900 | 0900 | 1900 | 900 | 0900 +(10 rows) + +DELETE FROM join_tbl; +RESET enable_hashjoin; +-- Test interaction of async execution with plan-time partition pruning +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE a < 3000; + QUERY PLAN +----------------------------------------------------------------------------- + Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a < 3000)) + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((a < 3000)) +(7 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE a < 2000; + QUERY PLAN +----------------------------------------------------------------------- + Foreign Scan on public.async_p1 async_pt + Output: async_pt.a, async_pt.b, async_pt.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a < 2000)) +(3 rows) + +-- Test interaction of async execution with run-time partition pruning +SET plan_cache_mode TO force_generic_plan; +PREPARE async_pt_query (int, int) AS + INSERT INTO result_tbl SELECT * FROM async_pt WHERE a < $1 AND b === $2; +EXPLAIN (VERBOSE, COSTS OFF) +EXECUTE async_pt_query (3000, 505); + QUERY PLAN +------------------------------------------------------------------------------------------ + Insert on public.result_tbl + -> Append + Subplans Removed: 1 + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === $2) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a < $1::integer)) + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Filter: (async_pt_2.b === $2) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((a < $1::integer)) +(11 rows) + +EXECUTE async_pt_query (3000, 505); +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1505 | 505 | 0505 + 2505 | 505 | 0505 +(2 rows) + +DELETE FROM result_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +EXECUTE async_pt_query (2000, 505); + QUERY PLAN +------------------------------------------------------------------------------------------ + Insert on public.result_tbl + -> Append + Subplans Removed: 2 + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === $2) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a < $1::integer)) +(7 rows) + +EXECUTE async_pt_query (2000, 505); +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+-----+------ + 1505 | 505 | 0505 +(1 row) + +DELETE FROM result_tbl; +RESET plan_cache_mode; +CREATE TABLE local_tbl(a int, b int, c text); +INSERT INTO local_tbl VALUES (1505, 505, 'foo'), (2505, 505, 'bar'); +ANALYZE local_tbl; +CREATE INDEX base_tbl1_idx ON base_tbl1 (a); +CREATE INDEX base_tbl2_idx ON base_tbl2 (a); +CREATE INDEX async_p3_idx ON async_p3 (a); +ANALYZE base_tbl1; +ANALYZE base_tbl2; +ANALYZE async_p3; +ALTER FOREIGN TABLE async_p1 OPTIONS (use_remote_estimate 'true'); +ALTER FOREIGN TABLE async_p2 OPTIONS (use_remote_estimate 'true'); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; + QUERY PLAN +------------------------------------------------------------------------------------------ + Nested Loop + Output: local_tbl.a, local_tbl.b, local_tbl.c, async_pt.a, async_pt.b, async_pt.c + -> Seq Scan on public.local_tbl + Output: local_tbl.a, local_tbl.b, local_tbl.c + Filter: (local_tbl.c = 'bar'::text) + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a = $1::integer)) + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((a = $1::integer)) + -> Seq Scan on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c + Filter: (async_pt_3.a = local_tbl.a) +(15 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; + QUERY PLAN +------------------------------------------------------------------------------- + Nested Loop (actual rows=1 loops=1) + -> Seq Scan on local_tbl (actual rows=1 loops=1) + Filter: (c = 'bar'::text) + Rows Removed by Filter: 1 + -> Append (actual rows=1 loops=1) + -> Async Foreign Scan on async_p1 async_pt_1 (never executed) + -> Async Foreign Scan on async_p2 async_pt_2 (actual rows=1 loops=1) + -> Seq Scan on async_p3 async_pt_3 (never executed) + Filter: (a = local_tbl.a) +(9 rows) + +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; + a | b | c | a | b | c +------+-----+-----+------+-----+------ + 2505 | 505 | bar | 2505 | 505 | 0505 +(1 row) + +ALTER FOREIGN TABLE async_p1 OPTIONS (DROP use_remote_estimate); +ALTER FOREIGN TABLE async_p2 OPTIONS (DROP use_remote_estimate); +DROP TABLE local_tbl; +DROP INDEX base_tbl1_idx; +DROP INDEX base_tbl2_idx; +DROP INDEX async_p3_idx; +-- UNION queries +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); + QUERY PLAN +----------------------------------------------------------------------------------------------------------------- + Insert on public.result_tbl + -> HashAggregate + Output: async_p1.a, async_p1.b, (('AAA'::text || async_p1.c)) + Group Key: async_p1.a, async_p1.b, (('AAA'::text || async_p1.c)) + -> Append + -> Async Foreign Scan on public.async_p1 + Output: async_p1.a, async_p1.b, ('AAA'::text || async_p1.c) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 ORDER BY a ASC NULLS LAST LIMIT 10::bigint + -> Async Foreign Scan on public.async_p2 + Output: async_p2.a, async_p2.b, ('AAA'::text || async_p2.c) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((b < 10)) +(11 rows) + +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+----+--------- + 1000 | 0 | AAA0000 + 1005 | 5 | AAA0005 + 1010 | 10 | AAA0010 + 1015 | 15 | AAA0015 + 1020 | 20 | AAA0020 + 1025 | 25 | AAA0025 + 1030 | 30 | AAA0030 + 1035 | 35 | AAA0035 + 1040 | 40 | AAA0040 + 1045 | 45 | AAA0045 + 2000 | 0 | AAA0000 + 2005 | 5 | AAA0005 +(12 rows) + +DELETE FROM result_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); + QUERY PLAN +----------------------------------------------------------------------------------------------------------- + Insert on public.result_tbl + -> Append + -> Async Foreign Scan on public.async_p1 + Output: async_p1.a, async_p1.b, ('AAA'::text || async_p1.c) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 ORDER BY a ASC NULLS LAST LIMIT 10::bigint + -> Async Foreign Scan on public.async_p2 + Output: async_p2.a, async_p2.b, ('AAA'::text || async_p2.c) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((b < 10)) +(8 rows) + +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); +SELECT * FROM result_tbl ORDER BY a; + a | b | c +------+----+--------- + 1000 | 0 | AAA0000 + 1005 | 5 | AAA0005 + 1010 | 10 | AAA0010 + 1015 | 15 | AAA0015 + 1020 | 20 | AAA0020 + 1025 | 25 | AAA0025 + 1030 | 30 | AAA0030 + 1035 | 35 | AAA0035 + 1040 | 40 | AAA0040 + 1045 | 45 | AAA0045 + 2000 | 0 | AAA0000 + 2005 | 5 | AAA0005 +(12 rows) + +DELETE FROM result_tbl; +-- Disable async execution if we use gating Result nodes for pseudoconstant +-- quals +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE CURRENT_USER = SESSION_USER; + QUERY PLAN +---------------------------------------------------------------- + Append + -> Result + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Result + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Result + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Seq Scan on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c +(18 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +(SELECT * FROM async_p1 WHERE CURRENT_USER = SESSION_USER) +UNION ALL +(SELECT * FROM async_p2 WHERE CURRENT_USER = SESSION_USER); + QUERY PLAN +---------------------------------------------------------------- + Append + -> Result + Output: async_p1.a, async_p1.b, async_p1.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p1 + Output: async_p1.a, async_p1.b, async_p1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Result + Output: async_p2.a, async_p2.b, async_p2.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p2 + Output: async_p2.a, async_p2.b, async_p2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 +(13 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ((SELECT * FROM async_p1 WHERE b < 10) UNION ALL (SELECT * FROM async_p2 WHERE b < 10)) s WHERE CURRENT_USER = SESSION_USER; + QUERY PLAN +--------------------------------------------------------------------------------- + Append + -> Result + Output: async_p1.a, async_p1.b, async_p1.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p1 + Output: async_p1.a, async_p1.b, async_p1.c + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((b < 10)) + -> Result + Output: async_p2.a, async_p2.b, async_p2.c + One-Time Filter: (CURRENT_USER = SESSION_USER) + -> Foreign Scan on public.async_p2 + Output: async_p2.a, async_p2.b, async_p2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((b < 10)) +(13 rows) + +-- Test that pending requests are processed properly +SET enable_mergejoin TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt t1, async_p2 t2 WHERE t1.a = t2.a AND t1.b === 505; + QUERY PLAN +---------------------------------------------------------------- + Nested Loop + Output: t1.a, t1.b, t1.c, t2.a, t2.b, t2.c + Join Filter: (t1.a = t2.a) + -> Append + -> Async Foreign Scan on public.async_p1 t1_1 + Output: t1_1.a, t1_1.b, t1_1.c + Filter: (t1_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 t1_2 + Output: t1_2.a, t1_2.b, t1_2.c + Filter: (t1_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Seq Scan on public.async_p3 t1_3 + Output: t1_3.a, t1_3.b, t1_3.c + Filter: (t1_3.b === 505) + -> Materialize + Output: t2.a, t2.b, t2.c + -> Foreign Scan on public.async_p2 t2 + Output: t2.a, t2.b, t2.c + Remote SQL: SELECT a, b, c FROM public.base_tbl2 +(20 rows) + +SELECT * FROM async_pt t1, async_p2 t2 WHERE t1.a = t2.a AND t1.b === 505; + a | b | c | a | b | c +------+-----+------+------+-----+------ + 2505 | 505 | 0505 | 2505 | 505 | 0505 +(1 row) + +CREATE TABLE local_tbl (a int, b int, c text); +INSERT INTO local_tbl VALUES (1505, 505, 'foo'); +ANALYZE local_tbl; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; + QUERY PLAN +---------------------------------------------------------------------------------------- + Nested Loop Left Join + Output: t1.a, t1.b, t1.c, async_pt.a, async_pt.b, async_pt.c, ($0) + Join Filter: (t1.a = async_pt.a) + InitPlan 1 (returns $0) + -> Aggregate + Output: count(*) + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_4 + Remote SQL: SELECT NULL FROM public.base_tbl1 WHERE ((a < 3000)) + -> Async Foreign Scan on public.async_p2 async_pt_5 + Remote SQL: SELECT NULL FROM public.base_tbl2 WHERE ((a < 3000)) + -> Seq Scan on public.local_tbl t1 + Output: t1.a, t1.b, t1.c + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c, $0 + Remote SQL: SELECT a, b, c FROM public.base_tbl1 WHERE ((a < 3000)) + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c, $0 + Remote SQL: SELECT a, b, c FROM public.base_tbl2 WHERE ((a < 3000)) +(20 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; + QUERY PLAN +----------------------------------------------------------------------------------------- + Nested Loop Left Join (actual rows=1 loops=1) + Join Filter: (t1.a = async_pt.a) + Rows Removed by Join Filter: 399 + InitPlan 1 (returns $0) + -> Aggregate (actual rows=1 loops=1) + -> Append (actual rows=400 loops=1) + -> Async Foreign Scan on async_p1 async_pt_4 (actual rows=200 loops=1) + -> Async Foreign Scan on async_p2 async_pt_5 (actual rows=200 loops=1) + -> Seq Scan on local_tbl t1 (actual rows=1 loops=1) + -> Append (actual rows=400 loops=1) + -> Async Foreign Scan on async_p1 async_pt_1 (actual rows=200 loops=1) + -> Async Foreign Scan on async_p2 async_pt_2 (actual rows=200 loops=1) +(12 rows) + +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; + a | b | c | a | b | c | count +------+-----+-----+------+-----+------+------- + 1505 | 505 | foo | 1505 | 505 | 0505 | 400 +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; + QUERY PLAN +---------------------------------------------------------------- + Limit + Output: t1.a, t1.b, t1.c + -> Append + -> Async Foreign Scan on public.async_p1 t1_1 + Output: t1_1.a, t1_1.b, t1_1.c + Filter: (t1_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 t1_2 + Output: t1_2.a, t1_2.b, t1_2.c + Filter: (t1_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Seq Scan on public.async_p3 t1_3 + Output: t1_3.a, t1_3.b, t1_3.c + Filter: (t1_3.b === 505) +(14 rows) + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; + QUERY PLAN +------------------------------------------------------------------------- + Limit (actual rows=1 loops=1) + -> Append (actual rows=1 loops=1) + -> Async Foreign Scan on async_p1 t1_1 (actual rows=0 loops=1) + Filter: (b === 505) + -> Async Foreign Scan on async_p2 t1_2 (actual rows=0 loops=1) + Filter: (b === 505) + -> Seq Scan on async_p3 t1_3 (actual rows=1 loops=1) + Filter: (b === 505) + Rows Removed by Filter: 101 +(9 rows) + +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; + a | b | c +------+-----+------ + 3505 | 505 | 0505 +(1 row) + +-- Check with foreign modify +CREATE TABLE base_tbl3 (a int, b int, c text); +CREATE FOREIGN TABLE remote_tbl (a int, b int, c text) + SERVER loopback OPTIONS (table_name 'base_tbl3'); +INSERT INTO remote_tbl VALUES (2505, 505, 'bar'); +CREATE TABLE base_tbl4 (a int, b int, c text); +CREATE FOREIGN TABLE insert_tbl (a int, b int, c text) + SERVER loopback OPTIONS (table_name 'base_tbl4'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO insert_tbl (SELECT * FROM local_tbl UNION ALL SELECT * FROM remote_tbl); + QUERY PLAN +------------------------------------------------------------------------- + Insert on public.insert_tbl + Remote SQL: INSERT INTO public.base_tbl4(a, b, c) VALUES ($1, $2, $3) + Batch Size: 1 + -> Append + -> Seq Scan on public.local_tbl + Output: local_tbl.a, local_tbl.b, local_tbl.c + -> Async Foreign Scan on public.remote_tbl + Output: remote_tbl.a, remote_tbl.b, remote_tbl.c + Remote SQL: SELECT a, b, c FROM public.base_tbl3 +(9 rows) + +INSERT INTO insert_tbl (SELECT * FROM local_tbl UNION ALL SELECT * FROM remote_tbl); +SELECT * FROM insert_tbl ORDER BY a; + a | b | c +------+-----+----- + 1505 | 505 | foo + 2505 | 505 | bar +(2 rows) + +-- Check with direct modify +EXPLAIN (VERBOSE, COSTS OFF) +WITH t AS (UPDATE remote_tbl SET c = c || c RETURNING *) +INSERT INTO join_tbl SELECT * FROM async_pt LEFT JOIN t ON (async_pt.a = t.a AND async_pt.b = t.b) WHERE async_pt.b === 505; + QUERY PLAN +---------------------------------------------------------------------------------------- + Insert on public.join_tbl + CTE t + -> Update on public.remote_tbl + Output: remote_tbl.a, remote_tbl.b, remote_tbl.c + -> Foreign Update on public.remote_tbl + Remote SQL: UPDATE public.base_tbl3 SET c = (c || c) RETURNING a, b, c + -> Nested Loop Left Join + Output: async_pt.a, async_pt.b, async_pt.c, t.a, t.b, t.c + Join Filter: ((async_pt.a = t.a) AND (async_pt.b = t.b)) + -> Append + -> Async Foreign Scan on public.async_p1 async_pt_1 + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Filter: (async_pt_1.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl1 + -> Async Foreign Scan on public.async_p2 async_pt_2 + Output: async_pt_2.a, async_pt_2.b, async_pt_2.c + Filter: (async_pt_2.b === 505) + Remote SQL: SELECT a, b, c FROM public.base_tbl2 + -> Seq Scan on public.async_p3 async_pt_3 + Output: async_pt_3.a, async_pt_3.b, async_pt_3.c + Filter: (async_pt_3.b === 505) + -> CTE Scan on t + Output: t.a, t.b, t.c +(23 rows) + +WITH t AS (UPDATE remote_tbl SET c = c || c RETURNING *) +INSERT INTO join_tbl SELECT * FROM async_pt LEFT JOIN t ON (async_pt.a = t.a AND async_pt.b = t.b) WHERE async_pt.b === 505; +SELECT * FROM join_tbl ORDER BY a1; + a1 | b1 | c1 | a2 | b2 | c2 +------+-----+------+------+-----+-------- + 1505 | 505 | 0505 | | | + 2505 | 505 | 0505 | 2505 | 505 | barbar + 3505 | 505 | 0505 | | | +(3 rows) + +DELETE FROM join_tbl; +DROP TABLE local_tbl; +DROP FOREIGN TABLE remote_tbl; +DROP FOREIGN TABLE insert_tbl; +DROP TABLE base_tbl3; +DROP TABLE base_tbl4; +RESET enable_mergejoin; +RESET enable_hashjoin; +-- Test that UPDATE/DELETE with inherited target works with async_capable enabled +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE async_pt SET c = c || c WHERE b = 0 RETURNING *; + QUERY PLAN +---------------------------------------------------------------------------------------------------------- + Update on public.async_pt + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Foreign Update on public.async_p1 async_pt_1 + Foreign Update on public.async_p2 async_pt_2 + Update on public.async_p3 async_pt_3 + -> Append + -> Foreign Update on public.async_p1 async_pt_1 + Remote SQL: UPDATE public.base_tbl1 SET c = (c || c) WHERE ((b = 0)) RETURNING a, b, c + -> Foreign Update on public.async_p2 async_pt_2 + Remote SQL: UPDATE public.base_tbl2 SET c = (c || c) WHERE ((b = 0)) RETURNING a, b, c + -> Seq Scan on public.async_p3 async_pt_3 + Output: (async_pt_3.c || async_pt_3.c), async_pt_3.tableoid, async_pt_3.ctid, NULL::record + Filter: (async_pt_3.b = 0) +(13 rows) + +UPDATE async_pt SET c = c || c WHERE b = 0 RETURNING *; + a | b | c +------+---+---------- + 1000 | 0 | 00000000 + 2000 | 0 | 00000000 + 3000 | 0 | 00000000 +(3 rows) + +EXPLAIN (VERBOSE, COSTS OFF) +DELETE FROM async_pt WHERE b = 0 RETURNING *; + QUERY PLAN +------------------------------------------------------------------------------------------ + Delete on public.async_pt + Output: async_pt_1.a, async_pt_1.b, async_pt_1.c + Foreign Delete on public.async_p1 async_pt_1 + Foreign Delete on public.async_p2 async_pt_2 + Delete on public.async_p3 async_pt_3 + -> Append + -> Foreign Delete on public.async_p1 async_pt_1 + Remote SQL: DELETE FROM public.base_tbl1 WHERE ((b = 0)) RETURNING a, b, c + -> Foreign Delete on public.async_p2 async_pt_2 + Remote SQL: DELETE FROM public.base_tbl2 WHERE ((b = 0)) RETURNING a, b, c + -> Seq Scan on public.async_p3 async_pt_3 + Output: async_pt_3.tableoid, async_pt_3.ctid + Filter: (async_pt_3.b = 0) +(13 rows) + +DELETE FROM async_pt WHERE b = 0 RETURNING *; + a | b | c +------+---+---------- + 1000 | 0 | 00000000 + 2000 | 0 | 00000000 + 3000 | 0 | 00000000 +(3 rows) + +-- Check EXPLAIN ANALYZE for a query that scans empty partitions asynchronously +DELETE FROM async_p1; +DELETE FROM async_p2; +DELETE FROM async_p3; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM async_pt; + QUERY PLAN +------------------------------------------------------------------------- + Append (actual rows=0 loops=1) + -> Async Foreign Scan on async_p1 async_pt_1 (actual rows=0 loops=1) + -> Async Foreign Scan on async_p2 async_pt_2 (actual rows=0 loops=1) + -> Seq Scan on async_p3 async_pt_3 (actual rows=0 loops=1) +(4 rows) + +-- Clean up +DROP TABLE async_pt; +DROP TABLE base_tbl1; +DROP TABLE base_tbl2; +DROP TABLE result_tbl; +DROP TABLE join_tbl; +-- Test that an asynchronous fetch is processed before restarting the scan in +-- ReScanForeignScan +CREATE TABLE base_tbl (a int, b int); +INSERT INTO base_tbl VALUES (1, 11), (2, 22), (3, 33); +CREATE FOREIGN TABLE foreign_tbl (b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +CREATE FOREIGN TABLE foreign_tbl2 () INHERITS (foreign_tbl) + SERVER loopback OPTIONS (table_name 'base_tbl'); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); + QUERY PLAN +----------------------------------------------------------------------------- + Seq Scan on public.base_tbl + Output: base_tbl.a + Filter: (SubPlan 1) + SubPlan 1 + -> Result + Output: base_tbl.a + -> Append + -> Async Foreign Scan on public.foreign_tbl foreign_tbl_1 + Remote SQL: SELECT NULL FROM public.base_tbl + -> Async Foreign Scan on public.foreign_tbl2 foreign_tbl_2 + Remote SQL: SELECT NULL FROM public.base_tbl +(11 rows) + +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); + a +--- + 1 + 2 + 3 +(3 rows) + +-- Clean up +DROP FOREIGN TABLE foreign_tbl CASCADE; +NOTICE: drop cascades to foreign table foreign_tbl2 +DROP TABLE base_tbl; +ALTER SERVER loopback OPTIONS (DROP async_capable); +ALTER SERVER loopback2 OPTIONS (DROP async_capable); +-- =================================================================== +-- test invalid server, foreign table and foreign data wrapper options +-- =================================================================== +-- Invalid fdw_startup_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_startup_cost '100$%$#$#'); +ERROR: invalid value for floating point option "fdw_startup_cost": 100$%$#$# +-- Invalid fdw_tuple_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_tuple_cost '100$%$#$#'); +ERROR: invalid value for floating point option "fdw_tuple_cost": 100$%$#$# +-- Invalid fetch_size option +CREATE FOREIGN TABLE inv_fsz (c1 int ) + SERVER loopback OPTIONS (fetch_size '100$%$#$#'); +ERROR: invalid value for integer option "fetch_size": 100$%$#$# +-- Invalid batch_size option +CREATE FOREIGN TABLE inv_bsz (c1 int ) + SERVER loopback OPTIONS (batch_size '100$%$#$#'); +ERROR: invalid value for integer option "batch_size": 100$%$#$# +-- No option is allowed to be specified at foreign data wrapper level +ALTER FOREIGN DATA WRAPPER postgres_fdw OPTIONS (nonexistent 'fdw'); +ERROR: invalid option "nonexistent" +HINT: There are no valid options in this context. +-- =================================================================== +-- test postgres_fdw.application_name GUC +-- =================================================================== +-- To avoid race conditions in checking the remote session's application_name, +-- use this view to make the remote session itself read its application_name. +CREATE VIEW my_application_name AS + SELECT application_name FROM pg_stat_activity WHERE pid = pg_backend_pid(); +CREATE FOREIGN TABLE remote_application_name (application_name text) + SERVER loopback2 + OPTIONS (schema_name 'public', table_name 'my_application_name'); +SELECT count(*) FROM remote_application_name; + count +------- + 1 +(1 row) + +-- Specify escape sequences in application_name option of a server +-- object so as to test that they are replaced with status information +-- expectedly. Note that we are also relying on ALTER SERVER to force +-- the remote session to be restarted with its new application name. +-- +-- Since pg_stat_activity.application_name may be truncated to less than +-- NAMEDATALEN characters, note that substring() needs to be used +-- at the condition of test query to make sure that the string consisting +-- of database name and process ID is also less than that. +ALTER SERVER loopback2 OPTIONS (application_name 'fdw_%d%p'); +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_database() || pg_backend_pid() for + current_setting('max_identifier_length')::int); + count +------- + 1 +(1 row) + +-- postgres_fdw.application_name overrides application_name option +-- of a server object if both settings are present. +ALTER SERVER loopback2 OPTIONS (SET application_name 'fdw_wrong'); +SET postgres_fdw.application_name TO 'fdw_%a%u%%'; +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_setting('application_name') || + CURRENT_USER || '%' for current_setting('max_identifier_length')::int); + count +------- + 1 +(1 row) + +RESET postgres_fdw.application_name; +-- Test %c (session ID) and %C (cluster name) escape sequences. +ALTER SERVER loopback2 OPTIONS (SET application_name 'fdw_%C%c'); +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_setting('cluster_name') || + to_hex(trunc(EXTRACT(EPOCH FROM (SELECT backend_start FROM + pg_stat_get_activity(pg_backend_pid()))))::integer) || '.' || + to_hex(pg_backend_pid()) + for current_setting('max_identifier_length')::int); + count +------- + 1 +(1 row) + +-- Clean up. +DROP FOREIGN TABLE remote_application_name; +DROP VIEW my_application_name; +-- =================================================================== +-- test parallel commit and parallel abort +-- =================================================================== +ALTER SERVER loopback OPTIONS (ADD parallel_commit 'true'); +ALTER SERVER loopback OPTIONS (ADD parallel_abort 'true'); +ALTER SERVER loopback2 OPTIONS (ADD parallel_commit 'true'); +ALTER SERVER loopback2 OPTIONS (ADD parallel_abort 'true'); +CREATE TABLE ploc1 (f1 int, f2 text); +CREATE FOREIGN TABLE prem1 (f1 int, f2 text) + SERVER loopback OPTIONS (table_name 'ploc1'); +CREATE TABLE ploc2 (f1 int, f2 text); +CREATE FOREIGN TABLE prem2 (f1 int, f2 text) + SERVER loopback2 OPTIONS (table_name 'ploc2'); +BEGIN; +INSERT INTO prem1 VALUES (101, 'foo'); +INSERT INTO prem2 VALUES (201, 'bar'); +COMMIT; +SELECT * FROM prem1; + f1 | f2 +-----+----- + 101 | foo +(1 row) + +SELECT * FROM prem2; + f1 | f2 +-----+----- + 201 | bar +(1 row) + +BEGIN; +SAVEPOINT s; +INSERT INTO prem1 VALUES (102, 'foofoo'); +INSERT INTO prem2 VALUES (202, 'barbar'); +RELEASE SAVEPOINT s; +COMMIT; +SELECT * FROM prem1; + f1 | f2 +-----+-------- + 101 | foo + 102 | foofoo +(2 rows) + +SELECT * FROM prem2; + f1 | f2 +-----+-------- + 201 | bar + 202 | barbar +(2 rows) + +-- This tests executing DEALLOCATE ALL against foreign servers in parallel +-- during pre-commit +BEGIN; +SAVEPOINT s; +INSERT INTO prem1 VALUES (103, 'baz'); +INSERT INTO prem2 VALUES (203, 'qux'); +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +INSERT INTO prem1 VALUES (104, 'bazbaz'); +INSERT INTO prem2 VALUES (204, 'quxqux'); +COMMIT; +SELECT * FROM prem1; + f1 | f2 +-----+-------- + 101 | foo + 102 | foofoo + 104 | bazbaz +(3 rows) + +SELECT * FROM prem2; + f1 | f2 +-----+-------- + 201 | bar + 202 | barbar + 204 | quxqux +(3 rows) + +BEGIN; +INSERT INTO prem1 VALUES (105, 'test1'); +INSERT INTO prem2 VALUES (205, 'test2'); +ABORT; +SELECT * FROM prem1; + f1 | f2 +-----+-------- + 101 | foo + 102 | foofoo + 104 | bazbaz +(3 rows) + +SELECT * FROM prem2; + f1 | f2 +-----+-------- + 201 | bar + 202 | barbar + 204 | quxqux +(3 rows) + +-- This tests executing DEALLOCATE ALL against foreign servers in parallel +-- during post-abort +BEGIN; +SAVEPOINT s; +INSERT INTO prem1 VALUES (105, 'test1'); +INSERT INTO prem2 VALUES (205, 'test2'); +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +INSERT INTO prem1 VALUES (105, 'test1'); +INSERT INTO prem2 VALUES (205, 'test2'); +ABORT; +SELECT * FROM prem1; + f1 | f2 +-----+-------- + 101 | foo + 102 | foofoo + 104 | bazbaz +(3 rows) + +SELECT * FROM prem2; + f1 | f2 +-----+-------- + 201 | bar + 202 | barbar + 204 | quxqux +(3 rows) + +ALTER SERVER loopback OPTIONS (DROP parallel_commit); +ALTER SERVER loopback OPTIONS (DROP parallel_abort); +ALTER SERVER loopback2 OPTIONS (DROP parallel_commit); +ALTER SERVER loopback2 OPTIONS (DROP parallel_abort); +-- =================================================================== +-- test for ANALYZE sampling +-- =================================================================== +CREATE TABLE analyze_table (id int, a text, b bigint); +CREATE FOREIGN TABLE analyze_ftable (id int, a text, b bigint) + SERVER loopback OPTIONS (table_name 'analyze_rtable1'); +INSERT INTO analyze_table (SELECT x FROM generate_series(1,1000) x); +ANALYZE analyze_table; +SET default_statistics_target = 10; +ANALYZE analyze_table; +ALTER SERVER loopback OPTIONS (analyze_sampling 'invalid'); +ERROR: invalid value for string option "analyze_sampling": invalid +ALTER SERVER loopback OPTIONS (analyze_sampling 'auto'); +ANALYZE analyze_table; +ALTER SERVER loopback OPTIONS (SET analyze_sampling 'system'); +ANALYZE analyze_table; +ALTER SERVER loopback OPTIONS (SET analyze_sampling 'bernoulli'); +ANALYZE analyze_table; +ALTER SERVER loopback OPTIONS (SET analyze_sampling 'random'); +ANALYZE analyze_table; +ALTER SERVER loopback OPTIONS (SET analyze_sampling 'off'); +ANALYZE analyze_table; +-- cleanup +DROP FOREIGN TABLE analyze_ftable; +DROP TABLE analyze_table; diff --git a/contrib/postgres_fdw/meson.build b/contrib/postgres_fdw/meson.build new file mode 100644 index 0000000..2b451f1 --- /dev/null +++ b/contrib/postgres_fdw/meson.build @@ -0,0 +1,42 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +postgres_fdw_sources = files( + 'connection.c', + 'deparse.c', + 'option.c', + 'postgres_fdw.c', + 'shippable.c', +) + +if host_system == 'windows' + postgres_fdw_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'postgres_fdw', + '--FILEDESC', 'postgres_fdw - foreign data wrapper for PostgreSQL',]) +endif + +postgres_fdw = shared_module('postgres_fdw', + postgres_fdw_sources, + kwargs: contrib_mod_args + { + 'dependencies': contrib_mod_args['dependencies'] + [libpq], + }, +) +contrib_targets += postgres_fdw + +install_data( + 'postgres_fdw.control', + 'postgres_fdw--1.0.sql', + 'postgres_fdw--1.0--1.1.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'postgres_fdw', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'postgres_fdw', + ], + 'regress_args': ['--dlpath', meson.build_root() / 'src/test/regress'], + }, +} diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c new file mode 100644 index 0000000..8c822f4 --- /dev/null +++ b/contrib/postgres_fdw/option.c @@ -0,0 +1,590 @@ +/*------------------------------------------------------------------------- + * + * option.c + * FDW and GUC option handling for postgres_fdw + * + * Portions Copyright (c) 2012-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/postgres_fdw/option.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/reloptions.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_foreign_table.h" +#include "catalog/pg_user_mapping.h" +#include "commands/defrem.h" +#include "commands/extension.h" +#include "libpq/libpq-be.h" +#include "postgres_fdw.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/varlena.h" + +/* + * Describes the valid options for objects that this wrapper uses. + */ +typedef struct PgFdwOption +{ + const char *keyword; + Oid optcontext; /* OID of catalog in which option may appear */ + bool is_libpq_opt; /* true if it's used in libpq */ +} PgFdwOption; + +/* + * Valid options for postgres_fdw. + * Allocated and filled in InitPgFdwOptions. + */ +static PgFdwOption *postgres_fdw_options; + +/* + * Valid options for libpq. + * Allocated and filled in InitPgFdwOptions. + */ +static PQconninfoOption *libpq_options; + +/* + * GUC parameters + */ +char *pgfdw_application_name = NULL; + +/* + * Helper functions + */ +static void InitPgFdwOptions(void); +static bool is_valid_option(const char *keyword, Oid context); +static bool is_libpq_option(const char *keyword); + +#include "miscadmin.h" + +/* + * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER, + * USER MAPPING or FOREIGN TABLE that uses postgres_fdw. + * + * Raise an ERROR if the option or its value is considered invalid. + */ +PG_FUNCTION_INFO_V1(postgres_fdw_validator); + +Datum +postgres_fdw_validator(PG_FUNCTION_ARGS) +{ + List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + Oid catalog = PG_GETARG_OID(1); + ListCell *cell; + + /* Build our options lists if we didn't yet. */ + InitPgFdwOptions(); + + /* + * Check that only options supported by postgres_fdw, and allowed for the + * current object type, are given. + */ + foreach(cell, options_list) + { + DefElem *def = (DefElem *) lfirst(cell); + + if (!is_valid_option(def->defname, catalog)) + { + /* + * Unknown option specified, complain about it. Provide a hint + * with a valid option that looks similar, if there is one. + */ + PgFdwOption *opt; + const char *closest_match; + ClosestMatchState match_state; + bool has_valid_options = false; + + initClosestMatch(&match_state, def->defname, 4); + for (opt = postgres_fdw_options; opt->keyword; opt++) + { + if (catalog == opt->optcontext) + { + has_valid_options = true; + updateClosestMatch(&match_state, opt->keyword); + } + } + + closest_match = getClosestMatch(&match_state); + ereport(ERROR, + (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), + errmsg("invalid option \"%s\"", def->defname), + has_valid_options ? closest_match ? + errhint("Perhaps you meant the option \"%s\".", + closest_match) : 0 : + errhint("There are no valid options in this context."))); + } + + /* + * Validate option value, when we can do so without any context. + */ + if (strcmp(def->defname, "use_remote_estimate") == 0 || + strcmp(def->defname, "updatable") == 0 || + strcmp(def->defname, "truncatable") == 0 || + strcmp(def->defname, "async_capable") == 0 || + strcmp(def->defname, "parallel_commit") == 0 || + strcmp(def->defname, "parallel_abort") == 0 || + strcmp(def->defname, "keep_connections") == 0) + { + /* these accept only boolean values */ + (void) defGetBoolean(def); + } + else if (strcmp(def->defname, "fdw_startup_cost") == 0 || + strcmp(def->defname, "fdw_tuple_cost") == 0) + { + /* + * These must have a floating point value greater than or equal to + * zero. + */ + char *value; + double real_val; + bool is_parsed; + + value = defGetString(def); + is_parsed = parse_real(value, &real_val, 0, NULL); + + if (!is_parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for floating point option \"%s\": %s", + def->defname, value))); + + if (real_val < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" must be a floating point value greater than or equal to zero", + def->defname))); + } + else if (strcmp(def->defname, "extensions") == 0) + { + /* check list syntax, warn about uninstalled extensions */ + (void) ExtractExtensionList(defGetString(def), true); + } + else if (strcmp(def->defname, "fetch_size") == 0 || + strcmp(def->defname, "batch_size") == 0) + { + char *value; + int int_val; + bool is_parsed; + + value = defGetString(def); + is_parsed = parse_int(value, &int_val, 0, NULL); + + if (!is_parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for integer option \"%s\": %s", + def->defname, value))); + + if (int_val <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("\"%s\" must be an integer value greater than zero", + def->defname))); + } + else if (strcmp(def->defname, "password_required") == 0) + { + bool pw_required = defGetBoolean(def); + + /* + * Only the superuser may set this option on a user mapping, or + * alter a user mapping on which this option is set. We allow a + * user to clear this option if it's set - in fact, we don't have + * a choice since we can't see the old mapping when validating an + * alter. + */ + if (!superuser() && !pw_required) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("password_required=false is superuser-only"), + errhint("User mappings with the password_required option set to false may only be created or modified by the superuser."))); + } + else if (strcmp(def->defname, "sslcert") == 0 || + strcmp(def->defname, "sslkey") == 0) + { + /* similarly for sslcert / sslkey on user mapping */ + if (catalog == UserMappingRelationId && !superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("sslcert and sslkey are superuser-only"), + errhint("User mappings with the sslcert or sslkey options set may only be created or modified by the superuser."))); + } + else if (strcmp(def->defname, "analyze_sampling") == 0) + { + char *value; + + value = defGetString(def); + + /* we recognize off/auto/random/system/bernoulli */ + if (strcmp(value, "off") != 0 && + strcmp(value, "auto") != 0 && + strcmp(value, "random") != 0 && + strcmp(value, "system") != 0 && + strcmp(value, "bernoulli") != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for string option \"%s\": %s", + def->defname, value))); + } + } + + PG_RETURN_VOID(); +} + +/* + * Initialize option lists. + */ +static void +InitPgFdwOptions(void) +{ + int num_libpq_opts; + PQconninfoOption *lopt; + PgFdwOption *popt; + + /* non-libpq FDW-specific FDW options */ + static const PgFdwOption non_libpq_options[] = { + {"schema_name", ForeignTableRelationId, false}, + {"table_name", ForeignTableRelationId, false}, + {"column_name", AttributeRelationId, false}, + /* use_remote_estimate is available on both server and table */ + {"use_remote_estimate", ForeignServerRelationId, false}, + {"use_remote_estimate", ForeignTableRelationId, false}, + /* cost factors */ + {"fdw_startup_cost", ForeignServerRelationId, false}, + {"fdw_tuple_cost", ForeignServerRelationId, false}, + /* shippable extensions */ + {"extensions", ForeignServerRelationId, false}, + /* updatable is available on both server and table */ + {"updatable", ForeignServerRelationId, false}, + {"updatable", ForeignTableRelationId, false}, + /* truncatable is available on both server and table */ + {"truncatable", ForeignServerRelationId, false}, + {"truncatable", ForeignTableRelationId, false}, + /* fetch_size is available on both server and table */ + {"fetch_size", ForeignServerRelationId, false}, + {"fetch_size", ForeignTableRelationId, false}, + /* batch_size is available on both server and table */ + {"batch_size", ForeignServerRelationId, false}, + {"batch_size", ForeignTableRelationId, false}, + /* async_capable is available on both server and table */ + {"async_capable", ForeignServerRelationId, false}, + {"async_capable", ForeignTableRelationId, false}, + {"parallel_commit", ForeignServerRelationId, false}, + {"parallel_abort", ForeignServerRelationId, false}, + {"keep_connections", ForeignServerRelationId, false}, + {"password_required", UserMappingRelationId, false}, + + /* sampling is available on both server and table */ + {"analyze_sampling", ForeignServerRelationId, false}, + {"analyze_sampling", ForeignTableRelationId, false}, + + /* + * sslcert and sslkey are in fact libpq options, but we repeat them + * here to allow them to appear in both foreign server context (when + * we generate libpq options) and user mapping context (from here). + */ + {"sslcert", UserMappingRelationId, true}, + {"sslkey", UserMappingRelationId, true}, + + /* + * gssdelegation is also a libpq option but should be allowed in a + * user mapping context too + */ + {"gssdelegation", UserMappingRelationId, true}, + + {NULL, InvalidOid, false} + }; + + /* Prevent redundant initialization. */ + if (postgres_fdw_options) + return; + + /* + * Get list of valid libpq options. + * + * To avoid unnecessary work, we get the list once and use it throughout + * the lifetime of this backend process. We don't need to care about + * memory context issues, because PQconndefaults allocates with malloc. + */ + libpq_options = PQconndefaults(); + if (!libpq_options) /* assume reason for failure is OOM */ + ereport(ERROR, + (errcode(ERRCODE_FDW_OUT_OF_MEMORY), + errmsg("out of memory"), + errdetail("Could not get libpq's default connection options."))); + + /* Count how many libpq options are available. */ + num_libpq_opts = 0; + for (lopt = libpq_options; lopt->keyword; lopt++) + num_libpq_opts++; + + /* + * Construct an array which consists of all valid options for + * postgres_fdw, by appending FDW-specific options to libpq options. + * + * We use plain malloc here to allocate postgres_fdw_options because it + * lives as long as the backend process does. Besides, keeping + * libpq_options in memory allows us to avoid copying every keyword + * string. + */ + postgres_fdw_options = (PgFdwOption *) + malloc(sizeof(PgFdwOption) * num_libpq_opts + + sizeof(non_libpq_options)); + if (postgres_fdw_options == NULL) + ereport(ERROR, + (errcode(ERRCODE_FDW_OUT_OF_MEMORY), + errmsg("out of memory"))); + + popt = postgres_fdw_options; + for (lopt = libpq_options; lopt->keyword; lopt++) + { + /* Hide debug options, as well as settings we override internally. */ + if (strchr(lopt->dispchar, 'D') || + strcmp(lopt->keyword, "fallback_application_name") == 0 || + strcmp(lopt->keyword, "client_encoding") == 0) + continue; + + /* We don't have to copy keyword string, as described above. */ + popt->keyword = lopt->keyword; + + /* + * "user" and any secret options are allowed only on user mappings. + * Everything else is a server option. + */ + if (strcmp(lopt->keyword, "user") == 0 || strchr(lopt->dispchar, '*')) + popt->optcontext = UserMappingRelationId; + else + popt->optcontext = ForeignServerRelationId; + popt->is_libpq_opt = true; + + popt++; + } + + /* Append FDW-specific options and dummy terminator. */ + memcpy(popt, non_libpq_options, sizeof(non_libpq_options)); +} + +/* + * Check whether the given option is one of the valid postgres_fdw options. + * context is the Oid of the catalog holding the object the option is for. + */ +static bool +is_valid_option(const char *keyword, Oid context) +{ + PgFdwOption *opt; + + Assert(postgres_fdw_options); /* must be initialized already */ + + for (opt = postgres_fdw_options; opt->keyword; opt++) + { + if (context == opt->optcontext && strcmp(opt->keyword, keyword) == 0) + return true; + } + + return false; +} + +/* + * Check whether the given option is one of the valid libpq options. + */ +static bool +is_libpq_option(const char *keyword) +{ + PgFdwOption *opt; + + Assert(postgres_fdw_options); /* must be initialized already */ + + for (opt = postgres_fdw_options; opt->keyword; opt++) + { + if (opt->is_libpq_opt && strcmp(opt->keyword, keyword) == 0) + return true; + } + + return false; +} + +/* + * Generate key-value arrays which include only libpq options from the + * given list (which can contain any kind of options). Caller must have + * allocated large-enough arrays. Returns number of options found. + */ +int +ExtractConnectionOptions(List *defelems, const char **keywords, + const char **values) +{ + ListCell *lc; + int i; + + /* Build our options lists if we didn't yet. */ + InitPgFdwOptions(); + + i = 0; + foreach(lc, defelems) + { + DefElem *d = (DefElem *) lfirst(lc); + + if (is_libpq_option(d->defname)) + { + keywords[i] = d->defname; + values[i] = defGetString(d); + i++; + } + } + return i; +} + +/* + * Parse a comma-separated string and return a List of the OIDs of the + * extensions named in the string. If any names in the list cannot be + * found, report a warning if warnOnMissing is true, else just silently + * ignore them. + */ +List * +ExtractExtensionList(const char *extensionsString, bool warnOnMissing) +{ + List *extensionOids = NIL; + List *extlist; + ListCell *lc; + + /* SplitIdentifierString scribbles on its input, so pstrdup first */ + if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist)) + { + /* syntax error in name list */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" must be a list of extension names", + "extensions"))); + } + + foreach(lc, extlist) + { + const char *extension_name = (const char *) lfirst(lc); + Oid extension_oid = get_extension_oid(extension_name, true); + + if (OidIsValid(extension_oid)) + { + extensionOids = lappend_oid(extensionOids, extension_oid); + } + else if (warnOnMissing) + { + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("extension \"%s\" is not installed", + extension_name))); + } + } + + list_free(extlist); + return extensionOids; +} + +/* + * Replace escape sequences beginning with % character in the given + * application_name with status information, and return it. + * + * This function always returns a palloc'd string, so the caller is + * responsible for pfreeing it. + */ +char * +process_pgfdw_appname(const char *appname) +{ + const char *p; + StringInfoData buf; + + initStringInfo(&buf); + + for (p = appname; *p != '\0'; p++) + { + if (*p != '%') + { + /* literal char, just copy */ + appendStringInfoChar(&buf, *p); + continue; + } + + /* must be a '%', so skip to the next char */ + p++; + if (*p == '\0') + break; /* format error - ignore it */ + else if (*p == '%') + { + /* string contains %% */ + appendStringInfoChar(&buf, '%'); + continue; + } + + /* process the option */ + switch (*p) + { + case 'a': + appendStringInfoString(&buf, application_name); + break; + case 'c': + appendStringInfo(&buf, "%lx.%x", (long) (MyStartTime), MyProcPid); + break; + case 'C': + appendStringInfoString(&buf, cluster_name); + break; + case 'd': + if (MyProcPort) + { + const char *dbname = MyProcPort->database_name; + + if (dbname) + appendStringInfoString(&buf, dbname); + else + appendStringInfoString(&buf, "[unknown]"); + } + break; + case 'p': + appendStringInfo(&buf, "%d", MyProcPid); + break; + case 'u': + if (MyProcPort) + { + const char *username = MyProcPort->user_name; + + if (username) + appendStringInfoString(&buf, username); + else + appendStringInfoString(&buf, "[unknown]"); + } + break; + default: + /* format error - ignore it */ + break; + } + } + + return buf.data; +} + +/* + * Module load callback + */ +void +_PG_init(void) +{ + /* + * Unlike application_name GUC, don't set GUC_IS_NAME flag nor check_hook + * to allow postgres_fdw.application_name to be any string more than + * NAMEDATALEN characters and to include non-ASCII characters. Instead, + * remote server truncates application_name of remote connection to less + * than NAMEDATALEN and replaces any non-ASCII characters in it with a '?' + * character. + */ + DefineCustomStringVariable("postgres_fdw.application_name", + "Sets the application name to be used on the remote server.", + NULL, + &pgfdw_application_name, + NULL, + PGC_USERSET, + 0, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("postgres_fdw"); +} diff --git a/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql new file mode 100644 index 0000000..ed4ca37 --- /dev/null +++ b/contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql @@ -0,0 +1,20 @@ +/* contrib/postgres_fdw/postgres_fdw--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION postgres_fdw UPDATE TO '1.1'" to load this file. \quit + +CREATE FUNCTION postgres_fdw_get_connections (OUT server_name text, + OUT valid boolean) +RETURNS SETOF record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION postgres_fdw_disconnect (text) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION postgres_fdw_disconnect_all () +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT PARALLEL RESTRICTED; diff --git a/contrib/postgres_fdw/postgres_fdw--1.0.sql b/contrib/postgres_fdw/postgres_fdw--1.0.sql new file mode 100644 index 0000000..a0f0fc1 --- /dev/null +++ b/contrib/postgres_fdw/postgres_fdw--1.0.sql @@ -0,0 +1,18 @@ +/* contrib/postgres_fdw/postgres_fdw--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION postgres_fdw" to load this file. \quit + +CREATE FUNCTION postgres_fdw_handler() +RETURNS fdw_handler +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FUNCTION postgres_fdw_validator(text[], oid) +RETURNS void +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT; + +CREATE FOREIGN DATA WRAPPER postgres_fdw + HANDLER postgres_fdw_handler + VALIDATOR postgres_fdw_validator; diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c new file mode 100644 index 0000000..c5cada5 --- /dev/null +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -0,0 +1,7811 @@ +/*------------------------------------------------------------------------- + * + * postgres_fdw.c + * Foreign-data wrapper for remote PostgreSQL servers + * + * Portions Copyright (c) 2012-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/postgres_fdw/postgres_fdw.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/pg_class.h" +#include "catalog/pg_opfamily.h" +#include "commands/defrem.h" +#include "commands/explain.h" +#include "commands/vacuum.h" +#include "executor/execAsync.h" +#include "foreign/fdwapi.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" +#include "optimizer/inherit.h" +#include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/planmain.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/tlist.h" +#include "parser/parsetree.h" +#include "postgres_fdw.h" +#include "storage/latch.h" +#include "utils/builtins.h" +#include "utils/float.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/sampling.h" +#include "utils/selfuncs.h" + +PG_MODULE_MAGIC; + +/* Default CPU cost to start up a foreign query. */ +#define DEFAULT_FDW_STARTUP_COST 100.0 + +/* Default CPU cost to process 1 row (above and beyond cpu_tuple_cost). */ +#define DEFAULT_FDW_TUPLE_COST 0.01 + +/* If no remote estimates, assume a sort costs 20% extra */ +#define DEFAULT_FDW_SORT_MULTIPLIER 1.2 + +/* + * Indexes of FDW-private information stored in fdw_private lists. + * + * These items are indexed with the enum FdwScanPrivateIndex, so an item + * can be fetched with list_nth(). For example, to get the SELECT statement: + * sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql)); + */ +enum FdwScanPrivateIndex +{ + /* SQL statement to execute remotely (as a String node) */ + FdwScanPrivateSelectSql, + /* Integer list of attribute numbers retrieved by the SELECT */ + FdwScanPrivateRetrievedAttrs, + /* Integer representing the desired fetch_size */ + FdwScanPrivateFetchSize, + + /* + * String describing join i.e. names of relations being joined and types + * of join, added when the scan is join + */ + FdwScanPrivateRelations +}; + +/* + * Similarly, this enum describes what's kept in the fdw_private list for + * a ModifyTable node referencing a postgres_fdw foreign table. We store: + * + * 1) INSERT/UPDATE/DELETE statement text to be sent to the remote server + * 2) Integer list of target attribute numbers for INSERT/UPDATE + * (NIL for a DELETE) + * 3) Length till the end of VALUES clause for INSERT + * (-1 for a DELETE/UPDATE) + * 4) Boolean flag showing if the remote query has a RETURNING clause + * 5) Integer list of attribute numbers retrieved by RETURNING, if any + */ +enum FdwModifyPrivateIndex +{ + /* SQL statement to execute remotely (as a String node) */ + FdwModifyPrivateUpdateSql, + /* Integer list of target attribute numbers for INSERT/UPDATE */ + FdwModifyPrivateTargetAttnums, + /* Length till the end of VALUES clause (as an Integer node) */ + FdwModifyPrivateLen, + /* has-returning flag (as a Boolean node) */ + FdwModifyPrivateHasReturning, + /* Integer list of attribute numbers retrieved by RETURNING */ + FdwModifyPrivateRetrievedAttrs +}; + +/* + * Similarly, this enum describes what's kept in the fdw_private list for + * a ForeignScan node that modifies a foreign table directly. We store: + * + * 1) UPDATE/DELETE statement text to be sent to the remote server + * 2) Boolean flag showing if the remote query has a RETURNING clause + * 3) Integer list of attribute numbers retrieved by RETURNING, if any + * 4) Boolean flag showing if we set the command es_processed + */ +enum FdwDirectModifyPrivateIndex +{ + /* SQL statement to execute remotely (as a String node) */ + FdwDirectModifyPrivateUpdateSql, + /* has-returning flag (as a Boolean node) */ + FdwDirectModifyPrivateHasReturning, + /* Integer list of attribute numbers retrieved by RETURNING */ + FdwDirectModifyPrivateRetrievedAttrs, + /* set-processed flag (as a Boolean node) */ + FdwDirectModifyPrivateSetProcessed +}; + +/* + * Execution state of a foreign scan using postgres_fdw. + */ +typedef struct PgFdwScanState +{ + Relation rel; /* relcache entry for the foreign table. NULL + * for a foreign join scan. */ + TupleDesc tupdesc; /* tuple descriptor of scan */ + AttInMetadata *attinmeta; /* attribute datatype conversion metadata */ + + /* extracted fdw_private data */ + char *query; /* text of SELECT command */ + List *retrieved_attrs; /* list of retrieved attribute numbers */ + + /* for remote query execution */ + PGconn *conn; /* connection for the scan */ + PgFdwConnState *conn_state; /* extra per-connection state */ + unsigned int cursor_number; /* quasi-unique ID for my cursor */ + bool cursor_exists; /* have we created the cursor? */ + int numParams; /* number of parameters passed to query */ + FmgrInfo *param_flinfo; /* output conversion functions for them */ + List *param_exprs; /* executable expressions for param values */ + const char **param_values; /* textual values of query parameters */ + + /* for storing result tuples */ + HeapTuple *tuples; /* array of currently-retrieved tuples */ + int num_tuples; /* # of tuples in array */ + int next_tuple; /* index of next one to return */ + + /* batch-level state, for optimizing rewinds and avoiding useless fetch */ + int fetch_ct_2; /* Min(# of fetches done, 2) */ + bool eof_reached; /* true if last fetch reached EOF */ + + /* for asynchronous execution */ + bool async_capable; /* engage asynchronous-capable logic? */ + + /* working memory contexts */ + MemoryContext batch_cxt; /* context holding current batch of tuples */ + MemoryContext temp_cxt; /* context for per-tuple temporary data */ + + int fetch_size; /* number of tuples per fetch */ +} PgFdwScanState; + +/* + * Execution state of a foreign insert/update/delete operation. + */ +typedef struct PgFdwModifyState +{ + Relation rel; /* relcache entry for the foreign table */ + AttInMetadata *attinmeta; /* attribute datatype conversion metadata */ + + /* for remote query execution */ + PGconn *conn; /* connection for the scan */ + PgFdwConnState *conn_state; /* extra per-connection state */ + char *p_name; /* name of prepared statement, if created */ + + /* extracted fdw_private data */ + char *query; /* text of INSERT/UPDATE/DELETE command */ + char *orig_query; /* original text of INSERT command */ + List *target_attrs; /* list of target attribute numbers */ + int values_end; /* length up to the end of VALUES */ + int batch_size; /* value of FDW option "batch_size" */ + bool has_returning; /* is there a RETURNING clause? */ + List *retrieved_attrs; /* attr numbers retrieved by RETURNING */ + + /* info about parameters for prepared statement */ + AttrNumber ctidAttno; /* attnum of input resjunk ctid column */ + int p_nums; /* number of parameters to transmit */ + FmgrInfo *p_flinfo; /* output conversion functions for them */ + + /* batch operation stuff */ + int num_slots; /* number of slots to insert */ + + /* working memory context */ + MemoryContext temp_cxt; /* context for per-tuple temporary data */ + + /* for update row movement if subplan result rel */ + struct PgFdwModifyState *aux_fmstate; /* foreign-insert state, if + * created */ +} PgFdwModifyState; + +/* + * Execution state of a foreign scan that modifies a foreign table directly. + */ +typedef struct PgFdwDirectModifyState +{ + Relation rel; /* relcache entry for the foreign table */ + AttInMetadata *attinmeta; /* attribute datatype conversion metadata */ + + /* extracted fdw_private data */ + char *query; /* text of UPDATE/DELETE command */ + bool has_returning; /* is there a RETURNING clause? */ + List *retrieved_attrs; /* attr numbers retrieved by RETURNING */ + bool set_processed; /* do we set the command es_processed? */ + + /* for remote query execution */ + PGconn *conn; /* connection for the update */ + PgFdwConnState *conn_state; /* extra per-connection state */ + int numParams; /* number of parameters passed to query */ + FmgrInfo *param_flinfo; /* output conversion functions for them */ + List *param_exprs; /* executable expressions for param values */ + const char **param_values; /* textual values of query parameters */ + + /* for storing result tuples */ + PGresult *result; /* result for query */ + int num_tuples; /* # of result tuples */ + int next_tuple; /* index of next one to return */ + Relation resultRel; /* relcache entry for the target relation */ + AttrNumber *attnoMap; /* array of attnums of input user columns */ + AttrNumber ctidAttno; /* attnum of input ctid column */ + AttrNumber oidAttno; /* attnum of input oid column */ + bool hasSystemCols; /* are there system columns of resultRel? */ + + /* working memory context */ + MemoryContext temp_cxt; /* context for per-tuple temporary data */ +} PgFdwDirectModifyState; + +/* + * Workspace for analyzing a foreign table. + */ +typedef struct PgFdwAnalyzeState +{ + Relation rel; /* relcache entry for the foreign table */ + AttInMetadata *attinmeta; /* attribute datatype conversion metadata */ + List *retrieved_attrs; /* attr numbers retrieved by query */ + + /* collected sample rows */ + HeapTuple *rows; /* array of size targrows */ + int targrows; /* target # of sample rows */ + int numrows; /* # of sample rows collected */ + + /* for random sampling */ + double samplerows; /* # of rows fetched */ + double rowstoskip; /* # of rows to skip before next sample */ + ReservoirStateData rstate; /* state for reservoir sampling */ + + /* working memory contexts */ + MemoryContext anl_cxt; /* context for per-analyze lifespan data */ + MemoryContext temp_cxt; /* context for per-tuple temporary data */ +} PgFdwAnalyzeState; + +/* + * This enum describes what's kept in the fdw_private list for a ForeignPath. + * We store: + * + * 1) Boolean flag showing if the remote query has the final sort + * 2) Boolean flag showing if the remote query has the LIMIT clause + */ +enum FdwPathPrivateIndex +{ + /* has-final-sort flag (as a Boolean node) */ + FdwPathPrivateHasFinalSort, + /* has-limit flag (as a Boolean node) */ + FdwPathPrivateHasLimit +}; + +/* Struct for extra information passed to estimate_path_cost_size() */ +typedef struct +{ + PathTarget *target; + bool has_final_sort; + bool has_limit; + double limit_tuples; + int64 count_est; + int64 offset_est; +} PgFdwPathExtraData; + +/* + * Identify the attribute where data conversion fails. + */ +typedef struct ConversionLocation +{ + AttrNumber cur_attno; /* attribute number being processed, or 0 */ + Relation rel; /* foreign table being processed, or NULL */ + ForeignScanState *fsstate; /* plan node being processed, or NULL */ +} ConversionLocation; + +/* Callback argument for ec_member_matches_foreign */ +typedef struct +{ + Expr *current; /* current expr, or NULL if not yet found */ + List *already_used; /* expressions already dealt with */ +} ec_member_foreign_arg; + +/* + * SQL functions + */ +PG_FUNCTION_INFO_V1(postgres_fdw_handler); + +/* + * FDW callback routines + */ +static void postgresGetForeignRelSize(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid); +static void postgresGetForeignPaths(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid); +static ForeignScan *postgresGetForeignPlan(PlannerInfo *root, + RelOptInfo *foreignrel, + Oid foreigntableid, + ForeignPath *best_path, + List *tlist, + List *scan_clauses, + Plan *outer_plan); +static void postgresBeginForeignScan(ForeignScanState *node, int eflags); +static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node); +static void postgresReScanForeignScan(ForeignScanState *node); +static void postgresEndForeignScan(ForeignScanState *node); +static void postgresAddForeignUpdateTargets(PlannerInfo *root, + Index rtindex, + RangeTblEntry *target_rte, + Relation target_relation); +static List *postgresPlanForeignModify(PlannerInfo *root, + ModifyTable *plan, + Index resultRelation, + int subplan_index); +static void postgresBeginForeignModify(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + List *fdw_private, + int subplan_index, + int eflags); +static TupleTableSlot *postgresExecForeignInsert(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + TupleTableSlot *planSlot); +static TupleTableSlot **postgresExecForeignBatchInsert(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot **slots, + TupleTableSlot **planSlots, + int *numSlots); +static int postgresGetForeignModifyBatchSize(ResultRelInfo *resultRelInfo); +static TupleTableSlot *postgresExecForeignUpdate(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + TupleTableSlot *planSlot); +static TupleTableSlot *postgresExecForeignDelete(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + TupleTableSlot *planSlot); +static void postgresEndForeignModify(EState *estate, + ResultRelInfo *resultRelInfo); +static void postgresBeginForeignInsert(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo); +static void postgresEndForeignInsert(EState *estate, + ResultRelInfo *resultRelInfo); +static int postgresIsForeignRelUpdatable(Relation rel); +static bool postgresPlanDirectModify(PlannerInfo *root, + ModifyTable *plan, + Index resultRelation, + int subplan_index); +static void postgresBeginDirectModify(ForeignScanState *node, int eflags); +static TupleTableSlot *postgresIterateDirectModify(ForeignScanState *node); +static void postgresEndDirectModify(ForeignScanState *node); +static void postgresExplainForeignScan(ForeignScanState *node, + ExplainState *es); +static void postgresExplainForeignModify(ModifyTableState *mtstate, + ResultRelInfo *rinfo, + List *fdw_private, + int subplan_index, + ExplainState *es); +static void postgresExplainDirectModify(ForeignScanState *node, + ExplainState *es); +static void postgresExecForeignTruncate(List *rels, + DropBehavior behavior, + bool restart_seqs); +static bool postgresAnalyzeForeignTable(Relation relation, + AcquireSampleRowsFunc *func, + BlockNumber *totalpages); +static List *postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, + Oid serverOid); +static void postgresGetForeignJoinPaths(PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + JoinPathExtraData *extra); +static bool postgresRecheckForeignScan(ForeignScanState *node, + TupleTableSlot *slot); +static void postgresGetForeignUpperPaths(PlannerInfo *root, + UpperRelationKind stage, + RelOptInfo *input_rel, + RelOptInfo *output_rel, + void *extra); +static bool postgresIsForeignPathAsyncCapable(ForeignPath *path); +static void postgresForeignAsyncRequest(AsyncRequest *areq); +static void postgresForeignAsyncConfigureWait(AsyncRequest *areq); +static void postgresForeignAsyncNotify(AsyncRequest *areq); + +/* + * Helper functions + */ +static void estimate_path_cost_size(PlannerInfo *root, + RelOptInfo *foreignrel, + List *param_join_conds, + List *pathkeys, + PgFdwPathExtraData *fpextra, + double *p_rows, int *p_width, + Cost *p_startup_cost, Cost *p_total_cost); +static void get_remote_estimate(const char *sql, + PGconn *conn, + double *rows, + int *width, + Cost *startup_cost, + Cost *total_cost); +static void adjust_foreign_grouping_path_cost(PlannerInfo *root, + List *pathkeys, + double retrieved_rows, + double width, + double limit_tuples, + Cost *p_startup_cost, + Cost *p_run_cost); +static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel, + EquivalenceClass *ec, EquivalenceMember *em, + void *arg); +static void create_cursor(ForeignScanState *node); +static void fetch_more_data(ForeignScanState *node); +static void close_cursor(PGconn *conn, unsigned int cursor_number, + PgFdwConnState *conn_state); +static PgFdwModifyState *create_foreign_modify(EState *estate, + RangeTblEntry *rte, + ResultRelInfo *resultRelInfo, + CmdType operation, + Plan *subplan, + char *query, + List *target_attrs, + int values_end, + bool has_returning, + List *retrieved_attrs); +static TupleTableSlot **execute_foreign_modify(EState *estate, + ResultRelInfo *resultRelInfo, + CmdType operation, + TupleTableSlot **slots, + TupleTableSlot **planSlots, + int *numSlots); +static void prepare_foreign_modify(PgFdwModifyState *fmstate); +static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate, + ItemPointer tupleid, + TupleTableSlot **slots, + int numSlots); +static void store_returning_result(PgFdwModifyState *fmstate, + TupleTableSlot *slot, PGresult *res); +static void finish_foreign_modify(PgFdwModifyState *fmstate); +static void deallocate_query(PgFdwModifyState *fmstate); +static List *build_remote_returning(Index rtindex, Relation rel, + List *returningList); +static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist); +static void execute_dml_stmt(ForeignScanState *node); +static TupleTableSlot *get_returning_data(ForeignScanState *node); +static void init_returning_filter(PgFdwDirectModifyState *dmstate, + List *fdw_scan_tlist, + Index rtindex); +static TupleTableSlot *apply_returning_filter(PgFdwDirectModifyState *dmstate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + EState *estate); +static void prepare_query_params(PlanState *node, + List *fdw_exprs, + int numParams, + FmgrInfo **param_flinfo, + List **param_exprs, + const char ***param_values); +static void process_query_params(ExprContext *econtext, + FmgrInfo *param_flinfo, + List *param_exprs, + const char **param_values); +static int postgresAcquireSampleRowsFunc(Relation relation, int elevel, + HeapTuple *rows, int targrows, + double *totalrows, + double *totaldeadrows); +static void analyze_row_processor(PGresult *res, int row, + PgFdwAnalyzeState *astate); +static void produce_tuple_asynchronously(AsyncRequest *areq, bool fetch); +static void fetch_more_data_begin(AsyncRequest *areq); +static void complete_pending_request(AsyncRequest *areq); +static HeapTuple make_tuple_from_result_row(PGresult *res, + int row, + Relation rel, + AttInMetadata *attinmeta, + List *retrieved_attrs, + ForeignScanState *fsstate, + MemoryContext temp_context); +static void conversion_error_callback(void *arg); +static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, + JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel, + JoinPathExtraData *extra); +static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel, + Node *havingQual); +static List *get_useful_pathkeys_for_relation(PlannerInfo *root, + RelOptInfo *rel); +static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel); +static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, + Path *epq_path); +static void add_foreign_grouping_paths(PlannerInfo *root, + RelOptInfo *input_rel, + RelOptInfo *grouped_rel, + GroupPathExtraData *extra); +static void add_foreign_ordered_paths(PlannerInfo *root, + RelOptInfo *input_rel, + RelOptInfo *ordered_rel); +static void add_foreign_final_paths(PlannerInfo *root, + RelOptInfo *input_rel, + RelOptInfo *final_rel, + FinalPathExtraData *extra); +static void apply_server_options(PgFdwRelationInfo *fpinfo); +static void apply_table_options(PgFdwRelationInfo *fpinfo); +static void merge_fdw_options(PgFdwRelationInfo *fpinfo, + const PgFdwRelationInfo *fpinfo_o, + const PgFdwRelationInfo *fpinfo_i); +static int get_batch_size_option(Relation rel); + + +/* + * Foreign-data wrapper handler function: return a struct with pointers + * to my callback routines. + */ +Datum +postgres_fdw_handler(PG_FUNCTION_ARGS) +{ + FdwRoutine *routine = makeNode(FdwRoutine); + + /* Functions for scanning foreign tables */ + routine->GetForeignRelSize = postgresGetForeignRelSize; + routine->GetForeignPaths = postgresGetForeignPaths; + routine->GetForeignPlan = postgresGetForeignPlan; + routine->BeginForeignScan = postgresBeginForeignScan; + routine->IterateForeignScan = postgresIterateForeignScan; + routine->ReScanForeignScan = postgresReScanForeignScan; + routine->EndForeignScan = postgresEndForeignScan; + + /* Functions for updating foreign tables */ + routine->AddForeignUpdateTargets = postgresAddForeignUpdateTargets; + routine->PlanForeignModify = postgresPlanForeignModify; + routine->BeginForeignModify = postgresBeginForeignModify; + routine->ExecForeignInsert = postgresExecForeignInsert; + routine->ExecForeignBatchInsert = postgresExecForeignBatchInsert; + routine->GetForeignModifyBatchSize = postgresGetForeignModifyBatchSize; + routine->ExecForeignUpdate = postgresExecForeignUpdate; + routine->ExecForeignDelete = postgresExecForeignDelete; + routine->EndForeignModify = postgresEndForeignModify; + routine->BeginForeignInsert = postgresBeginForeignInsert; + routine->EndForeignInsert = postgresEndForeignInsert; + routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable; + routine->PlanDirectModify = postgresPlanDirectModify; + routine->BeginDirectModify = postgresBeginDirectModify; + routine->IterateDirectModify = postgresIterateDirectModify; + routine->EndDirectModify = postgresEndDirectModify; + + /* Function for EvalPlanQual rechecks */ + routine->RecheckForeignScan = postgresRecheckForeignScan; + /* Support functions for EXPLAIN */ + routine->ExplainForeignScan = postgresExplainForeignScan; + routine->ExplainForeignModify = postgresExplainForeignModify; + routine->ExplainDirectModify = postgresExplainDirectModify; + + /* Support function for TRUNCATE */ + routine->ExecForeignTruncate = postgresExecForeignTruncate; + + /* Support functions for ANALYZE */ + routine->AnalyzeForeignTable = postgresAnalyzeForeignTable; + + /* Support functions for IMPORT FOREIGN SCHEMA */ + routine->ImportForeignSchema = postgresImportForeignSchema; + + /* Support functions for join push-down */ + routine->GetForeignJoinPaths = postgresGetForeignJoinPaths; + + /* Support functions for upper relation push-down */ + routine->GetForeignUpperPaths = postgresGetForeignUpperPaths; + + /* Support functions for asynchronous execution */ + routine->IsForeignPathAsyncCapable = postgresIsForeignPathAsyncCapable; + routine->ForeignAsyncRequest = postgresForeignAsyncRequest; + routine->ForeignAsyncConfigureWait = postgresForeignAsyncConfigureWait; + routine->ForeignAsyncNotify = postgresForeignAsyncNotify; + + PG_RETURN_POINTER(routine); +} + +/* + * postgresGetForeignRelSize + * Estimate # of rows and width of the result of the scan + * + * We should consider the effect of all baserestrictinfo clauses here, but + * not any join clauses. + */ +static void +postgresGetForeignRelSize(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid) +{ + PgFdwRelationInfo *fpinfo; + ListCell *lc; + + /* + * We use PgFdwRelationInfo to pass various information to subsequent + * functions. + */ + fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo)); + baserel->fdw_private = (void *) fpinfo; + + /* Base foreign tables need to be pushed down always. */ + fpinfo->pushdown_safe = true; + + /* Look up foreign-table catalog info. */ + fpinfo->table = GetForeignTable(foreigntableid); + fpinfo->server = GetForeignServer(fpinfo->table->serverid); + + /* + * Extract user-settable option values. Note that per-table settings of + * use_remote_estimate, fetch_size and async_capable override per-server + * settings of them, respectively. + */ + fpinfo->use_remote_estimate = false; + fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST; + fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST; + fpinfo->shippable_extensions = NIL; + fpinfo->fetch_size = 100; + fpinfo->async_capable = false; + + apply_server_options(fpinfo); + apply_table_options(fpinfo); + + /* + * If the table or the server is configured to use remote estimates, + * identify which user to do remote access as during planning. This + * should match what ExecCheckPermissions() does. If we fail due to lack + * of permissions, the query would have failed at runtime anyway. + */ + if (fpinfo->use_remote_estimate) + { + Oid userid; + + userid = OidIsValid(baserel->userid) ? baserel->userid : GetUserId(); + fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid); + } + else + fpinfo->user = NULL; + + /* + * Identify which baserestrictinfo clauses can be sent to the remote + * server and which can't. + */ + classifyConditions(root, baserel, baserel->baserestrictinfo, + &fpinfo->remote_conds, &fpinfo->local_conds); + + /* + * Identify which attributes will need to be retrieved from the remote + * server. These include all attrs needed for joins or final output, plus + * all attrs used in the local_conds. (Note: if we end up using a + * parameterized scan, it's possible that some of the join clauses will be + * sent to the remote and thus we wouldn't really need to retrieve the + * columns used in them. Doesn't seem worth detecting that case though.) + */ + fpinfo->attrs_used = NULL; + pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, + &fpinfo->attrs_used); + foreach(lc, fpinfo->local_conds) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + pull_varattnos((Node *) rinfo->clause, baserel->relid, + &fpinfo->attrs_used); + } + + /* + * Compute the selectivity and cost of the local_conds, so we don't have + * to do it over again for each path. The best we can do for these + * conditions is to estimate selectivity on the basis of local statistics. + */ + fpinfo->local_conds_sel = clauselist_selectivity(root, + fpinfo->local_conds, + baserel->relid, + JOIN_INNER, + NULL); + + cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); + + /* + * Set # of retrieved rows and cached relation costs to some negative + * value, so that we can detect when they are set to some sensible values, + * during one (usually the first) of the calls to estimate_path_cost_size. + */ + fpinfo->retrieved_rows = -1; + fpinfo->rel_startup_cost = -1; + fpinfo->rel_total_cost = -1; + + /* + * If the table or the server is configured to use remote estimates, + * connect to the foreign server and execute EXPLAIN to estimate the + * number of rows selected by the restriction clauses, as well as the + * average row width. Otherwise, estimate using whatever statistics we + * have locally, in a way similar to ordinary tables. + */ + if (fpinfo->use_remote_estimate) + { + /* + * Get cost/size estimates with help of remote server. Save the + * values in fpinfo so we don't need to do it again to generate the + * basic foreign path. + */ + estimate_path_cost_size(root, baserel, NIL, NIL, NULL, + &fpinfo->rows, &fpinfo->width, + &fpinfo->startup_cost, &fpinfo->total_cost); + + /* Report estimated baserel size to planner. */ + baserel->rows = fpinfo->rows; + baserel->reltarget->width = fpinfo->width; + } + else + { + /* + * If the foreign table has never been ANALYZEd, it will have + * reltuples < 0, meaning "unknown". We can't do much if we're not + * allowed to consult the remote server, but we can use a hack similar + * to plancat.c's treatment of empty relations: use a minimum size + * estimate of 10 pages, and divide by the column-datatype-based width + * estimate to get the corresponding number of tuples. + */ + if (baserel->tuples < 0) + { + baserel->pages = 10; + baserel->tuples = + (10 * BLCKSZ) / (baserel->reltarget->width + + MAXALIGN(SizeofHeapTupleHeader)); + } + + /* Estimate baserel size as best we can with local statistics. */ + set_baserel_size_estimates(root, baserel); + + /* Fill in basically-bogus cost estimates for use later. */ + estimate_path_cost_size(root, baserel, NIL, NIL, NULL, + &fpinfo->rows, &fpinfo->width, + &fpinfo->startup_cost, &fpinfo->total_cost); + } + + /* + * fpinfo->relation_name gets the numeric rangetable index of the foreign + * table RTE. (If this query gets EXPLAIN'd, we'll convert that to a + * human-readable string at that time.) + */ + fpinfo->relation_name = psprintf("%u", baserel->relid); + + /* No outer and inner relations. */ + fpinfo->make_outerrel_subquery = false; + fpinfo->make_innerrel_subquery = false; + fpinfo->lower_subquery_rels = NULL; + /* Set the relation index. */ + fpinfo->relation_index = baserel->relid; +} + +/* + * get_useful_ecs_for_relation + * Determine which EquivalenceClasses might be involved in useful + * orderings of this relation. + * + * This function is in some respects a mirror image of the core function + * pathkeys_useful_for_merging: for a regular table, we know what indexes + * we have and want to test whether any of them are useful. For a foreign + * table, we don't know what indexes are present on the remote side but + * want to speculate about which ones we'd like to use if they existed. + * + * This function returns a list of potentially-useful equivalence classes, + * but it does not guarantee that an EquivalenceMember exists which contains + * Vars only from the given relation. For example, given ft1 JOIN t1 ON + * ft1.x + t1.x = 0, this function will say that the equivalence class + * containing ft1.x + t1.x is potentially useful. Supposing ft1 is remote and + * t1 is local (or on a different server), it will turn out that no useful + * ORDER BY clause can be generated. It's not our job to figure that out + * here; we're only interested in identifying relevant ECs. + */ +static List * +get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel) +{ + List *useful_eclass_list = NIL; + ListCell *lc; + Relids relids; + + /* + * First, consider whether any active EC is potentially useful for a merge + * join against this relation. + */ + if (rel->has_eclass_joins) + { + foreach(lc, root->eq_classes) + { + EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc); + + if (eclass_useful_for_merging(root, cur_ec, rel)) + useful_eclass_list = lappend(useful_eclass_list, cur_ec); + } + } + + /* + * Next, consider whether there are any non-EC derivable join clauses that + * are merge-joinable. If the joininfo list is empty, we can exit + * quickly. + */ + if (rel->joininfo == NIL) + return useful_eclass_list; + + /* If this is a child rel, we must use the topmost parent rel to search. */ + if (IS_OTHER_REL(rel)) + { + Assert(!bms_is_empty(rel->top_parent_relids)); + relids = rel->top_parent_relids; + } + else + relids = rel->relids; + + /* Check each join clause in turn. */ + foreach(lc, rel->joininfo) + { + RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc); + + /* Consider only mergejoinable clauses */ + if (restrictinfo->mergeopfamilies == NIL) + continue; + + /* Make sure we've got canonical ECs. */ + update_mergeclause_eclasses(root, restrictinfo); + + /* + * restrictinfo->mergeopfamilies != NIL is sufficient to guarantee + * that left_ec and right_ec will be initialized, per comments in + * distribute_qual_to_rels. + * + * We want to identify which side of this merge-joinable clause + * contains columns from the relation produced by this RelOptInfo. We + * test for overlap, not containment, because there could be extra + * relations on either side. For example, suppose we've got something + * like ((A JOIN B ON A.x = B.x) JOIN C ON A.y = C.y) LEFT JOIN D ON + * A.y = D.y. The input rel might be the joinrel between A and B, and + * we'll consider the join clause A.y = D.y. relids contains a + * relation not involved in the join class (B) and the equivalence + * class for the left-hand side of the clause contains a relation not + * involved in the input rel (C). Despite the fact that we have only + * overlap and not containment in either direction, A.y is potentially + * useful as a sort column. + * + * Note that it's even possible that relids overlaps neither side of + * the join clause. For example, consider A LEFT JOIN B ON A.x = B.x + * AND A.x = 1. The clause A.x = 1 will appear in B's joininfo list, + * but overlaps neither side of B. In that case, we just skip this + * join clause, since it doesn't suggest a useful sort order for this + * relation. + */ + if (bms_overlap(relids, restrictinfo->right_ec->ec_relids)) + useful_eclass_list = list_append_unique_ptr(useful_eclass_list, + restrictinfo->right_ec); + else if (bms_overlap(relids, restrictinfo->left_ec->ec_relids)) + useful_eclass_list = list_append_unique_ptr(useful_eclass_list, + restrictinfo->left_ec); + } + + return useful_eclass_list; +} + +/* + * get_useful_pathkeys_for_relation + * Determine which orderings of a relation might be useful. + * + * Getting data in sorted order can be useful either because the requested + * order matches the final output ordering for the overall query we're + * planning, or because it enables an efficient merge join. Here, we try + * to figure out which pathkeys to consider. + */ +static List * +get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel) +{ + List *useful_pathkeys_list = NIL; + List *useful_eclass_list; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private; + EquivalenceClass *query_ec = NULL; + ListCell *lc; + + /* + * Pushing the query_pathkeys to the remote server is always worth + * considering, because it might let us avoid a local sort. + */ + fpinfo->qp_is_pushdown_safe = false; + if (root->query_pathkeys) + { + bool query_pathkeys_ok = true; + + foreach(lc, root->query_pathkeys) + { + PathKey *pathkey = (PathKey *) lfirst(lc); + + /* + * The planner and executor don't have any clever strategy for + * taking data sorted by a prefix of the query's pathkeys and + * getting it to be sorted by all of those pathkeys. We'll just + * end up resorting the entire data set. So, unless we can push + * down all of the query pathkeys, forget it. + */ + if (!is_foreign_pathkey(root, rel, pathkey)) + { + query_pathkeys_ok = false; + break; + } + } + + if (query_pathkeys_ok) + { + useful_pathkeys_list = list_make1(list_copy(root->query_pathkeys)); + fpinfo->qp_is_pushdown_safe = true; + } + } + + /* + * Even if we're not using remote estimates, having the remote side do the + * sort generally won't be any worse than doing it locally, and it might + * be much better if the remote side can generate data in the right order + * without needing a sort at all. However, what we're going to do next is + * try to generate pathkeys that seem promising for possible merge joins, + * and that's more speculative. A wrong choice might hurt quite a bit, so + * bail out if we can't use remote estimates. + */ + if (!fpinfo->use_remote_estimate) + return useful_pathkeys_list; + + /* Get the list of interesting EquivalenceClasses. */ + useful_eclass_list = get_useful_ecs_for_relation(root, rel); + + /* Extract unique EC for query, if any, so we don't consider it again. */ + if (list_length(root->query_pathkeys) == 1) + { + PathKey *query_pathkey = linitial(root->query_pathkeys); + + query_ec = query_pathkey->pk_eclass; + } + + /* + * As a heuristic, the only pathkeys we consider here are those of length + * one. It's surely possible to consider more, but since each one we + * choose to consider will generate a round-trip to the remote side, we + * need to be a bit cautious here. It would sure be nice to have a local + * cache of information about remote index definitions... + */ + foreach(lc, useful_eclass_list) + { + EquivalenceClass *cur_ec = lfirst(lc); + PathKey *pathkey; + + /* If redundant with what we did above, skip it. */ + if (cur_ec == query_ec) + continue; + + /* Can't push down the sort if the EC's opfamily is not shippable. */ + if (!is_shippable(linitial_oid(cur_ec->ec_opfamilies), + OperatorFamilyRelationId, fpinfo)) + continue; + + /* If no pushable expression for this rel, skip it. */ + if (find_em_for_rel(root, cur_ec, rel) == NULL) + continue; + + /* Looks like we can generate a pathkey, so let's do it. */ + pathkey = make_canonical_pathkey(root, cur_ec, + linitial_oid(cur_ec->ec_opfamilies), + BTLessStrategyNumber, + false); + useful_pathkeys_list = lappend(useful_pathkeys_list, + list_make1(pathkey)); + } + + return useful_pathkeys_list; +} + +/* + * postgresGetForeignPaths + * Create possible scan paths for a scan on the foreign table + */ +static void +postgresGetForeignPaths(PlannerInfo *root, + RelOptInfo *baserel, + Oid foreigntableid) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private; + ForeignPath *path; + List *ppi_list; + ListCell *lc; + + /* + * Create simplest ForeignScan path node and add it to baserel. This path + * corresponds to SeqScan path of regular tables (though depending on what + * baserestrict conditions we were able to send to remote, there might + * actually be an indexscan happening there). We already did all the work + * to estimate cost and size of this path. + * + * Although this path uses no join clauses, it could still have required + * parameterization due to LATERAL refs in its tlist. + */ + path = create_foreignscan_path(root, baserel, + NULL, /* default pathtarget */ + fpinfo->rows, + fpinfo->startup_cost, + fpinfo->total_cost, + NIL, /* no pathkeys */ + baserel->lateral_relids, + NULL, /* no extra plan */ + NIL); /* no fdw_private list */ + add_path(baserel, (Path *) path); + + /* Add paths with pathkeys */ + add_paths_with_pathkeys_for_rel(root, baserel, NULL); + + /* + * If we're not using remote estimates, stop here. We have no way to + * estimate whether any join clauses would be worth sending across, so + * don't bother building parameterized paths. + */ + if (!fpinfo->use_remote_estimate) + return; + + /* + * Thumb through all join clauses for the rel to identify which outer + * relations could supply one or more safe-to-send-to-remote join clauses. + * We'll build a parameterized path for each such outer relation. + * + * It's convenient to manage this by representing each candidate outer + * relation by the ParamPathInfo node for it. We can then use the + * ppi_clauses list in the ParamPathInfo node directly as a list of the + * interesting join clauses for that rel. This takes care of the + * possibility that there are multiple safe join clauses for such a rel, + * and also ensures that we account for unsafe join clauses that we'll + * still have to enforce locally (since the parameterized-path machinery + * insists that we handle all movable clauses). + */ + ppi_list = NIL; + foreach(lc, baserel->joininfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Relids required_outer; + ParamPathInfo *param_info; + + /* Check if clause can be moved to this rel */ + if (!join_clause_is_movable_to(rinfo, baserel)) + continue; + + /* See if it is safe to send to remote */ + if (!is_foreign_expr(root, baserel, rinfo->clause)) + continue; + + /* Calculate required outer rels for the resulting path */ + required_outer = bms_union(rinfo->clause_relids, + baserel->lateral_relids); + /* We do not want the foreign rel itself listed in required_outer */ + required_outer = bms_del_member(required_outer, baserel->relid); + + /* + * required_outer probably can't be empty here, but if it were, we + * couldn't make a parameterized path. + */ + if (bms_is_empty(required_outer)) + continue; + + /* Get the ParamPathInfo */ + param_info = get_baserel_parampathinfo(root, baserel, + required_outer); + Assert(param_info != NULL); + + /* + * Add it to list unless we already have it. Testing pointer equality + * is OK since get_baserel_parampathinfo won't make duplicates. + */ + ppi_list = list_append_unique_ptr(ppi_list, param_info); + } + + /* + * The above scan examined only "generic" join clauses, not those that + * were absorbed into EquivalenceClauses. See if we can make anything out + * of EquivalenceClauses. + */ + if (baserel->has_eclass_joins) + { + /* + * We repeatedly scan the eclass list looking for column references + * (or expressions) belonging to the foreign rel. Each time we find + * one, we generate a list of equivalence joinclauses for it, and then + * see if any are safe to send to the remote. Repeat till there are + * no more candidate EC members. + */ + ec_member_foreign_arg arg; + + arg.already_used = NIL; + for (;;) + { + List *clauses; + + /* Make clauses, skipping any that join to lateral_referencers */ + arg.current = NULL; + clauses = generate_implied_equalities_for_column(root, + baserel, + ec_member_matches_foreign, + (void *) &arg, + baserel->lateral_referencers); + + /* Done if there are no more expressions in the foreign rel */ + if (arg.current == NULL) + { + Assert(clauses == NIL); + break; + } + + /* Scan the extracted join clauses */ + foreach(lc, clauses) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Relids required_outer; + ParamPathInfo *param_info; + + /* Check if clause can be moved to this rel */ + if (!join_clause_is_movable_to(rinfo, baserel)) + continue; + + /* See if it is safe to send to remote */ + if (!is_foreign_expr(root, baserel, rinfo->clause)) + continue; + + /* Calculate required outer rels for the resulting path */ + required_outer = bms_union(rinfo->clause_relids, + baserel->lateral_relids); + required_outer = bms_del_member(required_outer, baserel->relid); + if (bms_is_empty(required_outer)) + continue; + + /* Get the ParamPathInfo */ + param_info = get_baserel_parampathinfo(root, baserel, + required_outer); + Assert(param_info != NULL); + + /* Add it to list unless we already have it */ + ppi_list = list_append_unique_ptr(ppi_list, param_info); + } + + /* Try again, now ignoring the expression we found this time */ + arg.already_used = lappend(arg.already_used, arg.current); + } + } + + /* + * Now build a path for each useful outer relation. + */ + foreach(lc, ppi_list) + { + ParamPathInfo *param_info = (ParamPathInfo *) lfirst(lc); + double rows; + int width; + Cost startup_cost; + Cost total_cost; + + /* Get a cost estimate from the remote */ + estimate_path_cost_size(root, baserel, + param_info->ppi_clauses, NIL, NULL, + &rows, &width, + &startup_cost, &total_cost); + + /* + * ppi_rows currently won't get looked at by anything, but still we + * may as well ensure that it matches our idea of the rowcount. + */ + param_info->ppi_rows = rows; + + /* Make the path */ + path = create_foreignscan_path(root, baserel, + NULL, /* default pathtarget */ + rows, + startup_cost, + total_cost, + NIL, /* no pathkeys */ + param_info->ppi_req_outer, + NULL, + NIL); /* no fdw_private list */ + add_path(baserel, (Path *) path); + } +} + +/* + * postgresGetForeignPlan + * Create ForeignScan plan node which implements selected best path + */ +static ForeignScan * +postgresGetForeignPlan(PlannerInfo *root, + RelOptInfo *foreignrel, + Oid foreigntableid, + ForeignPath *best_path, + List *tlist, + List *scan_clauses, + Plan *outer_plan) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; + Index scan_relid; + List *fdw_private; + List *remote_exprs = NIL; + List *local_exprs = NIL; + List *params_list = NIL; + List *fdw_scan_tlist = NIL; + List *fdw_recheck_quals = NIL; + List *retrieved_attrs; + StringInfoData sql; + bool has_final_sort = false; + bool has_limit = false; + ListCell *lc; + + /* + * Get FDW private data created by postgresGetForeignUpperPaths(), if any. + */ + if (best_path->fdw_private) + { + has_final_sort = boolVal(list_nth(best_path->fdw_private, + FdwPathPrivateHasFinalSort)); + has_limit = boolVal(list_nth(best_path->fdw_private, + FdwPathPrivateHasLimit)); + } + + if (IS_SIMPLE_REL(foreignrel)) + { + /* + * For base relations, set scan_relid as the relid of the relation. + */ + scan_relid = foreignrel->relid; + + /* + * In a base-relation scan, we must apply the given scan_clauses. + * + * Separate the scan_clauses into those that can be executed remotely + * and those that can't. baserestrictinfo clauses that were + * previously determined to be safe or unsafe by classifyConditions + * are found in fpinfo->remote_conds and fpinfo->local_conds. Anything + * else in the scan_clauses list will be a join clause, which we have + * to check for remote-safety. + * + * Note: the join clauses we see here should be the exact same ones + * previously examined by postgresGetForeignPaths. Possibly it'd be + * worth passing forward the classification work done then, rather + * than repeating it here. + * + * This code must match "extract_actual_clauses(scan_clauses, false)" + * except for the additional decision about remote versus local + * execution. + */ + foreach(lc, scan_clauses) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + /* Ignore any pseudoconstants, they're dealt with elsewhere */ + if (rinfo->pseudoconstant) + continue; + + if (list_member_ptr(fpinfo->remote_conds, rinfo)) + remote_exprs = lappend(remote_exprs, rinfo->clause); + else if (list_member_ptr(fpinfo->local_conds, rinfo)) + local_exprs = lappend(local_exprs, rinfo->clause); + else if (is_foreign_expr(root, foreignrel, rinfo->clause)) + remote_exprs = lappend(remote_exprs, rinfo->clause); + else + local_exprs = lappend(local_exprs, rinfo->clause); + } + + /* + * For a base-relation scan, we have to support EPQ recheck, which + * should recheck all the remote quals. + */ + fdw_recheck_quals = remote_exprs; + } + else + { + /* + * Join relation or upper relation - set scan_relid to 0. + */ + scan_relid = 0; + + /* + * For a join rel, baserestrictinfo is NIL and we are not considering + * parameterization right now, so there should be no scan_clauses for + * a joinrel or an upper rel either. + */ + Assert(!scan_clauses); + + /* + * Instead we get the conditions to apply from the fdw_private + * structure. + */ + remote_exprs = extract_actual_clauses(fpinfo->remote_conds, false); + local_exprs = extract_actual_clauses(fpinfo->local_conds, false); + + /* + * We leave fdw_recheck_quals empty in this case, since we never need + * to apply EPQ recheck clauses. In the case of a joinrel, EPQ + * recheck is handled elsewhere --- see postgresGetForeignJoinPaths(). + * If we're planning an upperrel (ie, remote grouping or aggregation) + * then there's no EPQ to do because SELECT FOR UPDATE wouldn't be + * allowed, and indeed we *can't* put the remote clauses into + * fdw_recheck_quals because the unaggregated Vars won't be available + * locally. + */ + + /* Build the list of columns to be fetched from the foreign server. */ + fdw_scan_tlist = build_tlist_to_deparse(foreignrel); + + /* + * Ensure that the outer plan produces a tuple whose descriptor + * matches our scan tuple slot. Also, remove the local conditions + * from outer plan's quals, lest they be evaluated twice, once by the + * local plan and once by the scan. + */ + if (outer_plan) + { + /* + * Right now, we only consider grouping and aggregation beyond + * joins. Queries involving aggregates or grouping do not require + * EPQ mechanism, hence should not have an outer plan here. + */ + Assert(!IS_UPPER_REL(foreignrel)); + + /* + * First, update the plan's qual list if possible. In some cases + * the quals might be enforced below the topmost plan level, in + * which case we'll fail to remove them; it's not worth working + * harder than this. + */ + foreach(lc, local_exprs) + { + Node *qual = lfirst(lc); + + outer_plan->qual = list_delete(outer_plan->qual, qual); + + /* + * For an inner join the local conditions of foreign scan plan + * can be part of the joinquals as well. (They might also be + * in the mergequals or hashquals, but we can't touch those + * without breaking the plan.) + */ + if (IsA(outer_plan, NestLoop) || + IsA(outer_plan, MergeJoin) || + IsA(outer_plan, HashJoin)) + { + Join *join_plan = (Join *) outer_plan; + + if (join_plan->jointype == JOIN_INNER) + join_plan->joinqual = list_delete(join_plan->joinqual, + qual); + } + } + + /* + * Now fix the subplan's tlist --- this might result in inserting + * a Result node atop the plan tree. + */ + outer_plan = change_plan_targetlist(outer_plan, fdw_scan_tlist, + best_path->path.parallel_safe); + } + } + + /* + * Build the query string to be sent for execution, and identify + * expressions to be sent as parameters. + */ + initStringInfo(&sql); + deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist, + remote_exprs, best_path->path.pathkeys, + has_final_sort, has_limit, false, + &retrieved_attrs, ¶ms_list); + + /* Remember remote_exprs for possible use by postgresPlanDirectModify */ + fpinfo->final_remote_exprs = remote_exprs; + + /* + * Build the fdw_private list that will be available to the executor. + * Items in the list must match order in enum FdwScanPrivateIndex. + */ + fdw_private = list_make3(makeString(sql.data), + retrieved_attrs, + makeInteger(fpinfo->fetch_size)); + if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) + fdw_private = lappend(fdw_private, + makeString(fpinfo->relation_name)); + + /* + * Create the ForeignScan node for the given relation. + * + * Note that the remote parameter expressions are stored in the fdw_exprs + * field of the finished plan node; we can't keep them in private state + * because then they wouldn't be subject to later planner processing. + */ + return make_foreignscan(tlist, + local_exprs, + scan_relid, + params_list, + fdw_private, + fdw_scan_tlist, + fdw_recheck_quals, + outer_plan); +} + +/* + * Construct a tuple descriptor for the scan tuples handled by a foreign join. + */ +static TupleDesc +get_tupdesc_for_join_scan_tuples(ForeignScanState *node) +{ + ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; + EState *estate = node->ss.ps.state; + TupleDesc tupdesc; + + /* + * The core code has already set up a scan tuple slot based on + * fsplan->fdw_scan_tlist, and this slot's tupdesc is mostly good enough, + * but there's one case where it isn't. If we have any whole-row row + * identifier Vars, they may have vartype RECORD, and we need to replace + * that with the associated table's actual composite type. This ensures + * that when we read those ROW() expression values from the remote server, + * we can convert them to a composite type the local server knows. + */ + tupdesc = CreateTupleDescCopy(node->ss.ss_ScanTupleSlot->tts_tupleDescriptor); + for (int i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + Var *var; + RangeTblEntry *rte; + Oid reltype; + + /* Nothing to do if it's not a generic RECORD attribute */ + if (att->atttypid != RECORDOID || att->atttypmod >= 0) + continue; + + /* + * If we can't identify the referenced table, do nothing. This'll + * likely lead to failure later, but perhaps we can muddle through. + */ + var = (Var *) list_nth_node(TargetEntry, fsplan->fdw_scan_tlist, + i)->expr; + if (!IsA(var, Var) || var->varattno != 0) + continue; + rte = list_nth(estate->es_range_table, var->varno - 1); + if (rte->rtekind != RTE_RELATION) + continue; + reltype = get_rel_type_id(rte->relid); + if (!OidIsValid(reltype)) + continue; + att->atttypid = reltype; + /* shouldn't need to change anything else */ + } + return tupdesc; +} + +/* + * postgresBeginForeignScan + * Initiate an executor scan of a foreign PostgreSQL table. + */ +static void +postgresBeginForeignScan(ForeignScanState *node, int eflags) +{ + ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; + EState *estate = node->ss.ps.state; + PgFdwScanState *fsstate; + RangeTblEntry *rte; + Oid userid; + ForeignTable *table; + UserMapping *user; + int rtindex; + int numParams; + + /* + * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. + */ + if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + return; + + /* + * We'll save private state in node->fdw_state. + */ + fsstate = (PgFdwScanState *) palloc0(sizeof(PgFdwScanState)); + node->fdw_state = (void *) fsstate; + + /* + * Identify which user to do the remote access as. This should match what + * ExecCheckPermissions() does. + */ + userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId(); + if (fsplan->scan.scanrelid > 0) + rtindex = fsplan->scan.scanrelid; + else + rtindex = bms_next_member(fsplan->fs_base_relids, -1); + rte = exec_rt_fetch(rtindex, estate); + + /* Get info about foreign table. */ + table = GetForeignTable(rte->relid); + user = GetUserMapping(userid, table->serverid); + + /* + * Get connection to the foreign server. Connection manager will + * establish new connection if necessary. + */ + fsstate->conn = GetConnection(user, false, &fsstate->conn_state); + + /* Assign a unique ID for my cursor */ + fsstate->cursor_number = GetCursorNumber(fsstate->conn); + fsstate->cursor_exists = false; + + /* Get private info created by planner functions. */ + fsstate->query = strVal(list_nth(fsplan->fdw_private, + FdwScanPrivateSelectSql)); + fsstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private, + FdwScanPrivateRetrievedAttrs); + fsstate->fetch_size = intVal(list_nth(fsplan->fdw_private, + FdwScanPrivateFetchSize)); + + /* Create contexts for batches of tuples and per-tuple temp workspace. */ + fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt, + "postgres_fdw tuple data", + ALLOCSET_DEFAULT_SIZES); + fsstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, + "postgres_fdw temporary data", + ALLOCSET_SMALL_SIZES); + + /* + * Get info we'll need for converting data fetched from the foreign server + * into local representation and error reporting during that process. + */ + if (fsplan->scan.scanrelid > 0) + { + fsstate->rel = node->ss.ss_currentRelation; + fsstate->tupdesc = RelationGetDescr(fsstate->rel); + } + else + { + fsstate->rel = NULL; + fsstate->tupdesc = get_tupdesc_for_join_scan_tuples(node); + } + + fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc); + + /* + * Prepare for processing of parameters used in remote query, if any. + */ + numParams = list_length(fsplan->fdw_exprs); + fsstate->numParams = numParams; + if (numParams > 0) + prepare_query_params((PlanState *) node, + fsplan->fdw_exprs, + numParams, + &fsstate->param_flinfo, + &fsstate->param_exprs, + &fsstate->param_values); + + /* Set the async-capable flag */ + fsstate->async_capable = node->ss.ps.async_capable; +} + +/* + * postgresIterateForeignScan + * Retrieve next row from the result set, or clear tuple slot to indicate + * EOF. + */ +static TupleTableSlot * +postgresIterateForeignScan(ForeignScanState *node) +{ + PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state; + TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; + + /* + * In sync mode, if this is the first call after Begin or ReScan, we need + * to create the cursor on the remote side. In async mode, we would have + * already created the cursor before we get here, even if this is the + * first call after Begin or ReScan. + */ + if (!fsstate->cursor_exists) + create_cursor(node); + + /* + * Get some more tuples, if we've run out. + */ + if (fsstate->next_tuple >= fsstate->num_tuples) + { + /* In async mode, just clear tuple slot. */ + if (fsstate->async_capable) + return ExecClearTuple(slot); + /* No point in another fetch if we already detected EOF, though. */ + if (!fsstate->eof_reached) + fetch_more_data(node); + /* If we didn't get any tuples, must be end of data. */ + if (fsstate->next_tuple >= fsstate->num_tuples) + return ExecClearTuple(slot); + } + + /* + * Return the next tuple. + */ + ExecStoreHeapTuple(fsstate->tuples[fsstate->next_tuple++], + slot, + false); + + return slot; +} + +/* + * postgresReScanForeignScan + * Restart the scan. + */ +static void +postgresReScanForeignScan(ForeignScanState *node) +{ + PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state; + char sql[64]; + PGresult *res; + + /* If we haven't created the cursor yet, nothing to do. */ + if (!fsstate->cursor_exists) + return; + + /* + * If the node is async-capable, and an asynchronous fetch for it has + * begun, the asynchronous fetch might not have yet completed. Check if + * the node is async-capable, and an asynchronous fetch for it is still in + * progress; if so, complete the asynchronous fetch before restarting the + * scan. + */ + if (fsstate->async_capable && + fsstate->conn_state->pendingAreq && + fsstate->conn_state->pendingAreq->requestee == (PlanState *) node) + fetch_more_data(node); + + /* + * If any internal parameters affecting this node have changed, we'd + * better destroy and recreate the cursor. Otherwise, rewinding it should + * be good enough. If we've only fetched zero or one batch, we needn't + * even rewind the cursor, just rescan what we have. + */ + if (node->ss.ps.chgParam != NULL) + { + fsstate->cursor_exists = false; + snprintf(sql, sizeof(sql), "CLOSE c%u", + fsstate->cursor_number); + } + else if (fsstate->fetch_ct_2 > 1) + { + snprintf(sql, sizeof(sql), "MOVE BACKWARD ALL IN c%u", + fsstate->cursor_number); + } + else + { + /* Easy: just rescan what we already have in memory, if anything */ + fsstate->next_tuple = 0; + return; + } + + /* + * We don't use a PG_TRY block here, so be careful not to throw error + * without releasing the PGresult. + */ + res = pgfdw_exec_query(fsstate->conn, sql, fsstate->conn_state); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pgfdw_report_error(ERROR, res, fsstate->conn, true, sql); + PQclear(res); + + /* Now force a fresh FETCH. */ + fsstate->tuples = NULL; + fsstate->num_tuples = 0; + fsstate->next_tuple = 0; + fsstate->fetch_ct_2 = 0; + fsstate->eof_reached = false; +} + +/* + * postgresEndForeignScan + * Finish scanning foreign table and dispose objects used for this scan + */ +static void +postgresEndForeignScan(ForeignScanState *node) +{ + PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state; + + /* if fsstate is NULL, we are in EXPLAIN; nothing to do */ + if (fsstate == NULL) + return; + + /* Close the cursor if open, to prevent accumulation of cursors */ + if (fsstate->cursor_exists) + close_cursor(fsstate->conn, fsstate->cursor_number, + fsstate->conn_state); + + /* Release remote connection */ + ReleaseConnection(fsstate->conn); + fsstate->conn = NULL; + + /* MemoryContexts will be deleted automatically. */ +} + +/* + * postgresAddForeignUpdateTargets + * Add resjunk column(s) needed for update/delete on a foreign table + */ +static void +postgresAddForeignUpdateTargets(PlannerInfo *root, + Index rtindex, + RangeTblEntry *target_rte, + Relation target_relation) +{ + Var *var; + + /* + * In postgres_fdw, what we need is the ctid, same as for a regular table. + */ + + /* Make a Var representing the desired value */ + var = makeVar(rtindex, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + + /* Register it as a row-identity column needed by this target rel */ + add_row_identity_var(root, var, rtindex, "ctid"); +} + +/* + * postgresPlanForeignModify + * Plan an insert/update/delete operation on a foreign table + */ +static List * +postgresPlanForeignModify(PlannerInfo *root, + ModifyTable *plan, + Index resultRelation, + int subplan_index) +{ + CmdType operation = plan->operation; + RangeTblEntry *rte = planner_rt_fetch(resultRelation, root); + Relation rel; + StringInfoData sql; + List *targetAttrs = NIL; + List *withCheckOptionList = NIL; + List *returningList = NIL; + List *retrieved_attrs = NIL; + bool doNothing = false; + int values_end_len = -1; + + initStringInfo(&sql); + + /* + * Core code already has some lock on each rel being planned, so we can + * use NoLock here. + */ + rel = table_open(rte->relid, NoLock); + + /* + * In an INSERT, we transmit all columns that are defined in the foreign + * table. In an UPDATE, if there are BEFORE ROW UPDATE triggers on the + * foreign table, we transmit all columns like INSERT; else we transmit + * only columns that were explicitly targets of the UPDATE, so as to avoid + * unnecessary data transmission. (We can't do that for INSERT since we + * would miss sending default values for columns not listed in the source + * statement, and for UPDATE if there are BEFORE ROW UPDATE triggers since + * those triggers might change values for non-target columns, in which + * case we would miss sending changed values for those columns.) + */ + if (operation == CMD_INSERT || + (operation == CMD_UPDATE && + rel->trigdesc && + rel->trigdesc->trig_update_before_row)) + { + TupleDesc tupdesc = RelationGetDescr(rel); + int attnum; + + for (attnum = 1; attnum <= tupdesc->natts; attnum++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); + + if (!attr->attisdropped) + targetAttrs = lappend_int(targetAttrs, attnum); + } + } + else if (operation == CMD_UPDATE) + { + int col; + RelOptInfo *rel = find_base_rel(root, resultRelation); + Bitmapset *allUpdatedCols = get_rel_all_updated_cols(root, rel); + + col = -1; + while ((col = bms_next_member(allUpdatedCols, col)) >= 0) + { + /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */ + AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber; + + if (attno <= InvalidAttrNumber) /* shouldn't happen */ + elog(ERROR, "system-column update is not supported"); + targetAttrs = lappend_int(targetAttrs, attno); + } + } + + /* + * Extract the relevant WITH CHECK OPTION list if any. + */ + if (plan->withCheckOptionLists) + withCheckOptionList = (List *) list_nth(plan->withCheckOptionLists, + subplan_index); + + /* + * Extract the relevant RETURNING list if any. + */ + if (plan->returningLists) + returningList = (List *) list_nth(plan->returningLists, subplan_index); + + /* + * ON CONFLICT DO UPDATE and DO NOTHING case with inference specification + * should have already been rejected in the optimizer, as presently there + * is no way to recognize an arbiter index on a foreign table. Only DO + * NOTHING is supported without an inference specification. + */ + if (plan->onConflictAction == ONCONFLICT_NOTHING) + doNothing = true; + else if (plan->onConflictAction != ONCONFLICT_NONE) + elog(ERROR, "unexpected ON CONFLICT specification: %d", + (int) plan->onConflictAction); + + /* + * Construct the SQL command string. + */ + switch (operation) + { + case CMD_INSERT: + deparseInsertSql(&sql, rte, resultRelation, rel, + targetAttrs, doNothing, + withCheckOptionList, returningList, + &retrieved_attrs, &values_end_len); + break; + case CMD_UPDATE: + deparseUpdateSql(&sql, rte, resultRelation, rel, + targetAttrs, + withCheckOptionList, returningList, + &retrieved_attrs); + break; + case CMD_DELETE: + deparseDeleteSql(&sql, rte, resultRelation, rel, + returningList, + &retrieved_attrs); + break; + default: + elog(ERROR, "unexpected operation: %d", (int) operation); + break; + } + + table_close(rel, NoLock); + + /* + * Build the fdw_private list that will be available to the executor. + * Items in the list must match enum FdwModifyPrivateIndex, above. + */ + return list_make5(makeString(sql.data), + targetAttrs, + makeInteger(values_end_len), + makeBoolean((retrieved_attrs != NIL)), + retrieved_attrs); +} + +/* + * postgresBeginForeignModify + * Begin an insert/update/delete operation on a foreign table + */ +static void +postgresBeginForeignModify(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + List *fdw_private, + int subplan_index, + int eflags) +{ + PgFdwModifyState *fmstate; + char *query; + List *target_attrs; + bool has_returning; + int values_end_len; + List *retrieved_attrs; + RangeTblEntry *rte; + + /* + * Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState + * stays NULL. + */ + if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + return; + + /* Deconstruct fdw_private data. */ + query = strVal(list_nth(fdw_private, + FdwModifyPrivateUpdateSql)); + target_attrs = (List *) list_nth(fdw_private, + FdwModifyPrivateTargetAttnums); + values_end_len = intVal(list_nth(fdw_private, + FdwModifyPrivateLen)); + has_returning = boolVal(list_nth(fdw_private, + FdwModifyPrivateHasReturning)); + retrieved_attrs = (List *) list_nth(fdw_private, + FdwModifyPrivateRetrievedAttrs); + + /* Find RTE. */ + rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex, + mtstate->ps.state); + + /* Construct an execution state. */ + fmstate = create_foreign_modify(mtstate->ps.state, + rte, + resultRelInfo, + mtstate->operation, + outerPlanState(mtstate)->plan, + query, + target_attrs, + values_end_len, + has_returning, + retrieved_attrs); + + resultRelInfo->ri_FdwState = fmstate; +} + +/* + * postgresExecForeignInsert + * Insert one row into a foreign table + */ +static TupleTableSlot * +postgresExecForeignInsert(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + TupleTableSlot *planSlot) +{ + PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState; + TupleTableSlot **rslot; + int numSlots = 1; + + /* + * If the fmstate has aux_fmstate set, use the aux_fmstate (see + * postgresBeginForeignInsert()) + */ + if (fmstate->aux_fmstate) + resultRelInfo->ri_FdwState = fmstate->aux_fmstate; + rslot = execute_foreign_modify(estate, resultRelInfo, CMD_INSERT, + &slot, &planSlot, &numSlots); + /* Revert that change */ + if (fmstate->aux_fmstate) + resultRelInfo->ri_FdwState = fmstate; + + return rslot ? *rslot : NULL; +} + +/* + * postgresExecForeignBatchInsert + * Insert multiple rows into a foreign table + */ +static TupleTableSlot ** +postgresExecForeignBatchInsert(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot **slots, + TupleTableSlot **planSlots, + int *numSlots) +{ + PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState; + TupleTableSlot **rslot; + + /* + * If the fmstate has aux_fmstate set, use the aux_fmstate (see + * postgresBeginForeignInsert()) + */ + if (fmstate->aux_fmstate) + resultRelInfo->ri_FdwState = fmstate->aux_fmstate; + rslot = execute_foreign_modify(estate, resultRelInfo, CMD_INSERT, + slots, planSlots, numSlots); + /* Revert that change */ + if (fmstate->aux_fmstate) + resultRelInfo->ri_FdwState = fmstate; + + return rslot; +} + +/* + * postgresGetForeignModifyBatchSize + * Determine the maximum number of tuples that can be inserted in bulk + * + * Returns the batch size specified for server or table. When batching is not + * allowed (e.g. for tables with BEFORE/AFTER ROW triggers or with RETURNING + * clause), returns 1. + */ +static int +postgresGetForeignModifyBatchSize(ResultRelInfo *resultRelInfo) +{ + int batch_size; + PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState; + + /* should be called only once */ + Assert(resultRelInfo->ri_BatchSize == 0); + + /* + * Should never get called when the insert is being performed on a table + * that is also among the target relations of an UPDATE operation, because + * postgresBeginForeignInsert() currently rejects such insert attempts. + */ + Assert(fmstate == NULL || fmstate->aux_fmstate == NULL); + + /* + * In EXPLAIN without ANALYZE, ri_FdwState is NULL, so we have to lookup + * the option directly in server/table options. Otherwise just use the + * value we determined earlier. + */ + if (fmstate) + batch_size = fmstate->batch_size; + else + batch_size = get_batch_size_option(resultRelInfo->ri_RelationDesc); + + /* + * Disable batching when we have to use RETURNING, there are any + * BEFORE/AFTER ROW INSERT triggers on the foreign table, or there are any + * WITH CHECK OPTION constraints from parent views. + * + * When there are any BEFORE ROW INSERT triggers on the table, we can't + * support it, because such triggers might query the table we're inserting + * into and act differently if the tuples that have already been processed + * and prepared for insertion are not there. + */ + if (resultRelInfo->ri_projectReturning != NULL || + resultRelInfo->ri_WithCheckOptions != NIL || + (resultRelInfo->ri_TrigDesc && + (resultRelInfo->ri_TrigDesc->trig_insert_before_row || + resultRelInfo->ri_TrigDesc->trig_insert_after_row))) + return 1; + + /* + * If the foreign table has no columns, disable batching as the INSERT + * syntax doesn't allow batching multiple empty rows into a zero-column + * table in a single statement. This is needed for COPY FROM, in which + * case fmstate must be non-NULL. + */ + if (fmstate && list_length(fmstate->target_attrs) == 0) + return 1; + + /* + * Otherwise use the batch size specified for server/table. The number of + * parameters in a batch is limited to 65535 (uint16), so make sure we + * don't exceed this limit by using the maximum batch_size possible. + */ + if (fmstate && fmstate->p_nums > 0) + batch_size = Min(batch_size, PQ_QUERY_PARAM_MAX_LIMIT / fmstate->p_nums); + + return batch_size; +} + +/* + * postgresExecForeignUpdate + * Update one row in a foreign table + */ +static TupleTableSlot * +postgresExecForeignUpdate(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + TupleTableSlot *planSlot) +{ + TupleTableSlot **rslot; + int numSlots = 1; + + rslot = execute_foreign_modify(estate, resultRelInfo, CMD_UPDATE, + &slot, &planSlot, &numSlots); + + return rslot ? rslot[0] : NULL; +} + +/* + * postgresExecForeignDelete + * Delete one row from a foreign table + */ +static TupleTableSlot * +postgresExecForeignDelete(EState *estate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + TupleTableSlot *planSlot) +{ + TupleTableSlot **rslot; + int numSlots = 1; + + rslot = execute_foreign_modify(estate, resultRelInfo, CMD_DELETE, + &slot, &planSlot, &numSlots); + + return rslot ? rslot[0] : NULL; +} + +/* + * postgresEndForeignModify + * Finish an insert/update/delete operation on a foreign table + */ +static void +postgresEndForeignModify(EState *estate, + ResultRelInfo *resultRelInfo) +{ + PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState; + + /* If fmstate is NULL, we are in EXPLAIN; nothing to do */ + if (fmstate == NULL) + return; + + /* Destroy the execution state */ + finish_foreign_modify(fmstate); +} + +/* + * postgresBeginForeignInsert + * Begin an insert operation on a foreign table + */ +static void +postgresBeginForeignInsert(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo) +{ + PgFdwModifyState *fmstate; + ModifyTable *plan = castNode(ModifyTable, mtstate->ps.plan); + EState *estate = mtstate->ps.state; + Index resultRelation; + Relation rel = resultRelInfo->ri_RelationDesc; + RangeTblEntry *rte; + TupleDesc tupdesc = RelationGetDescr(rel); + int attnum; + int values_end_len; + StringInfoData sql; + List *targetAttrs = NIL; + List *retrieved_attrs = NIL; + bool doNothing = false; + + /* + * If the foreign table we are about to insert routed rows into is also an + * UPDATE subplan result rel that will be updated later, proceeding with + * the INSERT will result in the later UPDATE incorrectly modifying those + * routed rows, so prevent the INSERT --- it would be nice if we could + * handle this case; but for now, throw an error for safety. + */ + if (plan && plan->operation == CMD_UPDATE && + (resultRelInfo->ri_usesFdwDirectModify || + resultRelInfo->ri_FdwState)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot route tuples into foreign table to be updated \"%s\"", + RelationGetRelationName(rel)))); + + initStringInfo(&sql); + + /* We transmit all columns that are defined in the foreign table. */ + for (attnum = 1; attnum <= tupdesc->natts; attnum++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); + + if (!attr->attisdropped) + targetAttrs = lappend_int(targetAttrs, attnum); + } + + /* Check if we add the ON CONFLICT clause to the remote query. */ + if (plan) + { + OnConflictAction onConflictAction = plan->onConflictAction; + + /* We only support DO NOTHING without an inference specification. */ + if (onConflictAction == ONCONFLICT_NOTHING) + doNothing = true; + else if (onConflictAction != ONCONFLICT_NONE) + elog(ERROR, "unexpected ON CONFLICT specification: %d", + (int) onConflictAction); + } + + /* + * If the foreign table is a partition that doesn't have a corresponding + * RTE entry, we need to create a new RTE describing the foreign table for + * use by deparseInsertSql and create_foreign_modify() below, after first + * copying the parent's RTE and modifying some fields to describe the + * foreign partition to work on. However, if this is invoked by UPDATE, + * the existing RTE may already correspond to this partition if it is one + * of the UPDATE subplan target rels; in that case, we can just use the + * existing RTE as-is. + */ + if (resultRelInfo->ri_RangeTableIndex == 0) + { + ResultRelInfo *rootResultRelInfo = resultRelInfo->ri_RootResultRelInfo; + + rte = exec_rt_fetch(rootResultRelInfo->ri_RangeTableIndex, estate); + rte = copyObject(rte); + rte->relid = RelationGetRelid(rel); + rte->relkind = RELKIND_FOREIGN_TABLE; + + /* + * For UPDATE, we must use the RT index of the first subplan target + * rel's RTE, because the core code would have built expressions for + * the partition, such as RETURNING, using that RT index as varno of + * Vars contained in those expressions. + */ + if (plan && plan->operation == CMD_UPDATE && + rootResultRelInfo->ri_RangeTableIndex == plan->rootRelation) + resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex; + else + resultRelation = rootResultRelInfo->ri_RangeTableIndex; + } + else + { + resultRelation = resultRelInfo->ri_RangeTableIndex; + rte = exec_rt_fetch(resultRelation, estate); + } + + /* Construct the SQL command string. */ + deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing, + resultRelInfo->ri_WithCheckOptions, + resultRelInfo->ri_returningList, + &retrieved_attrs, &values_end_len); + + /* Construct an execution state. */ + fmstate = create_foreign_modify(mtstate->ps.state, + rte, + resultRelInfo, + CMD_INSERT, + NULL, + sql.data, + targetAttrs, + values_end_len, + retrieved_attrs != NIL, + retrieved_attrs); + + /* + * If the given resultRelInfo already has PgFdwModifyState set, it means + * the foreign table is an UPDATE subplan result rel; in which case, store + * the resulting state into the aux_fmstate of the PgFdwModifyState. + */ + if (resultRelInfo->ri_FdwState) + { + Assert(plan && plan->operation == CMD_UPDATE); + Assert(resultRelInfo->ri_usesFdwDirectModify == false); + ((PgFdwModifyState *) resultRelInfo->ri_FdwState)->aux_fmstate = fmstate; + } + else + resultRelInfo->ri_FdwState = fmstate; +} + +/* + * postgresEndForeignInsert + * Finish an insert operation on a foreign table + */ +static void +postgresEndForeignInsert(EState *estate, + ResultRelInfo *resultRelInfo) +{ + PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState; + + Assert(fmstate != NULL); + + /* + * If the fmstate has aux_fmstate set, get the aux_fmstate (see + * postgresBeginForeignInsert()) + */ + if (fmstate->aux_fmstate) + fmstate = fmstate->aux_fmstate; + + /* Destroy the execution state */ + finish_foreign_modify(fmstate); +} + +/* + * postgresIsForeignRelUpdatable + * Determine whether a foreign table supports INSERT, UPDATE and/or + * DELETE. + */ +static int +postgresIsForeignRelUpdatable(Relation rel) +{ + bool updatable; + ForeignTable *table; + ForeignServer *server; + ListCell *lc; + + /* + * By default, all postgres_fdw foreign tables are assumed updatable. This + * can be overridden by a per-server setting, which in turn can be + * overridden by a per-table setting. + */ + updatable = true; + + table = GetForeignTable(RelationGetRelid(rel)); + server = GetForeignServer(table->serverid); + + foreach(lc, server->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "updatable") == 0) + updatable = defGetBoolean(def); + } + foreach(lc, table->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "updatable") == 0) + updatable = defGetBoolean(def); + } + + /* + * Currently "updatable" means support for INSERT, UPDATE and DELETE. + */ + return updatable ? + (1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE) : 0; +} + +/* + * postgresRecheckForeignScan + * Execute a local join execution plan for a foreign join + */ +static bool +postgresRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot) +{ + Index scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid; + PlanState *outerPlan = outerPlanState(node); + TupleTableSlot *result; + + /* For base foreign relations, it suffices to set fdw_recheck_quals */ + if (scanrelid > 0) + return true; + + Assert(outerPlan != NULL); + + /* Execute a local join execution plan */ + result = ExecProcNode(outerPlan); + if (TupIsNull(result)) + return false; + + /* Store result in the given slot */ + ExecCopySlot(slot, result); + + return true; +} + +/* + * find_modifytable_subplan + * Helper routine for postgresPlanDirectModify to find the + * ModifyTable subplan node that scans the specified RTI. + * + * Returns NULL if the subplan couldn't be identified. That's not a fatal + * error condition, we just abandon trying to do the update directly. + */ +static ForeignScan * +find_modifytable_subplan(PlannerInfo *root, + ModifyTable *plan, + Index rtindex, + int subplan_index) +{ + Plan *subplan = outerPlan(plan); + + /* + * The cases we support are (1) the desired ForeignScan is the immediate + * child of ModifyTable, or (2) it is the subplan_index'th child of an + * Append node that is the immediate child of ModifyTable. There is no + * point in looking further down, as that would mean that local joins are + * involved, so we can't do the update directly. + * + * There could be a Result atop the Append too, acting to compute the + * UPDATE targetlist values. We ignore that here; the tlist will be + * checked by our caller. + * + * In principle we could examine all the children of the Append, but it's + * currently unlikely that the core planner would generate such a plan + * with the children out-of-order. Moreover, such a search risks costing + * O(N^2) time when there are a lot of children. + */ + if (IsA(subplan, Append)) + { + Append *appendplan = (Append *) subplan; + + if (subplan_index < list_length(appendplan->appendplans)) + subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index); + } + else if (IsA(subplan, Result) && + outerPlan(subplan) != NULL && + IsA(outerPlan(subplan), Append)) + { + Append *appendplan = (Append *) outerPlan(subplan); + + if (subplan_index < list_length(appendplan->appendplans)) + subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index); + } + + /* Now, have we got a ForeignScan on the desired rel? */ + if (IsA(subplan, ForeignScan)) + { + ForeignScan *fscan = (ForeignScan *) subplan; + + if (bms_is_member(rtindex, fscan->fs_base_relids)) + return fscan; + } + + return NULL; +} + +/* + * postgresPlanDirectModify + * Consider a direct foreign table modification + * + * Decide whether it is safe to modify a foreign table directly, and if so, + * rewrite subplan accordingly. + */ +static bool +postgresPlanDirectModify(PlannerInfo *root, + ModifyTable *plan, + Index resultRelation, + int subplan_index) +{ + CmdType operation = plan->operation; + RelOptInfo *foreignrel; + RangeTblEntry *rte; + PgFdwRelationInfo *fpinfo; + Relation rel; + StringInfoData sql; + ForeignScan *fscan; + List *processed_tlist = NIL; + List *targetAttrs = NIL; + List *remote_exprs; + List *params_list = NIL; + List *returningList = NIL; + List *retrieved_attrs = NIL; + + /* + * Decide whether it is safe to modify a foreign table directly. + */ + + /* + * The table modification must be an UPDATE or DELETE. + */ + if (operation != CMD_UPDATE && operation != CMD_DELETE) + return false; + + /* + * Try to locate the ForeignScan subplan that's scanning resultRelation. + */ + fscan = find_modifytable_subplan(root, plan, resultRelation, subplan_index); + if (!fscan) + return false; + + /* + * It's unsafe to modify a foreign table directly if there are any quals + * that should be evaluated locally. + */ + if (fscan->scan.plan.qual != NIL) + return false; + + /* Safe to fetch data about the target foreign rel */ + if (fscan->scan.scanrelid == 0) + { + foreignrel = find_join_rel(root, fscan->fs_relids); + /* We should have a rel for this foreign join. */ + Assert(foreignrel); + } + else + foreignrel = root->simple_rel_array[resultRelation]; + rte = root->simple_rte_array[resultRelation]; + fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; + + /* + * It's unsafe to update a foreign table directly, if any expressions to + * assign to the target columns are unsafe to evaluate remotely. + */ + if (operation == CMD_UPDATE) + { + ListCell *lc, + *lc2; + + /* + * The expressions of concern are the first N columns of the processed + * targetlist, where N is the length of the rel's update_colnos. + */ + get_translated_update_targetlist(root, resultRelation, + &processed_tlist, &targetAttrs); + forboth(lc, processed_tlist, lc2, targetAttrs) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + AttrNumber attno = lfirst_int(lc2); + + /* update's new-value expressions shouldn't be resjunk */ + Assert(!tle->resjunk); + + if (attno <= InvalidAttrNumber) /* shouldn't happen */ + elog(ERROR, "system-column update is not supported"); + + if (!is_foreign_expr(root, foreignrel, (Expr *) tle->expr)) + return false; + } + } + + /* + * Ok, rewrite subplan so as to modify the foreign table directly. + */ + initStringInfo(&sql); + + /* + * Core code already has some lock on each rel being planned, so we can + * use NoLock here. + */ + rel = table_open(rte->relid, NoLock); + + /* + * Recall the qual clauses that must be evaluated remotely. (These are + * bare clauses not RestrictInfos, but deparse.c's appendConditions() + * doesn't care.) + */ + remote_exprs = fpinfo->final_remote_exprs; + + /* + * Extract the relevant RETURNING list if any. + */ + if (plan->returningLists) + { + returningList = (List *) list_nth(plan->returningLists, subplan_index); + + /* + * When performing an UPDATE/DELETE .. RETURNING on a join directly, + * we fetch from the foreign server any Vars specified in RETURNING + * that refer not only to the target relation but to non-target + * relations. So we'll deparse them into the RETURNING clause of the + * remote query; use a targetlist consisting of them instead, which + * will be adjusted to be new fdw_scan_tlist of the foreign-scan plan + * node below. + */ + if (fscan->scan.scanrelid == 0) + returningList = build_remote_returning(resultRelation, rel, + returningList); + } + + /* + * Construct the SQL command string. + */ + switch (operation) + { + case CMD_UPDATE: + deparseDirectUpdateSql(&sql, root, resultRelation, rel, + foreignrel, + processed_tlist, + targetAttrs, + remote_exprs, ¶ms_list, + returningList, &retrieved_attrs); + break; + case CMD_DELETE: + deparseDirectDeleteSql(&sql, root, resultRelation, rel, + foreignrel, + remote_exprs, ¶ms_list, + returningList, &retrieved_attrs); + break; + default: + elog(ERROR, "unexpected operation: %d", (int) operation); + break; + } + + /* + * Update the operation and target relation info. + */ + fscan->operation = operation; + fscan->resultRelation = resultRelation; + + /* + * Update the fdw_exprs list that will be available to the executor. + */ + fscan->fdw_exprs = params_list; + + /* + * Update the fdw_private list that will be available to the executor. + * Items in the list must match enum FdwDirectModifyPrivateIndex, above. + */ + fscan->fdw_private = list_make4(makeString(sql.data), + makeBoolean((retrieved_attrs != NIL)), + retrieved_attrs, + makeBoolean(plan->canSetTag)); + + /* + * Update the foreign-join-related fields. + */ + if (fscan->scan.scanrelid == 0) + { + /* No need for the outer subplan. */ + fscan->scan.plan.lefttree = NULL; + + /* Build new fdw_scan_tlist if UPDATE/DELETE .. RETURNING. */ + if (returningList) + rebuild_fdw_scan_tlist(fscan, returningList); + } + + /* + * Finally, unset the async-capable flag if it is set, as we currently + * don't support asynchronous execution of direct modifications. + */ + if (fscan->scan.plan.async_capable) + fscan->scan.plan.async_capable = false; + + table_close(rel, NoLock); + return true; +} + +/* + * postgresBeginDirectModify + * Prepare a direct foreign table modification + */ +static void +postgresBeginDirectModify(ForeignScanState *node, int eflags) +{ + ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; + EState *estate = node->ss.ps.state; + PgFdwDirectModifyState *dmstate; + Index rtindex; + Oid userid; + ForeignTable *table; + UserMapping *user; + int numParams; + + /* + * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. + */ + if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + return; + + /* + * We'll save private state in node->fdw_state. + */ + dmstate = (PgFdwDirectModifyState *) palloc0(sizeof(PgFdwDirectModifyState)); + node->fdw_state = (void *) dmstate; + + /* + * Identify which user to do the remote access as. This should match what + * ExecCheckPermissions() does. + */ + userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId(); + + /* Get info about foreign table. */ + rtindex = node->resultRelInfo->ri_RangeTableIndex; + if (fsplan->scan.scanrelid == 0) + dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags); + else + dmstate->rel = node->ss.ss_currentRelation; + table = GetForeignTable(RelationGetRelid(dmstate->rel)); + user = GetUserMapping(userid, table->serverid); + + /* + * Get connection to the foreign server. Connection manager will + * establish new connection if necessary. + */ + dmstate->conn = GetConnection(user, false, &dmstate->conn_state); + + /* Update the foreign-join-related fields. */ + if (fsplan->scan.scanrelid == 0) + { + /* Save info about foreign table. */ + dmstate->resultRel = dmstate->rel; + + /* + * Set dmstate->rel to NULL to teach get_returning_data() and + * make_tuple_from_result_row() that columns fetched from the remote + * server are described by fdw_scan_tlist of the foreign-scan plan + * node, not the tuple descriptor for the target relation. + */ + dmstate->rel = NULL; + } + + /* Initialize state variable */ + dmstate->num_tuples = -1; /* -1 means not set yet */ + + /* Get private info created by planner functions. */ + dmstate->query = strVal(list_nth(fsplan->fdw_private, + FdwDirectModifyPrivateUpdateSql)); + dmstate->has_returning = boolVal(list_nth(fsplan->fdw_private, + FdwDirectModifyPrivateHasReturning)); + dmstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private, + FdwDirectModifyPrivateRetrievedAttrs); + dmstate->set_processed = boolVal(list_nth(fsplan->fdw_private, + FdwDirectModifyPrivateSetProcessed)); + + /* Create context for per-tuple temp workspace. */ + dmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, + "postgres_fdw temporary data", + ALLOCSET_SMALL_SIZES); + + /* Prepare for input conversion of RETURNING results. */ + if (dmstate->has_returning) + { + TupleDesc tupdesc; + + if (fsplan->scan.scanrelid == 0) + tupdesc = get_tupdesc_for_join_scan_tuples(node); + else + tupdesc = RelationGetDescr(dmstate->rel); + + dmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc); + + /* + * When performing an UPDATE/DELETE .. RETURNING on a join directly, + * initialize a filter to extract an updated/deleted tuple from a scan + * tuple. + */ + if (fsplan->scan.scanrelid == 0) + init_returning_filter(dmstate, fsplan->fdw_scan_tlist, rtindex); + } + + /* + * Prepare for processing of parameters used in remote query, if any. + */ + numParams = list_length(fsplan->fdw_exprs); + dmstate->numParams = numParams; + if (numParams > 0) + prepare_query_params((PlanState *) node, + fsplan->fdw_exprs, + numParams, + &dmstate->param_flinfo, + &dmstate->param_exprs, + &dmstate->param_values); +} + +/* + * postgresIterateDirectModify + * Execute a direct foreign table modification + */ +static TupleTableSlot * +postgresIterateDirectModify(ForeignScanState *node) +{ + PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state; + EState *estate = node->ss.ps.state; + ResultRelInfo *resultRelInfo = node->resultRelInfo; + + /* + * If this is the first call after Begin, execute the statement. + */ + if (dmstate->num_tuples == -1) + execute_dml_stmt(node); + + /* + * If the local query doesn't specify RETURNING, just clear tuple slot. + */ + if (!resultRelInfo->ri_projectReturning) + { + TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; + Instrumentation *instr = node->ss.ps.instrument; + + Assert(!dmstate->has_returning); + + /* Increment the command es_processed count if necessary. */ + if (dmstate->set_processed) + estate->es_processed += dmstate->num_tuples; + + /* Increment the tuple count for EXPLAIN ANALYZE if necessary. */ + if (instr) + instr->tuplecount += dmstate->num_tuples; + + return ExecClearTuple(slot); + } + + /* + * Get the next RETURNING tuple. + */ + return get_returning_data(node); +} + +/* + * postgresEndDirectModify + * Finish a direct foreign table modification + */ +static void +postgresEndDirectModify(ForeignScanState *node) +{ + PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state; + + /* if dmstate is NULL, we are in EXPLAIN; nothing to do */ + if (dmstate == NULL) + return; + + /* Release PGresult */ + PQclear(dmstate->result); + + /* Release remote connection */ + ReleaseConnection(dmstate->conn); + dmstate->conn = NULL; + + /* MemoryContext will be deleted automatically. */ +} + +/* + * postgresExplainForeignScan + * Produce extra output for EXPLAIN of a ForeignScan on a foreign table + */ +static void +postgresExplainForeignScan(ForeignScanState *node, ExplainState *es) +{ + ForeignScan *plan = castNode(ForeignScan, node->ss.ps.plan); + List *fdw_private = plan->fdw_private; + + /* + * Identify foreign scans that are really joins or upper relations. The + * input looks something like "(1) LEFT JOIN (2)", and we must replace the + * digit string(s), which are RT indexes, with the correct relation names. + * We do that here, not when the plan is created, because we can't know + * what aliases ruleutils.c will assign at plan creation time. + */ + if (list_length(fdw_private) > FdwScanPrivateRelations) + { + StringInfo relations; + char *rawrelations; + char *ptr; + int minrti, + rtoffset; + + rawrelations = strVal(list_nth(fdw_private, FdwScanPrivateRelations)); + + /* + * A difficulty with using a string representation of RT indexes is + * that setrefs.c won't update the string when flattening the + * rangetable. To find out what rtoffset was applied, identify the + * minimum RT index appearing in the string and compare it to the + * minimum member of plan->fs_base_relids. (We expect all the relids + * in the join will have been offset by the same amount; the Asserts + * below should catch it if that ever changes.) + */ + minrti = INT_MAX; + ptr = rawrelations; + while (*ptr) + { + if (isdigit((unsigned char) *ptr)) + { + int rti = strtol(ptr, &ptr, 10); + + if (rti < minrti) + minrti = rti; + } + else + ptr++; + } + rtoffset = bms_next_member(plan->fs_base_relids, -1) - minrti; + + /* Now we can translate the string */ + relations = makeStringInfo(); + ptr = rawrelations; + while (*ptr) + { + if (isdigit((unsigned char) *ptr)) + { + int rti = strtol(ptr, &ptr, 10); + RangeTblEntry *rte; + char *relname; + char *refname; + + rti += rtoffset; + Assert(bms_is_member(rti, plan->fs_base_relids)); + rte = rt_fetch(rti, es->rtable); + Assert(rte->rtekind == RTE_RELATION); + /* This logic should agree with explain.c's ExplainTargetRel */ + relname = get_rel_name(rte->relid); + if (es->verbose) + { + char *namespace; + + namespace = get_namespace_name_or_temp(get_rel_namespace(rte->relid)); + appendStringInfo(relations, "%s.%s", + quote_identifier(namespace), + quote_identifier(relname)); + } + else + appendStringInfoString(relations, + quote_identifier(relname)); + refname = (char *) list_nth(es->rtable_names, rti - 1); + if (refname == NULL) + refname = rte->eref->aliasname; + if (strcmp(refname, relname) != 0) + appendStringInfo(relations, " %s", + quote_identifier(refname)); + } + else + appendStringInfoChar(relations, *ptr++); + } + ExplainPropertyText("Relations", relations->data, es); + } + + /* + * Add remote query, when VERBOSE option is specified. + */ + if (es->verbose) + { + char *sql; + + sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql)); + ExplainPropertyText("Remote SQL", sql, es); + } +} + +/* + * postgresExplainForeignModify + * Produce extra output for EXPLAIN of a ModifyTable on a foreign table + */ +static void +postgresExplainForeignModify(ModifyTableState *mtstate, + ResultRelInfo *rinfo, + List *fdw_private, + int subplan_index, + ExplainState *es) +{ + if (es->verbose) + { + char *sql = strVal(list_nth(fdw_private, + FdwModifyPrivateUpdateSql)); + + ExplainPropertyText("Remote SQL", sql, es); + + /* + * For INSERT we should always have batch size >= 1, but UPDATE and + * DELETE don't support batching so don't show the property. + */ + if (rinfo->ri_BatchSize > 0) + ExplainPropertyInteger("Batch Size", NULL, rinfo->ri_BatchSize, es); + } +} + +/* + * postgresExplainDirectModify + * Produce extra output for EXPLAIN of a ForeignScan that modifies a + * foreign table directly + */ +static void +postgresExplainDirectModify(ForeignScanState *node, ExplainState *es) +{ + List *fdw_private; + char *sql; + + if (es->verbose) + { + fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private; + sql = strVal(list_nth(fdw_private, FdwDirectModifyPrivateUpdateSql)); + ExplainPropertyText("Remote SQL", sql, es); + } +} + +/* + * postgresExecForeignTruncate + * Truncate one or more foreign tables + */ +static void +postgresExecForeignTruncate(List *rels, + DropBehavior behavior, + bool restart_seqs) +{ + Oid serverid = InvalidOid; + UserMapping *user = NULL; + PGconn *conn = NULL; + StringInfoData sql; + ListCell *lc; + bool server_truncatable = true; + + /* + * By default, all postgres_fdw foreign tables are assumed truncatable. + * This can be overridden by a per-server setting, which in turn can be + * overridden by a per-table setting. + */ + foreach(lc, rels) + { + ForeignServer *server = NULL; + Relation rel = lfirst(lc); + ForeignTable *table = GetForeignTable(RelationGetRelid(rel)); + ListCell *cell; + bool truncatable; + + /* + * First time through, determine whether the foreign server allows + * truncates. Since all specified foreign tables are assumed to belong + * to the same foreign server, this result can be used for other + * foreign tables. + */ + if (!OidIsValid(serverid)) + { + serverid = table->serverid; + server = GetForeignServer(serverid); + + foreach(cell, server->options) + { + DefElem *defel = (DefElem *) lfirst(cell); + + if (strcmp(defel->defname, "truncatable") == 0) + { + server_truncatable = defGetBoolean(defel); + break; + } + } + } + + /* + * Confirm that all specified foreign tables belong to the same + * foreign server. + */ + Assert(table->serverid == serverid); + + /* Determine whether this foreign table allows truncations */ + truncatable = server_truncatable; + foreach(cell, table->options) + { + DefElem *defel = (DefElem *) lfirst(cell); + + if (strcmp(defel->defname, "truncatable") == 0) + { + truncatable = defGetBoolean(defel); + break; + } + } + + if (!truncatable) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("foreign table \"%s\" does not allow truncates", + RelationGetRelationName(rel)))); + } + Assert(OidIsValid(serverid)); + + /* + * Get connection to the foreign server. Connection manager will + * establish new connection if necessary. + */ + user = GetUserMapping(GetUserId(), serverid); + conn = GetConnection(user, false, NULL); + + /* Construct the TRUNCATE command string */ + initStringInfo(&sql); + deparseTruncateSql(&sql, rels, behavior, restart_seqs); + + /* Issue the TRUNCATE command to remote server */ + do_sql_command(conn, sql.data); + + pfree(sql.data); +} + +/* + * estimate_path_cost_size + * Get cost and size estimates for a foreign scan on given foreign relation + * either a base relation or a join between foreign relations or an upper + * relation containing foreign relations. + * + * param_join_conds are the parameterization clauses with outer relations. + * pathkeys specify the expected sort order if any for given path being costed. + * fpextra specifies additional post-scan/join-processing steps such as the + * final sort and the LIMIT restriction. + * + * The function returns the cost and size estimates in p_rows, p_width, + * p_startup_cost and p_total_cost variables. + */ +static void +estimate_path_cost_size(PlannerInfo *root, + RelOptInfo *foreignrel, + List *param_join_conds, + List *pathkeys, + PgFdwPathExtraData *fpextra, + double *p_rows, int *p_width, + Cost *p_startup_cost, Cost *p_total_cost) +{ + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; + double rows; + double retrieved_rows; + int width; + Cost startup_cost; + Cost total_cost; + + /* Make sure the core code has set up the relation's reltarget */ + Assert(foreignrel->reltarget); + + /* + * If the table or the server is configured to use remote estimates, + * connect to the foreign server and execute EXPLAIN to estimate the + * number of rows selected by the restriction+join clauses. Otherwise, + * estimate rows using whatever statistics we have locally, in a way + * similar to ordinary tables. + */ + if (fpinfo->use_remote_estimate) + { + List *remote_param_join_conds; + List *local_param_join_conds; + StringInfoData sql; + PGconn *conn; + Selectivity local_sel; + QualCost local_cost; + List *fdw_scan_tlist = NIL; + List *remote_conds; + + /* Required only to be passed to deparseSelectStmtForRel */ + List *retrieved_attrs; + + /* + * param_join_conds might contain both clauses that are safe to send + * across, and clauses that aren't. + */ + classifyConditions(root, foreignrel, param_join_conds, + &remote_param_join_conds, &local_param_join_conds); + + /* Build the list of columns to be fetched from the foreign server. */ + if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) + fdw_scan_tlist = build_tlist_to_deparse(foreignrel); + else + fdw_scan_tlist = NIL; + + /* + * The complete list of remote conditions includes everything from + * baserestrictinfo plus any extra join_conds relevant to this + * particular path. + */ + remote_conds = list_concat(remote_param_join_conds, + fpinfo->remote_conds); + + /* + * Construct EXPLAIN query including the desired SELECT, FROM, and + * WHERE clauses. Params and other-relation Vars are replaced by dummy + * values, so don't request params_list. + */ + initStringInfo(&sql); + appendStringInfoString(&sql, "EXPLAIN "); + deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist, + remote_conds, pathkeys, + fpextra ? fpextra->has_final_sort : false, + fpextra ? fpextra->has_limit : false, + false, &retrieved_attrs, NULL); + + /* Get the remote estimate */ + conn = GetConnection(fpinfo->user, false, NULL); + get_remote_estimate(sql.data, conn, &rows, &width, + &startup_cost, &total_cost); + ReleaseConnection(conn); + + retrieved_rows = rows; + + /* Factor in the selectivity of the locally-checked quals */ + local_sel = clauselist_selectivity(root, + local_param_join_conds, + foreignrel->relid, + JOIN_INNER, + NULL); + local_sel *= fpinfo->local_conds_sel; + + rows = clamp_row_est(rows * local_sel); + + /* Add in the eval cost of the locally-checked quals */ + startup_cost += fpinfo->local_conds_cost.startup; + total_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; + cost_qual_eval(&local_cost, local_param_join_conds, root); + startup_cost += local_cost.startup; + total_cost += local_cost.per_tuple * retrieved_rows; + + /* + * Add in tlist eval cost for each output row. In case of an + * aggregate, some of the tlist expressions such as grouping + * expressions will be evaluated remotely, so adjust the costs. + */ + startup_cost += foreignrel->reltarget->cost.startup; + total_cost += foreignrel->reltarget->cost.startup; + total_cost += foreignrel->reltarget->cost.per_tuple * rows; + if (IS_UPPER_REL(foreignrel)) + { + QualCost tlist_cost; + + cost_qual_eval(&tlist_cost, fdw_scan_tlist, root); + startup_cost -= tlist_cost.startup; + total_cost -= tlist_cost.startup; + total_cost -= tlist_cost.per_tuple * rows; + } + } + else + { + Cost run_cost = 0; + + /* + * We don't support join conditions in this mode (hence, no + * parameterized paths can be made). + */ + Assert(param_join_conds == NIL); + + /* + * We will come here again and again with different set of pathkeys or + * additional post-scan/join-processing steps that caller wants to + * cost. We don't need to calculate the cost/size estimates for the + * underlying scan, join, or grouping each time. Instead, use those + * estimates if we have cached them already. + */ + if (fpinfo->rel_startup_cost >= 0 && fpinfo->rel_total_cost >= 0) + { + Assert(fpinfo->retrieved_rows >= 0); + + rows = fpinfo->rows; + retrieved_rows = fpinfo->retrieved_rows; + width = fpinfo->width; + startup_cost = fpinfo->rel_startup_cost; + run_cost = fpinfo->rel_total_cost - fpinfo->rel_startup_cost; + + /* + * If we estimate the costs of a foreign scan or a foreign join + * with additional post-scan/join-processing steps, the scan or + * join costs obtained from the cache wouldn't yet contain the + * eval costs for the final scan/join target, which would've been + * updated by apply_scanjoin_target_to_paths(); add the eval costs + * now. + */ + if (fpextra && !IS_UPPER_REL(foreignrel)) + { + /* Shouldn't get here unless we have LIMIT */ + Assert(fpextra->has_limit); + Assert(foreignrel->reloptkind == RELOPT_BASEREL || + foreignrel->reloptkind == RELOPT_JOINREL); + startup_cost += foreignrel->reltarget->cost.startup; + run_cost += foreignrel->reltarget->cost.per_tuple * rows; + } + } + else if (IS_JOIN_REL(foreignrel)) + { + PgFdwRelationInfo *fpinfo_i; + PgFdwRelationInfo *fpinfo_o; + QualCost join_cost; + QualCost remote_conds_cost; + double nrows; + + /* Use rows/width estimates made by the core code. */ + rows = foreignrel->rows; + width = foreignrel->reltarget->width; + + /* For join we expect inner and outer relations set */ + Assert(fpinfo->innerrel && fpinfo->outerrel); + + fpinfo_i = (PgFdwRelationInfo *) fpinfo->innerrel->fdw_private; + fpinfo_o = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private; + + /* Estimate of number of rows in cross product */ + nrows = fpinfo_i->rows * fpinfo_o->rows; + + /* + * Back into an estimate of the number of retrieved rows. Just in + * case this is nuts, clamp to at most nrows. + */ + retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); + retrieved_rows = Min(retrieved_rows, nrows); + + /* + * The cost of foreign join is estimated as cost of generating + * rows for the joining relations + cost for applying quals on the + * rows. + */ + + /* + * Calculate the cost of clauses pushed down to the foreign server + */ + cost_qual_eval(&remote_conds_cost, fpinfo->remote_conds, root); + /* Calculate the cost of applying join clauses */ + cost_qual_eval(&join_cost, fpinfo->joinclauses, root); + + /* + * Startup cost includes startup cost of joining relations and the + * startup cost for join and other clauses. We do not include the + * startup cost specific to join strategy (e.g. setting up hash + * tables) since we do not know what strategy the foreign server + * is going to use. + */ + startup_cost = fpinfo_i->rel_startup_cost + fpinfo_o->rel_startup_cost; + startup_cost += join_cost.startup; + startup_cost += remote_conds_cost.startup; + startup_cost += fpinfo->local_conds_cost.startup; + + /* + * Run time cost includes: + * + * 1. Run time cost (total_cost - startup_cost) of relations being + * joined + * + * 2. Run time cost of applying join clauses on the cross product + * of the joining relations. + * + * 3. Run time cost of applying pushed down other clauses on the + * result of join + * + * 4. Run time cost of applying nonpushable other clauses locally + * on the result fetched from the foreign server. + */ + run_cost = fpinfo_i->rel_total_cost - fpinfo_i->rel_startup_cost; + run_cost += fpinfo_o->rel_total_cost - fpinfo_o->rel_startup_cost; + run_cost += nrows * join_cost.per_tuple; + nrows = clamp_row_est(nrows * fpinfo->joinclause_sel); + run_cost += nrows * remote_conds_cost.per_tuple; + run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; + + /* Add in tlist eval cost for each output row */ + startup_cost += foreignrel->reltarget->cost.startup; + run_cost += foreignrel->reltarget->cost.per_tuple * rows; + } + else if (IS_UPPER_REL(foreignrel)) + { + RelOptInfo *outerrel = fpinfo->outerrel; + PgFdwRelationInfo *ofpinfo; + AggClauseCosts aggcosts; + double input_rows; + int numGroupCols; + double numGroups = 1; + + /* The upper relation should have its outer relation set */ + Assert(outerrel); + /* and that outer relation should have its reltarget set */ + Assert(outerrel->reltarget); + + /* + * This cost model is mixture of costing done for sorted and + * hashed aggregates in cost_agg(). We are not sure which + * strategy will be considered at remote side, thus for + * simplicity, we put all startup related costs in startup_cost + * and all finalization and run cost are added in total_cost. + */ + + ofpinfo = (PgFdwRelationInfo *) outerrel->fdw_private; + + /* Get rows from input rel */ + input_rows = ofpinfo->rows; + + /* Collect statistics about aggregates for estimating costs. */ + MemSet(&aggcosts, 0, sizeof(AggClauseCosts)); + if (root->parse->hasAggs) + { + get_agg_clause_costs(root, AGGSPLIT_SIMPLE, &aggcosts); + } + + /* Get number of grouping columns and possible number of groups */ + numGroupCols = list_length(root->processed_groupClause); + numGroups = estimate_num_groups(root, + get_sortgrouplist_exprs(root->processed_groupClause, + fpinfo->grouped_tlist), + input_rows, NULL, NULL); + + /* + * Get the retrieved_rows and rows estimates. If there are HAVING + * quals, account for their selectivity. + */ + if (root->hasHavingQual) + { + /* Factor in the selectivity of the remotely-checked quals */ + retrieved_rows = + clamp_row_est(numGroups * + clauselist_selectivity(root, + fpinfo->remote_conds, + 0, + JOIN_INNER, + NULL)); + /* Factor in the selectivity of the locally-checked quals */ + rows = clamp_row_est(retrieved_rows * fpinfo->local_conds_sel); + } + else + { + rows = retrieved_rows = numGroups; + } + + /* Use width estimate made by the core code. */ + width = foreignrel->reltarget->width; + + /*----- + * Startup cost includes: + * 1. Startup cost for underneath input relation, adjusted for + * tlist replacement by apply_scanjoin_target_to_paths() + * 2. Cost of performing aggregation, per cost_agg() + *----- + */ + startup_cost = ofpinfo->rel_startup_cost; + startup_cost += outerrel->reltarget->cost.startup; + startup_cost += aggcosts.transCost.startup; + startup_cost += aggcosts.transCost.per_tuple * input_rows; + startup_cost += aggcosts.finalCost.startup; + startup_cost += (cpu_operator_cost * numGroupCols) * input_rows; + + /*----- + * Run time cost includes: + * 1. Run time cost of underneath input relation, adjusted for + * tlist replacement by apply_scanjoin_target_to_paths() + * 2. Run time cost of performing aggregation, per cost_agg() + *----- + */ + run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost; + run_cost += outerrel->reltarget->cost.per_tuple * input_rows; + run_cost += aggcosts.finalCost.per_tuple * numGroups; + run_cost += cpu_tuple_cost * numGroups; + + /* Account for the eval cost of HAVING quals, if any */ + if (root->hasHavingQual) + { + QualCost remote_cost; + + /* Add in the eval cost of the remotely-checked quals */ + cost_qual_eval(&remote_cost, fpinfo->remote_conds, root); + startup_cost += remote_cost.startup; + run_cost += remote_cost.per_tuple * numGroups; + /* Add in the eval cost of the locally-checked quals */ + startup_cost += fpinfo->local_conds_cost.startup; + run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; + } + + /* Add in tlist eval cost for each output row */ + startup_cost += foreignrel->reltarget->cost.startup; + run_cost += foreignrel->reltarget->cost.per_tuple * rows; + } + else + { + Cost cpu_per_tuple; + + /* Use rows/width estimates made by set_baserel_size_estimates. */ + rows = foreignrel->rows; + width = foreignrel->reltarget->width; + + /* + * Back into an estimate of the number of retrieved rows. Just in + * case this is nuts, clamp to at most foreignrel->tuples. + */ + retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); + retrieved_rows = Min(retrieved_rows, foreignrel->tuples); + + /* + * Cost as though this were a seqscan, which is pessimistic. We + * effectively imagine the local_conds are being evaluated + * remotely, too. + */ + startup_cost = 0; + run_cost = 0; + run_cost += seq_page_cost * foreignrel->pages; + + startup_cost += foreignrel->baserestrictcost.startup; + cpu_per_tuple = cpu_tuple_cost + foreignrel->baserestrictcost.per_tuple; + run_cost += cpu_per_tuple * foreignrel->tuples; + + /* Add in tlist eval cost for each output row */ + startup_cost += foreignrel->reltarget->cost.startup; + run_cost += foreignrel->reltarget->cost.per_tuple * rows; + } + + /* + * Without remote estimates, we have no real way to estimate the cost + * of generating sorted output. It could be free if the query plan + * the remote side would have chosen generates properly-sorted output + * anyway, but in most cases it will cost something. Estimate a value + * high enough that we won't pick the sorted path when the ordering + * isn't locally useful, but low enough that we'll err on the side of + * pushing down the ORDER BY clause when it's useful to do so. + */ + if (pathkeys != NIL) + { + if (IS_UPPER_REL(foreignrel)) + { + Assert(foreignrel->reloptkind == RELOPT_UPPER_REL && + fpinfo->stage == UPPERREL_GROUP_AGG); + adjust_foreign_grouping_path_cost(root, pathkeys, + retrieved_rows, width, + fpextra->limit_tuples, + &startup_cost, &run_cost); + } + else + { + startup_cost *= DEFAULT_FDW_SORT_MULTIPLIER; + run_cost *= DEFAULT_FDW_SORT_MULTIPLIER; + } + } + + total_cost = startup_cost + run_cost; + + /* Adjust the cost estimates if we have LIMIT */ + if (fpextra && fpextra->has_limit) + { + adjust_limit_rows_costs(&rows, &startup_cost, &total_cost, + fpextra->offset_est, fpextra->count_est); + retrieved_rows = rows; + } + } + + /* + * If this includes the final sort step, the given target, which will be + * applied to the resulting path, might have different expressions from + * the foreignrel's reltarget (see make_sort_input_target()); adjust tlist + * eval costs. + */ + if (fpextra && fpextra->has_final_sort && + fpextra->target != foreignrel->reltarget) + { + QualCost oldcost = foreignrel->reltarget->cost; + QualCost newcost = fpextra->target->cost; + + startup_cost += newcost.startup - oldcost.startup; + total_cost += newcost.startup - oldcost.startup; + total_cost += (newcost.per_tuple - oldcost.per_tuple) * rows; + } + + /* + * Cache the retrieved rows and cost estimates for scans, joins, or + * groupings without any parameterization, pathkeys, or additional + * post-scan/join-processing steps, before adding the costs for + * transferring data from the foreign server. These estimates are useful + * for costing remote joins involving this relation or costing other + * remote operations on this relation such as remote sorts and remote + * LIMIT restrictions, when the costs can not be obtained from the foreign + * server. This function will be called at least once for every foreign + * relation without any parameterization, pathkeys, or additional + * post-scan/join-processing steps. + */ + if (pathkeys == NIL && param_join_conds == NIL && fpextra == NULL) + { + fpinfo->retrieved_rows = retrieved_rows; + fpinfo->rel_startup_cost = startup_cost; + fpinfo->rel_total_cost = total_cost; + } + + /* + * Add some additional cost factors to account for connection overhead + * (fdw_startup_cost), transferring data across the network + * (fdw_tuple_cost per retrieved row), and local manipulation of the data + * (cpu_tuple_cost per retrieved row). + */ + startup_cost += fpinfo->fdw_startup_cost; + total_cost += fpinfo->fdw_startup_cost; + total_cost += fpinfo->fdw_tuple_cost * retrieved_rows; + total_cost += cpu_tuple_cost * retrieved_rows; + + /* + * If we have LIMIT, we should prefer performing the restriction remotely + * rather than locally, as the former avoids extra row fetches from the + * remote that the latter might cause. But since the core code doesn't + * account for such fetches when estimating the costs of the local + * restriction (see create_limit_path()), there would be no difference + * between the costs of the local restriction and the costs of the remote + * restriction estimated above if we don't use remote estimates (except + * for the case where the foreignrel is a grouping relation, the given + * pathkeys is not NIL, and the effects of a bounded sort for that rel is + * accounted for in costing the remote restriction). Tweak the costs of + * the remote restriction to ensure we'll prefer it if LIMIT is a useful + * one. + */ + if (!fpinfo->use_remote_estimate && + fpextra && fpextra->has_limit && + fpextra->limit_tuples > 0 && + fpextra->limit_tuples < fpinfo->rows) + { + Assert(fpinfo->rows > 0); + total_cost -= (total_cost - startup_cost) * 0.05 * + (fpinfo->rows - fpextra->limit_tuples) / fpinfo->rows; + } + + /* Return results. */ + *p_rows = rows; + *p_width = width; + *p_startup_cost = startup_cost; + *p_total_cost = total_cost; +} + +/* + * Estimate costs of executing a SQL statement remotely. + * The given "sql" must be an EXPLAIN command. + */ +static void +get_remote_estimate(const char *sql, PGconn *conn, + double *rows, int *width, + Cost *startup_cost, Cost *total_cost) +{ + PGresult *volatile res = NULL; + + /* PGresult must be released before leaving this function. */ + PG_TRY(); + { + char *line; + char *p; + int n; + + /* + * Execute EXPLAIN remotely. + */ + res = pgfdw_exec_query(conn, sql, NULL); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pgfdw_report_error(ERROR, res, conn, false, sql); + + /* + * Extract cost numbers for topmost plan node. Note we search for a + * left paren from the end of the line to avoid being confused by + * other uses of parentheses. + */ + line = PQgetvalue(res, 0, 0); + p = strrchr(line, '('); + if (p == NULL) + elog(ERROR, "could not interpret EXPLAIN output: \"%s\"", line); + n = sscanf(p, "(cost=%lf..%lf rows=%lf width=%d)", + startup_cost, total_cost, rows, width); + if (n != 4) + elog(ERROR, "could not interpret EXPLAIN output: \"%s\"", line); + } + PG_FINALLY(); + { + PQclear(res); + } + PG_END_TRY(); +} + +/* + * Adjust the cost estimates of a foreign grouping path to include the cost of + * generating properly-sorted output. + */ +static void +adjust_foreign_grouping_path_cost(PlannerInfo *root, + List *pathkeys, + double retrieved_rows, + double width, + double limit_tuples, + Cost *p_startup_cost, + Cost *p_run_cost) +{ + /* + * If the GROUP BY clause isn't sort-able, the plan chosen by the remote + * side is unlikely to generate properly-sorted output, so it would need + * an explicit sort; adjust the given costs with cost_sort(). Likewise, + * if the GROUP BY clause is sort-able but isn't a superset of the given + * pathkeys, adjust the costs with that function. Otherwise, adjust the + * costs by applying the same heuristic as for the scan or join case. + */ + if (!grouping_is_sortable(root->processed_groupClause) || + !pathkeys_contained_in(pathkeys, root->group_pathkeys)) + { + Path sort_path; /* dummy for result of cost_sort */ + + cost_sort(&sort_path, + root, + pathkeys, + *p_startup_cost + *p_run_cost, + retrieved_rows, + width, + 0.0, + work_mem, + limit_tuples); + + *p_startup_cost = sort_path.startup_cost; + *p_run_cost = sort_path.total_cost - sort_path.startup_cost; + } + else + { + /* + * The default extra cost seems too large for foreign-grouping cases; + * add 1/4th of that default. + */ + double sort_multiplier = 1.0 + (DEFAULT_FDW_SORT_MULTIPLIER + - 1.0) * 0.25; + + *p_startup_cost *= sort_multiplier; + *p_run_cost *= sort_multiplier; + } +} + +/* + * Detect whether we want to process an EquivalenceClass member. + * + * This is a callback for use by generate_implied_equalities_for_column. + */ +static bool +ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel, + EquivalenceClass *ec, EquivalenceMember *em, + void *arg) +{ + ec_member_foreign_arg *state = (ec_member_foreign_arg *) arg; + Expr *expr = em->em_expr; + + /* + * If we've identified what we're processing in the current scan, we only + * want to match that expression. + */ + if (state->current != NULL) + return equal(expr, state->current); + + /* + * Otherwise, ignore anything we've already processed. + */ + if (list_member(state->already_used, expr)) + return false; + + /* This is the new target to process. */ + state->current = expr; + return true; +} + +/* + * Create cursor for node's query with current parameter values. + */ +static void +create_cursor(ForeignScanState *node) +{ + PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state; + ExprContext *econtext = node->ss.ps.ps_ExprContext; + int numParams = fsstate->numParams; + const char **values = fsstate->param_values; + PGconn *conn = fsstate->conn; + StringInfoData buf; + PGresult *res; + + /* First, process a pending asynchronous request, if any. */ + if (fsstate->conn_state->pendingAreq) + process_pending_request(fsstate->conn_state->pendingAreq); + + /* + * Construct array of query parameter values in text format. We do the + * conversions in the short-lived per-tuple context, so as not to cause a + * memory leak over repeated scans. + */ + if (numParams > 0) + { + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + + process_query_params(econtext, + fsstate->param_flinfo, + fsstate->param_exprs, + values); + + MemoryContextSwitchTo(oldcontext); + } + + /* Construct the DECLARE CURSOR command */ + initStringInfo(&buf); + appendStringInfo(&buf, "DECLARE c%u CURSOR FOR\n%s", + fsstate->cursor_number, fsstate->query); + + /* + * Notice that we pass NULL for paramTypes, thus forcing the remote server + * to infer types for all parameters. Since we explicitly cast every + * parameter (see deparse.c), the "inference" is trivial and will produce + * the desired result. This allows us to avoid assuming that the remote + * server has the same OIDs we do for the parameters' types. + */ + if (!PQsendQueryParams(conn, buf.data, numParams, + NULL, values, NULL, NULL, 0)) + pgfdw_report_error(ERROR, NULL, conn, false, buf.data); + + /* + * Get the result, and check for success. + * + * We don't use a PG_TRY block here, so be careful not to throw error + * without releasing the PGresult. + */ + res = pgfdw_get_result(conn, buf.data); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pgfdw_report_error(ERROR, res, conn, true, fsstate->query); + PQclear(res); + + /* Mark the cursor as created, and show no tuples have been retrieved */ + fsstate->cursor_exists = true; + fsstate->tuples = NULL; + fsstate->num_tuples = 0; + fsstate->next_tuple = 0; + fsstate->fetch_ct_2 = 0; + fsstate->eof_reached = false; + + /* Clean up */ + pfree(buf.data); +} + +/* + * Fetch some more rows from the node's cursor. + */ +static void +fetch_more_data(ForeignScanState *node) +{ + PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state; + PGresult *volatile res = NULL; + MemoryContext oldcontext; + + /* + * We'll store the tuples in the batch_cxt. First, flush the previous + * batch. + */ + fsstate->tuples = NULL; + MemoryContextReset(fsstate->batch_cxt); + oldcontext = MemoryContextSwitchTo(fsstate->batch_cxt); + + /* PGresult must be released before leaving this function. */ + PG_TRY(); + { + PGconn *conn = fsstate->conn; + int numrows; + int i; + + if (fsstate->async_capable) + { + Assert(fsstate->conn_state->pendingAreq); + + /* + * The query was already sent by an earlier call to + * fetch_more_data_begin. So now we just fetch the result. + */ + res = pgfdw_get_result(conn, fsstate->query); + /* On error, report the original query, not the FETCH. */ + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pgfdw_report_error(ERROR, res, conn, false, fsstate->query); + + /* Reset per-connection state */ + fsstate->conn_state->pendingAreq = NULL; + } + else + { + char sql[64]; + + /* This is a regular synchronous fetch. */ + snprintf(sql, sizeof(sql), "FETCH %d FROM c%u", + fsstate->fetch_size, fsstate->cursor_number); + + res = pgfdw_exec_query(conn, sql, fsstate->conn_state); + /* On error, report the original query, not the FETCH. */ + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pgfdw_report_error(ERROR, res, conn, false, fsstate->query); + } + + /* Convert the data into HeapTuples */ + numrows = PQntuples(res); + fsstate->tuples = (HeapTuple *) palloc0(numrows * sizeof(HeapTuple)); + fsstate->num_tuples = numrows; + fsstate->next_tuple = 0; + + for (i = 0; i < numrows; i++) + { + Assert(IsA(node->ss.ps.plan, ForeignScan)); + + fsstate->tuples[i] = + make_tuple_from_result_row(res, i, + fsstate->rel, + fsstate->attinmeta, + fsstate->retrieved_attrs, + node, + fsstate->temp_cxt); + } + + /* Update fetch_ct_2 */ + if (fsstate->fetch_ct_2 < 2) + fsstate->fetch_ct_2++; + + /* Must be EOF if we didn't get as many tuples as we asked for. */ + fsstate->eof_reached = (numrows < fsstate->fetch_size); + } + PG_FINALLY(); + { + PQclear(res); + } + PG_END_TRY(); + + MemoryContextSwitchTo(oldcontext); +} + +/* + * Force assorted GUC parameters to settings that ensure that we'll output + * data values in a form that is unambiguous to the remote server. + * + * This is rather expensive and annoying to do once per row, but there's + * little choice if we want to be sure values are transmitted accurately; + * we can't leave the settings in place between rows for fear of affecting + * user-visible computations. + * + * We use the equivalent of a function SET option to allow the settings to + * persist only until the caller calls reset_transmission_modes(). If an + * error is thrown in between, guc.c will take care of undoing the settings. + * + * The return value is the nestlevel that must be passed to + * reset_transmission_modes() to undo things. + */ +int +set_transmission_modes(void) +{ + int nestlevel = NewGUCNestLevel(); + + /* + * The values set here should match what pg_dump does. See also + * configure_remote_session in connection.c. + */ + if (DateStyle != USE_ISO_DATES) + (void) set_config_option("datestyle", "ISO", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + if (IntervalStyle != INTSTYLE_POSTGRES) + (void) set_config_option("intervalstyle", "postgres", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + if (extra_float_digits < 3) + (void) set_config_option("extra_float_digits", "3", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + + /* + * In addition force restrictive search_path, in case there are any + * regproc or similar constants to be printed. + */ + (void) set_config_option("search_path", "pg_catalog", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + + return nestlevel; +} + +/* + * Undo the effects of set_transmission_modes(). + */ +void +reset_transmission_modes(int nestlevel) +{ + AtEOXact_GUC(true, nestlevel); +} + +/* + * Utility routine to close a cursor. + */ +static void +close_cursor(PGconn *conn, unsigned int cursor_number, + PgFdwConnState *conn_state) +{ + char sql[64]; + PGresult *res; + + snprintf(sql, sizeof(sql), "CLOSE c%u", cursor_number); + + /* + * We don't use a PG_TRY block here, so be careful not to throw error + * without releasing the PGresult. + */ + res = pgfdw_exec_query(conn, sql, conn_state); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pgfdw_report_error(ERROR, res, conn, true, sql); + PQclear(res); +} + +/* + * create_foreign_modify + * Construct an execution state of a foreign insert/update/delete + * operation + */ +static PgFdwModifyState * +create_foreign_modify(EState *estate, + RangeTblEntry *rte, + ResultRelInfo *resultRelInfo, + CmdType operation, + Plan *subplan, + char *query, + List *target_attrs, + int values_end, + bool has_returning, + List *retrieved_attrs) +{ + PgFdwModifyState *fmstate; + Relation rel = resultRelInfo->ri_RelationDesc; + TupleDesc tupdesc = RelationGetDescr(rel); + Oid userid; + ForeignTable *table; + UserMapping *user; + AttrNumber n_params; + Oid typefnoid; + bool isvarlena; + ListCell *lc; + + /* Begin constructing PgFdwModifyState. */ + fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState)); + fmstate->rel = rel; + + /* Identify which user to do the remote access as. */ + userid = ExecGetResultRelCheckAsUser(resultRelInfo, estate); + + /* Get info about foreign table. */ + table = GetForeignTable(RelationGetRelid(rel)); + user = GetUserMapping(userid, table->serverid); + + /* Open connection; report that we'll create a prepared statement. */ + fmstate->conn = GetConnection(user, true, &fmstate->conn_state); + fmstate->p_name = NULL; /* prepared statement not made yet */ + + /* Set up remote query information. */ + fmstate->query = query; + if (operation == CMD_INSERT) + { + fmstate->query = pstrdup(fmstate->query); + fmstate->orig_query = pstrdup(fmstate->query); + } + fmstate->target_attrs = target_attrs; + fmstate->values_end = values_end; + fmstate->has_returning = has_returning; + fmstate->retrieved_attrs = retrieved_attrs; + + /* Create context for per-tuple temp workspace. */ + fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, + "postgres_fdw temporary data", + ALLOCSET_SMALL_SIZES); + + /* Prepare for input conversion of RETURNING results. */ + if (fmstate->has_returning) + fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc); + + /* Prepare for output conversion of parameters used in prepared stmt. */ + n_params = list_length(fmstate->target_attrs) + 1; + fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params); + fmstate->p_nums = 0; + + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + Assert(subplan != NULL); + + /* Find the ctid resjunk column in the subplan's result */ + fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist, + "ctid"); + if (!AttributeNumberIsValid(fmstate->ctidAttno)) + elog(ERROR, "could not find junk ctid column"); + + /* First transmittable parameter will be ctid */ + getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena); + fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); + fmstate->p_nums++; + } + + if (operation == CMD_INSERT || operation == CMD_UPDATE) + { + /* Set up for remaining transmittable parameters */ + foreach(lc, fmstate->target_attrs) + { + int attnum = lfirst_int(lc); + Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); + + Assert(!attr->attisdropped); + + /* Ignore generated columns; they are set to DEFAULT */ + if (attr->attgenerated) + continue; + getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena); + fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); + fmstate->p_nums++; + } + } + + Assert(fmstate->p_nums <= n_params); + + /* Set batch_size from foreign server/table options. */ + if (operation == CMD_INSERT) + fmstate->batch_size = get_batch_size_option(rel); + + fmstate->num_slots = 1; + + /* Initialize auxiliary state */ + fmstate->aux_fmstate = NULL; + + return fmstate; +} + +/* + * execute_foreign_modify + * Perform foreign-table modification as required, and fetch RETURNING + * result if any. (This is the shared guts of postgresExecForeignInsert, + * postgresExecForeignBatchInsert, postgresExecForeignUpdate, and + * postgresExecForeignDelete.) + */ +static TupleTableSlot ** +execute_foreign_modify(EState *estate, + ResultRelInfo *resultRelInfo, + CmdType operation, + TupleTableSlot **slots, + TupleTableSlot **planSlots, + int *numSlots) +{ + PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState; + ItemPointer ctid = NULL; + const char **p_values; + PGresult *res; + int n_rows; + StringInfoData sql; + + /* The operation should be INSERT, UPDATE, or DELETE */ + Assert(operation == CMD_INSERT || + operation == CMD_UPDATE || + operation == CMD_DELETE); + + /* First, process a pending asynchronous request, if any. */ + if (fmstate->conn_state->pendingAreq) + process_pending_request(fmstate->conn_state->pendingAreq); + + /* + * If the existing query was deparsed and prepared for a different number + * of rows, rebuild it for the proper number. + */ + if (operation == CMD_INSERT && fmstate->num_slots != *numSlots) + { + /* Destroy the prepared statement created previously */ + if (fmstate->p_name) + deallocate_query(fmstate); + + /* Build INSERT string with numSlots records in its VALUES clause. */ + initStringInfo(&sql); + rebuildInsertSql(&sql, fmstate->rel, + fmstate->orig_query, fmstate->target_attrs, + fmstate->values_end, fmstate->p_nums, + *numSlots - 1); + pfree(fmstate->query); + fmstate->query = sql.data; + fmstate->num_slots = *numSlots; + } + + /* Set up the prepared statement on the remote server, if we didn't yet */ + if (!fmstate->p_name) + prepare_foreign_modify(fmstate); + + /* + * For UPDATE/DELETE, get the ctid that was passed up as a resjunk column + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + Datum datum; + bool isNull; + + datum = ExecGetJunkAttribute(planSlots[0], + fmstate->ctidAttno, + &isNull); + /* shouldn't ever get a null result... */ + if (isNull) + elog(ERROR, "ctid is NULL"); + ctid = (ItemPointer) DatumGetPointer(datum); + } + + /* Convert parameters needed by prepared statement to text form */ + p_values = convert_prep_stmt_params(fmstate, ctid, slots, *numSlots); + + /* + * Execute the prepared statement. + */ + if (!PQsendQueryPrepared(fmstate->conn, + fmstate->p_name, + fmstate->p_nums * (*numSlots), + p_values, + NULL, + NULL, + 0)) + pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query); + + /* + * Get the result, and check for success. + * + * We don't use a PG_TRY block here, so be careful not to throw error + * without releasing the PGresult. + */ + res = pgfdw_get_result(fmstate->conn, fmstate->query); + if (PQresultStatus(res) != + (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK)) + pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query); + + /* Check number of rows affected, and fetch RETURNING tuple if any */ + if (fmstate->has_returning) + { + Assert(*numSlots == 1); + n_rows = PQntuples(res); + if (n_rows > 0) + store_returning_result(fmstate, slots[0], res); + } + else + n_rows = atoi(PQcmdTuples(res)); + + /* And clean up */ + PQclear(res); + + MemoryContextReset(fmstate->temp_cxt); + + *numSlots = n_rows; + + /* + * Return NULL if nothing was inserted/updated/deleted on the remote end + */ + return (n_rows > 0) ? slots : NULL; +} + +/* + * prepare_foreign_modify + * Establish a prepared statement for execution of INSERT/UPDATE/DELETE + */ +static void +prepare_foreign_modify(PgFdwModifyState *fmstate) +{ + char prep_name[NAMEDATALEN]; + char *p_name; + PGresult *res; + + /* + * The caller would already have processed a pending asynchronous request + * if any, so no need to do it here. + */ + + /* Construct name we'll use for the prepared statement. */ + snprintf(prep_name, sizeof(prep_name), "pgsql_fdw_prep_%u", + GetPrepStmtNumber(fmstate->conn)); + p_name = pstrdup(prep_name); + + /* + * We intentionally do not specify parameter types here, but leave the + * remote server to derive them by default. This avoids possible problems + * with the remote server using different type OIDs than we do. All of + * the prepared statements we use in this module are simple enough that + * the remote server will make the right choices. + */ + if (!PQsendPrepare(fmstate->conn, + p_name, + fmstate->query, + 0, + NULL)) + pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query); + + /* + * Get the result, and check for success. + * + * We don't use a PG_TRY block here, so be careful not to throw error + * without releasing the PGresult. + */ + res = pgfdw_get_result(fmstate->conn, fmstate->query); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query); + PQclear(res); + + /* This action shows that the prepare has been done. */ + fmstate->p_name = p_name; +} + +/* + * convert_prep_stmt_params + * Create array of text strings representing parameter values + * + * tupleid is ctid to send, or NULL if none + * slot is slot to get remaining parameters from, or NULL if none + * + * Data is constructed in temp_cxt; caller should reset that after use. + */ +static const char ** +convert_prep_stmt_params(PgFdwModifyState *fmstate, + ItemPointer tupleid, + TupleTableSlot **slots, + int numSlots) +{ + const char **p_values; + int i; + int j; + int pindex = 0; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(fmstate->temp_cxt); + + p_values = (const char **) palloc(sizeof(char *) * fmstate->p_nums * numSlots); + + /* ctid is provided only for UPDATE/DELETE, which don't allow batching */ + Assert(!(tupleid != NULL && numSlots > 1)); + + /* 1st parameter should be ctid, if it's in use */ + if (tupleid != NULL) + { + Assert(numSlots == 1); + /* don't need set_transmission_modes for TID output */ + p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex], + PointerGetDatum(tupleid)); + pindex++; + } + + /* get following parameters from slots */ + if (slots != NULL && fmstate->target_attrs != NIL) + { + TupleDesc tupdesc = RelationGetDescr(fmstate->rel); + int nestlevel; + ListCell *lc; + + nestlevel = set_transmission_modes(); + + for (i = 0; i < numSlots; i++) + { + j = (tupleid != NULL) ? 1 : 0; + foreach(lc, fmstate->target_attrs) + { + int attnum = lfirst_int(lc); + Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); + Datum value; + bool isnull; + + /* Ignore generated columns; they are set to DEFAULT */ + if (attr->attgenerated) + continue; + value = slot_getattr(slots[i], attnum, &isnull); + if (isnull) + p_values[pindex] = NULL; + else + p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[j], + value); + pindex++; + j++; + } + } + + reset_transmission_modes(nestlevel); + } + + Assert(pindex == fmstate->p_nums * numSlots); + + MemoryContextSwitchTo(oldcontext); + + return p_values; +} + +/* + * store_returning_result + * Store the result of a RETURNING clause + * + * On error, be sure to release the PGresult on the way out. Callers do not + * have PG_TRY blocks to ensure this happens. + */ +static void +store_returning_result(PgFdwModifyState *fmstate, + TupleTableSlot *slot, PGresult *res) +{ + PG_TRY(); + { + HeapTuple newtup; + + newtup = make_tuple_from_result_row(res, 0, + fmstate->rel, + fmstate->attinmeta, + fmstate->retrieved_attrs, + NULL, + fmstate->temp_cxt); + + /* + * The returning slot will not necessarily be suitable to store + * heaptuples directly, so allow for conversion. + */ + ExecForceStoreHeapTuple(newtup, slot, true); + } + PG_CATCH(); + { + PQclear(res); + PG_RE_THROW(); + } + PG_END_TRY(); +} + +/* + * finish_foreign_modify + * Release resources for a foreign insert/update/delete operation + */ +static void +finish_foreign_modify(PgFdwModifyState *fmstate) +{ + Assert(fmstate != NULL); + + /* If we created a prepared statement, destroy it */ + deallocate_query(fmstate); + + /* Release remote connection */ + ReleaseConnection(fmstate->conn); + fmstate->conn = NULL; +} + +/* + * deallocate_query + * Deallocate a prepared statement for a foreign insert/update/delete + * operation + */ +static void +deallocate_query(PgFdwModifyState *fmstate) +{ + char sql[64]; + PGresult *res; + + /* do nothing if the query is not allocated */ + if (!fmstate->p_name) + return; + + snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name); + + /* + * We don't use a PG_TRY block here, so be careful not to throw error + * without releasing the PGresult. + */ + res = pgfdw_exec_query(fmstate->conn, sql, fmstate->conn_state); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pgfdw_report_error(ERROR, res, fmstate->conn, true, sql); + PQclear(res); + pfree(fmstate->p_name); + fmstate->p_name = NULL; +} + +/* + * build_remote_returning + * Build a RETURNING targetlist of a remote query for performing an + * UPDATE/DELETE .. RETURNING on a join directly + */ +static List * +build_remote_returning(Index rtindex, Relation rel, List *returningList) +{ + bool have_wholerow = false; + List *tlist = NIL; + List *vars; + ListCell *lc; + + Assert(returningList); + + vars = pull_var_clause((Node *) returningList, PVC_INCLUDE_PLACEHOLDERS); + + /* + * If there's a whole-row reference to the target relation, then we'll + * need all the columns of the relation. + */ + foreach(lc, vars) + { + Var *var = (Var *) lfirst(lc); + + if (IsA(var, Var) && + var->varno == rtindex && + var->varattno == InvalidAttrNumber) + { + have_wholerow = true; + break; + } + } + + if (have_wholerow) + { + TupleDesc tupdesc = RelationGetDescr(rel); + int i; + + for (i = 1; i <= tupdesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1); + Var *var; + + /* Ignore dropped attributes. */ + if (attr->attisdropped) + continue; + + var = makeVar(rtindex, + i, + attr->atttypid, + attr->atttypmod, + attr->attcollation, + 0); + + tlist = lappend(tlist, + makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + NULL, + false)); + } + } + + /* Now add any remaining columns to tlist. */ + foreach(lc, vars) + { + Var *var = (Var *) lfirst(lc); + + /* + * No need for whole-row references to the target relation. We don't + * need system columns other than ctid and oid either, since those are + * set locally. + */ + if (IsA(var, Var) && + var->varno == rtindex && + var->varattno <= InvalidAttrNumber && + var->varattno != SelfItemPointerAttributeNumber) + continue; /* don't need it */ + + if (tlist_member((Expr *) var, tlist)) + continue; /* already got it */ + + tlist = lappend(tlist, + makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + NULL, + false)); + } + + list_free(vars); + + return tlist; +} + +/* + * rebuild_fdw_scan_tlist + * Build new fdw_scan_tlist of given foreign-scan plan node from given + * tlist + * + * There might be columns that the fdw_scan_tlist of the given foreign-scan + * plan node contains that the given tlist doesn't. The fdw_scan_tlist would + * have contained resjunk columns such as 'ctid' of the target relation and + * 'wholerow' of non-target relations, but the tlist might not contain them, + * for example. So, adjust the tlist so it contains all the columns specified + * in the fdw_scan_tlist; else setrefs.c will get confused. + */ +static void +rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist) +{ + List *new_tlist = tlist; + List *old_tlist = fscan->fdw_scan_tlist; + ListCell *lc; + + foreach(lc, old_tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + if (tlist_member(tle->expr, new_tlist)) + continue; /* already got it */ + + new_tlist = lappend(new_tlist, + makeTargetEntry(tle->expr, + list_length(new_tlist) + 1, + NULL, + false)); + } + fscan->fdw_scan_tlist = new_tlist; +} + +/* + * Execute a direct UPDATE/DELETE statement. + */ +static void +execute_dml_stmt(ForeignScanState *node) +{ + PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state; + ExprContext *econtext = node->ss.ps.ps_ExprContext; + int numParams = dmstate->numParams; + const char **values = dmstate->param_values; + + /* First, process a pending asynchronous request, if any. */ + if (dmstate->conn_state->pendingAreq) + process_pending_request(dmstate->conn_state->pendingAreq); + + /* + * Construct array of query parameter values in text format. + */ + if (numParams > 0) + process_query_params(econtext, + dmstate->param_flinfo, + dmstate->param_exprs, + values); + + /* + * Notice that we pass NULL for paramTypes, thus forcing the remote server + * to infer types for all parameters. Since we explicitly cast every + * parameter (see deparse.c), the "inference" is trivial and will produce + * the desired result. This allows us to avoid assuming that the remote + * server has the same OIDs we do for the parameters' types. + */ + if (!PQsendQueryParams(dmstate->conn, dmstate->query, numParams, + NULL, values, NULL, NULL, 0)) + pgfdw_report_error(ERROR, NULL, dmstate->conn, false, dmstate->query); + + /* + * Get the result, and check for success. + * + * We don't use a PG_TRY block here, so be careful not to throw error + * without releasing the PGresult. + */ + dmstate->result = pgfdw_get_result(dmstate->conn, dmstate->query); + if (PQresultStatus(dmstate->result) != + (dmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK)) + pgfdw_report_error(ERROR, dmstate->result, dmstate->conn, true, + dmstate->query); + + /* Get the number of rows affected. */ + if (dmstate->has_returning) + dmstate->num_tuples = PQntuples(dmstate->result); + else + dmstate->num_tuples = atoi(PQcmdTuples(dmstate->result)); +} + +/* + * Get the result of a RETURNING clause. + */ +static TupleTableSlot * +get_returning_data(ForeignScanState *node) +{ + PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state; + EState *estate = node->ss.ps.state; + ResultRelInfo *resultRelInfo = node->resultRelInfo; + TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; + TupleTableSlot *resultSlot; + + Assert(resultRelInfo->ri_projectReturning); + + /* If we didn't get any tuples, must be end of data. */ + if (dmstate->next_tuple >= dmstate->num_tuples) + return ExecClearTuple(slot); + + /* Increment the command es_processed count if necessary. */ + if (dmstate->set_processed) + estate->es_processed += 1; + + /* + * Store a RETURNING tuple. If has_returning is false, just emit a dummy + * tuple. (has_returning is false when the local query is of the form + * "UPDATE/DELETE .. RETURNING 1" for example.) + */ + if (!dmstate->has_returning) + { + ExecStoreAllNullTuple(slot); + resultSlot = slot; + } + else + { + /* + * On error, be sure to release the PGresult on the way out. Callers + * do not have PG_TRY blocks to ensure this happens. + */ + PG_TRY(); + { + HeapTuple newtup; + + newtup = make_tuple_from_result_row(dmstate->result, + dmstate->next_tuple, + dmstate->rel, + dmstate->attinmeta, + dmstate->retrieved_attrs, + node, + dmstate->temp_cxt); + ExecStoreHeapTuple(newtup, slot, false); + } + PG_CATCH(); + { + PQclear(dmstate->result); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* Get the updated/deleted tuple. */ + if (dmstate->rel) + resultSlot = slot; + else + resultSlot = apply_returning_filter(dmstate, resultRelInfo, slot, estate); + } + dmstate->next_tuple++; + + /* Make slot available for evaluation of the local query RETURNING list. */ + resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple = + resultSlot; + + return slot; +} + +/* + * Initialize a filter to extract an updated/deleted tuple from a scan tuple. + */ +static void +init_returning_filter(PgFdwDirectModifyState *dmstate, + List *fdw_scan_tlist, + Index rtindex) +{ + TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel); + ListCell *lc; + int i; + + /* + * Calculate the mapping between the fdw_scan_tlist's entries and the + * result tuple's attributes. + * + * The "map" is an array of indexes of the result tuple's attributes in + * fdw_scan_tlist, i.e., one entry for every attribute of the result + * tuple. We store zero for any attributes that don't have the + * corresponding entries in that list, marking that a NULL is needed in + * the result tuple. + * + * Also get the indexes of the entries for ctid and oid if any. + */ + dmstate->attnoMap = (AttrNumber *) + palloc0(resultTupType->natts * sizeof(AttrNumber)); + + dmstate->ctidAttno = dmstate->oidAttno = 0; + + i = 1; + dmstate->hasSystemCols = false; + foreach(lc, fdw_scan_tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + Var *var = (Var *) tle->expr; + + Assert(IsA(var, Var)); + + /* + * If the Var is a column of the target relation to be retrieved from + * the foreign server, get the index of the entry. + */ + if (var->varno == rtindex && + list_member_int(dmstate->retrieved_attrs, i)) + { + int attrno = var->varattno; + + if (attrno < 0) + { + /* + * We don't retrieve system columns other than ctid and oid. + */ + if (attrno == SelfItemPointerAttributeNumber) + dmstate->ctidAttno = i; + else + Assert(false); + dmstate->hasSystemCols = true; + } + else + { + /* + * We don't retrieve whole-row references to the target + * relation either. + */ + Assert(attrno > 0); + + dmstate->attnoMap[attrno - 1] = i; + } + } + i++; + } +} + +/* + * Extract and return an updated/deleted tuple from a scan tuple. + */ +static TupleTableSlot * +apply_returning_filter(PgFdwDirectModifyState *dmstate, + ResultRelInfo *resultRelInfo, + TupleTableSlot *slot, + EState *estate) +{ + TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel); + TupleTableSlot *resultSlot; + Datum *values; + bool *isnull; + Datum *old_values; + bool *old_isnull; + int i; + + /* + * Use the return tuple slot as a place to store the result tuple. + */ + resultSlot = ExecGetReturningSlot(estate, resultRelInfo); + + /* + * Extract all the values of the scan tuple. + */ + slot_getallattrs(slot); + old_values = slot->tts_values; + old_isnull = slot->tts_isnull; + + /* + * Prepare to build the result tuple. + */ + ExecClearTuple(resultSlot); + values = resultSlot->tts_values; + isnull = resultSlot->tts_isnull; + + /* + * Transpose data into proper fields of the result tuple. + */ + for (i = 0; i < resultTupType->natts; i++) + { + int j = dmstate->attnoMap[i]; + + if (j == 0) + { + values[i] = (Datum) 0; + isnull[i] = true; + } + else + { + values[i] = old_values[j - 1]; + isnull[i] = old_isnull[j - 1]; + } + } + + /* + * Build the virtual tuple. + */ + ExecStoreVirtualTuple(resultSlot); + + /* + * If we have any system columns to return, materialize a heap tuple in + * the slot from column values set above and install system columns in + * that tuple. + */ + if (dmstate->hasSystemCols) + { + HeapTuple resultTup = ExecFetchSlotHeapTuple(resultSlot, true, NULL); + + /* ctid */ + if (dmstate->ctidAttno) + { + ItemPointer ctid = NULL; + + ctid = (ItemPointer) DatumGetPointer(old_values[dmstate->ctidAttno - 1]); + resultTup->t_self = *ctid; + } + + /* + * And remaining columns + * + * Note: since we currently don't allow the target relation to appear + * on the nullable side of an outer join, any system columns wouldn't + * go to NULL. + * + * Note: no need to care about tableoid here because it will be + * initialized in ExecProcessReturning(). + */ + HeapTupleHeaderSetXmin(resultTup->t_data, InvalidTransactionId); + HeapTupleHeaderSetXmax(resultTup->t_data, InvalidTransactionId); + HeapTupleHeaderSetCmin(resultTup->t_data, InvalidTransactionId); + } + + /* + * And return the result tuple. + */ + return resultSlot; +} + +/* + * Prepare for processing of parameters used in remote query. + */ +static void +prepare_query_params(PlanState *node, + List *fdw_exprs, + int numParams, + FmgrInfo **param_flinfo, + List **param_exprs, + const char ***param_values) +{ + int i; + ListCell *lc; + + Assert(numParams > 0); + + /* Prepare for output conversion of parameters used in remote query. */ + *param_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * numParams); + + i = 0; + foreach(lc, fdw_exprs) + { + Node *param_expr = (Node *) lfirst(lc); + Oid typefnoid; + bool isvarlena; + + getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena); + fmgr_info(typefnoid, &(*param_flinfo)[i]); + i++; + } + + /* + * Prepare remote-parameter expressions for evaluation. (Note: in + * practice, we expect that all these expressions will be just Params, so + * we could possibly do something more efficient than using the full + * expression-eval machinery for this. But probably there would be little + * benefit, and it'd require postgres_fdw to know more than is desirable + * about Param evaluation.) + */ + *param_exprs = ExecInitExprList(fdw_exprs, node); + + /* Allocate buffer for text form of query parameters. */ + *param_values = (const char **) palloc0(numParams * sizeof(char *)); +} + +/* + * Construct array of query parameter values in text format. + */ +static void +process_query_params(ExprContext *econtext, + FmgrInfo *param_flinfo, + List *param_exprs, + const char **param_values) +{ + int nestlevel; + int i; + ListCell *lc; + + nestlevel = set_transmission_modes(); + + i = 0; + foreach(lc, param_exprs) + { + ExprState *expr_state = (ExprState *) lfirst(lc); + Datum expr_value; + bool isNull; + + /* Evaluate the parameter expression */ + expr_value = ExecEvalExpr(expr_state, econtext, &isNull); + + /* + * Get string representation of each parameter value by invoking + * type-specific output function, unless the value is null. + */ + if (isNull) + param_values[i] = NULL; + else + param_values[i] = OutputFunctionCall(¶m_flinfo[i], expr_value); + + i++; + } + + reset_transmission_modes(nestlevel); +} + +/* + * postgresAnalyzeForeignTable + * Test whether analyzing this foreign table is supported + */ +static bool +postgresAnalyzeForeignTable(Relation relation, + AcquireSampleRowsFunc *func, + BlockNumber *totalpages) +{ + ForeignTable *table; + UserMapping *user; + PGconn *conn; + StringInfoData sql; + PGresult *volatile res = NULL; + + /* Return the row-analysis function pointer */ + *func = postgresAcquireSampleRowsFunc; + + /* + * Now we have to get the number of pages. It's annoying that the ANALYZE + * API requires us to return that now, because it forces some duplication + * of effort between this routine and postgresAcquireSampleRowsFunc. But + * it's probably not worth redefining that API at this point. + */ + + /* + * Get the connection to use. We do the remote access as the table's + * owner, even if the ANALYZE was started by some other user. + */ + table = GetForeignTable(RelationGetRelid(relation)); + user = GetUserMapping(relation->rd_rel->relowner, table->serverid); + conn = GetConnection(user, false, NULL); + + /* + * Construct command to get page count for relation. + */ + initStringInfo(&sql); + deparseAnalyzeSizeSql(&sql, relation); + + /* In what follows, do not risk leaking any PGresults. */ + PG_TRY(); + { + res = pgfdw_exec_query(conn, sql.data, NULL); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pgfdw_report_error(ERROR, res, conn, false, sql.data); + + if (PQntuples(res) != 1 || PQnfields(res) != 1) + elog(ERROR, "unexpected result from deparseAnalyzeSizeSql query"); + *totalpages = strtoul(PQgetvalue(res, 0, 0), NULL, 10); + } + PG_FINALLY(); + { + PQclear(res); + } + PG_END_TRY(); + + ReleaseConnection(conn); + + return true; +} + +/* + * postgresGetAnalyzeInfoForForeignTable + * Count tuples in foreign table (just get pg_class.reltuples). + * + * can_tablesample determines if the remote relation supports acquiring the + * sample using TABLESAMPLE. + */ +static double +postgresGetAnalyzeInfoForForeignTable(Relation relation, bool *can_tablesample) +{ + ForeignTable *table; + UserMapping *user; + PGconn *conn; + StringInfoData sql; + PGresult *volatile res = NULL; + volatile double reltuples = -1; + volatile char relkind = 0; + + /* assume the remote relation does not support TABLESAMPLE */ + *can_tablesample = false; + + /* + * Get the connection to use. We do the remote access as the table's + * owner, even if the ANALYZE was started by some other user. + */ + table = GetForeignTable(RelationGetRelid(relation)); + user = GetUserMapping(relation->rd_rel->relowner, table->serverid); + conn = GetConnection(user, false, NULL); + + /* + * Construct command to get page count for relation. + */ + initStringInfo(&sql); + deparseAnalyzeInfoSql(&sql, relation); + + /* In what follows, do not risk leaking any PGresults. */ + PG_TRY(); + { + res = pgfdw_exec_query(conn, sql.data, NULL); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pgfdw_report_error(ERROR, res, conn, false, sql.data); + + if (PQntuples(res) != 1 || PQnfields(res) != 2) + elog(ERROR, "unexpected result from deparseAnalyzeInfoSql query"); + reltuples = strtod(PQgetvalue(res, 0, 0), NULL); + relkind = *(PQgetvalue(res, 0, 1)); + } + PG_FINALLY(); + { + if (res) + PQclear(res); + } + PG_END_TRY(); + + ReleaseConnection(conn); + + /* TABLESAMPLE is supported only for regular tables and matviews */ + *can_tablesample = (relkind == RELKIND_RELATION || + relkind == RELKIND_MATVIEW || + relkind == RELKIND_PARTITIONED_TABLE); + + return reltuples; +} + +/* + * Acquire a random sample of rows from foreign table managed by postgres_fdw. + * + * Selected rows are returned in the caller-allocated array rows[], + * which must have at least targrows entries. + * The actual number of rows selected is returned as the function result. + * We also count the total number of rows in the table and return it into + * *totalrows. Note that *totaldeadrows is always set to 0. + * + * Note that the returned list of rows is not always in order by physical + * position in the table. Therefore, correlation estimates derived later + * may be meaningless, but it's OK because we don't use the estimates + * currently (the planner only pays attention to correlation for indexscans). + */ +static int +postgresAcquireSampleRowsFunc(Relation relation, int elevel, + HeapTuple *rows, int targrows, + double *totalrows, + double *totaldeadrows) +{ + PgFdwAnalyzeState astate; + ForeignTable *table; + ForeignServer *server; + UserMapping *user; + PGconn *conn; + int server_version_num; + PgFdwSamplingMethod method = ANALYZE_SAMPLE_AUTO; /* auto is default */ + double sample_frac = -1.0; + double reltuples; + unsigned int cursor_number; + StringInfoData sql; + PGresult *volatile res = NULL; + ListCell *lc; + + /* Initialize workspace state */ + astate.rel = relation; + astate.attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(relation)); + + astate.rows = rows; + astate.targrows = targrows; + astate.numrows = 0; + astate.samplerows = 0; + astate.rowstoskip = -1; /* -1 means not set yet */ + reservoir_init_selection_state(&astate.rstate, targrows); + + /* Remember ANALYZE context, and create a per-tuple temp context */ + astate.anl_cxt = CurrentMemoryContext; + astate.temp_cxt = AllocSetContextCreate(CurrentMemoryContext, + "postgres_fdw temporary data", + ALLOCSET_SMALL_SIZES); + + /* + * Get the connection to use. We do the remote access as the table's + * owner, even if the ANALYZE was started by some other user. + */ + table = GetForeignTable(RelationGetRelid(relation)); + server = GetForeignServer(table->serverid); + user = GetUserMapping(relation->rd_rel->relowner, table->serverid); + conn = GetConnection(user, false, NULL); + + /* We'll need server version, so fetch it now. */ + server_version_num = PQserverVersion(conn); + + /* + * What sampling method should we use? + */ + foreach(lc, server->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "analyze_sampling") == 0) + { + char *value = defGetString(def); + + if (strcmp(value, "off") == 0) + method = ANALYZE_SAMPLE_OFF; + else if (strcmp(value, "auto") == 0) + method = ANALYZE_SAMPLE_AUTO; + else if (strcmp(value, "random") == 0) + method = ANALYZE_SAMPLE_RANDOM; + else if (strcmp(value, "system") == 0) + method = ANALYZE_SAMPLE_SYSTEM; + else if (strcmp(value, "bernoulli") == 0) + method = ANALYZE_SAMPLE_BERNOULLI; + + break; + } + } + + foreach(lc, table->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "analyze_sampling") == 0) + { + char *value = defGetString(def); + + if (strcmp(value, "off") == 0) + method = ANALYZE_SAMPLE_OFF; + else if (strcmp(value, "auto") == 0) + method = ANALYZE_SAMPLE_AUTO; + else if (strcmp(value, "random") == 0) + method = ANALYZE_SAMPLE_RANDOM; + else if (strcmp(value, "system") == 0) + method = ANALYZE_SAMPLE_SYSTEM; + else if (strcmp(value, "bernoulli") == 0) + method = ANALYZE_SAMPLE_BERNOULLI; + + break; + } + } + + /* + * Error-out if explicitly required one of the TABLESAMPLE methods, but + * the server does not support it. + */ + if ((server_version_num < 95000) && + (method == ANALYZE_SAMPLE_SYSTEM || + method == ANALYZE_SAMPLE_BERNOULLI)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("remote server does not support TABLESAMPLE feature"))); + + /* + * If we've decided to do remote sampling, calculate the sampling rate. We + * need to get the number of tuples from the remote server, but skip that + * network round-trip if not needed. + */ + if (method != ANALYZE_SAMPLE_OFF) + { + bool can_tablesample; + + reltuples = postgresGetAnalyzeInfoForForeignTable(relation, + &can_tablesample); + + /* + * Make sure we're not choosing TABLESAMPLE when the remote relation + * does not support that. But only do this for "auto" - if the user + * explicitly requested BERNOULLI/SYSTEM, it's better to fail. + */ + if (!can_tablesample && (method == ANALYZE_SAMPLE_AUTO)) + method = ANALYZE_SAMPLE_RANDOM; + + /* + * Remote's reltuples could be 0 or -1 if the table has never been + * vacuumed/analyzed. In that case, disable sampling after all. + */ + if ((reltuples <= 0) || (targrows >= reltuples)) + method = ANALYZE_SAMPLE_OFF; + else + { + /* + * All supported sampling methods require sampling rate, not + * target rows directly, so we calculate that using the remote + * reltuples value. That's imperfect, because it might be off a + * good deal, but that's not something we can (or should) address + * here. + * + * If reltuples is too low (i.e. when table grew), we'll end up + * sampling more rows - but then we'll apply the local sampling, + * so we get the expected sample size. This is the same outcome as + * without remote sampling. + * + * If reltuples is too high (e.g. after bulk DELETE), we will end + * up sampling too few rows. + * + * We can't really do much better here - we could try sampling a + * bit more rows, but we don't know how off the reltuples value is + * so how much is "a bit more"? + * + * Furthermore, the targrows value for partitions is determined + * based on table size (relpages), which can be off in different + * ways too. Adjusting the sampling rate here might make the issue + * worse. + */ + sample_frac = targrows / reltuples; + + /* + * We should never get sampling rate outside the valid range + * (between 0.0 and 1.0), because those cases should be covered by + * the previous branch that sets ANALYZE_SAMPLE_OFF. + */ + Assert(sample_frac >= 0.0 && sample_frac <= 1.0); + } + } + + /* + * For "auto" method, pick the one we believe is best. For servers with + * TABLESAMPLE support we pick BERNOULLI, for old servers we fall-back to + * random() to at least reduce network transfer. + */ + if (method == ANALYZE_SAMPLE_AUTO) + { + if (server_version_num < 95000) + method = ANALYZE_SAMPLE_RANDOM; + else + method = ANALYZE_SAMPLE_BERNOULLI; + } + + /* + * Construct cursor that retrieves whole rows from remote. + */ + cursor_number = GetCursorNumber(conn); + initStringInfo(&sql); + appendStringInfo(&sql, "DECLARE c%u CURSOR FOR ", cursor_number); + + deparseAnalyzeSql(&sql, relation, method, sample_frac, &astate.retrieved_attrs); + + /* In what follows, do not risk leaking any PGresults. */ + PG_TRY(); + { + char fetch_sql[64]; + int fetch_size; + + res = pgfdw_exec_query(conn, sql.data, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pgfdw_report_error(ERROR, res, conn, false, sql.data); + PQclear(res); + res = NULL; + + /* + * Determine the fetch size. The default is arbitrary, but shouldn't + * be enormous. + */ + fetch_size = 100; + foreach(lc, server->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "fetch_size") == 0) + { + (void) parse_int(defGetString(def), &fetch_size, 0, NULL); + break; + } + } + foreach(lc, table->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "fetch_size") == 0) + { + (void) parse_int(defGetString(def), &fetch_size, 0, NULL); + break; + } + } + + /* Construct command to fetch rows from remote. */ + snprintf(fetch_sql, sizeof(fetch_sql), "FETCH %d FROM c%u", + fetch_size, cursor_number); + + /* Retrieve and process rows a batch at a time. */ + for (;;) + { + int numrows; + int i; + + /* Allow users to cancel long query */ + CHECK_FOR_INTERRUPTS(); + + /* + * XXX possible future improvement: if rowstoskip is large, we + * could issue a MOVE rather than physically fetching the rows, + * then just adjust rowstoskip and samplerows appropriately. + */ + + /* Fetch some rows */ + res = pgfdw_exec_query(conn, fetch_sql, NULL); + /* On error, report the original query, not the FETCH. */ + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pgfdw_report_error(ERROR, res, conn, false, sql.data); + + /* Process whatever we got. */ + numrows = PQntuples(res); + for (i = 0; i < numrows; i++) + analyze_row_processor(res, i, &astate); + + PQclear(res); + res = NULL; + + /* Must be EOF if we didn't get all the rows requested. */ + if (numrows < fetch_size) + break; + } + + /* Close the cursor, just to be tidy. */ + close_cursor(conn, cursor_number, NULL); + } + PG_CATCH(); + { + PQclear(res); + PG_RE_THROW(); + } + PG_END_TRY(); + + ReleaseConnection(conn); + + /* We assume that we have no dead tuple. */ + *totaldeadrows = 0.0; + + /* + * Without sampling, we've retrieved all living tuples from foreign + * server, so report that as totalrows. Otherwise use the reltuples + * estimate we got from the remote side. + */ + if (method == ANALYZE_SAMPLE_OFF) + *totalrows = astate.samplerows; + else + *totalrows = reltuples; + + /* + * Emit some interesting relation info + */ + ereport(elevel, + (errmsg("\"%s\": table contains %.0f rows, %d rows in sample", + RelationGetRelationName(relation), + *totalrows, astate.numrows))); + + return astate.numrows; +} + +/* + * Collect sample rows from the result of query. + * - Use all tuples in sample until target # of samples are collected. + * - Subsequently, replace already-sampled tuples randomly. + */ +static void +analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate) +{ + int targrows = astate->targrows; + int pos; /* array index to store tuple in */ + MemoryContext oldcontext; + + /* Always increment sample row counter. */ + astate->samplerows += 1; + + /* + * Determine the slot where this sample row should be stored. Set pos to + * negative value to indicate the row should be skipped. + */ + if (astate->numrows < targrows) + { + /* First targrows rows are always included into the sample */ + pos = astate->numrows++; + } + else + { + /* + * Now we start replacing tuples in the sample until we reach the end + * of the relation. Same algorithm as in acquire_sample_rows in + * analyze.c; see Jeff Vitter's paper. + */ + if (astate->rowstoskip < 0) + astate->rowstoskip = reservoir_get_next_S(&astate->rstate, astate->samplerows, targrows); + + if (astate->rowstoskip <= 0) + { + /* Choose a random reservoir element to replace. */ + pos = (int) (targrows * sampler_random_fract(&astate->rstate.randstate)); + Assert(pos >= 0 && pos < targrows); + heap_freetuple(astate->rows[pos]); + } + else + { + /* Skip this tuple. */ + pos = -1; + } + + astate->rowstoskip -= 1; + } + + if (pos >= 0) + { + /* + * Create sample tuple from current result row, and store it in the + * position determined above. The tuple has to be created in anl_cxt. + */ + oldcontext = MemoryContextSwitchTo(astate->anl_cxt); + + astate->rows[pos] = make_tuple_from_result_row(res, row, + astate->rel, + astate->attinmeta, + astate->retrieved_attrs, + NULL, + astate->temp_cxt); + + MemoryContextSwitchTo(oldcontext); + } +} + +/* + * Import a foreign schema + */ +static List * +postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) +{ + List *commands = NIL; + bool import_collate = true; + bool import_default = false; + bool import_generated = true; + bool import_not_null = true; + ForeignServer *server; + UserMapping *mapping; + PGconn *conn; + StringInfoData buf; + PGresult *volatile res = NULL; + int numrows, + i; + ListCell *lc; + + /* Parse statement options */ + foreach(lc, stmt->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "import_collate") == 0) + import_collate = defGetBoolean(def); + else if (strcmp(def->defname, "import_default") == 0) + import_default = defGetBoolean(def); + else if (strcmp(def->defname, "import_generated") == 0) + import_generated = defGetBoolean(def); + else if (strcmp(def->defname, "import_not_null") == 0) + import_not_null = defGetBoolean(def); + else + ereport(ERROR, + (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), + errmsg("invalid option \"%s\"", def->defname))); + } + + /* + * Get connection to the foreign server. Connection manager will + * establish new connection if necessary. + */ + server = GetForeignServer(serverOid); + mapping = GetUserMapping(GetUserId(), server->serverid); + conn = GetConnection(mapping, false, NULL); + + /* Don't attempt to import collation if remote server hasn't got it */ + if (PQserverVersion(conn) < 90100) + import_collate = false; + + /* Create workspace for strings */ + initStringInfo(&buf); + + /* In what follows, do not risk leaking any PGresults. */ + PG_TRY(); + { + /* Check that the schema really exists */ + appendStringInfoString(&buf, "SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = "); + deparseStringLiteral(&buf, stmt->remote_schema); + + res = pgfdw_exec_query(conn, buf.data, NULL); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pgfdw_report_error(ERROR, res, conn, false, buf.data); + + if (PQntuples(res) != 1) + ereport(ERROR, + (errcode(ERRCODE_FDW_SCHEMA_NOT_FOUND), + errmsg("schema \"%s\" is not present on foreign server \"%s\"", + stmt->remote_schema, server->servername))); + + PQclear(res); + res = NULL; + resetStringInfo(&buf); + + /* + * Fetch all table data from this schema, possibly restricted by + * EXCEPT or LIMIT TO. (We don't actually need to pay any attention + * to EXCEPT/LIMIT TO here, because the core code will filter the + * statements we return according to those lists anyway. But it + * should save a few cycles to not process excluded tables in the + * first place.) + * + * Import table data for partitions only when they are explicitly + * specified in LIMIT TO clause. Otherwise ignore them and only + * include the definitions of the root partitioned tables to allow + * access to the complete remote data set locally in the schema + * imported. + * + * Note: because we run the connection with search_path restricted to + * pg_catalog, the format_type() and pg_get_expr() outputs will always + * include a schema name for types/functions in other schemas, which + * is what we want. + */ + appendStringInfoString(&buf, + "SELECT relname, " + " attname, " + " format_type(atttypid, atttypmod), " + " attnotnull, " + " pg_get_expr(adbin, adrelid), "); + + /* Generated columns are supported since Postgres 12 */ + if (PQserverVersion(conn) >= 120000) + appendStringInfoString(&buf, + " attgenerated, "); + else + appendStringInfoString(&buf, + " NULL, "); + + if (import_collate) + appendStringInfoString(&buf, + " collname, " + " collnsp.nspname "); + else + appendStringInfoString(&buf, + " NULL, NULL "); + + appendStringInfoString(&buf, + "FROM pg_class c " + " JOIN pg_namespace n ON " + " relnamespace = n.oid " + " LEFT JOIN pg_attribute a ON " + " attrelid = c.oid AND attnum > 0 " + " AND NOT attisdropped " + " LEFT JOIN pg_attrdef ad ON " + " adrelid = c.oid AND adnum = attnum "); + + if (import_collate) + appendStringInfoString(&buf, + " LEFT JOIN pg_collation coll ON " + " coll.oid = attcollation " + " LEFT JOIN pg_namespace collnsp ON " + " collnsp.oid = collnamespace "); + + appendStringInfoString(&buf, + "WHERE c.relkind IN (" + CppAsString2(RELKIND_RELATION) "," + CppAsString2(RELKIND_VIEW) "," + CppAsString2(RELKIND_FOREIGN_TABLE) "," + CppAsString2(RELKIND_MATVIEW) "," + CppAsString2(RELKIND_PARTITIONED_TABLE) ") " + " AND n.nspname = "); + deparseStringLiteral(&buf, stmt->remote_schema); + + /* Partitions are supported since Postgres 10 */ + if (PQserverVersion(conn) >= 100000 && + stmt->list_type != FDW_IMPORT_SCHEMA_LIMIT_TO) + appendStringInfoString(&buf, " AND NOT c.relispartition "); + + /* Apply restrictions for LIMIT TO and EXCEPT */ + if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO || + stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) + { + bool first_item = true; + + appendStringInfoString(&buf, " AND c.relname "); + if (stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) + appendStringInfoString(&buf, "NOT "); + appendStringInfoString(&buf, "IN ("); + + /* Append list of table names within IN clause */ + foreach(lc, stmt->table_list) + { + RangeVar *rv = (RangeVar *) lfirst(lc); + + if (first_item) + first_item = false; + else + appendStringInfoString(&buf, ", "); + deparseStringLiteral(&buf, rv->relname); + } + appendStringInfoChar(&buf, ')'); + } + + /* Append ORDER BY at the end of query to ensure output ordering */ + appendStringInfoString(&buf, " ORDER BY c.relname, a.attnum"); + + /* Fetch the data */ + res = pgfdw_exec_query(conn, buf.data, NULL); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pgfdw_report_error(ERROR, res, conn, false, buf.data); + + /* Process results */ + numrows = PQntuples(res); + /* note: incrementation of i happens in inner loop's while() test */ + for (i = 0; i < numrows;) + { + char *tablename = PQgetvalue(res, i, 0); + bool first_item = true; + + resetStringInfo(&buf); + appendStringInfo(&buf, "CREATE FOREIGN TABLE %s (\n", + quote_identifier(tablename)); + + /* Scan all rows for this table */ + do + { + char *attname; + char *typename; + char *attnotnull; + char *attgenerated; + char *attdefault; + char *collname; + char *collnamespace; + + /* If table has no columns, we'll see nulls here */ + if (PQgetisnull(res, i, 1)) + continue; + + attname = PQgetvalue(res, i, 1); + typename = PQgetvalue(res, i, 2); + attnotnull = PQgetvalue(res, i, 3); + attdefault = PQgetisnull(res, i, 4) ? (char *) NULL : + PQgetvalue(res, i, 4); + attgenerated = PQgetisnull(res, i, 5) ? (char *) NULL : + PQgetvalue(res, i, 5); + collname = PQgetisnull(res, i, 6) ? (char *) NULL : + PQgetvalue(res, i, 6); + collnamespace = PQgetisnull(res, i, 7) ? (char *) NULL : + PQgetvalue(res, i, 7); + + if (first_item) + first_item = false; + else + appendStringInfoString(&buf, ",\n"); + + /* Print column name and type */ + appendStringInfo(&buf, " %s %s", + quote_identifier(attname), + typename); + + /* + * Add column_name option so that renaming the foreign table's + * column doesn't break the association to the underlying + * column. + */ + appendStringInfoString(&buf, " OPTIONS (column_name "); + deparseStringLiteral(&buf, attname); + appendStringInfoChar(&buf, ')'); + + /* Add COLLATE if needed */ + if (import_collate && collname != NULL && collnamespace != NULL) + appendStringInfo(&buf, " COLLATE %s.%s", + quote_identifier(collnamespace), + quote_identifier(collname)); + + /* Add DEFAULT if needed */ + if (import_default && attdefault != NULL && + (!attgenerated || !attgenerated[0])) + appendStringInfo(&buf, " DEFAULT %s", attdefault); + + /* Add GENERATED if needed */ + if (import_generated && attgenerated != NULL && + attgenerated[0] == ATTRIBUTE_GENERATED_STORED) + { + Assert(attdefault != NULL); + appendStringInfo(&buf, + " GENERATED ALWAYS AS (%s) STORED", + attdefault); + } + + /* Add NOT NULL if needed */ + if (import_not_null && attnotnull[0] == 't') + appendStringInfoString(&buf, " NOT NULL"); + } + while (++i < numrows && + strcmp(PQgetvalue(res, i, 0), tablename) == 0); + + /* + * Add server name and table-level options. We specify remote + * schema and table name as options (the latter to ensure that + * renaming the foreign table doesn't break the association). + */ + appendStringInfo(&buf, "\n) SERVER %s\nOPTIONS (", + quote_identifier(server->servername)); + + appendStringInfoString(&buf, "schema_name "); + deparseStringLiteral(&buf, stmt->remote_schema); + appendStringInfoString(&buf, ", table_name "); + deparseStringLiteral(&buf, tablename); + + appendStringInfoString(&buf, ");"); + + commands = lappend(commands, pstrdup(buf.data)); + } + } + PG_FINALLY(); + { + PQclear(res); + } + PG_END_TRY(); + + ReleaseConnection(conn); + + return commands; +} + +/* + * Assess whether the join between inner and outer relations can be pushed down + * to the foreign server. As a side effect, save information we obtain in this + * function to PgFdwRelationInfo passed in. + */ +static bool +foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, + RelOptInfo *outerrel, RelOptInfo *innerrel, + JoinPathExtraData *extra) +{ + PgFdwRelationInfo *fpinfo; + PgFdwRelationInfo *fpinfo_o; + PgFdwRelationInfo *fpinfo_i; + ListCell *lc; + List *joinclauses; + + /* + * We support pushing down INNER, LEFT, RIGHT and FULL OUTER joins. + * Constructing queries representing SEMI and ANTI joins is hard, hence + * not considered right now. + */ + if (jointype != JOIN_INNER && jointype != JOIN_LEFT && + jointype != JOIN_RIGHT && jointype != JOIN_FULL) + return false; + + /* + * If either of the joining relations is marked as unsafe to pushdown, the + * join can not be pushed down. + */ + fpinfo = (PgFdwRelationInfo *) joinrel->fdw_private; + fpinfo_o = (PgFdwRelationInfo *) outerrel->fdw_private; + fpinfo_i = (PgFdwRelationInfo *) innerrel->fdw_private; + if (!fpinfo_o || !fpinfo_o->pushdown_safe || + !fpinfo_i || !fpinfo_i->pushdown_safe) + return false; + + /* + * If joining relations have local conditions, those conditions are + * required to be applied before joining the relations. Hence the join can + * not be pushed down. + */ + if (fpinfo_o->local_conds || fpinfo_i->local_conds) + return false; + + /* + * Merge FDW options. We might be tempted to do this after we have deemed + * the foreign join to be OK. But we must do this beforehand so that we + * know which quals can be evaluated on the foreign server, which might + * depend on shippable_extensions. + */ + fpinfo->server = fpinfo_o->server; + merge_fdw_options(fpinfo, fpinfo_o, fpinfo_i); + + /* + * Separate restrict list into join quals and pushed-down (other) quals. + * + * Join quals belonging to an outer join must all be shippable, else we + * cannot execute the join remotely. Add such quals to 'joinclauses'. + * + * Add other quals to fpinfo->remote_conds if they are shippable, else to + * fpinfo->local_conds. In an inner join it's okay to execute conditions + * either locally or remotely; the same is true for pushed-down conditions + * at an outer join. + * + * Note we might return failure after having already scribbled on + * fpinfo->remote_conds and fpinfo->local_conds. That's okay because we + * won't consult those lists again if we deem the join unshippable. + */ + joinclauses = NIL; + foreach(lc, extra->restrictlist) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + bool is_remote_clause = is_foreign_expr(root, joinrel, + rinfo->clause); + + if (IS_OUTER_JOIN(jointype) && + !RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids)) + { + if (!is_remote_clause) + return false; + joinclauses = lappend(joinclauses, rinfo); + } + else + { + if (is_remote_clause) + fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo); + else + fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo); + } + } + + /* + * deparseExplicitTargetList() isn't smart enough to handle anything other + * than a Var. In particular, if there's some PlaceHolderVar that would + * need to be evaluated within this join tree (because there's an upper + * reference to a quantity that may go to NULL as a result of an outer + * join), then we can't try to push the join down because we'll fail when + * we get to deparseExplicitTargetList(). However, a PlaceHolderVar that + * needs to be evaluated *at the top* of this join tree is OK, because we + * can do that locally after fetching the results from the remote side. + */ + foreach(lc, root->placeholder_list) + { + PlaceHolderInfo *phinfo = lfirst(lc); + Relids relids; + + /* PlaceHolderInfo refers to parent relids, not child relids. */ + relids = IS_OTHER_REL(joinrel) ? + joinrel->top_parent_relids : joinrel->relids; + + if (bms_is_subset(phinfo->ph_eval_at, relids) && + bms_nonempty_difference(relids, phinfo->ph_eval_at)) + return false; + } + + /* Save the join clauses, for later use. */ + fpinfo->joinclauses = joinclauses; + + fpinfo->outerrel = outerrel; + fpinfo->innerrel = innerrel; + fpinfo->jointype = jointype; + + /* + * By default, both the input relations are not required to be deparsed as + * subqueries, but there might be some relations covered by the input + * relations that are required to be deparsed as subqueries, so save the + * relids of those relations for later use by the deparser. + */ + fpinfo->make_outerrel_subquery = false; + fpinfo->make_innerrel_subquery = false; + Assert(bms_is_subset(fpinfo_o->lower_subquery_rels, outerrel->relids)); + Assert(bms_is_subset(fpinfo_i->lower_subquery_rels, innerrel->relids)); + fpinfo->lower_subquery_rels = bms_union(fpinfo_o->lower_subquery_rels, + fpinfo_i->lower_subquery_rels); + + /* + * Pull the other remote conditions from the joining relations into join + * clauses or other remote clauses (remote_conds) of this relation + * wherever possible. This avoids building subqueries at every join step. + * + * For an inner join, clauses from both the relations are added to the + * other remote clauses. For LEFT and RIGHT OUTER join, the clauses from + * the outer side are added to remote_conds since those can be evaluated + * after the join is evaluated. The clauses from inner side are added to + * the joinclauses, since they need to be evaluated while constructing the + * join. + * + * For a FULL OUTER JOIN, the other clauses from either relation can not + * be added to the joinclauses or remote_conds, since each relation acts + * as an outer relation for the other. + * + * The joining sides can not have local conditions, thus no need to test + * shippability of the clauses being pulled up. + */ + switch (jointype) + { + case JOIN_INNER: + fpinfo->remote_conds = list_concat(fpinfo->remote_conds, + fpinfo_i->remote_conds); + fpinfo->remote_conds = list_concat(fpinfo->remote_conds, + fpinfo_o->remote_conds); + break; + + case JOIN_LEFT: + fpinfo->joinclauses = list_concat(fpinfo->joinclauses, + fpinfo_i->remote_conds); + fpinfo->remote_conds = list_concat(fpinfo->remote_conds, + fpinfo_o->remote_conds); + break; + + case JOIN_RIGHT: + fpinfo->joinclauses = list_concat(fpinfo->joinclauses, + fpinfo_o->remote_conds); + fpinfo->remote_conds = list_concat(fpinfo->remote_conds, + fpinfo_i->remote_conds); + break; + + case JOIN_FULL: + + /* + * In this case, if any of the input relations has conditions, we + * need to deparse that relation as a subquery so that the + * conditions can be evaluated before the join. Remember it in + * the fpinfo of this relation so that the deparser can take + * appropriate action. Also, save the relids of base relations + * covered by that relation for later use by the deparser. + */ + if (fpinfo_o->remote_conds) + { + fpinfo->make_outerrel_subquery = true; + fpinfo->lower_subquery_rels = + bms_add_members(fpinfo->lower_subquery_rels, + outerrel->relids); + } + if (fpinfo_i->remote_conds) + { + fpinfo->make_innerrel_subquery = true; + fpinfo->lower_subquery_rels = + bms_add_members(fpinfo->lower_subquery_rels, + innerrel->relids); + } + break; + + default: + /* Should not happen, we have just checked this above */ + elog(ERROR, "unsupported join type %d", jointype); + } + + /* + * For an inner join, all restrictions can be treated alike. Treating the + * pushed down conditions as join conditions allows a top level full outer + * join to be deparsed without requiring subqueries. + */ + if (jointype == JOIN_INNER) + { + Assert(!fpinfo->joinclauses); + fpinfo->joinclauses = fpinfo->remote_conds; + fpinfo->remote_conds = NIL; + } + + /* Mark that this join can be pushed down safely */ + fpinfo->pushdown_safe = true; + + /* Get user mapping */ + if (fpinfo->use_remote_estimate) + { + if (fpinfo_o->use_remote_estimate) + fpinfo->user = fpinfo_o->user; + else + fpinfo->user = fpinfo_i->user; + } + else + fpinfo->user = NULL; + + /* + * Set # of retrieved rows and cached relation costs to some negative + * value, so that we can detect when they are set to some sensible values, + * during one (usually the first) of the calls to estimate_path_cost_size. + */ + fpinfo->retrieved_rows = -1; + fpinfo->rel_startup_cost = -1; + fpinfo->rel_total_cost = -1; + + /* + * Set the string describing this join relation to be used in EXPLAIN + * output of corresponding ForeignScan. Note that the decoration we add + * to the base relation names mustn't include any digits, or it'll confuse + * postgresExplainForeignScan. + */ + fpinfo->relation_name = psprintf("(%s) %s JOIN (%s)", + fpinfo_o->relation_name, + get_jointype_name(fpinfo->jointype), + fpinfo_i->relation_name); + + /* + * Set the relation index. This is defined as the position of this + * joinrel in the join_rel_list list plus the length of the rtable list. + * Note that since this joinrel is at the end of the join_rel_list list + * when we are called, we can get the position by list_length. + */ + Assert(fpinfo->relation_index == 0); /* shouldn't be set yet */ + fpinfo->relation_index = + list_length(root->parse->rtable) + list_length(root->join_rel_list); + + return true; +} + +static void +add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, + Path *epq_path) +{ + List *useful_pathkeys_list = NIL; /* List of all pathkeys */ + ListCell *lc; + + useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel); + + /* + * Before creating sorted paths, arrange for the passed-in EPQ path, if + * any, to return columns needed by the parent ForeignScan node so that + * they will propagate up through Sort nodes injected below, if necessary. + */ + if (epq_path != NULL && useful_pathkeys_list != NIL) + { + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private; + PathTarget *target = copy_pathtarget(epq_path->pathtarget); + + /* Include columns required for evaluating PHVs in the tlist. */ + add_new_columns_to_pathtarget(target, + pull_var_clause((Node *) target->exprs, + PVC_RECURSE_PLACEHOLDERS)); + + /* Include columns required for evaluating the local conditions. */ + foreach(lc, fpinfo->local_conds) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + add_new_columns_to_pathtarget(target, + pull_var_clause((Node *) rinfo->clause, + PVC_RECURSE_PLACEHOLDERS)); + } + + /* + * If we have added any new columns, adjust the tlist of the EPQ path. + * + * Note: the plan created using this path will only be used to execute + * EPQ checks, where accuracy of the plan cost and width estimates + * would not be important, so we do not do set_pathtarget_cost_width() + * for the new pathtarget here. See also postgresGetForeignPlan(). + */ + if (list_length(target->exprs) > list_length(epq_path->pathtarget->exprs)) + { + /* The EPQ path is a join path, so it is projection-capable. */ + Assert(is_projection_capable_path(epq_path)); + + /* + * Use create_projection_path() here, so as to avoid modifying it + * in place. + */ + epq_path = (Path *) create_projection_path(root, + rel, + epq_path, + target); + } + } + + /* Create one path for each set of pathkeys we found above. */ + foreach(lc, useful_pathkeys_list) + { + double rows; + int width; + Cost startup_cost; + Cost total_cost; + List *useful_pathkeys = lfirst(lc); + Path *sorted_epq_path; + + estimate_path_cost_size(root, rel, NIL, useful_pathkeys, NULL, + &rows, &width, &startup_cost, &total_cost); + + /* + * The EPQ path must be at least as well sorted as the path itself, in + * case it gets used as input to a mergejoin. + */ + sorted_epq_path = epq_path; + if (sorted_epq_path != NULL && + !pathkeys_contained_in(useful_pathkeys, + sorted_epq_path->pathkeys)) + sorted_epq_path = (Path *) + create_sort_path(root, + rel, + sorted_epq_path, + useful_pathkeys, + -1.0); + + if (IS_SIMPLE_REL(rel)) + add_path(rel, (Path *) + create_foreignscan_path(root, rel, + NULL, + rows, + startup_cost, + total_cost, + useful_pathkeys, + rel->lateral_relids, + sorted_epq_path, + NIL)); + else + add_path(rel, (Path *) + create_foreign_join_path(root, rel, + NULL, + rows, + startup_cost, + total_cost, + useful_pathkeys, + rel->lateral_relids, + sorted_epq_path, + NIL)); + } +} + +/* + * Parse options from foreign server and apply them to fpinfo. + * + * New options might also require tweaking merge_fdw_options(). + */ +static void +apply_server_options(PgFdwRelationInfo *fpinfo) +{ + ListCell *lc; + + foreach(lc, fpinfo->server->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "use_remote_estimate") == 0) + fpinfo->use_remote_estimate = defGetBoolean(def); + else if (strcmp(def->defname, "fdw_startup_cost") == 0) + (void) parse_real(defGetString(def), &fpinfo->fdw_startup_cost, 0, + NULL); + else if (strcmp(def->defname, "fdw_tuple_cost") == 0) + (void) parse_real(defGetString(def), &fpinfo->fdw_tuple_cost, 0, + NULL); + else if (strcmp(def->defname, "extensions") == 0) + fpinfo->shippable_extensions = + ExtractExtensionList(defGetString(def), false); + else if (strcmp(def->defname, "fetch_size") == 0) + (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL); + else if (strcmp(def->defname, "async_capable") == 0) + fpinfo->async_capable = defGetBoolean(def); + } +} + +/* + * Parse options from foreign table and apply them to fpinfo. + * + * New options might also require tweaking merge_fdw_options(). + */ +static void +apply_table_options(PgFdwRelationInfo *fpinfo) +{ + ListCell *lc; + + foreach(lc, fpinfo->table->options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "use_remote_estimate") == 0) + fpinfo->use_remote_estimate = defGetBoolean(def); + else if (strcmp(def->defname, "fetch_size") == 0) + (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL); + else if (strcmp(def->defname, "async_capable") == 0) + fpinfo->async_capable = defGetBoolean(def); + } +} + +/* + * Merge FDW options from input relations into a new set of options for a join + * or an upper rel. + * + * For a join relation, FDW-specific information about the inner and outer + * relations is provided using fpinfo_i and fpinfo_o. For an upper relation, + * fpinfo_o provides the information for the input relation; fpinfo_i is + * expected to NULL. + */ +static void +merge_fdw_options(PgFdwRelationInfo *fpinfo, + const PgFdwRelationInfo *fpinfo_o, + const PgFdwRelationInfo *fpinfo_i) +{ + /* We must always have fpinfo_o. */ + Assert(fpinfo_o); + + /* fpinfo_i may be NULL, but if present the servers must both match. */ + Assert(!fpinfo_i || + fpinfo_i->server->serverid == fpinfo_o->server->serverid); + + /* + * Copy the server specific FDW options. (For a join, both relations come + * from the same server, so the server options should have the same value + * for both relations.) + */ + fpinfo->fdw_startup_cost = fpinfo_o->fdw_startup_cost; + fpinfo->fdw_tuple_cost = fpinfo_o->fdw_tuple_cost; + fpinfo->shippable_extensions = fpinfo_o->shippable_extensions; + fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate; + fpinfo->fetch_size = fpinfo_o->fetch_size; + fpinfo->async_capable = fpinfo_o->async_capable; + + /* Merge the table level options from either side of the join. */ + if (fpinfo_i) + { + /* + * We'll prefer to use remote estimates for this join if any table + * from either side of the join is using remote estimates. This is + * most likely going to be preferred since they're already willing to + * pay the price of a round trip to get the remote EXPLAIN. In any + * case it's not entirely clear how we might otherwise handle this + * best. + */ + fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate || + fpinfo_i->use_remote_estimate; + + /* + * Set fetch size to maximum of the joining sides, since we are + * expecting the rows returned by the join to be proportional to the + * relation sizes. + */ + fpinfo->fetch_size = Max(fpinfo_o->fetch_size, fpinfo_i->fetch_size); + + /* + * We'll prefer to consider this join async-capable if any table from + * either side of the join is considered async-capable. This would be + * reasonable because in that case the foreign server would have its + * own resources to scan that table asynchronously, and the join could + * also be computed asynchronously using the resources. + */ + fpinfo->async_capable = fpinfo_o->async_capable || + fpinfo_i->async_capable; + } +} + +/* + * postgresGetForeignJoinPaths + * Add possible ForeignPath to joinrel, if join is safe to push down. + */ +static void +postgresGetForeignJoinPaths(PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + JoinPathExtraData *extra) +{ + PgFdwRelationInfo *fpinfo; + ForeignPath *joinpath; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + Path *epq_path; /* Path to create plan to be executed when + * EvalPlanQual gets triggered. */ + + /* + * Skip if this join combination has been considered already. + */ + if (joinrel->fdw_private) + return; + + /* + * This code does not work for joins with lateral references, since those + * must have parameterized paths, which we don't generate yet. + */ + if (!bms_is_empty(joinrel->lateral_relids)) + return; + + /* + * Create unfinished PgFdwRelationInfo entry which is used to indicate + * that the join relation is already considered, so that we won't waste + * time in judging safety of join pushdown and adding the same paths again + * if found safe. Once we know that this join can be pushed down, we fill + * the entry. + */ + fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo)); + fpinfo->pushdown_safe = false; + joinrel->fdw_private = fpinfo; + /* attrs_used is only for base relations. */ + fpinfo->attrs_used = NULL; + + /* + * If there is a possibility that EvalPlanQual will be executed, we need + * to be able to reconstruct the row using scans of the base relations. + * GetExistingLocalJoinPath will find a suitable path for this purpose in + * the path list of the joinrel, if one exists. We must be careful to + * call it before adding any ForeignPath, since the ForeignPath might + * dominate the only suitable local path available. We also do it before + * calling foreign_join_ok(), since that function updates fpinfo and marks + * it as pushable if the join is found to be pushable. + */ + if (root->parse->commandType == CMD_DELETE || + root->parse->commandType == CMD_UPDATE || + root->rowMarks) + { + epq_path = GetExistingLocalJoinPath(joinrel); + if (!epq_path) + { + elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks was not found"); + return; + } + } + else + epq_path = NULL; + + if (!foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, extra)) + { + /* Free path required for EPQ if we copied one; we don't need it now */ + if (epq_path) + pfree(epq_path); + return; + } + + /* + * Compute the selectivity and cost of the local_conds, so we don't have + * to do it over again for each path. The best we can do for these + * conditions is to estimate selectivity on the basis of local statistics. + * The local conditions are applied after the join has been computed on + * the remote side like quals in WHERE clause, so pass jointype as + * JOIN_INNER. + */ + fpinfo->local_conds_sel = clauselist_selectivity(root, + fpinfo->local_conds, + 0, + JOIN_INNER, + NULL); + cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); + + /* + * If we are going to estimate costs locally, estimate the join clause + * selectivity here while we have special join info. + */ + if (!fpinfo->use_remote_estimate) + fpinfo->joinclause_sel = clauselist_selectivity(root, fpinfo->joinclauses, + 0, fpinfo->jointype, + extra->sjinfo); + + /* Estimate costs for bare join relation */ + estimate_path_cost_size(root, joinrel, NIL, NIL, NULL, + &rows, &width, &startup_cost, &total_cost); + /* Now update this information in the joinrel */ + joinrel->rows = rows; + joinrel->reltarget->width = width; + fpinfo->rows = rows; + fpinfo->width = width; + fpinfo->startup_cost = startup_cost; + fpinfo->total_cost = total_cost; + + /* + * Create a new join path and add it to the joinrel which represents a + * join between foreign tables. + */ + joinpath = create_foreign_join_path(root, + joinrel, + NULL, /* default pathtarget */ + rows, + startup_cost, + total_cost, + NIL, /* no pathkeys */ + joinrel->lateral_relids, + epq_path, + NIL); /* no fdw_private */ + + /* Add generated path into joinrel by add_path(). */ + add_path(joinrel, (Path *) joinpath); + + /* Consider pathkeys for the join relation */ + add_paths_with_pathkeys_for_rel(root, joinrel, epq_path); + + /* XXX Consider parameterized paths for the join relation */ +} + +/* + * Assess whether the aggregation, grouping and having operations can be pushed + * down to the foreign server. As a side effect, save information we obtain in + * this function to PgFdwRelationInfo of the input relation. + */ +static bool +foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel, + Node *havingQual) +{ + Query *query = root->parse; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private; + PathTarget *grouping_target = grouped_rel->reltarget; + PgFdwRelationInfo *ofpinfo; + ListCell *lc; + int i; + List *tlist = NIL; + + /* We currently don't support pushing Grouping Sets. */ + if (query->groupingSets) + return false; + + /* Get the fpinfo of the underlying scan relation. */ + ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private; + + /* + * If underlying scan relation has any local conditions, those conditions + * are required to be applied before performing aggregation. Hence the + * aggregate cannot be pushed down. + */ + if (ofpinfo->local_conds) + return false; + + /* + * Examine grouping expressions, as well as other expressions we'd need to + * compute, and check whether they are safe to push down to the foreign + * server. All GROUP BY expressions will be part of the grouping target + * and thus there is no need to search for them separately. Add grouping + * expressions into target list which will be passed to foreign server. + * + * A tricky fine point is that we must not put any expression into the + * target list that is just a foreign param (that is, something that + * deparse.c would conclude has to be sent to the foreign server). If we + * do, the expression will also appear in the fdw_exprs list of the plan + * node, and setrefs.c will get confused and decide that the fdw_exprs + * entry is actually a reference to the fdw_scan_tlist entry, resulting in + * a broken plan. Somewhat oddly, it's OK if the expression contains such + * a node, as long as it's not at top level; then no match is possible. + */ + i = 0; + foreach(lc, grouping_target->exprs) + { + Expr *expr = (Expr *) lfirst(lc); + Index sgref = get_pathtarget_sortgroupref(grouping_target, i); + ListCell *l; + + /* + * Check whether this expression is part of GROUP BY clause. Note we + * check the whole GROUP BY clause not just processed_groupClause, + * because we will ship all of it, cf. appendGroupByClause. + */ + if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause)) + { + TargetEntry *tle; + + /* + * If any GROUP BY expression is not shippable, then we cannot + * push down aggregation to the foreign server. + */ + if (!is_foreign_expr(root, grouped_rel, expr)) + return false; + + /* + * If it would be a foreign param, we can't put it into the tlist, + * so we have to fail. + */ + if (is_foreign_param(root, grouped_rel, expr)) + return false; + + /* + * Pushable, so add to tlist. We need to create a TLE for this + * expression and apply the sortgroupref to it. We cannot use + * add_to_flat_tlist() here because that avoids making duplicate + * entries in the tlist. If there are duplicate entries with + * distinct sortgrouprefs, we have to duplicate that situation in + * the output tlist. + */ + tle = makeTargetEntry(expr, list_length(tlist) + 1, NULL, false); + tle->ressortgroupref = sgref; + tlist = lappend(tlist, tle); + } + else + { + /* + * Non-grouping expression we need to compute. Can we ship it + * as-is to the foreign server? + */ + if (is_foreign_expr(root, grouped_rel, expr) && + !is_foreign_param(root, grouped_rel, expr)) + { + /* Yes, so add to tlist as-is; OK to suppress duplicates */ + tlist = add_to_flat_tlist(tlist, list_make1(expr)); + } + else + { + /* Not pushable as a whole; extract its Vars and aggregates */ + List *aggvars; + + aggvars = pull_var_clause((Node *) expr, + PVC_INCLUDE_AGGREGATES); + + /* + * If any aggregate expression is not shippable, then we + * cannot push down aggregation to the foreign server. (We + * don't have to check is_foreign_param, since that certainly + * won't return true for any such expression.) + */ + if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars)) + return false; + + /* + * Add aggregates, if any, into the targetlist. Plain Vars + * outside an aggregate can be ignored, because they should be + * either same as some GROUP BY column or part of some GROUP + * BY expression. In either case, they are already part of + * the targetlist and thus no need to add them again. In fact + * including plain Vars in the tlist when they do not match a + * GROUP BY column would cause the foreign server to complain + * that the shipped query is invalid. + */ + foreach(l, aggvars) + { + Expr *aggref = (Expr *) lfirst(l); + + if (IsA(aggref, Aggref)) + tlist = add_to_flat_tlist(tlist, list_make1(aggref)); + } + } + } + + i++; + } + + /* + * Classify the pushable and non-pushable HAVING clauses and save them in + * remote_conds and local_conds of the grouped rel's fpinfo. + */ + if (havingQual) + { + foreach(lc, (List *) havingQual) + { + Expr *expr = (Expr *) lfirst(lc); + RestrictInfo *rinfo; + + /* + * Currently, the core code doesn't wrap havingQuals in + * RestrictInfos, so we must make our own. + */ + Assert(!IsA(expr, RestrictInfo)); + rinfo = make_restrictinfo(root, + expr, + true, + false, + false, + false, + root->qual_security_level, + grouped_rel->relids, + NULL, + NULL); + if (is_foreign_expr(root, grouped_rel, expr)) + fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo); + else + fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo); + } + } + + /* + * If there are any local conditions, pull Vars and aggregates from it and + * check whether they are safe to pushdown or not. + */ + if (fpinfo->local_conds) + { + List *aggvars = NIL; + + foreach(lc, fpinfo->local_conds) + { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + aggvars = list_concat(aggvars, + pull_var_clause((Node *) rinfo->clause, + PVC_INCLUDE_AGGREGATES)); + } + + foreach(lc, aggvars) + { + Expr *expr = (Expr *) lfirst(lc); + + /* + * If aggregates within local conditions are not safe to push + * down, then we cannot push down the query. Vars are already + * part of GROUP BY clause which are checked above, so no need to + * access them again here. Again, we need not check + * is_foreign_param for a foreign aggregate. + */ + if (IsA(expr, Aggref)) + { + if (!is_foreign_expr(root, grouped_rel, expr)) + return false; + + tlist = add_to_flat_tlist(tlist, list_make1(expr)); + } + } + } + + /* Store generated targetlist */ + fpinfo->grouped_tlist = tlist; + + /* Safe to pushdown */ + fpinfo->pushdown_safe = true; + + /* + * Set # of retrieved rows and cached relation costs to some negative + * value, so that we can detect when they are set to some sensible values, + * during one (usually the first) of the calls to estimate_path_cost_size. + */ + fpinfo->retrieved_rows = -1; + fpinfo->rel_startup_cost = -1; + fpinfo->rel_total_cost = -1; + + /* + * Set the string describing this grouped relation to be used in EXPLAIN + * output of corresponding ForeignScan. Note that the decoration we add + * to the base relation name mustn't include any digits, or it'll confuse + * postgresExplainForeignScan. + */ + fpinfo->relation_name = psprintf("Aggregate on (%s)", + ofpinfo->relation_name); + + return true; +} + +/* + * postgresGetForeignUpperPaths + * Add paths for post-join operations like aggregation, grouping etc. if + * corresponding operations are safe to push down. + */ +static void +postgresGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, + RelOptInfo *input_rel, RelOptInfo *output_rel, + void *extra) +{ + PgFdwRelationInfo *fpinfo; + + /* + * If input rel is not safe to pushdown, then simply return as we cannot + * perform any post-join operations on the foreign server. + */ + if (!input_rel->fdw_private || + !((PgFdwRelationInfo *) input_rel->fdw_private)->pushdown_safe) + return; + + /* Ignore stages we don't support; and skip any duplicate calls. */ + if ((stage != UPPERREL_GROUP_AGG && + stage != UPPERREL_ORDERED && + stage != UPPERREL_FINAL) || + output_rel->fdw_private) + return; + + fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo)); + fpinfo->pushdown_safe = false; + fpinfo->stage = stage; + output_rel->fdw_private = fpinfo; + + switch (stage) + { + case UPPERREL_GROUP_AGG: + add_foreign_grouping_paths(root, input_rel, output_rel, + (GroupPathExtraData *) extra); + break; + case UPPERREL_ORDERED: + add_foreign_ordered_paths(root, input_rel, output_rel); + break; + case UPPERREL_FINAL: + add_foreign_final_paths(root, input_rel, output_rel, + (FinalPathExtraData *) extra); + break; + default: + elog(ERROR, "unexpected upper relation: %d", (int) stage); + break; + } +} + +/* + * add_foreign_grouping_paths + * Add foreign path for grouping and/or aggregation. + * + * Given input_rel represents the underlying scan. The paths are added to the + * given grouped_rel. + */ +static void +add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, + RelOptInfo *grouped_rel, + GroupPathExtraData *extra) +{ + Query *parse = root->parse; + PgFdwRelationInfo *ifpinfo = input_rel->fdw_private; + PgFdwRelationInfo *fpinfo = grouped_rel->fdw_private; + ForeignPath *grouppath; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + + /* Nothing to be done, if there is no grouping or aggregation required. */ + if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs && + !root->hasHavingQual) + return; + + Assert(extra->patype == PARTITIONWISE_AGGREGATE_NONE || + extra->patype == PARTITIONWISE_AGGREGATE_FULL); + + /* save the input_rel as outerrel in fpinfo */ + fpinfo->outerrel = input_rel; + + /* + * Copy foreign table, foreign server, user mapping, FDW options etc. + * details from the input relation's fpinfo. + */ + fpinfo->table = ifpinfo->table; + fpinfo->server = ifpinfo->server; + fpinfo->user = ifpinfo->user; + merge_fdw_options(fpinfo, ifpinfo, NULL); + + /* + * Assess if it is safe to push down aggregation and grouping. + * + * Use HAVING qual from extra. In case of child partition, it will have + * translated Vars. + */ + if (!foreign_grouping_ok(root, grouped_rel, extra->havingQual)) + return; + + /* + * Compute the selectivity and cost of the local_conds, so we don't have + * to do it over again for each path. (Currently we create just a single + * path here, but in future it would be possible that we build more paths + * such as pre-sorted paths as in postgresGetForeignPaths and + * postgresGetForeignJoinPaths.) The best we can do for these conditions + * is to estimate selectivity on the basis of local statistics. + */ + fpinfo->local_conds_sel = clauselist_selectivity(root, + fpinfo->local_conds, + 0, + JOIN_INNER, + NULL); + + cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); + + /* Estimate the cost of push down */ + estimate_path_cost_size(root, grouped_rel, NIL, NIL, NULL, + &rows, &width, &startup_cost, &total_cost); + + /* Now update this information in the fpinfo */ + fpinfo->rows = rows; + fpinfo->width = width; + fpinfo->startup_cost = startup_cost; + fpinfo->total_cost = total_cost; + + /* Create and add foreign path to the grouping relation. */ + grouppath = create_foreign_upper_path(root, + grouped_rel, + grouped_rel->reltarget, + rows, + startup_cost, + total_cost, + NIL, /* no pathkeys */ + NULL, + NIL); /* no fdw_private */ + + /* Add generated path into grouped_rel by add_path(). */ + add_path(grouped_rel, (Path *) grouppath); +} + +/* + * add_foreign_ordered_paths + * Add foreign paths for performing the final sort remotely. + * + * Given input_rel contains the source-data Paths. The paths are added to the + * given ordered_rel. + */ +static void +add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, + RelOptInfo *ordered_rel) +{ + Query *parse = root->parse; + PgFdwRelationInfo *ifpinfo = input_rel->fdw_private; + PgFdwRelationInfo *fpinfo = ordered_rel->fdw_private; + PgFdwPathExtraData *fpextra; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + List *fdw_private; + ForeignPath *ordered_path; + ListCell *lc; + + /* Shouldn't get here unless the query has ORDER BY */ + Assert(parse->sortClause); + + /* We don't support cases where there are any SRFs in the targetlist */ + if (parse->hasTargetSRFs) + return; + + /* Save the input_rel as outerrel in fpinfo */ + fpinfo->outerrel = input_rel; + + /* + * Copy foreign table, foreign server, user mapping, FDW options etc. + * details from the input relation's fpinfo. + */ + fpinfo->table = ifpinfo->table; + fpinfo->server = ifpinfo->server; + fpinfo->user = ifpinfo->user; + merge_fdw_options(fpinfo, ifpinfo, NULL); + + /* + * If the input_rel is a base or join relation, we would already have + * considered pushing down the final sort to the remote server when + * creating pre-sorted foreign paths for that relation, because the + * query_pathkeys is set to the root->sort_pathkeys in that case (see + * standard_qp_callback()). + */ + if (input_rel->reloptkind == RELOPT_BASEREL || + input_rel->reloptkind == RELOPT_JOINREL) + { + Assert(root->query_pathkeys == root->sort_pathkeys); + + /* Safe to push down if the query_pathkeys is safe to push down */ + fpinfo->pushdown_safe = ifpinfo->qp_is_pushdown_safe; + + return; + } + + /* The input_rel should be a grouping relation */ + Assert(input_rel->reloptkind == RELOPT_UPPER_REL && + ifpinfo->stage == UPPERREL_GROUP_AGG); + + /* + * We try to create a path below by extending a simple foreign path for + * the underlying grouping relation to perform the final sort remotely, + * which is stored into the fdw_private list of the resulting path. + */ + + /* Assess if it is safe to push down the final sort */ + foreach(lc, root->sort_pathkeys) + { + PathKey *pathkey = (PathKey *) lfirst(lc); + EquivalenceClass *pathkey_ec = pathkey->pk_eclass; + + /* + * is_foreign_expr would detect volatile expressions as well, but + * checking ec_has_volatile here saves some cycles. + */ + if (pathkey_ec->ec_has_volatile) + return; + + /* + * Can't push down the sort if pathkey's opfamily is not shippable. + */ + if (!is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, + fpinfo)) + return; + + /* + * The EC must contain a shippable EM that is computed in input_rel's + * reltarget, else we can't push down the sort. + */ + if (find_em_for_rel_target(root, + pathkey_ec, + input_rel) == NULL) + return; + } + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + + /* Construct PgFdwPathExtraData */ + fpextra = (PgFdwPathExtraData *) palloc0(sizeof(PgFdwPathExtraData)); + fpextra->target = root->upper_targets[UPPERREL_ORDERED]; + fpextra->has_final_sort = true; + + /* Estimate the costs of performing the final sort remotely */ + estimate_path_cost_size(root, input_rel, NIL, root->sort_pathkeys, fpextra, + &rows, &width, &startup_cost, &total_cost); + + /* + * Build the fdw_private list that will be used by postgresGetForeignPlan. + * Items in the list must match order in enum FdwPathPrivateIndex. + */ + fdw_private = list_make2(makeBoolean(true), makeBoolean(false)); + + /* Create foreign ordering path */ + ordered_path = create_foreign_upper_path(root, + input_rel, + root->upper_targets[UPPERREL_ORDERED], + rows, + startup_cost, + total_cost, + root->sort_pathkeys, + NULL, /* no extra plan */ + fdw_private); + + /* and add it to the ordered_rel */ + add_path(ordered_rel, (Path *) ordered_path); +} + +/* + * add_foreign_final_paths + * Add foreign paths for performing the final processing remotely. + * + * Given input_rel contains the source-data Paths. The paths are added to the + * given final_rel. + */ +static void +add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, + RelOptInfo *final_rel, + FinalPathExtraData *extra) +{ + Query *parse = root->parse; + PgFdwRelationInfo *ifpinfo = (PgFdwRelationInfo *) input_rel->fdw_private; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) final_rel->fdw_private; + bool has_final_sort = false; + List *pathkeys = NIL; + PgFdwPathExtraData *fpextra; + bool save_use_remote_estimate = false; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + List *fdw_private; + ForeignPath *final_path; + + /* + * Currently, we only support this for SELECT commands + */ + if (parse->commandType != CMD_SELECT) + return; + + /* + * No work if there is no FOR UPDATE/SHARE clause and if there is no need + * to add a LIMIT node + */ + if (!parse->rowMarks && !extra->limit_needed) + return; + + /* We don't support cases where there are any SRFs in the targetlist */ + if (parse->hasTargetSRFs) + return; + + /* Save the input_rel as outerrel in fpinfo */ + fpinfo->outerrel = input_rel; + + /* + * Copy foreign table, foreign server, user mapping, FDW options etc. + * details from the input relation's fpinfo. + */ + fpinfo->table = ifpinfo->table; + fpinfo->server = ifpinfo->server; + fpinfo->user = ifpinfo->user; + merge_fdw_options(fpinfo, ifpinfo, NULL); + + /* + * If there is no need to add a LIMIT node, there might be a ForeignPath + * in the input_rel's pathlist that implements all behavior of the query. + * Note: we would already have accounted for the query's FOR UPDATE/SHARE + * (if any) before we get here. + */ + if (!extra->limit_needed) + { + ListCell *lc; + + Assert(parse->rowMarks); + + /* + * Grouping and aggregation are not supported with FOR UPDATE/SHARE, + * so the input_rel should be a base, join, or ordered relation; and + * if it's an ordered relation, its input relation should be a base or + * join relation. + */ + Assert(input_rel->reloptkind == RELOPT_BASEREL || + input_rel->reloptkind == RELOPT_JOINREL || + (input_rel->reloptkind == RELOPT_UPPER_REL && + ifpinfo->stage == UPPERREL_ORDERED && + (ifpinfo->outerrel->reloptkind == RELOPT_BASEREL || + ifpinfo->outerrel->reloptkind == RELOPT_JOINREL))); + + foreach(lc, input_rel->pathlist) + { + Path *path = (Path *) lfirst(lc); + + /* + * apply_scanjoin_target_to_paths() uses create_projection_path() + * to adjust each of its input paths if needed, whereas + * create_ordered_paths() uses apply_projection_to_path() to do + * that. So the former might have put a ProjectionPath on top of + * the ForeignPath; look through ProjectionPath and see if the + * path underneath it is ForeignPath. + */ + if (IsA(path, ForeignPath) || + (IsA(path, ProjectionPath) && + IsA(((ProjectionPath *) path)->subpath, ForeignPath))) + { + /* + * Create foreign final path; this gets rid of a + * no-longer-needed outer plan (if any), which makes the + * EXPLAIN output look cleaner + */ + final_path = create_foreign_upper_path(root, + path->parent, + path->pathtarget, + path->rows, + path->startup_cost, + path->total_cost, + path->pathkeys, + NULL, /* no extra plan */ + NULL); /* no fdw_private */ + + /* and add it to the final_rel */ + add_path(final_rel, (Path *) final_path); + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + + return; + } + } + + /* + * If we get here it means no ForeignPaths; since we would already + * have considered pushing down all operations for the query to the + * remote server, give up on it. + */ + return; + } + + Assert(extra->limit_needed); + + /* + * If the input_rel is an ordered relation, replace the input_rel with its + * input relation + */ + if (input_rel->reloptkind == RELOPT_UPPER_REL && + ifpinfo->stage == UPPERREL_ORDERED) + { + input_rel = ifpinfo->outerrel; + ifpinfo = (PgFdwRelationInfo *) input_rel->fdw_private; + has_final_sort = true; + pathkeys = root->sort_pathkeys; + } + + /* The input_rel should be a base, join, or grouping relation */ + Assert(input_rel->reloptkind == RELOPT_BASEREL || + input_rel->reloptkind == RELOPT_JOINREL || + (input_rel->reloptkind == RELOPT_UPPER_REL && + ifpinfo->stage == UPPERREL_GROUP_AGG)); + + /* + * We try to create a path below by extending a simple foreign path for + * the underlying base, join, or grouping relation to perform the final + * sort (if has_final_sort) and the LIMIT restriction remotely, which is + * stored into the fdw_private list of the resulting path. (We + * re-estimate the costs of sorting the underlying relation, if + * has_final_sort.) + */ + + /* + * Assess if it is safe to push down the LIMIT and OFFSET to the remote + * server + */ + + /* + * If the underlying relation has any local conditions, the LIMIT/OFFSET + * cannot be pushed down. + */ + if (ifpinfo->local_conds) + return; + + /* + * Also, the LIMIT/OFFSET cannot be pushed down, if their expressions are + * not safe to remote. + */ + if (!is_foreign_expr(root, input_rel, (Expr *) parse->limitOffset) || + !is_foreign_expr(root, input_rel, (Expr *) parse->limitCount)) + return; + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + + /* Construct PgFdwPathExtraData */ + fpextra = (PgFdwPathExtraData *) palloc0(sizeof(PgFdwPathExtraData)); + fpextra->target = root->upper_targets[UPPERREL_FINAL]; + fpextra->has_final_sort = has_final_sort; + fpextra->has_limit = extra->limit_needed; + fpextra->limit_tuples = extra->limit_tuples; + fpextra->count_est = extra->count_est; + fpextra->offset_est = extra->offset_est; + + /* + * Estimate the costs of performing the final sort and the LIMIT + * restriction remotely. If has_final_sort is false, we wouldn't need to + * execute EXPLAIN anymore if use_remote_estimate, since the costs can be + * roughly estimated using the costs we already have for the underlying + * relation, in the same way as when use_remote_estimate is false. Since + * it's pretty expensive to execute EXPLAIN, force use_remote_estimate to + * false in that case. + */ + if (!fpextra->has_final_sort) + { + save_use_remote_estimate = ifpinfo->use_remote_estimate; + ifpinfo->use_remote_estimate = false; + } + estimate_path_cost_size(root, input_rel, NIL, pathkeys, fpextra, + &rows, &width, &startup_cost, &total_cost); + if (!fpextra->has_final_sort) + ifpinfo->use_remote_estimate = save_use_remote_estimate; + + /* + * Build the fdw_private list that will be used by postgresGetForeignPlan. + * Items in the list must match order in enum FdwPathPrivateIndex. + */ + fdw_private = list_make2(makeBoolean(has_final_sort), + makeBoolean(extra->limit_needed)); + + /* + * Create foreign final path; this gets rid of a no-longer-needed outer + * plan (if any), which makes the EXPLAIN output look cleaner + */ + final_path = create_foreign_upper_path(root, + input_rel, + root->upper_targets[UPPERREL_FINAL], + rows, + startup_cost, + total_cost, + pathkeys, + NULL, /* no extra plan */ + fdw_private); + + /* and add it to the final_rel */ + add_path(final_rel, (Path *) final_path); +} + +/* + * postgresIsForeignPathAsyncCapable + * Check whether a given ForeignPath node is async-capable. + */ +static bool +postgresIsForeignPathAsyncCapable(ForeignPath *path) +{ + RelOptInfo *rel = ((Path *) path)->parent; + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private; + + return fpinfo->async_capable; +} + +/* + * postgresForeignAsyncRequest + * Asynchronously request next tuple from a foreign PostgreSQL table. + */ +static void +postgresForeignAsyncRequest(AsyncRequest *areq) +{ + produce_tuple_asynchronously(areq, true); +} + +/* + * postgresForeignAsyncConfigureWait + * Configure a file descriptor event for which we wish to wait. + */ +static void +postgresForeignAsyncConfigureWait(AsyncRequest *areq) +{ + ForeignScanState *node = (ForeignScanState *) areq->requestee; + PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state; + AsyncRequest *pendingAreq = fsstate->conn_state->pendingAreq; + AppendState *requestor = (AppendState *) areq->requestor; + WaitEventSet *set = requestor->as_eventset; + + /* This should not be called unless callback_pending */ + Assert(areq->callback_pending); + + /* + * If process_pending_request() has been invoked on the given request + * before we get here, we might have some tuples already; in which case + * complete the request + */ + if (fsstate->next_tuple < fsstate->num_tuples) + { + complete_pending_request(areq); + if (areq->request_complete) + return; + Assert(areq->callback_pending); + } + + /* We must have run out of tuples */ + Assert(fsstate->next_tuple >= fsstate->num_tuples); + + /* The core code would have registered postmaster death event */ + Assert(GetNumRegisteredWaitEvents(set) >= 1); + + /* Begin an asynchronous data fetch if not already done */ + if (!pendingAreq) + fetch_more_data_begin(areq); + else if (pendingAreq->requestor != areq->requestor) + { + /* + * This is the case when the in-process request was made by another + * Append. Note that it might be useless to process the request, + * because the query might not need tuples from that Append anymore. + * If there are any child subplans of the same parent that are ready + * for new requests, skip the given request. Likewise, if there are + * any configured events other than the postmaster death event, skip + * it. Otherwise, process the in-process request, then begin a fetch + * to configure the event below, because we might otherwise end up + * with no configured events other than the postmaster death event. + */ + if (!bms_is_empty(requestor->as_needrequest)) + return; + if (GetNumRegisteredWaitEvents(set) > 1) + return; + process_pending_request(pendingAreq); + fetch_more_data_begin(areq); + } + else if (pendingAreq->requestee != areq->requestee) + { + /* + * This is the case when the in-process request was made by the same + * parent but for a different child. Since we configure only the + * event for the request made for that child, skip the given request. + */ + return; + } + else + Assert(pendingAreq == areq); + + AddWaitEventToSet(set, WL_SOCKET_READABLE, PQsocket(fsstate->conn), + NULL, areq); +} + +/* + * postgresForeignAsyncNotify + * Fetch some more tuples from a file descriptor that becomes ready, + * requesting next tuple. + */ +static void +postgresForeignAsyncNotify(AsyncRequest *areq) +{ + ForeignScanState *node = (ForeignScanState *) areq->requestee; + PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state; + + /* The core code would have initialized the callback_pending flag */ + Assert(!areq->callback_pending); + + /* + * If process_pending_request() has been invoked on the given request + * before we get here, we might have some tuples already; in which case + * produce the next tuple + */ + if (fsstate->next_tuple < fsstate->num_tuples) + { + produce_tuple_asynchronously(areq, true); + return; + } + + /* We must have run out of tuples */ + Assert(fsstate->next_tuple >= fsstate->num_tuples); + + /* The request should be currently in-process */ + Assert(fsstate->conn_state->pendingAreq == areq); + + /* On error, report the original query, not the FETCH. */ + if (!PQconsumeInput(fsstate->conn)) + pgfdw_report_error(ERROR, NULL, fsstate->conn, false, fsstate->query); + + fetch_more_data(node); + + produce_tuple_asynchronously(areq, true); +} + +/* + * Asynchronously produce next tuple from a foreign PostgreSQL table. + */ +static void +produce_tuple_asynchronously(AsyncRequest *areq, bool fetch) +{ + ForeignScanState *node = (ForeignScanState *) areq->requestee; + PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state; + AsyncRequest *pendingAreq = fsstate->conn_state->pendingAreq; + TupleTableSlot *result; + + /* This should not be called if the request is currently in-process */ + Assert(areq != pendingAreq); + + /* Fetch some more tuples, if we've run out */ + if (fsstate->next_tuple >= fsstate->num_tuples) + { + /* No point in another fetch if we already detected EOF, though */ + if (!fsstate->eof_reached) + { + /* Mark the request as pending for a callback */ + ExecAsyncRequestPending(areq); + /* Begin another fetch if requested and if no pending request */ + if (fetch && !pendingAreq) + fetch_more_data_begin(areq); + } + else + { + /* There's nothing more to do; just return a NULL pointer */ + result = NULL; + /* Mark the request as complete */ + ExecAsyncRequestDone(areq, result); + } + return; + } + + /* Get a tuple from the ForeignScan node */ + result = areq->requestee->ExecProcNodeReal(areq->requestee); + if (!TupIsNull(result)) + { + /* Mark the request as complete */ + ExecAsyncRequestDone(areq, result); + return; + } + + /* We must have run out of tuples */ + Assert(fsstate->next_tuple >= fsstate->num_tuples); + + /* Fetch some more tuples, if we've not detected EOF yet */ + if (!fsstate->eof_reached) + { + /* Mark the request as pending for a callback */ + ExecAsyncRequestPending(areq); + /* Begin another fetch if requested and if no pending request */ + if (fetch && !pendingAreq) + fetch_more_data_begin(areq); + } + else + { + /* There's nothing more to do; just return a NULL pointer */ + result = NULL; + /* Mark the request as complete */ + ExecAsyncRequestDone(areq, result); + } +} + +/* + * Begin an asynchronous data fetch. + * + * Note: this function assumes there is no currently-in-progress asynchronous + * data fetch. + * + * Note: fetch_more_data must be called to fetch the result. + */ +static void +fetch_more_data_begin(AsyncRequest *areq) +{ + ForeignScanState *node = (ForeignScanState *) areq->requestee; + PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state; + char sql[64]; + + Assert(!fsstate->conn_state->pendingAreq); + + /* Create the cursor synchronously. */ + if (!fsstate->cursor_exists) + create_cursor(node); + + /* We will send this query, but not wait for the response. */ + snprintf(sql, sizeof(sql), "FETCH %d FROM c%u", + fsstate->fetch_size, fsstate->cursor_number); + + if (!PQsendQuery(fsstate->conn, sql)) + pgfdw_report_error(ERROR, NULL, fsstate->conn, false, fsstate->query); + + /* Remember that the request is in process */ + fsstate->conn_state->pendingAreq = areq; +} + +/* + * Process a pending asynchronous request. + */ +void +process_pending_request(AsyncRequest *areq) +{ + ForeignScanState *node = (ForeignScanState *) areq->requestee; + PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state; + + /* The request would have been pending for a callback */ + Assert(areq->callback_pending); + + /* The request should be currently in-process */ + Assert(fsstate->conn_state->pendingAreq == areq); + + fetch_more_data(node); + + /* + * If we didn't get any tuples, must be end of data; complete the request + * now. Otherwise, we postpone completing the request until we are called + * from postgresForeignAsyncConfigureWait()/postgresForeignAsyncNotify(). + */ + if (fsstate->next_tuple >= fsstate->num_tuples) + { + /* Unlike AsyncNotify, we unset callback_pending ourselves */ + areq->callback_pending = false; + /* Mark the request as complete */ + ExecAsyncRequestDone(areq, NULL); + /* Unlike AsyncNotify, we call ExecAsyncResponse ourselves */ + ExecAsyncResponse(areq); + } +} + +/* + * Complete a pending asynchronous request. + */ +static void +complete_pending_request(AsyncRequest *areq) +{ + /* The request would have been pending for a callback */ + Assert(areq->callback_pending); + + /* Unlike AsyncNotify, we unset callback_pending ourselves */ + areq->callback_pending = false; + + /* We begin a fetch afterwards if necessary; don't fetch */ + produce_tuple_asynchronously(areq, false); + + /* Unlike AsyncNotify, we call ExecAsyncResponse ourselves */ + ExecAsyncResponse(areq); + + /* Also, we do instrumentation ourselves, if required */ + if (areq->requestee->instrument) + InstrUpdateTupleCount(areq->requestee->instrument, + TupIsNull(areq->result) ? 0.0 : 1.0); +} + +/* + * Create a tuple from the specified row of the PGresult. + * + * rel is the local representation of the foreign table, attinmeta is + * conversion data for the rel's tupdesc, and retrieved_attrs is an + * integer list of the table column numbers present in the PGresult. + * fsstate is the ForeignScan plan node's execution state. + * temp_context is a working context that can be reset after each tuple. + * + * Note: either rel or fsstate, but not both, can be NULL. rel is NULL + * if we're processing a remote join, while fsstate is NULL in a non-query + * context such as ANALYZE, or if we're processing a non-scan query node. + */ +static HeapTuple +make_tuple_from_result_row(PGresult *res, + int row, + Relation rel, + AttInMetadata *attinmeta, + List *retrieved_attrs, + ForeignScanState *fsstate, + MemoryContext temp_context) +{ + HeapTuple tuple; + TupleDesc tupdesc; + Datum *values; + bool *nulls; + ItemPointer ctid = NULL; + ConversionLocation errpos; + ErrorContextCallback errcallback; + MemoryContext oldcontext; + ListCell *lc; + int j; + + Assert(row < PQntuples(res)); + + /* + * Do the following work in a temp context that we reset after each tuple. + * This cleans up not only the data we have direct access to, but any + * cruft the I/O functions might leak. + */ + oldcontext = MemoryContextSwitchTo(temp_context); + + /* + * Get the tuple descriptor for the row. Use the rel's tupdesc if rel is + * provided, otherwise look to the scan node's ScanTupleSlot. + */ + if (rel) + tupdesc = RelationGetDescr(rel); + else + { + Assert(fsstate); + tupdesc = fsstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; + } + + values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum)); + nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); + /* Initialize to nulls for any columns not present in result */ + memset(nulls, true, tupdesc->natts * sizeof(bool)); + + /* + * Set up and install callback to report where conversion error occurs. + */ + errpos.cur_attno = 0; + errpos.rel = rel; + errpos.fsstate = fsstate; + errcallback.callback = conversion_error_callback; + errcallback.arg = (void *) &errpos; + errcallback.previous = error_context_stack; + error_context_stack = &errcallback; + + /* + * i indexes columns in the relation, j indexes columns in the PGresult. + */ + j = 0; + foreach(lc, retrieved_attrs) + { + int i = lfirst_int(lc); + char *valstr; + + /* fetch next column's textual value */ + if (PQgetisnull(res, row, j)) + valstr = NULL; + else + valstr = PQgetvalue(res, row, j); + + /* + * convert value to internal representation + * + * Note: we ignore system columns other than ctid and oid in result + */ + errpos.cur_attno = i; + if (i > 0) + { + /* ordinary column */ + Assert(i <= tupdesc->natts); + nulls[i - 1] = (valstr == NULL); + /* Apply the input function even to nulls, to support domains */ + values[i - 1] = InputFunctionCall(&attinmeta->attinfuncs[i - 1], + valstr, + attinmeta->attioparams[i - 1], + attinmeta->atttypmods[i - 1]); + } + else if (i == SelfItemPointerAttributeNumber) + { + /* ctid */ + if (valstr != NULL) + { + Datum datum; + + datum = DirectFunctionCall1(tidin, CStringGetDatum(valstr)); + ctid = (ItemPointer) DatumGetPointer(datum); + } + } + errpos.cur_attno = 0; + + j++; + } + + /* Uninstall error context callback. */ + error_context_stack = errcallback.previous; + + /* + * Check we got the expected number of columns. Note: j == 0 and + * PQnfields == 1 is expected, since deparse emits a NULL if no columns. + */ + if (j > 0 && j != PQnfields(res)) + elog(ERROR, "remote query result does not match the foreign table"); + + /* + * Build the result tuple in caller's memory context. + */ + MemoryContextSwitchTo(oldcontext); + + tuple = heap_form_tuple(tupdesc, values, nulls); + + /* + * If we have a CTID to return, install it in both t_self and t_ctid. + * t_self is the normal place, but if the tuple is converted to a + * composite Datum, t_self will be lost; setting t_ctid allows CTID to be + * preserved during EvalPlanQual re-evaluations (see ROW_MARK_COPY code). + */ + if (ctid) + tuple->t_self = tuple->t_data->t_ctid = *ctid; + + /* + * Stomp on the xmin, xmax, and cmin fields from the tuple created by + * heap_form_tuple. heap_form_tuple actually creates the tuple with + * DatumTupleFields, not HeapTupleFields, but the executor expects + * HeapTupleFields and will happily extract system columns on that + * assumption. If we don't do this then, for example, the tuple length + * ends up in the xmin field, which isn't what we want. + */ + HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId); + HeapTupleHeaderSetXmin(tuple->t_data, InvalidTransactionId); + HeapTupleHeaderSetCmin(tuple->t_data, InvalidTransactionId); + + /* Clean up */ + MemoryContextReset(temp_context); + + return tuple; +} + +/* + * Callback function which is called when error occurs during column value + * conversion. Print names of column and relation. + * + * Note that this function mustn't do any catalog lookups, since we are in + * an already-failed transaction. Fortunately, we can get the needed info + * from the relation or the query's rangetable instead. + */ +static void +conversion_error_callback(void *arg) +{ + ConversionLocation *errpos = (ConversionLocation *) arg; + Relation rel = errpos->rel; + ForeignScanState *fsstate = errpos->fsstate; + const char *attname = NULL; + const char *relname = NULL; + bool is_wholerow = false; + + /* + * If we're in a scan node, always use aliases from the rangetable, for + * consistency between the simple-relation and remote-join cases. Look at + * the relation's tupdesc only if we're not in a scan node. + */ + if (fsstate) + { + /* ForeignScan case */ + ForeignScan *fsplan = castNode(ForeignScan, fsstate->ss.ps.plan); + int varno = 0; + AttrNumber colno = 0; + + if (fsplan->scan.scanrelid > 0) + { + /* error occurred in a scan against a foreign table */ + varno = fsplan->scan.scanrelid; + colno = errpos->cur_attno; + } + else + { + /* error occurred in a scan against a foreign join */ + TargetEntry *tle; + + tle = list_nth_node(TargetEntry, fsplan->fdw_scan_tlist, + errpos->cur_attno - 1); + + /* + * Target list can have Vars and expressions. For Vars, we can + * get some information, however for expressions we can't. Thus + * for expressions, just show generic context message. + */ + if (IsA(tle->expr, Var)) + { + Var *var = (Var *) tle->expr; + + varno = var->varno; + colno = var->varattno; + } + } + + if (varno > 0) + { + EState *estate = fsstate->ss.ps.state; + RangeTblEntry *rte = exec_rt_fetch(varno, estate); + + relname = rte->eref->aliasname; + + if (colno == 0) + is_wholerow = true; + else if (colno > 0 && colno <= list_length(rte->eref->colnames)) + attname = strVal(list_nth(rte->eref->colnames, colno - 1)); + else if (colno == SelfItemPointerAttributeNumber) + attname = "ctid"; + } + } + else if (rel) + { + /* Non-ForeignScan case (we should always have a rel here) */ + TupleDesc tupdesc = RelationGetDescr(rel); + + relname = RelationGetRelationName(rel); + if (errpos->cur_attno > 0 && errpos->cur_attno <= tupdesc->natts) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, + errpos->cur_attno - 1); + + attname = NameStr(attr->attname); + } + else if (errpos->cur_attno == SelfItemPointerAttributeNumber) + attname = "ctid"; + } + + if (relname && is_wholerow) + errcontext("whole-row reference to foreign table \"%s\"", relname); + else if (relname && attname) + errcontext("column \"%s\" of foreign table \"%s\"", attname, relname); + else + errcontext("processing expression at position %d in select list", + errpos->cur_attno); +} + +/* + * Given an EquivalenceClass and a foreign relation, find an EC member + * that can be used to sort the relation remotely according to a pathkey + * using this EC. + * + * If there is more than one suitable candidate, return an arbitrary + * one of them. If there is none, return NULL. + * + * This checks that the EC member expression uses only Vars from the given + * rel and is shippable. Caller must separately verify that the pathkey's + * ordering operator is shippable. + */ +EquivalenceMember * +find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel) +{ + ListCell *lc; + + foreach(lc, ec->ec_members) + { + EquivalenceMember *em = (EquivalenceMember *) lfirst(lc); + + /* + * Note we require !bms_is_empty, else we'd accept constant + * expressions which are not suitable for the purpose. + */ + if (bms_is_subset(em->em_relids, rel->relids) && + !bms_is_empty(em->em_relids) && + is_foreign_expr(root, rel, em->em_expr)) + return em; + } + + return NULL; +} + +/* + * Find an EquivalenceClass member that is to be computed as a sort column + * in the given rel's reltarget, and is shippable. + * + * If there is more than one suitable candidate, return an arbitrary + * one of them. If there is none, return NULL. + * + * This checks that the EC member expression uses only Vars from the given + * rel and is shippable. Caller must separately verify that the pathkey's + * ordering operator is shippable. + */ +EquivalenceMember * +find_em_for_rel_target(PlannerInfo *root, EquivalenceClass *ec, + RelOptInfo *rel) +{ + PathTarget *target = rel->reltarget; + ListCell *lc1; + int i; + + i = 0; + foreach(lc1, target->exprs) + { + Expr *expr = (Expr *) lfirst(lc1); + Index sgref = get_pathtarget_sortgroupref(target, i); + ListCell *lc2; + + /* Ignore non-sort expressions */ + if (sgref == 0 || + get_sortgroupref_clause_noerr(sgref, + root->parse->sortClause) == NULL) + { + i++; + continue; + } + + /* We ignore binary-compatible relabeling on both ends */ + while (expr && IsA(expr, RelabelType)) + expr = ((RelabelType *) expr)->arg; + + /* Locate an EquivalenceClass member matching this expr, if any */ + foreach(lc2, ec->ec_members) + { + EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2); + Expr *em_expr; + + /* Don't match constants */ + if (em->em_is_const) + continue; + + /* Ignore child members */ + if (em->em_is_child) + continue; + + /* Match if same expression (after stripping relabel) */ + em_expr = em->em_expr; + while (em_expr && IsA(em_expr, RelabelType)) + em_expr = ((RelabelType *) em_expr)->arg; + + if (!equal(em_expr, expr)) + continue; + + /* Check that expression (including relabels!) is shippable */ + if (is_foreign_expr(root, rel, em->em_expr)) + return em; + } + + i++; + } + + return NULL; +} + +/* + * Determine batch size for a given foreign table. The option specified for + * a table has precedence. + */ +static int +get_batch_size_option(Relation rel) +{ + Oid foreigntableid = RelationGetRelid(rel); + ForeignTable *table; + ForeignServer *server; + List *options; + ListCell *lc; + + /* we use 1 by default, which means "no batching" */ + int batch_size = 1; + + /* + * Load options for table and server. We append server options after table + * options, because table options take precedence. + */ + table = GetForeignTable(foreigntableid); + server = GetForeignServer(table->serverid); + + options = NIL; + options = list_concat(options, table->options); + options = list_concat(options, server->options); + + /* See if either table or server specifies batch_size. */ + foreach(lc, options) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "batch_size") == 0) + { + (void) parse_int(defGetString(def), &batch_size, 0, NULL); + break; + } + } + + return batch_size; +} diff --git a/contrib/postgres_fdw/postgres_fdw.control b/contrib/postgres_fdw/postgres_fdw.control new file mode 100644 index 0000000..d489382 --- /dev/null +++ b/contrib/postgres_fdw/postgres_fdw.control @@ -0,0 +1,5 @@ +# postgres_fdw extension +comment = 'foreign-data wrapper for remote PostgreSQL servers' +default_version = '1.1' +module_pathname = '$libdir/postgres_fdw' +relocatable = true diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h new file mode 100644 index 0000000..02c1152 --- /dev/null +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -0,0 +1,255 @@ +/*------------------------------------------------------------------------- + * + * postgres_fdw.h + * Foreign-data wrapper for remote PostgreSQL servers + * + * Portions Copyright (c) 2012-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/postgres_fdw/postgres_fdw.h + * + *------------------------------------------------------------------------- + */ +#ifndef POSTGRES_FDW_H +#define POSTGRES_FDW_H + +#include "foreign/foreign.h" +#include "lib/stringinfo.h" +#include "libpq-fe.h" +#include "nodes/execnodes.h" +#include "nodes/pathnodes.h" +#include "utils/relcache.h" + +/* + * FDW-specific planner information kept in RelOptInfo.fdw_private for a + * postgres_fdw foreign table. For a baserel, this struct is created by + * postgresGetForeignRelSize, although some fields are not filled till later. + * postgresGetForeignJoinPaths creates it for a joinrel, and + * postgresGetForeignUpperPaths creates it for an upperrel. + */ +typedef struct PgFdwRelationInfo +{ + /* + * True means that the relation can be pushed down. Always true for simple + * foreign scan. + */ + bool pushdown_safe; + + /* + * Restriction clauses, divided into safe and unsafe to pushdown subsets. + * All entries in these lists should have RestrictInfo wrappers; that + * improves efficiency of selectivity and cost estimation. + */ + List *remote_conds; + List *local_conds; + + /* Actual remote restriction clauses for scan (sans RestrictInfos) */ + List *final_remote_exprs; + + /* Bitmap of attr numbers we need to fetch from the remote server. */ + Bitmapset *attrs_used; + + /* True means that the query_pathkeys is safe to push down */ + bool qp_is_pushdown_safe; + + /* Cost and selectivity of local_conds. */ + QualCost local_conds_cost; + Selectivity local_conds_sel; + + /* Selectivity of join conditions */ + Selectivity joinclause_sel; + + /* Estimated size and cost for a scan, join, or grouping/aggregation. */ + double rows; + int width; + Cost startup_cost; + Cost total_cost; + + /* + * Estimated number of rows fetched from the foreign server, and costs + * excluding costs for transferring those rows from the foreign server. + * These are only used by estimate_path_cost_size(). + */ + double retrieved_rows; + Cost rel_startup_cost; + Cost rel_total_cost; + + /* Options extracted from catalogs. */ + bool use_remote_estimate; + Cost fdw_startup_cost; + Cost fdw_tuple_cost; + List *shippable_extensions; /* OIDs of shippable extensions */ + bool async_capable; + + /* Cached catalog information. */ + ForeignTable *table; + ForeignServer *server; + UserMapping *user; /* only set in use_remote_estimate mode */ + + int fetch_size; /* fetch size for this remote table */ + + /* + * Name of the relation, for use while EXPLAINing ForeignScan. It is used + * for join and upper relations but is set for all relations. For a base + * relation, this is really just the RT index as a string; we convert that + * while producing EXPLAIN output. For join and upper relations, the name + * indicates which base foreign tables are included and the join type or + * aggregation type used. + */ + char *relation_name; + + /* Join information */ + RelOptInfo *outerrel; + RelOptInfo *innerrel; + JoinType jointype; + /* joinclauses contains only JOIN/ON conditions for an outer join */ + List *joinclauses; /* List of RestrictInfo */ + + /* Upper relation information */ + UpperRelationKind stage; + + /* Grouping information */ + List *grouped_tlist; + + /* Subquery information */ + bool make_outerrel_subquery; /* do we deparse outerrel as a + * subquery? */ + bool make_innerrel_subquery; /* do we deparse innerrel as a + * subquery? */ + Relids lower_subquery_rels; /* all relids appearing in lower + * subqueries */ + + /* + * Index of the relation. It is used to create an alias to a subquery + * representing the relation. + */ + int relation_index; +} PgFdwRelationInfo; + +/* + * Extra control information relating to a connection. + */ +typedef struct PgFdwConnState +{ + AsyncRequest *pendingAreq; /* pending async request */ +} PgFdwConnState; + +/* + * Method used by ANALYZE to sample remote rows. + */ +typedef enum PgFdwSamplingMethod +{ + ANALYZE_SAMPLE_OFF, /* no remote sampling */ + ANALYZE_SAMPLE_AUTO, /* choose by server version */ + ANALYZE_SAMPLE_RANDOM, /* remote random() */ + ANALYZE_SAMPLE_SYSTEM, /* TABLESAMPLE system */ + ANALYZE_SAMPLE_BERNOULLI /* TABLESAMPLE bernoulli */ +} PgFdwSamplingMethod; + +/* in postgres_fdw.c */ +extern int set_transmission_modes(void); +extern void reset_transmission_modes(int nestlevel); +extern void process_pending_request(AsyncRequest *areq); + +/* in connection.c */ +extern PGconn *GetConnection(UserMapping *user, bool will_prep_stmt, + PgFdwConnState **state); +extern void ReleaseConnection(PGconn *conn); +extern unsigned int GetCursorNumber(PGconn *conn); +extern unsigned int GetPrepStmtNumber(PGconn *conn); +extern void do_sql_command(PGconn *conn, const char *sql); +extern PGresult *pgfdw_get_result(PGconn *conn, const char *query); +extern PGresult *pgfdw_exec_query(PGconn *conn, const char *query, + PgFdwConnState *state); +extern void pgfdw_report_error(int elevel, PGresult *res, PGconn *conn, + bool clear, const char *sql); + +/* in option.c */ +extern int ExtractConnectionOptions(List *defelems, + const char **keywords, + const char **values); +extern List *ExtractExtensionList(const char *extensionsString, + bool warnOnMissing); +extern char *process_pgfdw_appname(const char *appname); +extern char *pgfdw_application_name; + +/* in deparse.c */ +extern void classifyConditions(PlannerInfo *root, + RelOptInfo *baserel, + List *input_conds, + List **remote_conds, + List **local_conds); +extern bool is_foreign_expr(PlannerInfo *root, + RelOptInfo *baserel, + Expr *expr); +extern bool is_foreign_param(PlannerInfo *root, + RelOptInfo *baserel, + Expr *expr); +extern bool is_foreign_pathkey(PlannerInfo *root, + RelOptInfo *baserel, + PathKey *pathkey); +extern void deparseInsertSql(StringInfo buf, RangeTblEntry *rte, + Index rtindex, Relation rel, + List *targetAttrs, bool doNothing, + List *withCheckOptionList, List *returningList, + List **retrieved_attrs, int *values_end_len); +extern void rebuildInsertSql(StringInfo buf, Relation rel, + char *orig_query, List *target_attrs, + int values_end_len, int num_params, + int num_rows); +extern void deparseUpdateSql(StringInfo buf, RangeTblEntry *rte, + Index rtindex, Relation rel, + List *targetAttrs, + List *withCheckOptionList, List *returningList, + List **retrieved_attrs); +extern void deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, + Index rtindex, Relation rel, + RelOptInfo *foreignrel, + List *targetlist, + List *targetAttrs, + List *remote_conds, + List **params_list, + List *returningList, + List **retrieved_attrs); +extern void deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, + Index rtindex, Relation rel, + List *returningList, + List **retrieved_attrs); +extern void deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, + Index rtindex, Relation rel, + RelOptInfo *foreignrel, + List *remote_conds, + List **params_list, + List *returningList, + List **retrieved_attrs); +extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel); +extern void deparseAnalyzeInfoSql(StringInfo buf, Relation rel); +extern void deparseAnalyzeSql(StringInfo buf, Relation rel, + PgFdwSamplingMethod sample_method, + double sample_frac, + List **retrieved_attrs); +extern void deparseTruncateSql(StringInfo buf, + List *rels, + DropBehavior behavior, + bool restart_seqs); +extern void deparseStringLiteral(StringInfo buf, const char *val); +extern EquivalenceMember *find_em_for_rel(PlannerInfo *root, + EquivalenceClass *ec, + RelOptInfo *rel); +extern EquivalenceMember *find_em_for_rel_target(PlannerInfo *root, + EquivalenceClass *ec, + RelOptInfo *rel); +extern List *build_tlist_to_deparse(RelOptInfo *foreignrel); +extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, + RelOptInfo *rel, List *tlist, + List *remote_conds, List *pathkeys, + bool has_final_sort, bool has_limit, + bool is_subquery, + List **retrieved_attrs, List **params_list); +extern const char *get_jointype_name(JoinType jointype); + +/* in shippable.c */ +extern bool is_builtin(Oid objectId); +extern bool is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo); + +#endif /* POSTGRES_FDW_H */ diff --git a/contrib/postgres_fdw/shippable.c b/contrib/postgres_fdw/shippable.c new file mode 100644 index 0000000..07c11b7 --- /dev/null +++ b/contrib/postgres_fdw/shippable.c @@ -0,0 +1,205 @@ +/*------------------------------------------------------------------------- + * + * shippable.c + * Determine which database objects are shippable to a remote server. + * + * We need to determine whether particular functions, operators, and indeed + * data types are shippable to a remote server for execution --- that is, + * do they exist and have the same behavior remotely as they do locally? + * Built-in objects are generally considered shippable. Other objects can + * be shipped if they are declared as such by the user. + * + * Note: there are additional filter rules that prevent shipping mutable + * functions or functions using nonportable collations. Those considerations + * need not be accounted for here. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/postgres_fdw/shippable.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/transam.h" +#include "catalog/dependency.h" +#include "postgres_fdw.h" +#include "utils/hsearch.h" +#include "utils/inval.h" +#include "utils/syscache.h" + +/* Hash table for caching the results of shippability lookups */ +static HTAB *ShippableCacheHash = NULL; + +/* + * Hash key for shippability lookups. We include the FDW server OID because + * decisions may differ per-server. Otherwise, objects are identified by + * their (local!) OID and catalog OID. + */ +typedef struct +{ + /* XXX we assume this struct contains no padding bytes */ + Oid objid; /* function/operator/type OID */ + Oid classid; /* OID of its catalog (pg_proc, etc) */ + Oid serverid; /* FDW server we are concerned with */ +} ShippableCacheKey; + +typedef struct +{ + ShippableCacheKey key; /* hash key - must be first */ + bool shippable; +} ShippableCacheEntry; + + +/* + * Flush cache entries when pg_foreign_server is updated. + * + * We do this because of the possibility of ALTER SERVER being used to change + * a server's extensions option. We do not currently bother to check whether + * objects' extension membership changes once a shippability decision has been + * made for them, however. + */ +static void +InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + HASH_SEQ_STATUS status; + ShippableCacheEntry *entry; + + /* + * In principle we could flush only cache entries relating to the + * pg_foreign_server entry being outdated; but that would be more + * complicated, and it's probably not worth the trouble. So for now, just + * flush all entries. + */ + hash_seq_init(&status, ShippableCacheHash); + while ((entry = (ShippableCacheEntry *) hash_seq_search(&status)) != NULL) + { + if (hash_search(ShippableCacheHash, + &entry->key, + HASH_REMOVE, + NULL) == NULL) + elog(ERROR, "hash table corrupted"); + } +} + +/* + * Initialize the backend-lifespan cache of shippability decisions. + */ +static void +InitializeShippableCache(void) +{ + HASHCTL ctl; + + /* Create the hash table. */ + ctl.keysize = sizeof(ShippableCacheKey); + ctl.entrysize = sizeof(ShippableCacheEntry); + ShippableCacheHash = + hash_create("Shippability cache", 256, &ctl, HASH_ELEM | HASH_BLOBS); + + /* Set up invalidation callback on pg_foreign_server. */ + CacheRegisterSyscacheCallback(FOREIGNSERVEROID, + InvalidateShippableCacheCallback, + (Datum) 0); +} + +/* + * Returns true if given object (operator/function/type) is shippable + * according to the server options. + * + * Right now "shippability" is exclusively a function of whether the object + * belongs to an extension declared by the user. In the future we could + * additionally have a list of functions/operators declared one at a time. + */ +static bool +lookup_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo) +{ + Oid extensionOid; + + /* + * Is object a member of some extension? (Note: this is a fairly + * expensive lookup, which is why we try to cache the results.) + */ + extensionOid = getExtensionOfObject(classId, objectId); + + /* If so, is that extension in fpinfo->shippable_extensions? */ + if (OidIsValid(extensionOid) && + list_member_oid(fpinfo->shippable_extensions, extensionOid)) + return true; + + return false; +} + +/* + * Return true if given object is one of PostgreSQL's built-in objects. + * + * We use FirstGenbkiObjectId as the cutoff, so that we only consider + * objects with hand-assigned OIDs to be "built in", not for instance any + * function or type defined in the information_schema. + * + * Our constraints for dealing with types are tighter than they are for + * functions or operators: we want to accept only types that are in pg_catalog, + * else deparse_type_name might incorrectly fail to schema-qualify their names. + * Thus we must exclude information_schema types. + * + * XXX there is a problem with this, which is that the set of built-in + * objects expands over time. Something that is built-in to us might not + * be known to the remote server, if it's of an older version. But keeping + * track of that would be a huge exercise. + */ +bool +is_builtin(Oid objectId) +{ + return (objectId < FirstGenbkiObjectId); +} + +/* + * is_shippable + * Is this object (function/operator/type) shippable to foreign server? + */ +bool +is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo) +{ + ShippableCacheKey key; + ShippableCacheEntry *entry; + + /* Built-in objects are presumed shippable. */ + if (is_builtin(objectId)) + return true; + + /* Otherwise, give up if user hasn't specified any shippable extensions. */ + if (fpinfo->shippable_extensions == NIL) + return false; + + /* Initialize cache if first time through. */ + if (!ShippableCacheHash) + InitializeShippableCache(); + + /* Set up cache hash key */ + key.objid = objectId; + key.classid = classId; + key.serverid = fpinfo->server->serverid; + + /* See if we already cached the result. */ + entry = (ShippableCacheEntry *) + hash_search(ShippableCacheHash, &key, HASH_FIND, NULL); + + if (!entry) + { + /* Not found in cache, so perform shippability lookup. */ + bool shippable = lookup_shippable(objectId, classId, fpinfo); + + /* + * Don't create a new hash entry until *after* we have the shippable + * result in hand, as the underlying catalog lookups might trigger a + * cache invalidation. + */ + entry = (ShippableCacheEntry *) + hash_search(ShippableCacheHash, &key, HASH_ENTER, NULL); + + entry->shippable = shippable; + } + + return entry->shippable; +} diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql new file mode 100644 index 0000000..a846f4e --- /dev/null +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -0,0 +1,4050 @@ +-- =================================================================== +-- create FDW objects +-- =================================================================== + +CREATE EXTENSION postgres_fdw; + +CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback2 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + EXECUTE $$CREATE SERVER loopback3 FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; + +CREATE USER MAPPING FOR public SERVER testserver1 + OPTIONS (user 'value', password 'value'); +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; +CREATE USER MAPPING FOR public SERVER loopback3; + +-- =================================================================== +-- create objects used through FDW loopback server +-- =================================================================== +CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); +CREATE SCHEMA "S 1"; +CREATE TABLE "S 1"."T 1" ( + "C 1" int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10), + c8 user_enum, + CONSTRAINT t1_pkey PRIMARY KEY ("C 1") +); +CREATE TABLE "S 1"."T 2" ( + c1 int NOT NULL, + c2 text, + CONSTRAINT t2_pkey PRIMARY KEY (c1) +); +CREATE TABLE "S 1"."T 3" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t3_pkey PRIMARY KEY (c1) +); +CREATE TABLE "S 1"."T 4" ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + CONSTRAINT t4_pkey PRIMARY KEY (c1) +); + +-- Disable autovacuum for these tables to avoid unexpected effects of that +ALTER TABLE "S 1"."T 1" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 2" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 3" SET (autovacuum_enabled = 'false'); +ALTER TABLE "S 1"."T 4" SET (autovacuum_enabled = 'false'); + +INSERT INTO "S 1"."T 1" + SELECT id, + id % 10, + to_char(id, 'FM00000'), + '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval, + '1970-01-01'::timestamp + ((id % 100) || ' days')::interval, + id % 10, + id % 10, + 'foo'::user_enum + FROM generate_series(1, 1000) id; +INSERT INTO "S 1"."T 2" + SELECT id, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +INSERT INTO "S 1"."T 3" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 3" WHERE c1 % 2 != 0; -- delete for outer join tests +INSERT INTO "S 1"."T 4" + SELECT id, + id + 1, + 'AAA' || to_char(id, 'FM000') + FROM generate_series(1, 100) id; +DELETE FROM "S 1"."T 4" WHERE c1 % 3 != 0; -- delete for outer join tests + +ANALYZE "S 1"."T 1"; +ANALYZE "S 1"."T 2"; +ANALYZE "S 1"."T 3"; +ANALYZE "S 1"."T 4"; + +-- =================================================================== +-- create foreign tables +-- =================================================================== +CREATE FOREIGN TABLE ft1 ( + c0 int, + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft1 DROP COLUMN c0; + +CREATE FOREIGN TABLE ft2 ( + c1 int NOT NULL, + c2 int NOT NULL, + cx int, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft2', + c8 user_enum +) SERVER loopback; +ALTER FOREIGN TABLE ft2 DROP COLUMN cx; + +CREATE FOREIGN TABLE ft4 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 3'); + +CREATE FOREIGN TABLE ft5 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback OPTIONS (schema_name 'S 1', table_name 'T 4'); + +CREATE FOREIGN TABLE ft6 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback2 OPTIONS (schema_name 'S 1', table_name 'T 4'); + +CREATE FOREIGN TABLE ft7 ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text +) SERVER loopback3 OPTIONS (schema_name 'S 1', table_name 'T 4'); + +-- =================================================================== +-- tests for validator +-- =================================================================== +-- requiressl and some other parameters are omitted because +-- valid values for them depend on configure options +ALTER SERVER testserver1 OPTIONS ( + use_remote_estimate 'false', + updatable 'true', + fdw_startup_cost '123.456', + fdw_tuple_cost '0.123', + service 'value', + connect_timeout 'value', + dbname 'value', + host 'value', + hostaddr 'value', + port 'value', + --client_encoding 'value', + application_name 'value', + --fallback_application_name 'value', + keepalives 'value', + keepalives_idle 'value', + keepalives_interval 'value', + tcp_user_timeout 'value', + -- requiressl 'value', + sslcompression 'value', + sslmode 'value', + sslcert 'value', + sslkey 'value', + sslrootcert 'value', + sslcrl 'value', + --requirepeer 'value', + krbsrvname 'value', + gsslib 'value', + gssdelegation 'value' + --replication 'value' +); + +-- Error, invalid list syntax +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo; bar'); + +-- OK but gets a warning +ALTER SERVER testserver1 OPTIONS (ADD extensions 'foo, bar'); +ALTER SERVER testserver1 OPTIONS (DROP extensions); + +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (DROP user, DROP password); + +-- Attempt to add a valid option that's not allowed in a user mapping +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslmode 'require'); + +-- But we can add valid ones fine +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslpassword 'dummy'); + +-- Ensure valid options we haven't used in a user mapping yet are +-- permitted to check validation. +ALTER USER MAPPING FOR public SERVER testserver1 + OPTIONS (ADD sslkey 'value', ADD sslcert 'value'); + +ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1'); +ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1'); +\det+ + +-- Test that alteration of server options causes reconnection +-- Remote's errors might be non-English, so hide them to ensure stable results +\set VERBOSITY terse +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work +ALTER SERVER loopback OPTIONS (SET dbname 'no such database'); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail +DO $d$ + BEGIN + EXECUTE $$ALTER SERVER loopback + OPTIONS (SET dbname '$$||current_database()||$$')$$; + END; +$d$; +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again + +-- Test that alteration of user mapping options causes reconnection +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback + OPTIONS (ADD user 'no such user'); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should fail +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback + OPTIONS (DROP user); +SELECT c3, c4 FROM ft1 ORDER BY c3, c1 LIMIT 1; -- should work again +\set VERBOSITY default + +-- Now we should be able to run ANALYZE. +-- To exercise multiple code paths, we use local stats on ft1 +-- and remote-estimate mode on ft2. +ANALYZE ft1; +ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true'); + +-- =================================================================== +-- test error case for create publication on foreign table +-- =================================================================== +CREATE PUBLICATION testpub_ftbl FOR TABLE ft1; -- should fail + +-- =================================================================== +-- simple queries +-- =================================================================== +-- single table without alias +EXPLAIN (COSTS OFF) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; +SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10; +-- single table with alias - also test that tableoid sort is not pushed to remote side +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1, t1.tableoid OFFSET 100 LIMIT 10; +-- whole-row reference +EXPLAIN (VERBOSE, COSTS OFF) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- empty result +SELECT * FROM ft1 WHERE false; +-- with WHERE clause +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; +SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; +-- with FOR UPDATE/SHARE +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; +SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; +SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE; +-- aggregate +SELECT COUNT(*) FROM ft1 t1; +-- subquery +SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1; +-- subquery+MAX +SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1; +-- used in CTE +WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1; +-- fixed values +SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1; +-- Test forcing the remote server to produce sorted data for a merge join. +SET enable_hashjoin TO false; +SET enable_nestloop TO false; +-- inner join; expressions in the clauses appear in the equivalence class list +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +SELECT t1.c1, t2."C 1" FROM ft2 t1 JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1") OFFSET 100 LIMIT 10; +-- A join between local table and foreign join. ORDER BY clause is added to the +-- foreign join so that the local table can be joined using merge join strategy. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +-- Test similar to above, except that the full join prevents any equivalence +-- classes from being merged. This produces single relation equivalence classes +-- included in join restrictions. +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 left join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +-- Test similar to above with all full outer joins +EXPLAIN (VERBOSE, COSTS OFF) + SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +SELECT t1."C 1", t2.c1, t3.c1 FROM "S 1"."T 1" t1 full join ft1 t2 full join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; +RESET enable_hashjoin; +RESET enable_nestloop; + +-- Test executing assertion in estimate_path_cost_size() that makes sure that +-- retrieved_rows for foreign rel re-used to cost pre-sorted foreign paths is +-- a sensible value even when the rel has tuples=0 +CREATE TABLE loct_empty (c1 int NOT NULL, c2 text); +CREATE FOREIGN TABLE ft_empty (c1 int NOT NULL, c2 text) + SERVER loopback OPTIONS (table_name 'loct_empty'); +INSERT INTO loct_empty + SELECT id, 'AAA' || to_char(id, 'FM000') FROM generate_series(1, 100) id; +DELETE FROM loct_empty; +ANALYZE ft_empty; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft_empty ORDER BY c1; + +-- =================================================================== +-- WHERE with remotely-executable conditions +-- =================================================================== +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l) +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote +-- parameterized remote path for foreign table +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; +SELECT * FROM "S 1"."T 1" a, ft2 b WHERE a."C 1" = 47 AND b.c1 = a.c2; + +-- check both safe and unsafe join conditions +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 a, ft2 b + WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); +SELECT * FROM ft2 a, ft2 b +WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7); +-- bug before 9.3.5 due to sloppy handling of remote-estimate parameters +SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5)); +SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5)); +-- we should not push order by clause with volatile expressions or unsafe +-- collations +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, random(); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft2 ORDER BY ft2.c1, ft2.c3 collate "C"; + +-- user-defined operator/function +CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$ +BEGIN +RETURN abs($1); +END +$$ LANGUAGE plpgsql IMMUTABLE; +CREATE OPERATOR === ( + LEFTARG = int, + RIGHTARG = int, + PROCEDURE = int4eq, + COMMUTATOR = === +); + +-- built-in operators and functions can be shipped for remote execution +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = t1.c2; + +-- by default, user-defined ones cannot +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + +-- ORDER BY can be shipped, though +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + +-- but let's put them in an extension ... +ALTER EXTENSION postgres_fdw ADD FUNCTION postgres_fdw_abs(int); +ALTER EXTENSION postgres_fdw ADD OPERATOR === (int, int); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +-- ... now they can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2); +EXPLAIN (VERBOSE, COSTS OFF) + SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; + +-- and both ORDER BY and LIMIT can be shipped +EXPLAIN (VERBOSE, COSTS OFF) + SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; +SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2 order by t1.c2 limit 1; + +-- Test CASE pushdown +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1,c2,c3 FROM ft2 WHERE CASE WHEN c1 > 990 THEN c1 END < 1000 ORDER BY c1; +SELECT c1,c2,c3 FROM ft2 WHERE CASE WHEN c1 > 990 THEN c1 END < 1000 ORDER BY c1; + +-- Nested CASE +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1,c2,c3 FROM ft2 WHERE CASE CASE WHEN c2 > 0 THEN c2 END WHEN 100 THEN 601 WHEN c2 THEN c2 ELSE 0 END > 600 ORDER BY c1; + +SELECT c1,c2,c3 FROM ft2 WHERE CASE CASE WHEN c2 > 0 THEN c2 END WHEN 100 THEN 601 WHEN c2 THEN c2 ELSE 0 END > 600 ORDER BY c1; + +-- CASE arg WHEN +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c1 > (CASE mod(c1, 4) WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END); + +-- CASE cannot be pushed down because of unshippable arg clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c1 > (CASE random()::integer WHEN 0 THEN 1 WHEN 2 THEN 50 ELSE 100 END); + +-- these are shippable +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c6 WHEN 'foo' THEN true ELSE c3 < 'bar' END; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c3 WHEN c6 THEN true ELSE c3 < 'bar' END; + +-- but this is not because of collation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE CASE c3 COLLATE "C" WHEN c6 THEN true ELSE c3 < 'bar' END; + +-- a regconfig constant referring to this text search configuration +-- is initially unshippable +CREATE TEXT SEARCH CONFIGURATION public.custom_search + (COPY = pg_catalog.english); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; +-- but if it's in a shippable extension, it can be shipped +ALTER EXTENSION postgres_fdw ADD TEXT SEARCH CONFIGURATION public.custom_search; +-- however, that doesn't flush the shippability cache, so do a quick reconnect +\c - +EXPLAIN (VERBOSE, COSTS OFF) +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; +SELECT c1, to_tsvector('custom_search'::regconfig, c3) FROM ft1 +WHERE c1 = 642 AND length(to_tsvector('custom_search'::regconfig, c3)) > 0; + +-- =================================================================== +-- JOIN queries +-- =================================================================== +-- Analyze ft4 and ft5 so that we have better statistics. These tables do not +-- have use_remote_estimate set. +ANALYZE ft4; +ANALYZE ft5; + +-- join two tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; +-- left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +-- left outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- left outer join + placement of clauses. +-- clauses within the nullable side are not pulled up, but top level clause on +-- non-nullable side is pushed into non-nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; +-- clauses within the nullable side are not pulled up, but the top level clause +-- on nullable side is not pushed down into nullable side +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; +SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM ft4 t1 LEFT JOIN (SELECT * FROM ft5 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) + WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; +-- right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft5 t1 RIGHT JOIN ft4 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 OFFSET 10 LIMIT 10; +-- right outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 45 LIMIT 10; +-- full outer join with restrictions on the joining relations +-- a. the joining relations are both base relations +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; +SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; +SELECT 1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (TRUE) OFFSET 10 LIMIT 10; +-- b. one of the joining relations is a base relation and the other is a join +-- relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM ft4 t2 LEFT JOIN ft5 t3 ON (t2.c1 = t3.c1) WHERE (t2.c1 between 50 and 60)) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +-- c. test deparsing the remote query as nested subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (t1.c1 = ss.a) ORDER BY t1.c1, ss.a, ss.b; +-- d. test deparsing rowmarked relations as subqueries +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +SELECT t1.c1, ss.a, ss.b FROM (SELECT c1 FROM "S 1"."T 3" WHERE c1 = 50) t1 INNER JOIN (SELECT t2.c1, t3.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t2 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t3 ON (t2.c1 = t3.c1) WHERE t2.c1 IS NULL OR t2.c1 IS NOT NULL) ss(a, b) ON (TRUE) ORDER BY t1.c1, ss.a, ss.b FOR UPDATE OF t1; +-- full outer join + inner join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; +SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; +-- full outer join three tables +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- right outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- left outer join + full outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SET enable_memoize TO off; +-- right outer join + left outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +RESET enable_memoize; +-- left outer join + right outer join +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause, only matched rows +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 = t2.c1 OR t1.c1 IS NULL) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +-- full outer join + WHERE clause with shippable extensions set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +ALTER SERVER loopback OPTIONS (DROP extensions); +-- full outer join + WHERE clause with shippable extensions not set +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2, t1.c3 FROM ft1 t1 FULL JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE postgres_fdw_abs(t1.c1) > 0 OFFSET 10 LIMIT 10; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); +-- join two tables with FOR UPDATE clause +-- tests whole-row reference for row marks +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; +-- join two tables with FOR SHARE clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; +-- join in CTE +EXPLAIN (VERBOSE, COSTS OFF) +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; +-- ctid with whole-row reference +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- SEMI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1 FROM ft1 t1 WHERE EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +-- ANTI JOIN, not pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 OFFSET 100 LIMIT 10; +-- CROSS JOIN can be pushed down +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- different server, not pushed down. No result expected. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft5 t1 JOIN ft6 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- unsafe join conditions (c8 has a UDT), not pushed down. Practically a CROSS +-- JOIN since c8 in both tables has same value. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c8 = t2.c8) ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10; +-- unsafe conditions on one side (c8 has a UDT), not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = 'foo' ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- join where unsafe to pushdown condition in WHERE clause has a column not +-- in the SELECT clause. In this test unsafe clause needs to have column +-- references from both joining sides so that the clause is not pushed down +-- into one of the joining sides. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; +-- Aggregate after UNION, for testing setrefs +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; +SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; +-- join with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; +-- join with pseudoconstant quals, not pushed down. +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1 AND CURRENT_USER = SESSION_USER) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; + +-- non-Var items in targetlist of the nullable rel of a join preventing +-- push-down in some cases +-- unable to push {ft1, ft2} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; +SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; + +-- ok to push {ft1, ft2} but not {ft1, ft2, ft4} +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; +SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + +-- join with nullable side with some columns with null values +UPDATE ft5 SET c3 = null where c1 % 9 = 0; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; +SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; + +-- multi-way join involving multiple merge joins +-- (this case used to have EPQ-related planning problems) +CREATE TABLE local_tbl (c1 int NOT NULL, c2 int NOT NULL, c3 text, CONSTRAINT local_tbl_pkey PRIMARY KEY (c1)); +INSERT INTO local_tbl SELECT id, id % 10, to_char(id, 'FM0000') FROM generate_series(1, 1000) id; +ANALYZE local_tbl; +SET enable_nestloop TO false; +SET enable_hashjoin TO false; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1 + AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE; +RESET enable_nestloop; +RESET enable_hashjoin; + +-- test that add_paths_with_pathkeys_for_rel() arranges for the epq_path to +-- return columns needed by the parent ForeignScan node +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.*, COALESCE(ft1.c3 || ft2.c3, 'foobar') FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100)) ss ON (local_tbl.c1 = ss.c1) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl; + +ALTER SERVER loopback OPTIONS (DROP extensions); +ALTER SERVER loopback OPTIONS (ADD fdw_startup_cost '10000.0'); +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl LEFT JOIN (SELECT ft1.* FROM ft1 INNER JOIN ft2 ON (ft1.c1 = ft2.c1 AND ft1.c1 < 100 AND (ft1.c1 - postgres_fdw_abs(ft2.c2)) = 0)) ss ON (local_tbl.c3 = ss.c3) ORDER BY local_tbl.c1 FOR UPDATE OF local_tbl; +ALTER SERVER loopback OPTIONS (DROP fdw_startup_cost); +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +DROP TABLE local_tbl; + +-- check join pushdown in situations where multiple userids are involved +CREATE ROLE regress_view_owner SUPERUSER; +CREATE USER MAPPING FOR regress_view_owner SERVER loopback; +GRANT SELECT ON ft4 TO regress_view_owner; +GRANT SELECT ON ft5 TO regress_view_owner; + +CREATE VIEW v4 AS SELECT * FROM ft4; +CREATE VIEW v5 AS SELECT * FROM ft5; +ALTER VIEW v5 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, different view owners +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO regress_view_owner; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN v5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can't be pushed down, view owner not current user +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO CURRENT_USER; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; -- can be pushed down +SELECT t1.c1, t2.c2 FROM v4 t1 LEFT JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 OFFSET 10 LIMIT 10; +ALTER VIEW v4 OWNER TO regress_view_owner; + +-- ==================================================================== +-- Check that userid to use when querying the remote table is correctly +-- propagated into foreign rels present in subqueries under an UNION ALL +-- ==================================================================== +CREATE ROLE regress_view_owner_another; +ALTER VIEW v4 OWNER TO regress_view_owner_another; +GRANT SELECT ON ft4 TO regress_view_owner_another; +ALTER FOREIGN TABLE ft4 OPTIONS (ADD use_remote_estimate 'true'); +-- The following should query the remote backing table of ft4 as user +-- regress_view_owner_another, the view owner, though it fails as expected +-- due to the lack of a user mapping for that user. +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM v4; +-- Likewise, but with the query under an UNION ALL +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM (SELECT * FROM v4 UNION ALL SELECT * FROM v4); +-- Should not get that error once a user mapping is created +CREATE USER MAPPING FOR regress_view_owner_another SERVER loopback OPTIONS (password_required 'false'); +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM v4; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM (SELECT * FROM v4 UNION ALL SELECT * FROM v4); +DROP USER MAPPING FOR regress_view_owner_another SERVER loopback; +DROP OWNED BY regress_view_owner_another; +DROP ROLE regress_view_owner_another; +ALTER FOREIGN TABLE ft4 OPTIONS (SET use_remote_estimate 'false'); + +-- cleanup +DROP OWNED BY regress_view_owner; +DROP ROLE regress_view_owner; + + +-- =================================================================== +-- Aggregate and grouping queries +-- =================================================================== + +-- Simple aggregates +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2; + +explain (verbose, costs off) +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; +select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1; + +-- Aggregate is not pushed down as aggregation contains random() +explain (verbose, costs off) +select sum(c1 * (random() <= 1)::int) as sum, avg(c1) from ft1; + +-- Aggregate over join query +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; +select count(*), sum(t1.c1), avg(t2.c1) from ft1 t1 inner join ft1 t2 on (t1.c2 = t2.c2) where t1.c2 = 6; + +-- Not pushed down due to local conditions present in underneath input rel +explain (verbose, costs off) +select sum(t1.c1), count(t2.c1) from ft1 t1 inner join ft2 t2 on (t1.c1 = t2.c1) where ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; + +-- GROUP BY clause having expressions +explain (verbose, costs off) +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; +select c2/2, sum(c2) * (c2/2) from ft1 group by c2/2 order by c2/2; + +-- Aggregates in subquery are pushed down. +set enable_incremental_sort = off; +explain (verbose, costs off) +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; +select count(x.a), sum(x.a) from (select c2 a, sum(c1) b from ft1 group by c2, sqrt(c1) order by 1, 2) x; +reset enable_incremental_sort; + +-- Aggregate is still pushed down by taking unshippable expression out +explain (verbose, costs off) +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; +select c2 * (random() <= 1)::int as sum1, sum(c1) * c2 as sum2 from ft1 group by c2 order by 1, 2; + +-- Aggregate with unshippable GROUP BY clause are not pushed +explain (verbose, costs off) +select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::int order by 1; + +-- GROUP BY clause in various forms, cardinal, alias and constant expression +explain (verbose, costs off) +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; +select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2; + +-- GROUP BY clause referring to same column multiple times +-- Also, ORDER BY contains an aggregate function +explain (verbose, costs off) +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); +select c2, c2 from ft1 where c2 > 6 group by 1, 2 order by sum(c1); + +-- Testing HAVING clause shippability +explain (verbose, costs off) +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; +select c2, sum(c1) from ft2 group by c2 having avg(c1) < 500 and sum(c1) < 49800 order by c2; + +-- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down +explain (verbose, costs off) +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; +select count(*) from (select c5, count(c1) from ft1 group by c5, sqrt(c2) having (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; + +-- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down +explain (verbose, costs off) +select sum(c1) from ft1 group by c2 having avg(c1 * (random() <= 1)::int) > 100 order by 1; + +-- Remote aggregate in combination with a local Param (for the output +-- of an initplan) can be trouble, per bug #15781 +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1; +select exists(select 1 from pg_enum), sum(c1) from ft1; + +explain (verbose, costs off) +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; +select exists(select 1 from pg_enum), sum(c1) from ft1 group by 1; + + +-- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates + +-- ORDER BY within aggregate, same column used to order +explain (verbose, costs off) +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; +select array_agg(c1 order by c1) from ft1 where c1 < 100 group by c2 order by 1; + +-- ORDER BY within aggregate, different column used to order also using DESC +explain (verbose, costs off) +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; +select array_agg(c5 order by c1 desc) from ft2 where c2 = 6 and c1 < 50; + +-- DISTINCT within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +-- DISTINCT combined with ORDER BY within aggregate +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +explain (verbose, costs off) +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; +select array_agg(distinct (t1.c1)%5 order by (t1.c1)%5 desc nulls last) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) where t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) group by (t2.c1)%3 order by 1; + +-- FILTER within aggregate +explain (verbose, costs off) +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; +select sum(c1) filter (where c1 < 100 and c2 > 5) from ft1 group by c2 order by 1 nulls last; + +-- DISTINCT, ORDER BY and FILTER within aggregate +explain (verbose, costs off) +select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; +select sum(c1%3), sum(distinct c1%3 order by c1%3) filter (where c1%3 < 2), c2 from ft1 where c2 = 6 group by c2; + +-- Outer query is aggregation query +explain (verbose, costs off) +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +select distinct (select count(*) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +-- Inner query is aggregation query +explain (verbose, costs off) +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; +select distinct (select count(t1.c1) filter (where t2.c2 = 6 and t2.c1 < 10) from ft1 t1 where t1.c1 = 6) from ft2 t2 where t2.c2 % 6 = 0 order by 1; + +-- Aggregate not pushed down as FILTER condition is not pushable +explain (verbose, costs off) +select sum(c1) filter (where (c1 / c1) * random() <= 1) from ft1 group by c2 order by 1; +explain (verbose, costs off) +select sum(c2) filter (where c2 in (select c2 from ft1 where c2 < 5)) from ft1; + +-- Ordered-sets within aggregate +explain (verbose, costs off) +select c2, rank('10'::varchar) within group (order by c6), percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; +select c2, rank('10'::varchar) within group (order by c6), percentile_cont(c2/10::numeric) within group (order by c1) from ft1 where c2 < 10 group by c2 having percentile_cont(c2/10::numeric) within group (order by c1) < 500 order by c2; + +-- Using multiple arguments within aggregates +explain (verbose, costs off) +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; +select c1, rank(c1, c2) within group (order by c1, c2) from ft1 group by c1, c2 having c1 = 6 order by 1; + +-- User defined function for user defined aggregate, VARIADIC +create function least_accum(anyelement, variadic anyarray) +returns anyelement language sql as + 'select least($1, min($2[i])) from generate_subscripts($2,1) g(i)'; +create aggregate least_agg(variadic items anyarray) ( + stype = anyelement, sfunc = least_accum +); + +-- Disable hash aggregation for plan stability. +set enable_hashagg to false; + +-- Not pushed down due to user defined aggregate +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + +-- Add function and aggregate into extension +alter extension postgres_fdw add function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw add aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Now aggregate will be pushed. Aggregate will display VARIADIC argument. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; +select c2, least_agg(c1) from ft1 where c2 < 100 group by c2 order by c2; + +-- Remove function and aggregate from extension +alter extension postgres_fdw drop function least_accum(anyelement, variadic anyarray); +alter extension postgres_fdw drop aggregate least_agg(variadic items anyarray); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Not pushed down as we have dropped objects from extension. +explain (verbose, costs off) +select c2, least_agg(c1) from ft1 group by c2 order by c2; + +-- Cleanup +reset enable_hashagg; +drop aggregate least_agg(variadic items anyarray); +drop function least_accum(anyelement, variadic anyarray); + + +-- Testing USING OPERATOR() in ORDER BY within aggregate. +-- For this, we need user defined operators along with operator family and +-- operator class. Create those and then add them in extension. Note that +-- user defined objects are considered unshippable unless they are part of +-- the extension. +create operator public.<^ ( + leftarg = int4, + rightarg = int4, + procedure = int4eq +); + +create operator public.=^ ( + leftarg = int4, + rightarg = int4, + procedure = int4lt +); + +create operator public.>^ ( + leftarg = int4, + rightarg = int4, + procedure = int4gt +); + +create operator family my_op_family using btree; + +create function my_op_cmp(a int, b int) returns int as + $$begin return btint4cmp(a, b); end $$ language plpgsql; + +create operator class my_op_class for type int using btree family my_op_family as + operator 1 public.<^, + operator 3 public.=^, + operator 5 public.>^, + function 1 my_op_cmp(int, int); + +-- This will not be pushed as user defined sort operator is not part of the +-- extension yet. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + +-- This should not be pushed either. +explain (verbose, costs off) +select * from ft2 order by c1 using operator(public.<^); + +-- Update local stats on ft2 +ANALYZE ft2; + +-- Add into extension +alter extension postgres_fdw add operator class my_op_class using btree; +alter extension postgres_fdw add function my_op_cmp(a int, b int); +alter extension postgres_fdw add operator family my_op_family using btree; +alter extension postgres_fdw add operator public.<^(int, int); +alter extension postgres_fdw add operator public.=^(int, int); +alter extension postgres_fdw add operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); + +-- Now this will be pushed as sort operator is part of the extension. +alter server loopback options (add fdw_tuple_cost '0.5'); +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; +alter server loopback options (drop fdw_tuple_cost); + +-- This should be pushed too. +explain (verbose, costs off) +select * from ft2 order by c1 using operator(public.<^); + +-- Remove from extension +alter extension postgres_fdw drop operator class my_op_class using btree; +alter extension postgres_fdw drop function my_op_cmp(a int, b int); +alter extension postgres_fdw drop operator family my_op_family using btree; +alter extension postgres_fdw drop operator public.<^(int, int); +alter extension postgres_fdw drop operator public.=^(int, int); +alter extension postgres_fdw drop operator public.>^(int, int); +alter server loopback options (set extensions 'postgres_fdw'); + +-- This will not be pushed as sort operator is now removed from the extension. +explain (verbose, costs off) +select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6 and c1 < 100 group by c2; + +-- Cleanup +drop operator class my_op_class using btree; +drop function my_op_cmp(a int, b int); +drop operator family my_op_family using btree; +drop operator public.>^(int, int); +drop operator public.=^(int, int); +drop operator public.<^(int, int); + +-- Input relation to aggregate push down hook is not safe to pushdown and thus +-- the aggregate cannot be pushed down to foreign server. +explain (verbose, costs off) +select count(t1.c3) from ft2 t1 left join ft2 t2 on (t1.c1 = random() * t2.c2); + +-- Subquery in FROM clause having aggregate +explain (verbose, costs off) +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; +select count(*), x.b from ft1, (select c2 a, sum(c1) b from ft1 group by c2) x where ft1.c2 = x.a group by x.b order by 1, 2; + +-- FULL join with IS NULL check in HAVING +explain (verbose, costs off) +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; +select avg(t1.c1), sum(t2.c1) from ft4 t1 full join ft5 t2 on (t1.c1 = t2.c1) group by t2.c1 having (avg(t1.c1) is null and sum(t2.c1) < 10) or sum(t2.c1) is null order by 1 nulls last, 2; + +-- Aggregate over FULL join needing to deparse the joining relations as +-- subqueries. +explain (verbose, costs off) +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); +select count(*), sum(t1.c1), avg(t2.c1) from (select c1 from ft4 where c1 between 50 and 60) t1 full join (select c1 from ft5 where c1 between 50 and 60) t2 on (t1.c1 = t2.c1); + +-- ORDER BY expression is part of the target list but not pushed down to +-- foreign server. +explain (verbose, costs off) +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; +select sum(c2) * (random() <= 1)::int as sum from ft1 order by 1; + +-- LATERAL join, with parameterization +set enable_hashagg to false; +explain (verbose, costs off) +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1; +select c2, sum from "S 1"."T 1" t1, lateral (select sum(t2.c1 + t1."C 1") sum from ft2 t2 group by t2.c1) qry where t1.c2 * 2 = qry.sum and t1.c2 < 3 and t1."C 1" < 100 order by 1; +reset enable_hashagg; + +-- bug #15613: bad plan for foreign table scan with lateral reference +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T 1" AS ref_0, + LATERAL ( + SELECT ref_0."C 1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C 1"; + +SELECT ref_0.c2, subq_1.* +FROM + "S 1"."T 1" AS ref_0, + LATERAL ( + SELECT ref_0."C 1" c1, subq_0.* + FROM (SELECT ref_0.c2, ref_1.c3 + FROM ft1 AS ref_1) AS subq_0 + RIGHT JOIN ft2 AS ref_3 ON (subq_0.c3 = ref_3.c3) + ) AS subq_1 +WHERE ref_0."C 1" < 10 AND subq_1.c3 = '00001' +ORDER BY ref_0."C 1"; + +-- Check with placeHolderVars +explain (verbose, costs off) +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); +select sum(q.a), count(q.b) from ft4 left join (select 13, avg(ft1.c1), sum(ft2.c1) from ft1 right join ft2 on (ft1.c1 = ft2.c1)) q(a, b, c) on (ft4.c1 <= q.b); + + +-- Not supported cases +-- Grouping sets +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by rollup(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +select c2, sum(c1) from ft1 where c2 < 3 group by cube(c2) order by 1 nulls last; +explain (verbose, costs off) +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +select c2, c6, sum(c1) from ft1 where c2 < 3 group by grouping sets(c2, c6) order by 1 nulls last, 2 nulls last; +explain (verbose, costs off) +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; +select c2, sum(c1), grouping(c2) from ft1 where c2 < 3 group by c2 order by 1 nulls last; + +-- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed +explain (verbose, costs off) +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; +select distinct sum(c1)/1000 s from ft2 where c2 < 6 group by c2 order by 1; + +-- WindowAgg +explain (verbose, costs off) +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +select c2, sum(c2), count(c2) over (partition by c2%2) from ft2 where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; +select c2, array_agg(c2) over (partition by c2%2 order by c2 desc) from ft1 where c2 < 10 group by c2 order by 1; +explain (verbose, costs off) +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; +select c2, array_agg(c2) over (partition by c2%2 order by c2 range between current row and unbounded following) from ft1 where c2 < 10 group by c2 order by 1; + + +-- =================================================================== +-- parameterized queries +-- =================================================================== +-- simple join +PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); +EXECUTE st1(1, 1); +EXECUTE st1(101, 101); +-- subquery using stable function (can't be sent to remote) +PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st2(10, 20); +EXECUTE st2(10, 20); +EXECUTE st2(101, 121); +-- subquery using immutable function (can be sent to remote) +PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st3(10, 20); +EXECUTE st3(10, 20); +EXECUTE st3(20, 30); +-- custom plan should be chosen initially +PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +-- once we try it enough times, should switch to generic plan +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st4(1); +-- value of $1 should not be sent to remote +PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st5('foo', 1); +EXECUTE st5('foo', 1); + +-- altering FDW options requires replanning +PREPARE st6 AS SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; +PREPARE st7 AS INSERT INTO ft1 (c1,c2,c3) VALUES (1001,101,'foo'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; +ALTER TABLE "S 1"."T 1" RENAME TO "T 0"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 0'); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st6; +EXECUTE st6; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st7; +ALTER TABLE "S 1"."T 0" RENAME TO "T 1"; +ALTER FOREIGN TABLE ft1 OPTIONS (SET table_name 'T 1'); + +PREPARE st8 AS SELECT count(c3) FROM ft1 t1 WHERE t1.c1 === t1.c2; +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +ALTER SERVER loopback OPTIONS (DROP extensions); +EXPLAIN (VERBOSE, COSTS OFF) EXECUTE st8; +EXECUTE st8; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +-- cleanup +DEALLOCATE st1; +DEALLOCATE st2; +DEALLOCATE st3; +DEALLOCATE st4; +DEALLOCATE st5; +DEALLOCATE st6; +DEALLOCATE st7; +DEALLOCATE st8; + +-- System columns, except ctid and oid, should not be sent to remote +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1; +SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; +SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; +SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)'; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT ctid, * FROM ft1 t1 LIMIT 1; +SELECT ctid, * FROM ft1 t1 LIMIT 1; + +-- =================================================================== +-- used in PL/pgSQL function +-- =================================================================== +CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$ +DECLARE + v_c1 int; +BEGIN + SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1; + PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1; + RETURN v_c1; +END; +$$ LANGUAGE plpgsql; +SELECT f_test(100); +DROP FUNCTION f_test(int); + +-- =================================================================== +-- REINDEX +-- =================================================================== +-- remote table is not created here +CREATE FOREIGN TABLE reindex_foreign (c1 int, c2 int) + SERVER loopback2 OPTIONS (table_name 'reindex_local'); +REINDEX TABLE reindex_foreign; -- error +REINDEX TABLE CONCURRENTLY reindex_foreign; -- error +DROP FOREIGN TABLE reindex_foreign; +-- partitions and foreign tables +CREATE TABLE reind_fdw_parent (c1 int) PARTITION BY RANGE (c1); +CREATE TABLE reind_fdw_0_10 PARTITION OF reind_fdw_parent + FOR VALUES FROM (0) TO (10); +CREATE FOREIGN TABLE reind_fdw_10_20 PARTITION OF reind_fdw_parent + FOR VALUES FROM (10) TO (20) + SERVER loopback OPTIONS (table_name 'reind_local_10_20'); +REINDEX TABLE reind_fdw_parent; -- ok +REINDEX TABLE CONCURRENTLY reind_fdw_parent; -- ok +DROP TABLE reind_fdw_parent; + +-- =================================================================== +-- conversion error +-- =================================================================== +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int; +SELECT * FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8) WHERE x1 = 1; -- ERROR +SELECT ftx.x1, ft2.c2, ftx.x8 FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +SELECT ftx.x1, ft2.c2, ftx FROM ft1 ftx(x1,x2,x3,x4,x5,x6,x7,x8), ft2 + WHERE ftx.x1 = ft2.c1 AND ftx.x1 = 1; -- ERROR +SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR +ANALYZE ft1; -- ERROR +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; + +-- =================================================================== +-- local type can be different from remote type in some cases, +-- in particular if similarly-named operators do equivalent things +-- =================================================================== +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE text; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1; +SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1; +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ft1 WHERE 'foo' = c8 LIMIT 1; +SELECT * FROM ft1 WHERE 'foo' = c8 LIMIT 1; +-- we declared c8 to be text locally, but it's still the same type on +-- the remote which will balk if we try to do anything incompatible +-- with that remote type +SELECT * FROM ft1 WHERE c8 LIKE 'foo' LIMIT 1; -- ERROR +SELECT * FROM ft1 WHERE c8::text LIKE 'foo' LIMIT 1; -- ERROR; cast not pushed down +ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum; + +-- =================================================================== +-- subtransaction +-- + local/remote error doesn't break cursor +-- =================================================================== +BEGIN; +DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1; +FETCH c; +SAVEPOINT s; +ERROR OUT; -- ERROR +ROLLBACK TO s; +FETCH c; +SAVEPOINT s; +SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR +ROLLBACK TO s; +FETCH c; +SELECT * FROM ft1 ORDER BY c1 LIMIT 1; +COMMIT; + +-- =================================================================== +-- test handling of collations +-- =================================================================== +create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique); +create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10)) + server loopback options (table_name 'loct3', use_remote_estimate 'true'); + +-- can be sent to remote +explain (verbose, costs off) select * from ft3 where f1 = 'foo'; +explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo'; +explain (verbose, costs off) select * from ft3 where f2 = 'foo'; +explain (verbose, costs off) select * from ft3 where f3 = 'foo'; +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 and l.f1 = 'foo'; +-- can't be sent to remote +explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo'; +explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C"; +explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo'; +explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C"; +explain (verbose, costs off) select * from ft3 f, loct3 l + where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo'; + +-- =================================================================== +-- test writable foreign table stuff +-- =================================================================== +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; +INSERT INTO ft2 (c1,c2,c3) + VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; +INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down +UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down +UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can be pushed down +UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT + FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; +EXPLAIN (verbose, costs off) + DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down +DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can be pushed down +DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; +SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1; +EXPLAIN (verbose, costs off) +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +INSERT INTO ft2 (c1,c2,c3) VALUES (1200,999,'foo') RETURNING tableoid::regclass; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +UPDATE ft2 SET c3 = 'bar' WHERE c1 = 1200 RETURNING tableoid::regclass; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; -- can be pushed down +DELETE FROM ft2 WHERE c1 = 1200 RETURNING tableoid::regclass; + +-- Test UPDATE/DELETE with RETURNING on a three-table join +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id - 1200, to_char(id, 'FM00000') FROM generate_series(1201, 1300) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; -- can be pushed down +UPDATE ft2 SET c3 = 'foo' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c2 = ft4.c1 + RETURNING ft2, ft2.*, ft4, ft4.*; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; -- can be pushed down +DELETE FROM ft2 + USING ft4 LEFT JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 1200 AND ft2.c1 % 10 = 0 AND ft2.c2 = ft4.c1 + RETURNING 100; +DELETE FROM ft2 WHERE ft2.c1 > 1200; + +-- Test UPDATE with a MULTIEXPR sub-select +-- (maybe someday this'll be remotely executable, but not today) +EXPLAIN (verbose, costs off) +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; +UPDATE ft2 AS target SET (c2, c7) = ( + SELECT c2 * 10, c7 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + +UPDATE ft2 AS target SET (c2) = ( + SELECT c2 / 10 + FROM ft2 AS src + WHERE target.c1 = src.c1 +) WHERE c1 > 1100; + +-- Test UPDATE involving a join that can be pushed down, +-- but a SET clause that can't be +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; +UPDATE ft2 d SET c2 = CASE WHEN random() >= 0 THEN d.c2 ELSE 0 END + FROM ft2 AS t WHERE d.c1 = t.c1 AND d.c1 > 1000; + +-- Test UPDATE/DELETE with WHERE or JOIN/ON conditions containing +-- user-defined operators/functions +ALTER SERVER loopback OPTIONS (DROP extensions); +INSERT INTO ft2 (c1,c2,c3) + SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down +UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; +EXPLAIN (verbose, costs off) +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; -- can't be pushed down +UPDATE ft2 SET c3 = 'baz' + FROM ft4 INNER JOIN ft5 ON (ft4.c1 = ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 === ft4.c1 + RETURNING ft2.*, ft4.*, ft5.*; +EXPLAIN (verbose, costs off) +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down +DELETE FROM ft2 + USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1) + WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1 + RETURNING ft2.c1, ft2.c2, ft2.c3; +DELETE FROM ft2 WHERE ft2.c1 > 2000; +ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw'); + +-- Test that trigger on remote table works as expected +CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$ +BEGIN + NEW.c3 = NEW.c3 || '_trig_update'; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE + ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG(); + +INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *; +INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *; +UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *; + +-- Test errors thrown on remote side during update +ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0); + +INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT DO NOTHING; -- works +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO NOTHING; -- unsupported +INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO UPDATE SET c3 = 'ffg'; -- unsupported +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive + +-- Test savepoint/rollback behavior +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; +begin; +update ft2 set c2 = 42 where c2 = 0; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +savepoint s1; +update ft2 set c2 = 44 where c2 = 4; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +release savepoint s1; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +savepoint s2; +update ft2 set c2 = 46 where c2 = 6; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +rollback to savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +release savepoint s2; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +savepoint s3; +update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side +rollback to savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +release savepoint s3; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +-- none of the above is committed yet remotely +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; +commit; +select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; +select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1; + +VACUUM ANALYZE "S 1"."T 1"; + +-- Above DMLs add data with c6 as NULL in ft1, so test ORDER BY NULLS LAST and NULLs +-- FIRST behavior here. +-- ORDER BY DESC NULLS LAST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 DESC NULLS LAST, c1 OFFSET 795 LIMIT 10; +-- ORDER BY DESC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +-- ORDER BY ASC NULLS FIRST options +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; +SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10; + +-- =================================================================== +-- test check constraints +-- =================================================================== + +-- Consistent check constraints provide consistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 < 0; +SELECT count(*) FROM ft1 WHERE c2 < 0; +RESET constraint_exclusion; +-- check constraint is enforced on the remote side, not locally +INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive +UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; + +-- But inconsistent check constraints provide inconsistent results +ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +SET constraint_exclusion = 'on'; +EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM ft1 WHERE c2 >= 0; +SELECT count(*) FROM ft1 WHERE c2 >= 0; +RESET constraint_exclusion; +-- local check constraint is not actually enforced +INSERT INTO ft1(c1, c2) VALUES(1111, 2); +UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1; +ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative; + +-- =================================================================== +-- test WITH CHECK OPTION constraints +-- =================================================================== + +CREATE FUNCTION row_before_insupd_trigfunc() RETURNS trigger AS $$BEGIN NEW.a := NEW.a + 10; RETURN NEW; END$$ LANGUAGE plpgsql; + +CREATE TABLE base_tbl (a int, b int); +ALTER TABLE base_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON base_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +CREATE VIEW rw_view AS SELECT * FROM foreign_tbl + WHERE a < b WITH CHECK OPTION; +\d+ rw_view + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); +INSERT INTO rw_view VALUES (0, 5); -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM foreign_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; +UPDATE rw_view SET b = b + 5; -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM foreign_tbl; + +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +SELECT * FROM foreign_tbl; +ALTER SERVER loopback OPTIONS (DROP batch_size); + +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON base_tbl; +DROP TABLE base_tbl; + +-- test WCO for partitions + +CREATE TABLE child_tbl (a int, b int); +ALTER TABLE child_tbl SET (autovacuum_enabled = 'false'); +CREATE TRIGGER row_before_insupd_trigger BEFORE INSERT OR UPDATE ON child_tbl FOR EACH ROW EXECUTE PROCEDURE row_before_insupd_trigfunc(); +CREATE FOREIGN TABLE foreign_tbl (a int, b int) + SERVER loopback OPTIONS (table_name 'child_tbl'); + +CREATE TABLE parent_tbl (a int, b int) PARTITION BY RANGE(a); +ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES FROM (0) TO (100); +-- Detach and re-attach once, to stress the concurrent detach case. +ALTER TABLE parent_tbl DETACH PARTITION foreign_tbl CONCURRENTLY; +ALTER TABLE parent_tbl ATTACH PARTITION foreign_tbl FOR VALUES FROM (0) TO (100); + +CREATE VIEW rw_view AS SELECT * FROM parent_tbl + WHERE a < b WITH CHECK OPTION; +\d+ rw_view + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 5); +INSERT INTO rw_view VALUES (0, 5); -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15); +INSERT INTO rw_view VALUES (0, 15); -- ok +SELECT * FROM foreign_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 5; +UPDATE rw_view SET b = b + 5; -- should fail +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = b + 15; +UPDATE rw_view SET b = b + 15; -- ok +SELECT * FROM foreign_tbl; + +-- We don't allow batch insert when there are any WCO constraints +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO rw_view VALUES (0, 15), (0, 5); +INSERT INTO rw_view VALUES (0, 15), (0, 5); -- should fail +SELECT * FROM foreign_tbl; +ALTER SERVER loopback OPTIONS (DROP batch_size); + +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TRIGGER row_before_insupd_trigger ON child_tbl; +DROP TABLE parent_tbl CASCADE; + +DROP FUNCTION row_before_insupd_trigfunc; + +-- Try a more complex permutation of WCO where there are multiple levels of +-- partitioned tables with columns not all in the same order +CREATE TABLE parent_tbl (a int, b text, c numeric) PARTITION BY RANGE(a); +CREATE TABLE sub_parent (c numeric, a int, b text) PARTITION BY RANGE(a); +ALTER TABLE parent_tbl ATTACH PARTITION sub_parent FOR VALUES FROM (1) TO (10); +CREATE TABLE child_local (b text, c numeric, a int); +CREATE FOREIGN TABLE child_foreign (b text, c numeric, a int) + SERVER loopback OPTIONS (table_name 'child_local'); +ALTER TABLE sub_parent ATTACH PARTITION child_foreign FOR VALUES FROM (1) TO (10); +CREATE VIEW rw_view AS SELECT * FROM parent_tbl WHERE a < 5 WITH CHECK OPTION; + +INSERT INTO parent_tbl (a) VALUES(1),(5); +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE rw_view SET b = 'text', c = 123.456; +UPDATE rw_view SET b = 'text', c = 123.456; +SELECT * FROM parent_tbl ORDER BY a; + +DROP VIEW rw_view; +DROP TABLE child_local; +DROP FOREIGN TABLE child_foreign; +DROP TABLE sub_parent; +DROP TABLE parent_tbl; + +-- =================================================================== +-- test serial columns (ie, sequence-based defaults) +-- =================================================================== +create table loc1 (f1 serial, f2 text); +alter table loc1 set (autovacuum_enabled = 'false'); +create foreign table rem1 (f1 serial, f2 text) + server loopback options(table_name 'loc1'); +select pg_catalog.setval('rem1_f1_seq', 10, false); +insert into loc1(f2) values('hi'); +insert into rem1(f2) values('hi remote'); +insert into loc1(f2) values('bye'); +insert into rem1(f2) values('bye remote'); +select * from loc1; +select * from rem1; + +-- =================================================================== +-- test generated columns +-- =================================================================== +create table gloc1 ( + a int, + b int generated always as (a * 2) stored); +alter table gloc1 set (autovacuum_enabled = 'false'); +create foreign table grem1 ( + a int, + b int generated always as (a * 2) stored) + server loopback options(table_name 'gloc1'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); +insert into grem1 (a) values (1), (2); +explain (verbose, costs off) +update grem1 set a = 22 where a = 2; +update grem1 set a = 22 where a = 2; +select * from gloc1; +select * from grem1; +delete from grem1; + +-- test copy from +copy grem1 from stdin; +1 +2 +\. +select * from gloc1; +select * from grem1; +delete from grem1; + +-- test batch insert +alter server loopback options (add batch_size '10'); +explain (verbose, costs off) +insert into grem1 (a) values (1), (2); +insert into grem1 (a) values (1), (2); +select * from gloc1; +select * from grem1; +delete from grem1; +-- batch insert with foreign partitions. +-- This schema uses two partitions, one local and one remote with a modulo +-- to loop across all of them in batches. +create table tab_batch_local (id int, data text); +insert into tab_batch_local select i, 'test'|| i from generate_series(1, 45) i; +create table tab_batch_sharded (id int, data text) partition by hash(id); +create table tab_batch_sharded_p0 partition of tab_batch_sharded + for values with (modulus 2, remainder 0); +create table tab_batch_sharded_p1_remote (id int, data text); +create foreign table tab_batch_sharded_p1 partition of tab_batch_sharded + for values with (modulus 2, remainder 1) + server loopback options (table_name 'tab_batch_sharded_p1_remote'); +insert into tab_batch_sharded select * from tab_batch_local; +select count(*) from tab_batch_sharded; +drop table tab_batch_local; +drop table tab_batch_sharded; +drop table tab_batch_sharded_p1_remote; + +alter server loopback options (drop batch_size); + +-- =================================================================== +-- test local triggers +-- =================================================================== + +-- Trigger functions "borrowed" from triggers regress test. +CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$ +BEGIN + RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %', + TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL; + RETURN NULL; +END;$$; + +CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE OR TRUNCATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE OR TRUNCATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); + +CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger +LANGUAGE plpgsql AS $$ + +declare + oldnew text[]; + relid text; + argstr text; +begin + + relid := TG_relid::regclass; + argstr := ''; + for i in 0 .. TG_nargs - 1 loop + if i > 0 then + argstr := argstr || ', '; + end if; + argstr := argstr || TG_argv[i]; + end loop; + + RAISE NOTICE '%(%) % % % ON %', + tg_name, argstr, TG_when, TG_level, TG_OP, relid; + oldnew := '{}'::text[]; + if TG_OP != 'INSERT' then + oldnew := array_append(oldnew, format('OLD: %s', OLD)); + end if; + + if TG_OP != 'DELETE' then + oldnew := array_append(oldnew, format('NEW: %s', NEW)); + end if; + + RAISE NOTICE '%', array_to_string(oldnew, ','); + + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end; +$$; + +-- Test basic functionality +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +delete from rem1; +insert into rem1 values(1,'insert'); +update rem1 set f2 = 'update' where f1 = 1; +update rem1 set f2 = f2 || f2; +truncate rem1; + + +-- cleanup +DROP TRIGGER trig_row_before ON rem1; +DROP TRIGGER trig_row_after ON rem1; +DROP TRIGGER trig_stmt_before ON rem1; +DROP TRIGGER trig_stmt_after ON rem1; + +DELETE from rem1; + +-- Test multiple AFTER ROW triggers on a foreign table +CREATE TRIGGER trig_row_after1 +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after2 +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +insert into rem1 values(1,'insert'); +update rem1 set f2 = 'update' where f1 = 1; +update rem1 set f2 = f2 || f2; +delete from rem1; + +-- cleanup +DROP TRIGGER trig_row_after1 ON rem1; +DROP TRIGGER trig_row_after2 ON rem1; + +-- Test WHEN conditions + +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after_insupd +AFTER INSERT OR UPDATE ON rem1 +FOR EACH ROW +WHEN (NEW.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +-- Insert or update not matching: nothing happens +INSERT INTO rem1 values(1, 'insert'); +UPDATE rem1 set f2 = 'test'; + +-- Insert or update matching: triggers are fired +INSERT INTO rem1 values(2, 'update'); +UPDATE rem1 set f2 = 'update update' where f1 = '2'; + +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON rem1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON rem1 +FOR EACH ROW +WHEN (OLD.f2 like '%update%') +EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +-- Trigger is fired for f1=2, not for f1=1 +DELETE FROM rem1; + +-- cleanup +DROP TRIGGER trig_row_before_insupd ON rem1; +DROP TRIGGER trig_row_after_insupd ON rem1; +DROP TRIGGER trig_row_before_delete ON rem1; +DROP TRIGGER trig_row_after_delete ON rem1; + + +-- Test various RETURN statements in BEFORE triggers. + +CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$ + BEGIN + NEW.f2 := NEW.f2 || ' triggered !'; + RETURN NEW; + END +$$ language plpgsql; + +CREATE TRIGGER trig_row_before_insupd +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); + +-- The new values should have 'triggered' appended +INSERT INTO rem1 values(1, 'insert'); +SELECT * from loc1; +INSERT INTO rem1 values(2, 'insert') RETURNING f2; +SELECT * from loc1; +UPDATE rem1 set f2 = ''; +SELECT * from loc1; +UPDATE rem1 set f2 = 'skidoo' RETURNING f2; +SELECT * from loc1; + +EXPLAIN (verbose, costs off) +UPDATE rem1 set f1 = 10; -- all columns should be transmitted +UPDATE rem1 set f1 = 10; +SELECT * from loc1; + +DELETE FROM rem1; + +-- Add a second trigger, to check that the changes are propagated correctly +-- from trigger to trigger +CREATE TRIGGER trig_row_before_insupd2 +BEFORE INSERT OR UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); + +INSERT INTO rem1 values(1, 'insert'); +SELECT * from loc1; +INSERT INTO rem1 values(2, 'insert') RETURNING f2; +SELECT * from loc1; +UPDATE rem1 set f2 = ''; +SELECT * from loc1; +UPDATE rem1 set f2 = 'skidoo' RETURNING f2; +SELECT * from loc1; + +DROP TRIGGER trig_row_before_insupd ON rem1; +DROP TRIGGER trig_row_before_insupd2 ON rem1; + +DELETE from rem1; + +INSERT INTO rem1 VALUES (1, 'test'); + +-- Test with a trigger returning NULL +CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$ + BEGIN + RETURN NULL; + END +$$ language plpgsql; + +CREATE TRIGGER trig_null +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trig_null(); + +-- Nothing should have changed. +INSERT INTO rem1 VALUES (2, 'test2'); + +SELECT * from loc1; + +UPDATE rem1 SET f2 = 'test2'; + +SELECT * from loc1; + +DELETE from rem1; + +SELECT * from loc1; + +DROP TRIGGER trig_null ON rem1; +DELETE from rem1; + +-- Test a combination of local and remote triggers +CREATE TRIGGER trig_row_before +BEFORE INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after +AFTER INSERT OR UPDATE OR DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1 +FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate(); + +INSERT INTO rem1(f2) VALUES ('test'); +UPDATE rem1 SET f2 = 'testo'; + +-- Test returning a system attribute +INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid; + +-- cleanup +DROP TRIGGER trig_row_before ON rem1; +DROP TRIGGER trig_row_after ON rem1; +DROP TRIGGER trig_local_before ON loc1; + + +-- Test direct foreign table modification functionality +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1 WHERE false; -- currently can't be pushed down + +-- Test with statement-level triggers +CREATE TRIGGER trig_stmt_before + BEFORE DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_stmt_before ON rem1; + +CREATE TRIGGER trig_stmt_after + AFTER DELETE OR INSERT OR UPDATE ON rem1 + FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func(); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_stmt_after ON rem1; + +-- Test with row-level ON INSERT triggers +CREATE TRIGGER trig_row_before_insert +BEFORE INSERT ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_before_insert ON rem1; + +CREATE TRIGGER trig_row_after_insert +AFTER INSERT ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_after_insert ON rem1; + +-- Test with row-level ON UPDATE triggers +CREATE TRIGGER trig_row_before_update +BEFORE UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_before_update ON rem1; + +CREATE TRIGGER trig_row_after_update +AFTER UPDATE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can't be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can be pushed down +DROP TRIGGER trig_row_after_update ON rem1; + +-- Test with row-level ON DELETE triggers +CREATE TRIGGER trig_row_before_delete +BEFORE DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down +DROP TRIGGER trig_row_before_delete ON rem1; + +CREATE TRIGGER trig_row_after_delete +AFTER DELETE ON rem1 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (verbose, costs off) +UPDATE rem1 set f2 = ''; -- can be pushed down +EXPLAIN (verbose, costs off) +DELETE FROM rem1; -- can't be pushed down +DROP TRIGGER trig_row_after_delete ON rem1; + +-- =================================================================== +-- test inheritance features +-- =================================================================== + +CREATE TABLE a (aa TEXT); +CREATE TABLE loct (aa TEXT, bb TEXT); +ALTER TABLE a SET (autovacuum_enabled = 'false'); +ALTER TABLE loct SET (autovacuum_enabled = 'false'); +CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a) + SERVER loopback OPTIONS (table_name 'loct'); + +INSERT INTO a(aa) VALUES('aaa'); +INSERT INTO a(aa) VALUES('aaaa'); +INSERT INTO a(aa) VALUES('aaaaa'); + +INSERT INTO b(aa) VALUES('bbb'); +INSERT INTO b(aa) VALUES('bbbb'); +INSERT INTO b(aa) VALUES('bbbbb'); + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%'; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +UPDATE b SET aa = 'new'; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +UPDATE a SET aa = 'newtoo'; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +DELETE FROM a; + +SELECT tableoid::regclass, * FROM a; +SELECT tableoid::regclass, * FROM b; +SELECT tableoid::regclass, * FROM ONLY a; + +DROP TABLE a CASCADE; +DROP TABLE loct; + +-- Check SELECT FOR UPDATE/SHARE with an inherited source table +create table loct1 (f1 int, f2 int, f3 int); +create table loct2 (f1 int, f2 int, f3 int); + +alter table loct1 set (autovacuum_enabled = 'false'); +alter table loct2 set (autovacuum_enabled = 'false'); + +create table foo (f1 int, f2 int); +create foreign table foo2 (f3 int) inherits (foo) + server loopback options (table_name 'loct1'); +create table bar (f1 int, f2 int); +create foreign table bar2 (f3 int) inherits (bar) + server loopback options (table_name 'loct2'); + +alter table foo set (autovacuum_enabled = 'false'); +alter table bar set (autovacuum_enabled = 'false'); + +insert into foo values(1,1); +insert into foo values(3,3); +insert into foo2 values(2,2,2); +insert into foo2 values(4,4,4); +insert into bar values(1,11); +insert into bar values(2,22); +insert into bar values(6,66); +insert into bar2 values(3,33,33); +insert into bar2 values(4,44,44); +insert into bar2 values(7,77,77); + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo) for update; +select * from bar where f1 in (select f1 from foo) for update; + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo) for share; +select * from bar where f1 in (select f1 from foo) for share; + +-- Now check SELECT FOR UPDATE/SHARE with an inherited source table, +-- where the parent is itself a foreign table +create table loct4 (f1 int, f2 int, f3 int); +create foreign table foo2child (f3 int) inherits (foo2) + server loopback options (table_name 'loct4'); + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo2) for share; +select * from bar where f1 in (select f1 from foo2) for share; + +drop foreign table foo2child; + +-- And with a local child relation of the foreign table parent +create table foo2child (f3 int) inherits (foo2); + +explain (verbose, costs off) +select * from bar where f1 in (select f1 from foo2) for share; +select * from bar where f1 in (select f1 from foo2) for share; + +drop table foo2child; + +-- Check UPDATE with inherited target and an inherited source table +explain (verbose, costs off) +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); +update bar set f2 = f2 + 100 where f1 in (select f1 from foo); + +select tableoid::regclass, * from bar order by 1,2; + +-- Check UPDATE with inherited target and an appendrel subquery +explain (verbose, costs off) +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; +update bar set f2 = f2 + 100 +from + ( select f1 from foo union all select f1+3 from foo ) ss +where bar.f1 = ss.f1; + +select tableoid::regclass, * from bar order by 1,2; + +-- Test forcing the remote server to produce sorted data for a merge join, +-- but the foreign table is an inheritance child. +truncate table loct1; +truncate table only foo; +\set num_rows_foo 2000 +insert into loct1 select generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2), generate_series(0, :num_rows_foo, 2); +insert into foo select generate_series(1, :num_rows_foo, 2), generate_series(1, :num_rows_foo, 2); +SET enable_hashjoin to false; +SET enable_nestloop to false; +alter foreign table foo2 options (use_remote_estimate 'true'); +create index i_loct1_f1 on loct1(f1); +create index i_foo_f1 on foo(f1); +analyze foo; +analyze loct1; +-- inner join; expressions in the clauses appear in the equivalence class list +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +select foo.f1, loct1.f1 from foo join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +-- outer join; expressions in the clauses do not appear in equivalence class +-- list but no output change as compared to the previous query +explain (verbose, costs off) + select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +select foo.f1, loct1.f1 from foo left join loct1 on (foo.f1 = loct1.f1) order by foo.f2 offset 10 limit 10; +RESET enable_hashjoin; +RESET enable_nestloop; + +-- Test that WHERE CURRENT OF is not supported +begin; +declare c cursor for select * from bar where f1 = 7; +fetch from c; +update bar set f2 = null where current of c; +rollback; + +explain (verbose, costs off) +delete from foo where f1 < 5 returning *; +delete from foo where f1 < 5 returning *; +explain (verbose, costs off) +update bar set f2 = f2 + 100 returning *; +update bar set f2 = f2 + 100 returning *; + +-- Test that UPDATE/DELETE with inherited target works with row-level triggers +CREATE TRIGGER trig_row_before +BEFORE UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER trig_row_after +AFTER UPDATE OR DELETE ON bar2 +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +explain (verbose, costs off) +update bar set f2 = f2 + 100; +update bar set f2 = f2 + 100; + +explain (verbose, costs off) +delete from bar where f2 < 400; +delete from bar where f2 < 400; + +-- cleanup +drop table foo cascade; +drop table bar cascade; +drop table loct1; +drop table loct2; + +-- Test pushing down UPDATE/DELETE joins to the remote server +create table parent (a int, b text); +create table loct1 (a int, b text); +create table loct2 (a int, b text); +create foreign table remt1 (a int, b text) + server loopback options (table_name 'loct1'); +create foreign table remt2 (a int, b text) + server loopback options (table_name 'loct2'); +alter foreign table remt1 inherit parent; + +insert into remt1 values (1, 'foo'); +insert into remt1 values (2, 'bar'); +insert into remt2 values (1, 'foo'); +insert into remt2 values (2, 'bar'); + +analyze remt1; +analyze remt2; + +explain (verbose, costs off) +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; +update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a returning *; +explain (verbose, costs off) +delete from parent using remt2 where parent.a = remt2.a returning parent; +delete from parent using remt2 where parent.a = remt2.a returning parent; + +-- cleanup +drop foreign table remt1; +drop foreign table remt2; +drop table loct1; +drop table loct2; +drop table parent; + +-- =================================================================== +-- test tuple routing for foreign-table partitions +-- =================================================================== + +-- Test insert tuple routing +create table itrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table itrtest attach partition remp1 for values in (1); +alter table itrtest attach partition remp2 for values in (2); + +insert into itrtest values (1, 'foo'); +insert into itrtest values (1, 'bar') returning *; +insert into itrtest values (2, 'baz'); +insert into itrtest values (2, 'qux') returning *; +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; + +select tableoid::regclass, * FROM itrtest; +select tableoid::regclass, * FROM remp1; +select tableoid::regclass, * FROM remp2; + +delete from itrtest; + +-- MERGE ought to fail cleanly +merge into itrtest using (select 1, 'foo') as source on (true) + when matched then do nothing; + +create unique index loct1_idx on loct1 (a); + +-- DO NOTHING without an inference specification is supported +insert into itrtest values (1, 'foo') on conflict do nothing returning *; +insert into itrtest values (1, 'foo') on conflict do nothing returning *; + +-- But other cases are not supported +insert into itrtest values (1, 'bar') on conflict (a) do nothing; +insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b; + +select tableoid::regclass, * FROM itrtest; + +delete from itrtest; + +drop index loct1_idx; + +-- Test that remote triggers work with insert tuple routing +create function br_insert_trigfunc() returns trigger as $$ +begin + new.b := new.b || ' triggered !'; + return new; +end +$$ language plpgsql; +create trigger loct1_br_insert_trigger before insert on loct1 + for each row execute procedure br_insert_trigfunc(); +create trigger loct2_br_insert_trigger before insert on loct2 + for each row execute procedure br_insert_trigfunc(); + +-- The new values are concatenated with ' triggered !' +insert into itrtest values (1, 'foo') returning *; +insert into itrtest values (2, 'qux') returning *; +insert into itrtest values (1, 'test1'), (2, 'test2') returning *; +with result as (insert into itrtest values (1, 'test1'), (2, 'test2') returning *) select * from result; + +drop trigger loct1_br_insert_trigger on loct1; +drop trigger loct2_br_insert_trigger on loct2; + +drop table itrtest; +drop table loct1; +drop table loct2; + +-- Test update tuple routing +create table utrtest (a int, b text) partition by list (a); +create table loct (a int check (a in (1)), b text); +create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct'); +create table locp (a int check (a in (2)), b text); +alter table utrtest attach partition remp for values in (1); +alter table utrtest attach partition locp for values in (2); + +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +select tableoid::regclass, * FROM utrtest; +select tableoid::regclass, * FROM remp; +select tableoid::regclass, * FROM locp; + +-- It's not allowed to move a row from a partition that is foreign to another +update utrtest set a = 2 where b = 'foo' returning *; + +-- But the reverse is allowed +update utrtest set a = 1 where b = 'qux' returning *; + +select tableoid::regclass, * FROM utrtest; +select tableoid::regclass, * FROM remp; +select tableoid::regclass, * FROM locp; + +-- The executor should not let unexercised FDWs shut down +update utrtest set a = 1 where b = 'foo'; + +-- Test that remote triggers work with update tuple routing +create trigger loct_br_insert_trigger before insert on loct + for each row execute procedure br_insert_trigfunc(); + +delete from utrtest; +insert into utrtest values (2, 'qux'); + +-- Check case where the foreign partition is a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 1 or a = 2 returning *; +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 1 or a = 2 returning *; + +delete from utrtest; +insert into utrtest values (2, 'qux'); + +-- Check case where the foreign partition isn't a subplan target rel +explain (verbose, costs off) +update utrtest set a = 1 where a = 2 returning *; +-- The new values are concatenated with ' triggered !' +update utrtest set a = 1 where a = 2 returning *; + +drop trigger loct_br_insert_trigger on loct; + +-- We can move rows to a foreign partition that has been updated already, +-- but can't move rows to a foreign partition that hasn't been updated yet + +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +-- Test the former case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 returning *; +update utrtest set a = 1 returning *; + +delete from utrtest; +insert into utrtest values (1, 'foo'); +insert into utrtest values (2, 'qux'); + +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; +update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *; + +-- Change the definition of utrtest so that the foreign partition get updated +-- after the local partition +delete from utrtest; +alter table utrtest detach partition remp; +drop foreign table remp; +alter table loct drop constraint loct_a_check; +alter table loct add check (a in (3)); +create foreign table remp (a int check (a in (3)), b text) server loopback options (table_name 'loct'); +alter table utrtest attach partition remp for values in (3); +insert into utrtest values (2, 'qux'); +insert into utrtest values (3, 'xyzzy'); + +-- Test the latter case: +-- with a direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 returning *; +update utrtest set a = 3 returning *; -- ERROR + +-- with a non-direct modification plan +explain (verbose, costs off) +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; +update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *; -- ERROR + +drop table utrtest; +drop table loct; + +-- Test copy tuple routing +create table ctrtest (a int, b text) partition by list (a); +create table loct1 (a int check (a in (1)), b text); +create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1'); +create table loct2 (a int check (a in (2)), b text); +create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2'); +alter table ctrtest attach partition remp1 for values in (1); +alter table ctrtest attach partition remp2 for values in (2); + +copy ctrtest from stdin; +1 foo +2 qux +\. + +select tableoid::regclass, * FROM ctrtest; +select tableoid::regclass, * FROM remp1; +select tableoid::regclass, * FROM remp2; + +-- Copying into foreign partitions directly should work as well +copy remp1 from stdin; +1 bar +\. + +select tableoid::regclass, * FROM remp1; + +delete from ctrtest; + +-- Test copy tuple routing with the batch_size option enabled +alter server loopback options (add batch_size '2'); + +copy ctrtest from stdin; +1 foo +1 bar +2 baz +2 qux +1 test1 +2 test2 +\. + +select tableoid::regclass, * FROM ctrtest; +select tableoid::regclass, * FROM remp1; +select tableoid::regclass, * FROM remp2; + +delete from ctrtest; + +alter server loopback options (drop batch_size); + +drop table ctrtest; +drop table loct1; +drop table loct2; + +-- =================================================================== +-- test COPY FROM +-- =================================================================== + +create table loc2 (f1 int, f2 text); +alter table loc2 set (autovacuum_enabled = 'false'); +create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2'); + +-- Test basic functionality +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +delete from rem2; + +-- Test check constraints +alter table loc2 add constraint loc2_f1positive check (f1 >= 0); +alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0); + +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +1 foo +2 bar +\. +copy rem2 from stdin; -- ERROR +-1 xyzzy +\. +select * from rem2; + +alter foreign table rem2 drop constraint rem2_f1positive; +alter table loc2 drop constraint loc2_f1positive; + +delete from rem2; + +-- Test local triggers +create trigger trig_stmt_before before insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_stmt_after after insert on rem2 + for each statement execute procedure trigger_func(); +create trigger trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); + +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before on rem2; +drop trigger trig_row_after on rem2; +drop trigger trig_stmt_before on rem2; +drop trigger trig_stmt_after on rem2; + +delete from rem2; + +create trigger trig_row_before_insert before insert on rem2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before_insert on rem2; + +delete from rem2; + +create trigger trig_null before insert on rem2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_null on rem2; + +delete from rem2; + +-- Test remote triggers +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_row_before_insert on loc2; + +delete from rem2; + +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger trig_null on loc2; + +delete from rem2; + +-- Test a combination of local and remote triggers +create trigger rem2_trig_row_before before insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger rem2_trig_row_after after insert on rem2 + for each row execute procedure trigger_data(23,'skidoo'); +create trigger loc2_trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +copy rem2 from stdin; +1 foo +2 bar +\. +select * from rem2; + +drop trigger rem2_trig_row_before on rem2; +drop trigger rem2_trig_row_after on rem2; +drop trigger loc2_trig_row_before_insert on loc2; + +delete from rem2; + +-- test COPY FROM with foreign table created in the same transaction +create table loc3 (f1 int, f2 text); +begin; +create foreign table rem3 (f1 int, f2 text) + server loopback options(table_name 'loc3'); +copy rem3 from stdin; +1 foo +2 bar +\. +commit; +select * from rem3; +drop foreign table rem3; +drop table loc3; + +-- Test COPY FROM with the batch_size option enabled +alter server loopback options (add batch_size '2'); + +-- Test basic functionality +copy rem2 from stdin; +1 foo +2 bar +3 baz +\. +select * from rem2; + +delete from rem2; + +-- Test check constraints +alter table loc2 add constraint loc2_f1positive check (f1 >= 0); +alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0); + +-- check constraint is enforced on the remote side, not locally +copy rem2 from stdin; +1 foo +2 bar +3 baz +\. +copy rem2 from stdin; -- ERROR +-1 xyzzy +\. +select * from rem2; + +alter foreign table rem2 drop constraint rem2_f1positive; +alter table loc2 drop constraint loc2_f1positive; + +delete from rem2; + +-- Test remote triggers +create trigger trig_row_before_insert before insert on loc2 + for each row execute procedure trig_row_before_insupdate(); + +-- The new values are concatenated with ' triggered !' +copy rem2 from stdin; +1 foo +2 bar +3 baz +\. +select * from rem2; + +drop trigger trig_row_before_insert on loc2; + +delete from rem2; + +create trigger trig_null before insert on loc2 + for each row execute procedure trig_null(); + +-- Nothing happens +copy rem2 from stdin; +1 foo +2 bar +3 baz +\. +select * from rem2; + +drop trigger trig_null on loc2; + +delete from rem2; + +-- Check with zero-column foreign table; batch insert will be disabled +alter table loc2 drop column f1; +alter table loc2 drop column f2; +alter table rem2 drop column f1; +alter table rem2 drop column f2; +copy rem2 from stdin; + + + +\. +select * from rem2; + +delete from rem2; + +alter server loopback options (drop batch_size); + +-- =================================================================== +-- test for TRUNCATE +-- =================================================================== +CREATE TABLE tru_rtable0 (id int primary key); +CREATE FOREIGN TABLE tru_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_rtable0'); +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(1,10) x); + +CREATE TABLE tru_ptable (id int) PARTITION BY HASH(id); +CREATE TABLE tru_ptable__p0 PARTITION OF tru_ptable + FOR VALUES WITH (MODULUS 2, REMAINDER 0); +CREATE TABLE tru_rtable1 (id int primary key); +CREATE FOREIGN TABLE tru_ftable__p1 PARTITION OF tru_ptable + FOR VALUES WITH (MODULUS 2, REMAINDER 1) + SERVER loopback OPTIONS (table_name 'tru_rtable1'); +INSERT INTO tru_ptable (SELECT x FROM generate_series(11,20) x); + +CREATE TABLE tru_pk_table(id int primary key); +CREATE TABLE tru_fk_table(fkey int references tru_pk_table(id)); +INSERT INTO tru_pk_table (SELECT x FROM generate_series(1,10) x); +INSERT INTO tru_fk_table (SELECT x % 10 + 1 FROM generate_series(5,25) x); +CREATE FOREIGN TABLE tru_pk_ftable (id int) + SERVER loopback OPTIONS (table_name 'tru_pk_table'); + +CREATE TABLE tru_rtable_parent (id int); +CREATE TABLE tru_rtable_child (id int); +CREATE FOREIGN TABLE tru_ftable_parent (id int) + SERVER loopback OPTIONS (table_name 'tru_rtable_parent'); +CREATE FOREIGN TABLE tru_ftable_child () INHERITS (tru_ftable_parent) + SERVER loopback OPTIONS (table_name 'tru_rtable_child'); +INSERT INTO tru_rtable_parent (SELECT x FROM generate_series(1,8) x); +INSERT INTO tru_rtable_child (SELECT x FROM generate_series(10, 18) x); + +-- normal truncate +SELECT sum(id) FROM tru_ftable; -- 55 +TRUNCATE tru_ftable; +SELECT count(*) FROM tru_rtable0; -- 0 +SELECT count(*) FROM tru_ftable; -- 0 + +-- 'truncatable' option +ALTER SERVER loopback OPTIONS (ADD truncatable 'false'); +TRUNCATE tru_ftable; -- error +ALTER FOREIGN TABLE tru_ftable OPTIONS (ADD truncatable 'true'); +TRUNCATE tru_ftable; -- accepted +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false'); +TRUNCATE tru_ftable; -- error +ALTER SERVER loopback OPTIONS (DROP truncatable); +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'false'); +TRUNCATE tru_ftable; -- error +ALTER FOREIGN TABLE tru_ftable OPTIONS (SET truncatable 'true'); +TRUNCATE tru_ftable; -- accepted + +-- partitioned table with both local and foreign tables as partitions +SELECT sum(id) FROM tru_ptable; -- 155 +TRUNCATE tru_ptable; +SELECT count(*) FROM tru_ptable; -- 0 +SELECT count(*) FROM tru_ptable__p0; -- 0 +SELECT count(*) FROM tru_ftable__p1; -- 0 +SELECT count(*) FROM tru_rtable1; -- 0 + +-- 'CASCADE' option +SELECT sum(id) FROM tru_pk_ftable; -- 55 +TRUNCATE tru_pk_ftable; -- failed by FK reference +TRUNCATE tru_pk_ftable CASCADE; +SELECT count(*) FROM tru_pk_ftable; -- 0 +SELECT count(*) FROM tru_fk_table; -- also truncated,0 + +-- truncate two tables at a command +INSERT INTO tru_ftable (SELECT x FROM generate_series(1,8) x); +INSERT INTO tru_pk_ftable (SELECT x FROM generate_series(3,10) x); +SELECT count(*) from tru_ftable; -- 8 +SELECT count(*) from tru_pk_ftable; -- 8 +TRUNCATE tru_ftable, tru_pk_ftable CASCADE; +SELECT count(*) from tru_ftable; -- 0 +SELECT count(*) from tru_pk_ftable; -- 0 + +-- truncate with ONLY clause +-- Since ONLY is specified, the table tru_ftable_child that inherits +-- tru_ftable_parent locally is not truncated. +TRUNCATE ONLY tru_ftable_parent; +SELECT sum(id) FROM tru_ftable_parent; -- 126 +TRUNCATE tru_ftable_parent; +SELECT count(*) FROM tru_ftable_parent; -- 0 + +-- in case when remote table has inherited children +CREATE TABLE tru_rtable0_child () INHERITS (tru_rtable0); +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(5,9) x); +INSERT INTO tru_rtable0_child (SELECT x FROM generate_series(10,14) x); +SELECT sum(id) FROM tru_ftable; -- 95 + +-- Both parent and child tables in the foreign server are truncated +-- even though ONLY is specified because ONLY has no effect +-- when truncating a foreign table. +TRUNCATE ONLY tru_ftable; +SELECT count(*) FROM tru_ftable; -- 0 + +INSERT INTO tru_rtable0 (SELECT x FROM generate_series(21,25) x); +INSERT INTO tru_rtable0_child (SELECT x FROM generate_series(26,30) x); +SELECT sum(id) FROM tru_ftable; -- 255 +TRUNCATE tru_ftable; -- truncate both of parent and child +SELECT count(*) FROM tru_ftable; -- 0 + +-- cleanup +DROP FOREIGN TABLE tru_ftable_parent, tru_ftable_child, tru_pk_ftable,tru_ftable__p1,tru_ftable; +DROP TABLE tru_rtable0, tru_rtable1, tru_ptable, tru_ptable__p0, tru_pk_table, tru_fk_table, +tru_rtable_parent,tru_rtable_child, tru_rtable0_child; + +-- =================================================================== +-- test IMPORT FOREIGN SCHEMA +-- =================================================================== + +CREATE SCHEMA import_source; +CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL); +CREATE TABLE import_source.t2 (c1 int default 42, c2 varchar NULL, c3 text collate "POSIX"); +CREATE TYPE typ1 AS (m1 int, m2 varchar); +CREATE TABLE import_source.t3 (c1 timestamptz default now(), c2 typ1); +CREATE TABLE import_source."x 4" (c1 float8, "C 2" text, c3 varchar(42)); +CREATE TABLE import_source."x 5" (c1 float8); +ALTER TABLE import_source."x 5" DROP COLUMN c1; +CREATE TABLE import_source."x 6" (c1 int, c2 int generated always as (c1 * 2) stored); +CREATE TABLE import_source.t4 (c1 int) PARTITION BY RANGE (c1); +CREATE TABLE import_source.t4_part PARTITION OF import_source.t4 + FOR VALUES FROM (1) TO (100); +CREATE TABLE import_source.t4_part2 PARTITION OF import_source.t4 + FOR VALUES FROM (100) TO (200); + +CREATE SCHEMA import_dest1; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1; +\det+ import_dest1.* +\d import_dest1.* + +-- Options +CREATE SCHEMA import_dest2; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest2 + OPTIONS (import_default 'true'); +\det+ import_dest2.* +\d import_dest2.* +CREATE SCHEMA import_dest3; +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest3 + OPTIONS (import_collate 'false', import_generated 'false', import_not_null 'false'); +\det+ import_dest3.* +\d import_dest3.* + +-- Check LIMIT TO and EXCEPT +CREATE SCHEMA import_dest4; +IMPORT FOREIGN SCHEMA import_source LIMIT TO (t1, nonesuch, t4_part) + FROM SERVER loopback INTO import_dest4; +\det+ import_dest4.* +IMPORT FOREIGN SCHEMA import_source EXCEPT (t1, "x 4", nonesuch, t4_part) + FROM SERVER loopback INTO import_dest4; +\det+ import_dest4.* + +-- Assorted error cases +IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest4; +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO import_dest4; +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO notthere; +IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere; + +-- Check case of a type present only on the remote server. +-- We can fake this by dropping the type locally in our transaction. +CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue'); +CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors"); + +CREATE SCHEMA import_dest5; +BEGIN; +DROP TYPE "Colors" CASCADE; +IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5) + FROM SERVER loopback INTO import_dest5; -- ERROR + +ROLLBACK; + +BEGIN; + + +CREATE SERVER fetch101 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( fetch_size '101' ); + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=101']; + +ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' ); + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=101']; + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'fetch101' +AND srvoptions @> array['fetch_size=202']; + +CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' ); + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=30000']; + +ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000'); + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=30000']; + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30000'::regclass +AND ftoptions @> array['fetch_size=60000']; + +ROLLBACK; + +-- =================================================================== +-- test partitionwise joins +-- =================================================================== +SET enable_partitionwise_join=on; + +CREATE TABLE fprt1 (a int, b int, c varchar) PARTITION BY RANGE(a); +CREATE TABLE fprt1_p1 (LIKE fprt1); +CREATE TABLE fprt1_p2 (LIKE fprt1); +ALTER TABLE fprt1_p1 SET (autovacuum_enabled = 'false'); +ALTER TABLE fprt1_p2 SET (autovacuum_enabled = 'false'); +INSERT INTO fprt1_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 2) i; +INSERT INTO fprt1_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 2) i; +CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (0) TO (250) + SERVER loopback OPTIONS (table_name 'fprt1_p1', use_remote_estimate 'true'); +CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (250) TO (500) + SERVER loopback OPTIONS (TABLE_NAME 'fprt1_p2'); +ANALYZE fprt1; +ANALYZE fprt1_p1; +ANALYZE fprt1_p2; + +CREATE TABLE fprt2 (a int, b int, c varchar) PARTITION BY RANGE(b); +CREATE TABLE fprt2_p1 (LIKE fprt2); +CREATE TABLE fprt2_p2 (LIKE fprt2); +ALTER TABLE fprt2_p1 SET (autovacuum_enabled = 'false'); +ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false'); +INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i; +INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i; +CREATE FOREIGN TABLE ftprt2_p1 (b int, c varchar, a int) + SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true'); +ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250); +CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500) + SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true'); +ANALYZE fprt2; +ANALYZE fprt2_p1; +ANALYZE fprt2_p2; + +-- inner join three tables +EXPLAIN (COSTS OFF) +SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; +SELECT t1.a,t2.b,t3.c FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) INNER JOIN fprt1 t3 ON (t2.b = t3.a) WHERE t1.a % 25 =0 ORDER BY 1,2,3; + +-- left outer join + nullable clause +EXPLAIN (VERBOSE, COSTS OFF) +SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; +SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a < 10 ORDER BY 1,2,3; + +-- with whole-row reference; partitionwise join does not apply +EXPLAIN (COSTS OFF) +SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; +SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; + +-- join with lateral reference +EXPLAIN (COSTS OFF) +SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2; +SELECT t1.a,t1.b FROM fprt1 t1, LATERAL (SELECT t2.a, t2.b FROM fprt2 t2 WHERE t1.a = t2.b AND t1.b = t2.a) q WHERE t1.a%25 = 0 ORDER BY 1,2; + +-- with PHVs, partitionwise join selected but no join pushdown +EXPLAIN (COSTS OFF) +SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; +SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; + +-- test FOR UPDATE; partitionwise join does not apply +EXPLAIN (COSTS OFF) +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; +SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; + +RESET enable_partitionwise_join; + + +-- =================================================================== +-- test partitionwise aggregates +-- =================================================================== + +CREATE TABLE pagg_tab (a int, b int, c text) PARTITION BY RANGE(a); + +CREATE TABLE pagg_tab_p1 (LIKE pagg_tab); +CREATE TABLE pagg_tab_p2 (LIKE pagg_tab); +CREATE TABLE pagg_tab_p3 (LIKE pagg_tab); + +INSERT INTO pagg_tab_p1 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 10; +INSERT INTO pagg_tab_p2 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 20 and (i % 30) >= 10; +INSERT INTO pagg_tab_p3 SELECT i % 30, i % 50, to_char(i/30, 'FM0000') FROM generate_series(1, 3000) i WHERE (i % 30) < 30 and (i % 30) >= 20; + +-- Create foreign partitions +CREATE FOREIGN TABLE fpagg_tab_p1 PARTITION OF pagg_tab FOR VALUES FROM (0) TO (10) SERVER loopback OPTIONS (table_name 'pagg_tab_p1'); +CREATE FOREIGN TABLE fpagg_tab_p2 PARTITION OF pagg_tab FOR VALUES FROM (10) TO (20) SERVER loopback OPTIONS (table_name 'pagg_tab_p2'); +CREATE FOREIGN TABLE fpagg_tab_p3 PARTITION OF pagg_tab FOR VALUES FROM (20) TO (30) SERVER loopback OPTIONS (table_name 'pagg_tab_p3'); + +ANALYZE pagg_tab; +ANALYZE fpagg_tab_p1; +ANALYZE fpagg_tab_p2; +ANALYZE fpagg_tab_p3; + +-- When GROUP BY clause matches with PARTITION KEY. +-- Plan with partitionwise aggregates is disabled +SET enable_partitionwise_aggregate TO false; +EXPLAIN (COSTS OFF) +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + +-- Plan with partitionwise aggregates is enabled +SET enable_partitionwise_aggregate TO true; +EXPLAIN (COSTS OFF) +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; +SELECT a, sum(b), min(b), count(*) FROM pagg_tab GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + +-- Check with whole-row reference +-- Should have all the columns in the target list for the given relation +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a, count(t1) FROM pagg_tab t1 GROUP BY a HAVING avg(b) < 22 ORDER BY 1; +SELECT a, count(t1) FROM pagg_tab t1 GROUP BY a HAVING avg(b) < 22 ORDER BY 1; + +-- When GROUP BY clause does not match with PARTITION KEY. +EXPLAIN (COSTS OFF) +SELECT b, avg(a), max(a), count(*) FROM pagg_tab GROUP BY b HAVING sum(a) < 700 ORDER BY 1; + +-- =================================================================== +-- access rights and superuser +-- =================================================================== + +-- Non-superuser cannot create a FDW without a password in the connstr +CREATE ROLE regress_nosuper NOSUPERUSER; + +GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO regress_nosuper; + +SET ROLE regress_nosuper; + +SHOW is_superuser; + +-- This will be OK, we can create the FDW +DO $d$ + BEGIN + EXECUTE $$CREATE SERVER loopback_nopw FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname '$$||current_database()||$$', + port '$$||current_setting('port')||$$' + )$$; + END; +$d$; + +-- But creation of user mappings for non-superusers should fail +CREATE USER MAPPING FOR public SERVER loopback_nopw; +CREATE USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; + +CREATE FOREIGN TABLE pg_temp.ft1_nopw ( + c1 int NOT NULL, + c2 int NOT NULL, + c3 text, + c4 timestamptz, + c5 timestamp, + c6 varchar(10), + c7 char(10) default 'ft1', + c8 user_enum +) SERVER loopback_nopw OPTIONS (schema_name 'public', table_name 'ft1'); + +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- If we add a password to the connstr it'll fail, because we don't allow passwords +-- in connstrs only in user mappings. + +ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw'); + +-- If we add a password for our user mapping instead, we should get a different +-- error because the password wasn't actually *used* when we run with trust auth. +-- +-- This won't work with installcheck, but neither will most of the FDW checks. + +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password 'dummypw'); + +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- Unpriv user cannot make the mapping passwordless +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD password_required 'false'); + + +SELECT 1 FROM ft1_nopw LIMIT 1; + +RESET ROLE; + +-- But the superuser can +ALTER USER MAPPING FOR regress_nosuper SERVER loopback_nopw OPTIONS (ADD password_required 'false'); + +SET ROLE regress_nosuper; + +-- Should finally work now +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- unpriv user also cannot set sslcert / sslkey on the user mapping +-- first set password_required so we see the right error messages +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (SET password_required 'true'); +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslcert 'foo.crt'); +ALTER USER MAPPING FOR CURRENT_USER SERVER loopback_nopw OPTIONS (ADD sslkey 'foo.key'); + +-- We're done with the role named after a specific user and need to check the +-- changes to the public mapping. +DROP USER MAPPING FOR CURRENT_USER SERVER loopback_nopw; + +-- This will fail again as it'll resolve the user mapping for public, which +-- lacks password_required=false +SELECT 1 FROM ft1_nopw LIMIT 1; + +RESET ROLE; + +-- The user mapping for public is passwordless and lacks the password_required=false +-- mapping option, but will work because the current user is a superuser. +SELECT 1 FROM ft1_nopw LIMIT 1; + +-- cleanup +DROP USER MAPPING FOR public SERVER loopback_nopw; +DROP OWNED BY regress_nosuper; +DROP ROLE regress_nosuper; + +-- Clean-up +RESET enable_partitionwise_aggregate; + +-- Two-phase transactions are not supported. +BEGIN; +SELECT count(*) FROM ft1; +-- error here +PREPARE TRANSACTION 'fdw_tpc'; +ROLLBACK; + +-- =================================================================== +-- reestablish new connection +-- =================================================================== + +-- Change application_name of remote connection to special one +-- so that we can easily terminate the connection later. +ALTER SERVER loopback OPTIONS (application_name 'fdw_retry_check'); + +-- Make sure we have a remote connection. +SELECT 1 FROM ft1 LIMIT 1; + +-- Terminate the remote connection and wait for the termination to complete. +-- (If a cache flush happens, the remote connection might have already been +-- dropped; so code this step in a way that doesn't fail if no connection.) +DO $$ BEGIN +PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity + WHERE application_name = 'fdw_retry_check'; +END $$; + +-- This query should detect the broken connection when starting new remote +-- transaction, reestablish new connection, and then succeed. +BEGIN; +SELECT 1 FROM ft1 LIMIT 1; + +-- If we detect the broken connection when starting a new remote +-- subtransaction, we should fail instead of establishing a new connection. +-- Terminate the remote connection and wait for the termination to complete. +DO $$ BEGIN +PERFORM pg_terminate_backend(pid, 180000) FROM pg_stat_activity + WHERE application_name = 'fdw_retry_check'; +END $$; +SAVEPOINT s; +-- The text of the error might vary across platforms, so only show SQLSTATE. +\set VERBOSITY sqlstate +SELECT 1 FROM ft1 LIMIT 1; -- should fail +\set VERBOSITY default +COMMIT; + +-- ============================================================================= +-- test connection invalidation cases and postgres_fdw_get_connections function +-- ============================================================================= +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +-- This test case is for closing the connection in pgfdw_xact_callback +BEGIN; +-- Connection xact depth becomes 1 i.e. the connection is in midst of the xact. +SELECT 1 FROM ft1 LIMIT 1; +SELECT 1 FROM ft7 LIMIT 1; +-- List all the existing cached connections. loopback and loopback3 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +-- Connections are not closed at the end of the alter and drop statements. +-- That's because the connections are in midst of this xact, +-- they are just marked as invalid in pgfdw_inval_callback. +ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off'); +DROP SERVER loopback3 CASCADE; +-- List all the existing cached connections. loopback and loopback3 +-- should be output as invalid connections. Also the server name for +-- loopback3 should be NULL because the server was dropped. +SELECT * FROM postgres_fdw_get_connections() ORDER BY 1; +-- The invalid connections get closed in pgfdw_xact_callback during commit. +COMMIT; +-- All cached connections were closed while committing above xact, so no +-- records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + +-- ======================================================================= +-- test postgres_fdw_disconnect and postgres_fdw_disconnect_all functions +-- ======================================================================= +BEGIN; +-- Ensure to cache loopback connection. +SELECT 1 FROM ft1 LIMIT 1; +-- Ensure to cache loopback2 connection. +SELECT 1 FROM ft6 LIMIT 1; +-- List all the existing cached connections. loopback and loopback2 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +-- Issue a warning and return false as loopback connection is still in use and +-- can not be closed. +SELECT postgres_fdw_disconnect('loopback'); +-- List all the existing cached connections. loopback and loopback2 should be +-- output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +-- Return false as connections are still in use, warnings are issued. +-- But disable warnings temporarily because the order of them is not stable. +SET client_min_messages = 'ERROR'; +SELECT postgres_fdw_disconnect_all(); +RESET client_min_messages; +COMMIT; +-- Ensure that loopback2 connection is closed. +SELECT 1 FROM postgres_fdw_disconnect('loopback2'); +SELECT server_name FROM postgres_fdw_get_connections() WHERE server_name = 'loopback2'; +-- Return false as loopback2 connection is closed already. +SELECT postgres_fdw_disconnect('loopback2'); +-- Return an error as there is no foreign server with given name. +SELECT postgres_fdw_disconnect('unknownserver'); +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + +-- ============================================================================= +-- test case for having multiple cached connections for a foreign server +-- ============================================================================= +CREATE ROLE regress_multi_conn_user1 SUPERUSER; +CREATE ROLE regress_multi_conn_user2 SUPERUSER; +CREATE USER MAPPING FOR regress_multi_conn_user1 SERVER loopback; +CREATE USER MAPPING FOR regress_multi_conn_user2 SERVER loopback; + +BEGIN; +-- Will cache loopback connection with user mapping for regress_multi_conn_user1 +SET ROLE regress_multi_conn_user1; +SELECT 1 FROM ft1 LIMIT 1; +RESET ROLE; + +-- Will cache loopback connection with user mapping for regress_multi_conn_user2 +SET ROLE regress_multi_conn_user2; +SELECT 1 FROM ft1 LIMIT 1; +RESET ROLE; + +-- Should output two connections for loopback server +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +COMMIT; +-- Let's ensure to close all the existing cached connections. +SELECT 1 FROM postgres_fdw_disconnect_all(); +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; + +-- Clean up +DROP USER MAPPING FOR regress_multi_conn_user1 SERVER loopback; +DROP USER MAPPING FOR regress_multi_conn_user2 SERVER loopback; +DROP ROLE regress_multi_conn_user1; +DROP ROLE regress_multi_conn_user2; + +-- =================================================================== +-- Test foreign server level option keep_connections +-- =================================================================== +-- By default, the connections associated with foreign server are cached i.e. +-- keep_connections option is on. Set it to off. +ALTER SERVER loopback OPTIONS (keep_connections 'off'); +-- connection to loopback server is closed at the end of xact +-- as keep_connections was set to off. +SELECT 1 FROM ft1 LIMIT 1; +-- No cached connections, so no records should be output. +SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1; +ALTER SERVER loopback OPTIONS (SET keep_connections 'on'); + +-- =================================================================== +-- batch insert +-- =================================================================== + +BEGIN; + +CREATE SERVER batch10 FOREIGN DATA WRAPPER postgres_fdw OPTIONS( batch_size '10' ); + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; + +ALTER SERVER batch10 OPTIONS( SET batch_size '20' ); + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=10']; + +SELECT count(*) +FROM pg_foreign_server +WHERE srvname = 'batch10' +AND srvoptions @> array['batch_size=20']; + +CREATE FOREIGN TABLE table30 ( x int ) SERVER batch10 OPTIONS ( batch_size '30' ); + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; + +ALTER FOREIGN TABLE table30 OPTIONS ( SET batch_size '40'); + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=30']; + +SELECT COUNT(*) +FROM pg_foreign_table +WHERE ftrelid = 'table30'::regclass +AND ftoptions @> array['batch_size=40']; + +ROLLBACK; + +CREATE TABLE batch_table ( x int ); + +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table', batch_size '10' ); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; +INSERT INTO ftable SELECT * FROM generate_series(1, 10) i; +INSERT INTO ftable SELECT * FROM generate_series(11, 31) i; +INSERT INTO ftable VALUES (32); +INSERT INTO ftable VALUES (33), (34); +SELECT COUNT(*) FROM ftable; +TRUNCATE batch_table; +DROP FOREIGN TABLE ftable; + +-- Disable batch insert +CREATE FOREIGN TABLE ftable ( x int ) SERVER loopback OPTIONS ( table_name 'batch_table', batch_size '1' ); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (1), (2); +INSERT INTO ftable VALUES (1), (2); +SELECT COUNT(*) FROM ftable; + +-- Disable batch inserting into foreign tables with BEFORE ROW INSERT triggers +-- even if the batch_size option is enabled. +ALTER FOREIGN TABLE ftable OPTIONS ( SET batch_size '10' ); +CREATE TRIGGER trig_row_before BEFORE INSERT ON ftable +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +EXPLAIN (VERBOSE, COSTS OFF) INSERT INTO ftable VALUES (3), (4); +INSERT INTO ftable VALUES (3), (4); +SELECT COUNT(*) FROM ftable; + +-- Clean up +DROP TRIGGER trig_row_before ON ftable; +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; + +-- Use partitioning +CREATE TABLE batch_table ( x int ) PARTITION BY HASH (x); + +CREATE TABLE batch_table_p0 (LIKE batch_table); +CREATE FOREIGN TABLE batch_table_p0f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 0) + SERVER loopback + OPTIONS (table_name 'batch_table_p0', batch_size '10'); + +CREATE TABLE batch_table_p1 (LIKE batch_table); +CREATE FOREIGN TABLE batch_table_p1f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 1) + SERVER loopback + OPTIONS (table_name 'batch_table_p1', batch_size '1'); + +CREATE TABLE batch_table_p2 + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 3, REMAINDER 2); + +INSERT INTO batch_table SELECT * FROM generate_series(1, 66) i; +SELECT COUNT(*) FROM batch_table; + +-- Clean up +DROP TABLE batch_table; +DROP TABLE batch_table_p0; +DROP TABLE batch_table_p1; + +-- Check that batched mode also works for some inserts made during +-- cross-partition updates +CREATE TABLE batch_cp_upd_test (a int) PARTITION BY LIST (a); +CREATE TABLE batch_cp_upd_test1 (LIKE batch_cp_upd_test); +CREATE FOREIGN TABLE batch_cp_upd_test1_f + PARTITION OF batch_cp_upd_test + FOR VALUES IN (1) + SERVER loopback + OPTIONS (table_name 'batch_cp_upd_test1', batch_size '10'); +CREATE TABLE batch_cp_upd_test2 PARTITION OF batch_cp_upd_test + FOR VALUES IN (2); +CREATE TABLE batch_cp_upd_test3 (LIKE batch_cp_upd_test); +CREATE FOREIGN TABLE batch_cp_upd_test3_f + PARTITION OF batch_cp_upd_test + FOR VALUES IN (3) + SERVER loopback + OPTIONS (table_name 'batch_cp_upd_test3', batch_size '1'); + +-- Create statement triggers on remote tables that "log" any INSERTs +-- performed on them. +CREATE TABLE cmdlog (cmd text); +CREATE FUNCTION log_stmt() RETURNS TRIGGER LANGUAGE plpgsql AS $$ + BEGIN INSERT INTO public.cmdlog VALUES (TG_OP || ' on ' || TG_RELNAME); RETURN NULL; END; +$$; +CREATE TRIGGER stmt_trig AFTER INSERT ON batch_cp_upd_test1 + FOR EACH STATEMENT EXECUTE FUNCTION log_stmt(); +CREATE TRIGGER stmt_trig AFTER INSERT ON batch_cp_upd_test3 + FOR EACH STATEMENT EXECUTE FUNCTION log_stmt(); + +-- This update moves rows from the local partition 'batch_cp_upd_test2' to the +-- foreign partition 'batch_cp_upd_test1', one that has insert batching +-- enabled, so a single INSERT for both rows. +INSERT INTO batch_cp_upd_test VALUES (2), (2); +UPDATE batch_cp_upd_test t SET a = 1 FROM (VALUES (1), (2)) s(a) WHERE t.a = s.a AND s.a = 2; + +-- This one moves rows from the local partition 'batch_cp_upd_test2' to the +-- foreign partition 'batch_cp_upd_test2', one that has insert batching +-- disabled, so separate INSERTs for the two rows. +INSERT INTO batch_cp_upd_test VALUES (2), (2); +UPDATE batch_cp_upd_test t SET a = 3 FROM (VALUES (1), (2)) s(a) WHERE t.a = s.a AND s.a = 2; + +SELECT tableoid::regclass, * FROM batch_cp_upd_test ORDER BY 1; + +-- Should see 1 INSERT on batch_cp_upd_test1 and 2 on batch_cp_upd_test3 as +-- described above. +SELECT * FROM cmdlog ORDER BY 1; + +-- Clean up +DROP TABLE batch_cp_upd_test; +DROP TABLE batch_cp_upd_test1; +DROP TABLE batch_cp_upd_test3; +DROP TABLE cmdlog; +DROP FUNCTION log_stmt(); + +-- Use partitioning +ALTER SERVER loopback OPTIONS (ADD batch_size '10'); + +CREATE TABLE batch_table ( x int, field1 text, field2 text) PARTITION BY HASH (x); + +CREATE TABLE batch_table_p0 (LIKE batch_table); +ALTER TABLE batch_table_p0 ADD CONSTRAINT p0_pkey PRIMARY KEY (x); +CREATE FOREIGN TABLE batch_table_p0f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 2, REMAINDER 0) + SERVER loopback + OPTIONS (table_name 'batch_table_p0'); + +CREATE TABLE batch_table_p1 (LIKE batch_table); +ALTER TABLE batch_table_p1 ADD CONSTRAINT p1_pkey PRIMARY KEY (x); +CREATE FOREIGN TABLE batch_table_p1f + PARTITION OF batch_table + FOR VALUES WITH (MODULUS 2, REMAINDER 1) + SERVER loopback + OPTIONS (table_name 'batch_table_p1'); + +INSERT INTO batch_table SELECT i, 'test'||i, 'test'|| i FROM generate_series(1, 50) i; +SELECT COUNT(*) FROM batch_table; +SELECT * FROM batch_table ORDER BY x; + +-- Clean up +DROP TABLE batch_table; +DROP TABLE batch_table_p0; +DROP TABLE batch_table_p1; + +ALTER SERVER loopback OPTIONS (DROP batch_size); + +-- Test that pending inserts are handled properly when needed +CREATE TABLE batch_table (a text, b int); +CREATE FOREIGN TABLE ftable (a text, b int) + SERVER loopback + OPTIONS (table_name 'batch_table', batch_size '2'); +CREATE TABLE ltable (a text, b int); +CREATE FUNCTION ftable_rowcount_trigf() RETURNS trigger LANGUAGE plpgsql AS +$$ +begin + raise notice '%: there are % rows in ftable', + TG_NAME, (SELECT count(*) FROM ftable); + if TG_OP = 'DELETE' then + return OLD; + else + return NEW; + end if; +end; +$$; +CREATE TRIGGER ftable_rowcount_trigger +BEFORE INSERT OR UPDATE OR DELETE ON ltable +FOR EACH ROW EXECUTE PROCEDURE ftable_rowcount_trigf(); + +WITH t AS ( + INSERT INTO ltable VALUES ('AAA', 42), ('BBB', 42) RETURNING * +) +INSERT INTO ftable SELECT * FROM t; + +SELECT * FROM ltable; +SELECT * FROM ftable; +DELETE FROM ftable; + +WITH t AS ( + UPDATE ltable SET b = b + 100 RETURNING * +) +INSERT INTO ftable SELECT * FROM t; + +SELECT * FROM ltable; +SELECT * FROM ftable; +DELETE FROM ftable; + +WITH t AS ( + DELETE FROM ltable RETURNING * +) +INSERT INTO ftable SELECT * FROM t; + +SELECT * FROM ltable; +SELECT * FROM ftable; +DELETE FROM ftable; + +-- Clean up +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +DROP TRIGGER ftable_rowcount_trigger ON ltable; +DROP TABLE ltable; + +CREATE TABLE parent (a text, b int) PARTITION BY LIST (a); +CREATE TABLE batch_table (a text, b int); +CREATE FOREIGN TABLE ftable + PARTITION OF parent + FOR VALUES IN ('AAA') + SERVER loopback + OPTIONS (table_name 'batch_table', batch_size '2'); +CREATE TABLE ltable + PARTITION OF parent + FOR VALUES IN ('BBB'); +CREATE TRIGGER ftable_rowcount_trigger +BEFORE INSERT ON ltable +FOR EACH ROW EXECUTE PROCEDURE ftable_rowcount_trigf(); + +INSERT INTO parent VALUES ('AAA', 42), ('BBB', 42), ('AAA', 42), ('BBB', 42); + +SELECT tableoid::regclass, * FROM parent; + +-- Clean up +DROP FOREIGN TABLE ftable; +DROP TABLE batch_table; +DROP TRIGGER ftable_rowcount_trigger ON ltable; +DROP TABLE ltable; +DROP TABLE parent; +DROP FUNCTION ftable_rowcount_trigf; + +-- =================================================================== +-- test asynchronous execution +-- =================================================================== + +ALTER SERVER loopback OPTIONS (DROP extensions); +ALTER SERVER loopback OPTIONS (ADD async_capable 'true'); +ALTER SERVER loopback2 OPTIONS (ADD async_capable 'true'); + +CREATE TABLE async_pt (a int, b int, c text) PARTITION BY RANGE (a); +CREATE TABLE base_tbl1 (a int, b int, c text); +CREATE TABLE base_tbl2 (a int, b int, c text); +CREATE FOREIGN TABLE async_p1 PARTITION OF async_pt FOR VALUES FROM (1000) TO (2000) + SERVER loopback OPTIONS (table_name 'base_tbl1'); +CREATE FOREIGN TABLE async_p2 PARTITION OF async_pt FOR VALUES FROM (2000) TO (3000) + SERVER loopback2 OPTIONS (table_name 'base_tbl2'); +INSERT INTO async_p1 SELECT 1000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +INSERT INTO async_p2 SELECT 2000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; + +-- simple queries +CREATE TABLE result_tbl (a int, b int, c text); + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b % 100 = 0; +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b % 100 = 0; + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT a, b, 'AAA' || c FROM async_pt WHERE b === 505; +INSERT INTO result_tbl SELECT a, b, 'AAA' || c FROM async_pt WHERE b === 505; + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +-- Test error handling, if accessing one of the foreign partitions errors out +CREATE FOREIGN TABLE async_p_broken PARTITION OF async_pt FOR VALUES FROM (10000) TO (10001) + SERVER loopback OPTIONS (table_name 'non_existent_table'); +SELECT * FROM async_pt; +DROP FOREIGN TABLE async_p_broken; + +-- Check case where multiple partitions use the same connection +CREATE TABLE base_tbl3 (a int, b int, c text); +CREATE FOREIGN TABLE async_p3 PARTITION OF async_pt FOR VALUES FROM (3000) TO (4000) + SERVER loopback2 OPTIONS (table_name 'base_tbl3'); +INSERT INTO async_p3 SELECT 3000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +DROP FOREIGN TABLE async_p3; +DROP TABLE base_tbl3; + +-- Check case where the partitioned table has local/remote partitions +CREATE TABLE async_p3 PARTITION OF async_pt FOR VALUES FROM (3000) TO (4000); +INSERT INTO async_p3 SELECT 3000 + i, i, to_char(i, 'FM0000') FROM generate_series(0, 999, 5) i; +ANALYZE async_pt; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; +INSERT INTO result_tbl SELECT * FROM async_pt WHERE b === 505; + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +-- partitionwise joins +SET enable_partitionwise_join TO true; + +CREATE TABLE join_tbl (a1 int, b1 int, c1 text, a2 int, b2 int, c2 text); + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +INSERT INTO join_tbl SELECT * FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + +SELECT * FROM join_tbl ORDER BY a1; +DELETE FROM join_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +INSERT INTO join_tbl SELECT t1.a, t1.b, 'AAA' || t1.c, t2.a, t2.b, 'AAA' || t2.c FROM async_pt t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + +SELECT * FROM join_tbl ORDER BY a1; +DELETE FROM join_tbl; + +RESET enable_partitionwise_join; + +-- Test rescan of an async Append node with do_exec_prune=false +SET enable_hashjoin TO false; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO join_tbl SELECT * FROM async_p1 t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; +INSERT INTO join_tbl SELECT * FROM async_p1 t1, async_pt t2 WHERE t1.a = t2.a AND t1.b = t2.b AND t1.b % 100 = 0; + +SELECT * FROM join_tbl ORDER BY a1; +DELETE FROM join_tbl; + +RESET enable_hashjoin; + +-- Test interaction of async execution with plan-time partition pruning +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE a < 3000; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE a < 2000; + +-- Test interaction of async execution with run-time partition pruning +SET plan_cache_mode TO force_generic_plan; + +PREPARE async_pt_query (int, int) AS + INSERT INTO result_tbl SELECT * FROM async_pt WHERE a < $1 AND b === $2; + +EXPLAIN (VERBOSE, COSTS OFF) +EXECUTE async_pt_query (3000, 505); +EXECUTE async_pt_query (3000, 505); + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +EXECUTE async_pt_query (2000, 505); +EXECUTE async_pt_query (2000, 505); + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +RESET plan_cache_mode; + +CREATE TABLE local_tbl(a int, b int, c text); +INSERT INTO local_tbl VALUES (1505, 505, 'foo'), (2505, 505, 'bar'); +ANALYZE local_tbl; + +CREATE INDEX base_tbl1_idx ON base_tbl1 (a); +CREATE INDEX base_tbl2_idx ON base_tbl2 (a); +CREATE INDEX async_p3_idx ON async_p3 (a); +ANALYZE base_tbl1; +ANALYZE base_tbl2; +ANALYZE async_p3; + +ALTER FOREIGN TABLE async_p1 OPTIONS (use_remote_estimate 'true'); +ALTER FOREIGN TABLE async_p2 OPTIONS (use_remote_estimate 'true'); + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; +SELECT * FROM local_tbl, async_pt WHERE local_tbl.a = async_pt.a AND local_tbl.c = 'bar'; + +ALTER FOREIGN TABLE async_p1 OPTIONS (DROP use_remote_estimate); +ALTER FOREIGN TABLE async_p2 OPTIONS (DROP use_remote_estimate); + +DROP TABLE local_tbl; +DROP INDEX base_tbl1_idx; +DROP INDEX base_tbl2_idx; +DROP INDEX async_p3_idx; + +-- UNION queries +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); +INSERT INTO result_tbl +(SELECT a, b, 'AAA' || c FROM async_p1 ORDER BY a LIMIT 10) +UNION ALL +(SELECT a, b, 'AAA' || c FROM async_p2 WHERE b < 10); + +SELECT * FROM result_tbl ORDER BY a; +DELETE FROM result_tbl; + +-- Disable async execution if we use gating Result nodes for pseudoconstant +-- quals +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt WHERE CURRENT_USER = SESSION_USER; + +EXPLAIN (VERBOSE, COSTS OFF) +(SELECT * FROM async_p1 WHERE CURRENT_USER = SESSION_USER) +UNION ALL +(SELECT * FROM async_p2 WHERE CURRENT_USER = SESSION_USER); + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM ((SELECT * FROM async_p1 WHERE b < 10) UNION ALL (SELECT * FROM async_p2 WHERE b < 10)) s WHERE CURRENT_USER = SESSION_USER; + +-- Test that pending requests are processed properly +SET enable_mergejoin TO false; +SET enable_hashjoin TO false; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt t1, async_p2 t2 WHERE t1.a = t2.a AND t1.b === 505; +SELECT * FROM async_pt t1, async_p2 t2 WHERE t1.a = t2.a AND t1.b === 505; + +CREATE TABLE local_tbl (a int, b int, c text); +INSERT INTO local_tbl VALUES (1505, 505, 'foo'); +ANALYZE local_tbl; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; +SELECT * FROM local_tbl t1 LEFT JOIN (SELECT *, (SELECT count(*) FROM async_pt WHERE a < 3000) FROM async_pt WHERE a < 3000) t2 ON t1.a = t2.a; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; +SELECT * FROM async_pt t1 WHERE t1.b === 505 LIMIT 1; + +-- Check with foreign modify +CREATE TABLE base_tbl3 (a int, b int, c text); +CREATE FOREIGN TABLE remote_tbl (a int, b int, c text) + SERVER loopback OPTIONS (table_name 'base_tbl3'); +INSERT INTO remote_tbl VALUES (2505, 505, 'bar'); + +CREATE TABLE base_tbl4 (a int, b int, c text); +CREATE FOREIGN TABLE insert_tbl (a int, b int, c text) + SERVER loopback OPTIONS (table_name 'base_tbl4'); + +EXPLAIN (VERBOSE, COSTS OFF) +INSERT INTO insert_tbl (SELECT * FROM local_tbl UNION ALL SELECT * FROM remote_tbl); +INSERT INTO insert_tbl (SELECT * FROM local_tbl UNION ALL SELECT * FROM remote_tbl); + +SELECT * FROM insert_tbl ORDER BY a; + +-- Check with direct modify +EXPLAIN (VERBOSE, COSTS OFF) +WITH t AS (UPDATE remote_tbl SET c = c || c RETURNING *) +INSERT INTO join_tbl SELECT * FROM async_pt LEFT JOIN t ON (async_pt.a = t.a AND async_pt.b = t.b) WHERE async_pt.b === 505; +WITH t AS (UPDATE remote_tbl SET c = c || c RETURNING *) +INSERT INTO join_tbl SELECT * FROM async_pt LEFT JOIN t ON (async_pt.a = t.a AND async_pt.b = t.b) WHERE async_pt.b === 505; + +SELECT * FROM join_tbl ORDER BY a1; +DELETE FROM join_tbl; + +DROP TABLE local_tbl; +DROP FOREIGN TABLE remote_tbl; +DROP FOREIGN TABLE insert_tbl; +DROP TABLE base_tbl3; +DROP TABLE base_tbl4; + +RESET enable_mergejoin; +RESET enable_hashjoin; + +-- Test that UPDATE/DELETE with inherited target works with async_capable enabled +EXPLAIN (VERBOSE, COSTS OFF) +UPDATE async_pt SET c = c || c WHERE b = 0 RETURNING *; +UPDATE async_pt SET c = c || c WHERE b = 0 RETURNING *; +EXPLAIN (VERBOSE, COSTS OFF) +DELETE FROM async_pt WHERE b = 0 RETURNING *; +DELETE FROM async_pt WHERE b = 0 RETURNING *; + +-- Check EXPLAIN ANALYZE for a query that scans empty partitions asynchronously +DELETE FROM async_p1; +DELETE FROM async_p2; +DELETE FROM async_p3; + +EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) +SELECT * FROM async_pt; + +-- Clean up +DROP TABLE async_pt; +DROP TABLE base_tbl1; +DROP TABLE base_tbl2; +DROP TABLE result_tbl; +DROP TABLE join_tbl; + +-- Test that an asynchronous fetch is processed before restarting the scan in +-- ReScanForeignScan +CREATE TABLE base_tbl (a int, b int); +INSERT INTO base_tbl VALUES (1, 11), (2, 22), (3, 33); +CREATE FOREIGN TABLE foreign_tbl (b int) + SERVER loopback OPTIONS (table_name 'base_tbl'); +CREATE FOREIGN TABLE foreign_tbl2 () INHERITS (foreign_tbl) + SERVER loopback OPTIONS (table_name 'base_tbl'); + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); +SELECT a FROM base_tbl WHERE a IN (SELECT a FROM foreign_tbl); + +-- Clean up +DROP FOREIGN TABLE foreign_tbl CASCADE; +DROP TABLE base_tbl; + +ALTER SERVER loopback OPTIONS (DROP async_capable); +ALTER SERVER loopback2 OPTIONS (DROP async_capable); + +-- =================================================================== +-- test invalid server, foreign table and foreign data wrapper options +-- =================================================================== +-- Invalid fdw_startup_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_startup_cost '100$%$#$#'); +-- Invalid fdw_tuple_cost option +CREATE SERVER inv_scst FOREIGN DATA WRAPPER postgres_fdw + OPTIONS(fdw_tuple_cost '100$%$#$#'); +-- Invalid fetch_size option +CREATE FOREIGN TABLE inv_fsz (c1 int ) + SERVER loopback OPTIONS (fetch_size '100$%$#$#'); +-- Invalid batch_size option +CREATE FOREIGN TABLE inv_bsz (c1 int ) + SERVER loopback OPTIONS (batch_size '100$%$#$#'); + +-- No option is allowed to be specified at foreign data wrapper level +ALTER FOREIGN DATA WRAPPER postgres_fdw OPTIONS (nonexistent 'fdw'); + +-- =================================================================== +-- test postgres_fdw.application_name GUC +-- =================================================================== +-- To avoid race conditions in checking the remote session's application_name, +-- use this view to make the remote session itself read its application_name. +CREATE VIEW my_application_name AS + SELECT application_name FROM pg_stat_activity WHERE pid = pg_backend_pid(); + +CREATE FOREIGN TABLE remote_application_name (application_name text) + SERVER loopback2 + OPTIONS (schema_name 'public', table_name 'my_application_name'); + +SELECT count(*) FROM remote_application_name; + +-- Specify escape sequences in application_name option of a server +-- object so as to test that they are replaced with status information +-- expectedly. Note that we are also relying on ALTER SERVER to force +-- the remote session to be restarted with its new application name. +-- +-- Since pg_stat_activity.application_name may be truncated to less than +-- NAMEDATALEN characters, note that substring() needs to be used +-- at the condition of test query to make sure that the string consisting +-- of database name and process ID is also less than that. +ALTER SERVER loopback2 OPTIONS (application_name 'fdw_%d%p'); +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_database() || pg_backend_pid() for + current_setting('max_identifier_length')::int); + +-- postgres_fdw.application_name overrides application_name option +-- of a server object if both settings are present. +ALTER SERVER loopback2 OPTIONS (SET application_name 'fdw_wrong'); +SET postgres_fdw.application_name TO 'fdw_%a%u%%'; +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_setting('application_name') || + CURRENT_USER || '%' for current_setting('max_identifier_length')::int); +RESET postgres_fdw.application_name; + +-- Test %c (session ID) and %C (cluster name) escape sequences. +ALTER SERVER loopback2 OPTIONS (SET application_name 'fdw_%C%c'); +SELECT count(*) FROM remote_application_name + WHERE application_name = + substring('fdw_' || current_setting('cluster_name') || + to_hex(trunc(EXTRACT(EPOCH FROM (SELECT backend_start FROM + pg_stat_get_activity(pg_backend_pid()))))::integer) || '.' || + to_hex(pg_backend_pid()) + for current_setting('max_identifier_length')::int); + +-- Clean up. +DROP FOREIGN TABLE remote_application_name; +DROP VIEW my_application_name; + +-- =================================================================== +-- test parallel commit and parallel abort +-- =================================================================== +ALTER SERVER loopback OPTIONS (ADD parallel_commit 'true'); +ALTER SERVER loopback OPTIONS (ADD parallel_abort 'true'); +ALTER SERVER loopback2 OPTIONS (ADD parallel_commit 'true'); +ALTER SERVER loopback2 OPTIONS (ADD parallel_abort 'true'); + +CREATE TABLE ploc1 (f1 int, f2 text); +CREATE FOREIGN TABLE prem1 (f1 int, f2 text) + SERVER loopback OPTIONS (table_name 'ploc1'); +CREATE TABLE ploc2 (f1 int, f2 text); +CREATE FOREIGN TABLE prem2 (f1 int, f2 text) + SERVER loopback2 OPTIONS (table_name 'ploc2'); + +BEGIN; +INSERT INTO prem1 VALUES (101, 'foo'); +INSERT INTO prem2 VALUES (201, 'bar'); +COMMIT; +SELECT * FROM prem1; +SELECT * FROM prem2; + +BEGIN; +SAVEPOINT s; +INSERT INTO prem1 VALUES (102, 'foofoo'); +INSERT INTO prem2 VALUES (202, 'barbar'); +RELEASE SAVEPOINT s; +COMMIT; +SELECT * FROM prem1; +SELECT * FROM prem2; + +-- This tests executing DEALLOCATE ALL against foreign servers in parallel +-- during pre-commit +BEGIN; +SAVEPOINT s; +INSERT INTO prem1 VALUES (103, 'baz'); +INSERT INTO prem2 VALUES (203, 'qux'); +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +INSERT INTO prem1 VALUES (104, 'bazbaz'); +INSERT INTO prem2 VALUES (204, 'quxqux'); +COMMIT; +SELECT * FROM prem1; +SELECT * FROM prem2; + +BEGIN; +INSERT INTO prem1 VALUES (105, 'test1'); +INSERT INTO prem2 VALUES (205, 'test2'); +ABORT; +SELECT * FROM prem1; +SELECT * FROM prem2; + +-- This tests executing DEALLOCATE ALL against foreign servers in parallel +-- during post-abort +BEGIN; +SAVEPOINT s; +INSERT INTO prem1 VALUES (105, 'test1'); +INSERT INTO prem2 VALUES (205, 'test2'); +ROLLBACK TO SAVEPOINT s; +RELEASE SAVEPOINT s; +INSERT INTO prem1 VALUES (105, 'test1'); +INSERT INTO prem2 VALUES (205, 'test2'); +ABORT; +SELECT * FROM prem1; +SELECT * FROM prem2; + +ALTER SERVER loopback OPTIONS (DROP parallel_commit); +ALTER SERVER loopback OPTIONS (DROP parallel_abort); +ALTER SERVER loopback2 OPTIONS (DROP parallel_commit); +ALTER SERVER loopback2 OPTIONS (DROP parallel_abort); + +-- =================================================================== +-- test for ANALYZE sampling +-- =================================================================== + +CREATE TABLE analyze_table (id int, a text, b bigint); + +CREATE FOREIGN TABLE analyze_ftable (id int, a text, b bigint) + SERVER loopback OPTIONS (table_name 'analyze_rtable1'); + +INSERT INTO analyze_table (SELECT x FROM generate_series(1,1000) x); +ANALYZE analyze_table; + +SET default_statistics_target = 10; +ANALYZE analyze_table; + +ALTER SERVER loopback OPTIONS (analyze_sampling 'invalid'); + +ALTER SERVER loopback OPTIONS (analyze_sampling 'auto'); +ANALYZE analyze_table; + +ALTER SERVER loopback OPTIONS (SET analyze_sampling 'system'); +ANALYZE analyze_table; + +ALTER SERVER loopback OPTIONS (SET analyze_sampling 'bernoulli'); +ANALYZE analyze_table; + +ALTER SERVER loopback OPTIONS (SET analyze_sampling 'random'); +ANALYZE analyze_table; + +ALTER SERVER loopback OPTIONS (SET analyze_sampling 'off'); +ANALYZE analyze_table; + +-- cleanup +DROP FOREIGN TABLE analyze_ftable; +DROP TABLE analyze_table; diff --git a/contrib/seg/.gitignore b/contrib/seg/.gitignore new file mode 100644 index 0000000..fa247a4 --- /dev/null +++ b/contrib/seg/.gitignore @@ -0,0 +1,7 @@ +/segparse.h +/segparse.c +/segscan.c +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/seg/Makefile b/contrib/seg/Makefile new file mode 100644 index 0000000..a1e49bf --- /dev/null +++ b/contrib/seg/Makefile @@ -0,0 +1,45 @@ +# contrib/seg/Makefile + +MODULE_big = seg +OBJS = \ + $(WIN32RES) \ + seg.o \ + segparse.o \ + segscan.o + +EXTENSION = seg +DATA = seg--1.1.sql seg--1.1--1.2.sql seg--1.2--1.3.sql seg--1.3--1.4.sql \ + seg--1.0--1.1.sql +PGFILEDESC = "seg - line segment data type" + +HEADERS = segdata.h + +REGRESS = security seg + +EXTRA_CLEAN = y.tab.c y.tab.h + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/seg +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + + +# See notes in src/backend/parser/Makefile about the following two rules +segparse.h: segparse.c + touch $@ + +segparse.c: BISONFLAGS += -d + +# Force these dependencies to be known even without dependency info built: +segparse.o segscan.o: segparse.h + +distprep: segparse.c segscan.c + +maintainer-clean: + rm -f segparse.h segparse.c segscan.c diff --git a/contrib/seg/data/test_seg.data b/contrib/seg/data/test_seg.data new file mode 100644 index 0000000..6965806 --- /dev/null +++ b/contrib/seg/data/test_seg.data @@ -0,0 +1,2578 @@ +...10.3 +...10.5 +...3.5 +...3.9 +...4.9 +...40 +...5.6 +...5.8 +...6.0 +...6.6 +...6.7 +...6.75 +...7.0 +...7.2 +...7.3 +...7.5 +...7.6 +...7.8 +...8.0 +...8.5 +...8.6 +...8.8 +...8.9 +...9.0 +...9.2 +...9.3 +...9.5 +...9.6 +...90 +...<3.0 +...<5.8 +...<7 +...<7.0 +...<8.0 +...<8.2 +...<8.9 +...>10 +...>7.2 +...>7.7 +...>8.0 +...>82 +...>9.0 +< 1.0...3.8 +< 1.5...4.1 +< 1.5...4.15 +< 10...>11.6 +< 2...>4 +< 2.0...3.4 +< 2.0...4.2 +< 2.0...>8.0 +< 2.5...4.7 +< 2.5...5.5 +< 2.5...>8.5 +< 3...>5 +< 3.0... +< 3.0...4.5 +< 3.0...6.0 +< 3.0...6.5 +< 3.0...9.0 +< 3.0...>10 +< 3.0...>11.0 +< 3.0...>8.0 +< 3.0...>9.0 +< 3.0...>9.5 +< 3.5 +< 3.5...4.3 +< 3.5...5.3 +< 3.5...5.6 +< 3.5...6.0 +< 3.5...8.4 +< 3.6...>6.5 +< 3.7...5.4 +< 4...9.5 +< 4...>5 +< 4...>6.0 +< 4.0... +< 4.0...10.5 +< 4.0...5.3 +< 4.0...5.75 +< 4.0...6.0 +< 4.0...7.3 +< 4.0...7.5 +< 4.0...7.7 +< 4.0...8.5 +< 4.0...>12 +< 4.0...>5.0 +< 4.0...>6.0 +< 4.0...>7.5 +< 4.0...>8.0 +< 4.0...>8.1 +< 4.0...>9.0 +< 4.2...5.2 +< 4.2...5.3 +< 4.2...5.4 +< 4.2...6.2 +< 4.3...>9.0 +< 4.4 +< 4.4...6.0 +< 4.4...<6.0 +< 4.5...6.3 +< 4.5...7.2 +< 4.5...>12 +< 4.5...>5.0 +< 4.5...>5.5 +< 4.5...>6 +< 4.5...>7.5 +< 4.5...>8 +< 4.5...>9.5 +< 4.7...>9.3 +< 4.8...>6.4 +< 5...7.5 +< 5...7.7 +< 5...>7 +< 5...>8 +< 5...>8.0 +< 5.0 +< 5.0...10 +< 5.0...6.3 +< 5.0...7.4 +< 5.0...7.7 +< 5.0...8.1 +< 5.0...8.3 +< 5.0...8.5 +< 5.0...8.8 +< 5.0...9.5 +< 5.0...<9.0 +< 5.0...>10 +< 5.0...>10.0 +< 5.0...>10.5 +< 5.0...>11 +< 5.0...>6.0 +< 5.0...>6.6 +< 5.0...>7.5 +< 5.0...>8.0 +< 5.0...>8.5 +< 5.0...>8.6 +< 5.0...>9.0 +< 5.2...7.3 +< 5.2...8.2 +< 5.2...8.5 +< 5.2...>5.8 +< 5.2...>7.6 +< 5.3 +< 5.3...6.5 +< 5.3...7.5 +< 5.3...7.8 +< 5.4...6.35 +< 5.5...10 +< 5.5...6.3 +< 5.5...7.0 +< 5.5...7.5 +< 5.5...8.4 +< 5.5...8.5 +< 5.5...>10 +< 5.5...>10.0 +< 5.5...>10.5 +< 5.5...>6.5 +< 5.5...>7 +< 5.5...>7.5 +< 5.5...>8 +< 5.5...>8.0 +< 5.5...>8.5 +< 5.5...>8.7 +< 5.5...>8.9 +< 5.5...>9.0 +< 5.6...6.75 +< 5.75...>9.0 +< 5.8...7.8 +< 5.8...>11 +< 5.8...>8.2 +< 5.9...>8.0 +< 6...>10.5 +< 6...>8 +< 6...>9.5 +< 6.0 +< 6.0...6.8 +< 6.0...7.15 +< 6.0...7.6 +< 6.0...7.7 +< 6.0...8.0 +< 6.0...8.2 +< 6.0...8.5 +< 6.0...9.0 +< 6.0...9.5 +< 6.0...>10 +< 6.0...>10.0 +< 6.0...>11 +< 6.0...>7.0 +< 6.0...>8.0 +< 6.0...>8.2 +< 6.0...>8.5 +< 6.0...>8.6 +< 6.0...>9.0 +< 6.0...>9.6 +< 6.3...9.5 +< 6.3...>6.7 +< 6.3...>7.8 +< 6.35...>7.7 +< 6.4...>8.0 +< 6.4...>8.7 +< 6.5...11 +< 6.5...7.25 +< 6.5...8.0 +< 6.5...8.2 +< 6.5...8.4 +< 6.5...8.5 +< 6.5...9.7 +< 6.5...>10.0 +< 6.5...>10.5 +< 6.5...>7.5 +< 6.5...>8.0 +< 6.5...>8.5 +< 6.5...>8.6 +< 6.5...>8.7 +< 6.5...>9.0 +< 6.5...>9.5 +< 6.6...>7.5 +< 6.6...>8.5 +< 6.8...7.8 +< 6.8...9.5 +< 6.8...>7.5 +< 6.8...>8.6 +< 6.8...>9.0 +< 6.9...9.0 +< 7...9.2 +< 7...>11 +< 7...>7.5 +< 7...>9.5 +< 7.0 +< 7.0...10 +< 7.0...10.5 +< 7.0...11.0 +< 7.0...8.2 +< 7.0...8.3 +< 7.0...8.4 +< 7.0...8.55 +< 7.0...8.7 +< 7.0...8.8 +< 7.0...9.0 +< 7.0...<8.0 +< 7.0...>10.5 +< 7.0...>11 +< 7.0...>11.0 +< 7.0...>7.0 +< 7.0...>7.3 +< 7.0...>7.4 +< 7.0...>7.5 +< 7.0...>7.75 +< 7.0...>7.8 +< 7.0...>8.0 +< 7.0...>8.2 +< 7.0...>8.5 +< 7.0...>8.6 +< 7.0...>8.9 +< 7.0...>9.0 +< 7.0...>9.5 +< 7.2...8.3 +< 7.2...>7.5 +< 7.2...>7.8 +< 7.2...>8.0 +< 7.2...>8.2 +< 7.2...>8.6 +< 7.2...>8.8 +< 7.4...>7.8 +< 7.4...>8.0 +< 7.4...>8.1 +< 7.4...>8.6 +< 7.4...>8.8 +< 7.5...9.0 +< 7.5...>10 +< 7.5...>8.0 +< 7.5...>8.5 +< 7.5...>8.7 +< 7.5...>9 +< 7.5...>9.0 +< 7.5...>9.5 +< 7.6...>7.8 +< 7.6...>8.0 +< 7.6...>8.5 +< 7.6...>8.6 +< 7.6...>8.9 +< 7.8...>9.0 +< 7.9...>8.3 +< 8.0 +< 8.0...>10.0 +< 8.0...>8.5 +< 8.0...>9.0 +< 8.0...>9.4 +< 8.0...>9.5 +< 8.2...9.8 +< 8.2...>8.6 +< 8.5...>10.5 +< 8.5...>11 +< 8.5...>9.5 +< 9...10.5 +<1.0...3.5 +<1.0...3.7 +<1.0...4.0 +<1.0...>13.0 +<1.0...>5.5 +<10.5...11.5 +<12.0...12.5 +<2...10 +<2...>4 +<2.0 +<2.0...11 +<2.0...3.5 +<2.0...5.7 +<2.0...6.0 +<2.0...6.8 +<2.0...9.7 +<2.0...>6.5 +<2.0...>9.0 +<2.3... +<2.3...>11.0 +<2.4...6.8 +<2.5...7.7 +<2.5...>8.0 +<2.8...>8.0 +<3.0...4.6 +<3.0...5.5 +<3.0...6.0 +<3.0...6.3 +<3.0...6.4 +<3.0...6.8 +<3.0...7.0 +<3.0...7.5 +<3.0...8.3 +<3.0...>10.5 +<3.0...>5.0 +<3.0...>7.0 +<3.0...>7.2 +<3.0...>8.5 +<3.0...>8.7 +<3.0...>9.0 +<3.5...6.0 +<3.5...8.0 +<3.5...<7.0 +<3.5...>10.0 +<3.55...5.3 +<3.7...4.9 +<3.7...5.0 +<4...6.5 +<4.0...10.0 +<4.0...10.5 +<4.0...6.1 +<4.0...6.3 +<4.0...6.5 +<4.0...7.7 +<4.0...8.2 +<4.0...<9.0 +<4.0...>10 +<4.0...>10.0 +<4.0...>10.5 +<4.0...>6.0 +<4.0...>8.0 +<4.0...>9.0 +<4.1...>8.0 +<4.3...7.6 +<4.5...6.3 +<4.5...6.5 +<4.5...>6.5 +<4.5...>9.5 +<4.8...>7.4 +<4.9...>9.5 +<5...>6 +<5...>6.0 +<5...>8 +<5.0...10.0 +<5.0...6.0 +<5.0...6.6 +<5.0...6.8 +<5.0...7.0 +<5.0...7.5 +<5.0...8.2 +<5.0...9.5 +<5.0...>11.0 +<5.0...>5.5 +<5.0...>7.0 +<5.0...>8.0 +<5.0...>9.0 +<5.2...7.5 +<5.2...>6.8 +<5.2...>8.0 +<5.3...10.2 +<5.3...>10.5 +<5.4...8.0 +<5.4...8.5 +<5.4...>8.2 +<5.4...>9.1 +<5.5...6.5 +<5.5...7.0 +<5.5...7.5 +<5.5...8.2 +<5.5...8.3 +<5.5...8.5 +<5.5...8.6 +<5.5...>10.0 +<5.5...>7.0 +<5.5...>7.5 +<5.5...>8.0 +<5.5...>8.5 +<5.5...>9.0 +<5.6...7.5 +<5.6...>6.4 +<5.6...>7.4 +<5.6...>8.0 +<5.6...>8.5 +<5.7...>7.1 +<5.8...7.4 +<5.8...>6.7 +<5.8...>6.8 +<5.8...>7.0 +<5.8...>7.8 +<5.8...>8.8 +<5.85...>9.5 +<6...>9 +<6.0...10.0 +<6.0...6.8 +<6.0...7.1 +<6.0...7.2 +<6.0...7.7 +<6.0...8.0 +<6.0...8.2 +<6.0...8.4 +<6.0...8.5 +<6.0...8.6 +<6.0...9.1 +<6.0...9.2 +<6.0...9.3 +<6.0...9.5 +<6.0...>10 +<6.0...>11.0 +<6.0...>7.0 +<6.0...>7.5 +<6.0...>7.6 +<6.0...>7.9 +<6.0...>8.0 +<6.0...>8.3 +<6.0...>8.5 +<6.0...>8.6 +<6.0...>9.0 +<6.0...>9.2 +<6.0...>9.5 +<6.1...8.4 +<6.1...>6.6 +<6.1...>8.1 +<6.2 +<6.2...6.9 +<6.2...8.3 +<6.2...>7.5 +<6.2...>8.5 +<6.2...>8.7 +<6.3...10.0 +<6.3...7.4 +<6.3...9.6 +<6.3...>7.0 +<6.3...>8.0 +<6.4...7.6 +<6.4...8.5 +<6.4...>8.5 +<6.4...>9.0 +<6.5...7.8 +<6.5...8.2 +<6.5...9.2 +<6.5...>7.0 +<6.5...>7.5 +<6.5...>8.0 +<6.5...>8.5 +<6.5...>8.7 +<6.5...>9.0 +<6.5...>9.5 +<6.6 +<6.6...8.0 +<6.8...8.0 +<6.8...8.2 +<6.8...>7.5 +<6.8...>8.5 +<6.8...>9.5 +<7...9 +<7...>10 +<7.0...7.5 +<7.0...9.0 +<7.0...9.0 +<7.0...9.4 +<7.0...9.5 +<7.0...9.5 +<7.0...>10 +<7.0...>10.0 +<7.0...>11.5 +<7.0...>7.5 +<7.0...>8.0 +<7.0...>8.3 +<7.0...>8.4 +<7.0...>8.5 +<7.0...>9.0 +<7.2...8.8 +<7.2...9.0 +<7.2...>8.2 +<7.2...>8.8 +<7.2...>9.5 +<7.3...>8.0 +<7.4...8.8 +<7.4...9.6 +<7.4...>8.5 +<7.4...>8.7 +<7.5...8.5 +<7.5...<9.0 +<7.5...>8.0 +<7.5...>8.5 +<7.5...>9.5 +<7.6...>8.4 +<7.6...>9.4 +<7.8...>10.2 +<7.8...>8.2 +<8.0 +<8.0...10.0 +<8.0...>8.5 +<8.0...>8.8 +<8.0...>9.0 +<8.2...>10.2 +<8.5...9.6 +<8.5...>9.0 +<8.5...>9.5 +<8.6...>10.6 +<9.1...10.3 +<9.2...>11.0 +<9.5...12 +<9.5...>12.2 +> 11.0 +> 3.0...<7.0 +> 4.0...<10.0 +> 4.0...<9.0 +> 4.5...<9.0 +> 5.0 +> 5.0...<10.0 +> 5.0...<11 +> 5.5... +> 5.6 +> 5.9...<9.5 +> 6.0...8.0 +> 6.0...<11.5 +> 6.0...<8.0 +> 6.0...>10.0 +> 6.2...<7.4 +> 7.0...<10 +> 7.0...>8.5 +> 7.0...>8.6 +> 7.0...>8.7 +> 7.15... +> 8.0 +> 8.7 +> 9.3 +> 9.5 +>10.5 +>120 +>2.3... +>3.0...<7.0 +>3.8...<8.0 +>4.0...<10 +>5.0...<7.0 +>5.0...>8.0 +>5.0...>9.2 +>5.25...6.5 +>5.5...>9.3 +>5.6...<8.2 +>6.0...>11.0 +>6.5...>8.0 +>6.6...<7.3 +>6.8...>8.5 +>6.9...>8.0 +>7.0 +>7.0...<9.0 +>7.0...<9.5 +>7.3... +>7.5...<10.5 +>8.0 +>8.4 +>8.5 +>9.0 +>9.4 +>9.5 +>95 +~10 +~23 +~5.0...>9.0 +~6.0 +~6.4...~8.5 +~6.8 +~7.0 +~7.5 +~8.0 +~8.0...~10.0 +1.0 +1.0...4.0 +1.0...5.0 +1.1 +1.1...3.6 +1.2...3.2 +1.2...3.5 +1.3...12.0 +1.4...2.0 +1.4...8.2 +1.5 +1.5...10.5 +1.5...4.2 +1.5...4.5 +1.6...3.8 +1.6...7.2 +1.7 +1.7...2.4 +1.7...3.2 +1.7...5.0 +1.7...5.7 +1.8 +1.8...3.8 +1.8...>9.0 +1.9...3.6 +2 +2.0 +2.0...10.0 +2.0...11.0 +2.0...11.5 +2.0...2.5 +2.0...3.2 +2.0...3.5 +2.0...4.0 +2.0...4.3 +2.0...4.4 +2.0...4.6 +2.0...4.75 +2.0...4.8 +2.0...5.0 +2.0...5.5 +2.0...6.5 +2.0...7.0 +2.0...7.5 +2.0...8.0 +2.0...9.0 +2.0...>10 +2.0...>4.0 +2.0...>5.5 +2.1 +2.1...11.8 +2.1...4.2 +2.2 +2.2...11.2 +2.2...4.1 +2.2...4.2 +2.2...5.9 +2.2...6.4 +2.2...6.8 +2.3...10.6 +2.3...4.1 +2.3...4.3 +2.3...4.5 +2.3...4.7 +2.3...6.0 +2.4 +2.4...11.3 +2.4...4.5 +2.4...5.6 +2.4...5.7 +2.4...6.3 +2.5 +2.5...11 +2.5...11.2 +2.5...11.5 +2.5...11.8 +2.5...3.0 +2.5...3.7 +2.5...4.1 +2.5...4.5 +2.5...4.6 +2.5...5.0 +2.5...5.1 +2.5...5.5 +2.5...6.0 +2.5...6.3 +2.5...7.0 +2.5...9.0 +2.5...>3.5 +2.5...>4.0 +2.52 +2.56 +2.6... +2.6...10.0 +2.6...9.3 +2.6...>4.0 +2.6...>9.4 +2.62 +2.7 +2.7...12.0 +2.7...3.7 +2.7...3.8 +2.7...4.5 +2.7...4.7 +2.7...6.0 +2.7...6.4 +2.7...7.0 +2.7...8.0 +2.75...5.25 +2.79 +2.8 +2.8...11.0 +2.8...3.4 +2.8...4.0 +2.8...7.0 +2.8...8.0 +2.8...9.0 +2.8...9.7 +2.9...3.7 +2.9...4.5 +2.9...5.0 +2.9...5.6 +2.9...6.1 +2.9...7.6 +2.9...9.0 +2.97 +3 +3...10.0 +3...10.5 +3...5.5 +3...58 +3...6 +3...8 +3.0 +3.0...10 +3.0...10.0 +3.0...10.5 +3.0...11.0 +3.0...3.5 +3.0...4.0 +3.0...4.5 +3.0...4.9 +3.0...5.0 +3.0...5.4 +3.0...5.6 +3.0...5.65 +3.0...6.0 +3.0...6.3 +3.0...6.5 +3.0...6.7 +3.0...6.9 +3.0...7.0 +3.0...7.2 +3.0...7.5 +3.0...8.0 +3.0...8.5 +3.0...8.7 +3.0...9.0 +3.0...9.7 +3.0...>10 +3.0...>10.0 +3.0...>8.0 +3.00...5.0 +3.1 +3.1...11.5 +3.1...5.2 +3.1...5.8 +3.1...6.8 +3.1...7.8 +3.15...7.25 +3.2 +3.2...10.0 +3.2...4.2 +3.2...4.6 +3.2...5.4 +3.2...5.8 +3.2...6.25 +3.2...6.8 +3.2...8.1 +3.2...>8.0 +3.2...>9.9 +3.22 +3.3 +3.3...4.7 +3.3...5.3 +3.3...5.5 +3.3...5.6 +3.3...6.0 +3.3...6.5 +3.3...6.7 +3.3...7.2 +3.3...7.4 +3.3...7.5 +3.3...7.8 +3.3...9.0 +3.4 +3.4...4.1 +3.4...4.7 +3.4...5.5 +3.4...6.2 +3.4...6.4 +3.4...8.2 +3.4...>8.0 +3.5 +3.5...10 +3.5...10.0 +3.5...10.5 +3.5...10.8 +3.5...11.0 +3.5...11.5 +3.5...12.2 +3.5...4.0 +3.5...4.4 +3.5...4.5 +3.5...5.0 +3.5...5.3 +3.5...5.5 +3.5...5.6 +3.5...6 +3.5...6.0 +3.5...6.3 +3.5...6.4 +3.5...6.5 +3.5...6.6 +3.5...6.7 +3.5...7.0 +3.5...7.2 +3.5...7.3 +3.5...7.5 +3.5...8.5 +3.5...8.6 +3.5...8.7 +3.5...9.0 +3.5...9.5 +3.5...>11.0 +3.5...>5.0 +3.5...>8.5 +3.5...>9.0 +3.6 +3.6...3.8 +3.6...4.55 +3.6...4.6 +3.6...4.8 +3.6...5.4 +3.6...5.5 +3.6...6 +3.6...6.0 +3.6...6.6 +3.6...6.8 +3.6...7.0 +3.6...7.7 +3.6...8.6 +3.6...8.7 +3.6...8.8 +3.7 +3.7...10.0 +3.7...10.6 +3.7...4.6 +3.7...5.3 +3.7...6.2 +3.7...6.3 +3.7...6.5 +3.7...6.7 +3.7...8.2 +3.7...8.3 +3.7...9.1 +3.7...>10 +3.7...>11 +3.75...5.7 +3.8 +3.8...10 +3.8...4.0 +3.8...4.5 +3.8...4.8 +3.8...5.5 +3.8...5.8 +3.8...5.9 +3.8...6.2 +3.8...7.7 +3.8...8.3 +3.8...8.7 +3.9 +3.9...5.5 +3.9...6.8 +3.9...7.8 +3.9...>7.5 +3.9...>8.5 +3.9...>9.0 +3.95 +4 +4...10 +4...11.0 +4...12 +4...5 +4...6.9 +4...7 +4...7.5 +4...8 +4...9 +4...>11 +4...>8 +4.0 +4.0... +4.0...10 +4.0...10.0 +4.0...10.4 +4.0...10.5 +4.0...11.0 +4.0...11.7 +4.0...12.5 +4.0...13.0 +4.0...4.4 +4.0...4.5 +4.0...4.8 +4.0...5.0 +4.0...5.1 +4.0...5.2 +4.0...5.5 +4.0...5.7 +4.0...5.75 +4.0...5.8 +4.0...6.0 +4.0...6.1 +4.0...6.2 +4.0...6.3 +4.0...6.5 +4.0...60 +4.0...7.0 +4.0...7.2 +4.0...7.3 +4.0...7.5 +4.0...7.7 +4.0...7.9 +4.0...8.0 +4.0...8.1 +4.0...8.2 +4.0...8.3 +4.0...8.5 +4.0...8.8 +4.0...9.0 +4.0...9.4 +4.0...>10 +4.0...>11 +4.0...>9.0 +4.1 +4.1...10.5 +4.1...5.6 +4.1...5.8 +4.1...6.0 +4.1...7.2 +4.1...8.0 +4.1...9.1 +4.15...5.25 +4.15...6.25 +4.15...6.5 +4.2 +4.2...11.0 +4.2...11.5 +4.2...11.7 +4.2...5.0 +4.2...5.3 +4.2...5.35 +4.2...5.4 +4.2...5.6 +4.2...5.7 +4.2...6.0 +4.2...6.3 +4.2...6.4 +4.2...6.7 +4.2...7.1 +4.2...7.2 +4.2...7.3 +4.2...8.0 +4.2...8.4 +4.2...9.5 +4.2...>7.0 +4.2...>7.3 +4.2...>9.0 +4.2...>9.5 +4.25 +4.25...5.00 +4.25...9.75 +4.3 +4.3...10.3 +4.3...5.3 +4.3...5.7 +4.3...5.8 +4.3...6.0 +4.3...6.3 +4.3...6.5 +4.3...6.8 +4.3...7.0 +4.3...7.2 +4.3...7.3 +4.3...7.5 +4.3...7.6 +4.3...8 +4.3...8.2 +4.3...8.5 +4.3...8.6 +4.3...>5.0 +4.3...>6.0 +4.3...>7.0 +4.3...>8.0 +4.35 +4.4 +4.4...10.7 +4.4...4.6 +4.4...4.8 +4.4...6.0 +4.4...6.2 +4.4...6.4 +4.4...6.6 +4.4...6.7 +4.4...7.0 +4.4...7.2 +4.4...7.5 +4.4...7.6 +4.4...8.5 +4.4...9.3 +4.5 +4.5...10 +4.5...10.0 +4.5...10.5 +4.5...11.0 +4.5...11.5 +4.5...115 +4.5...12.5 +4.5...4.8 +4.5...5.0 +4.5...5.2 +4.5...5.5 +4.5...5.7 +4.5...5.8 +4.5...6.0 +4.5...6.2 +4.5...6.4 +4.5...6.5 +4.5...6.8 +4.5...7 +4.5...7.0 +4.5...7.1 +4.5...7.3 +4.5...7.5 +4.5...7.6 +4.5...7.7 +4.5...7.8 +4.5...7.9 +4.5...8.0 +4.5...8.3 +4.5...8.5 +4.5...8.6 +4.5...8.7 +4.5...8.8 +4.5...8.9 +4.5...9.0 +4.5...9.3 +4.5...9.4 +4.5...9.5 +4.5...<12 +4.5...>10 +4.5...>12 +4.5...>6.0 +4.5...>7.0 +4.5...>7.5 +4.5...>8.0 +4.5...>8.5 +4.5...>9.0 +4.6 +4.6 +4.6...4.8 +4.6...5.0 +4.6...5.2 +4.6...6.3 +4.6...6.4 +4.6...6.5 +4.6...6.6 +4.6...7.0 +4.6...7.2 +4.6...7.4 +4.6...7.5 +4.6...8.2 +4.6...8.5 +4.6...8.6 +4.6...8.7 +4.6...<6.5 +4.6...>10 +4.6...>11 +4.6...>7.4 +4.6...>8.0 +4.6...~7.0 +4.7 +4.7...10.8 +4.7...11 +4.7...11.8 +4.7...5.6 +4.7...6.0 +4.7...6.2 +4.7...6.3 +4.7...6.6 +4.7...6.7 +4.7...7.0 +4.7...7.4 +4.7...7.5 +4.7...7.7 +4.7...7.8 +4.7...8.0 +4.7...8.3 +4.7...8.4 +4.7...8.5 +4.7...9.3 +4.7...9.5 +4.7...9.6 +4.7...9.7 +4.7...>10 +4.7...>10.0 +4.7...>10.5 +4.7...>6.5 +4.7...>8.0 +4.75 +4.8 +4.8... +4.8...10.3 +4.8...11.5 +4.8...11.6 +4.8...12.5 +4.8...5.2 +4.8...5.9 +4.8...6.0 +4.8...6.2 +4.8...6.3 +4.8...7.0 +4.8...7.3 +4.8...7.4 +4.8...7.5 +4.8...7.6 +4.8...7.7 +4.8...7.75 +4.8...8.0 +4.8...8.2 +4.8...8.4 +4.8...8.6 +4.8...9.0 +4.8...9.3 +4.8...9.7 +4.8...>10.0 +4.8...>8.0 +4.8...>9.0 +4.85 +4.9 +4.9... +4.9...6.5 +4.9...7.2 +4.9...7.8 +4.9...8.2 +4.9...8.3 +4.9...9.0 +4.9...9.5 +4.9...<6.0 +4.9...>12 +4.9...>7.5 +4.9...>9.5 +5 +5...10.0 +5...10.5 +5...11 +5...11.5 +5...12 +5...30 +5...6 +5...6.6 +5...7 +5...8 +5...8.5 +5...9 +5...9.5 +5.0 +5.0...10 +5.0...10.0 +5.0...10.2 +5.0...10.3 +5.0...10.5 +5.0...11 +5.0...11.0 +5.0...11.2 +5.0...11.4 +5.0...11.5 +5.0...11.6 +5.0...11.7 +5.0...12.0 +5.0...5.5 +5.0...5.7 +5.0...6.0 +5.0...6.1 +5.0...6.2 +5.0...6.4 +5.0...6.5 +5.0...6.6 +5.0...6.8 +5.0...6.9 +5.0...7.0 +5.0...7.2 +5.0...7.3 +5.0...7.4 +5.0...7.5 +5.0...7.6 +5.0...7.7 +5.0...7.8 +5.0...8.0 +5.0...8.2 +5.0...8.3 +5.0...8.4 +5.0...8.5 +5.0...8.6 +5.0...8.7 +5.0...9.0 +5.0...9.0 +5.0...9.2 +5.0...9.5 +5.0...>10 +5.0...>10.0 +5.0...>11 +5.0...>12 +5.0...>12.0 +5.0...>7.0 +5.0...>8.0 +5.0...>8.4 +5.0...>8.5 +5.0...>9.0 +5.02...6.74 +5.1 +5.1...5.3 +5.1...5.4 +5.1...6.2 +5.1...6.3 +5.1...6.4 +5.1...6.6 +5.1...6.8 +5.1...7.0 +5.1...7.2 +5.1...7.3 +5.1...7.8 +5.1...8.0 +5.1...8.3 +5.1...8.7 +5.1...>7.0 +5.15 +5.2 +5.2...10.0 +5.2...10.8 +5.2...11.5 +5.2...5.3 +5.2...5.5 +5.2...5.7 +5.2...6.0 +5.2...6.2 +5.2...6.4 +5.2...6.7 +5.2...6.8 +5.2...6.9 +5.2...7.0 +5.2...7.3 +5.2...7.4 +5.2...7.5 +5.2...7.6 +5.2...7.7 +5.2...7.9 +5.2...8.0 +5.2...8.2 +5.2...8.4 +5.2...8.5 +5.2...8.7 +5.2...8.8 +5.2...9.0 +5.2...9.5 +5.2...9.6 +5.2...9.7 +5.2...9.9 +5.2...>10.0 +5.2...>11 +5.2...>12 +5.2...>6.5 +5.2...>7.0 +5.2...>7.5 +5.2...>8 +5.2...>8.0 +5.25...7.5 +5.25...8.5 +5.25...>12 +5.3 +5.3... +5.3...10.0 +5.3...10.1 +5.3...10.2 +5.3...10.5 +5.3...10.6 +5.3...11.5 +5.3...13 +5.3...5.5 +5.3...5.8 +5.3...7.0 +5.3...7.2 +5.3...7.4 +5.3...7.5 +5.3...7.6 +5.3...7.8 +5.3...8.0 +5.3...8.2 +5.3...8.3 +5.3...8.6 +5.3...8.7 +5.3...8.8 +5.3...8.9 +5.3...9.0 +5.3...9.3 +5.3...9.5 +5.3...9.7 +5.3...9.9 +5.3...>7.8 +5.3...>9.0 +5.3...>90 +5.35 +5.4 +5.4... +5.4...10 +5.4...10.5 +5.4...5.8 +5.4...6.8 +5.4...7.0 +5.4...7.1 +5.4...7.2 +5.4...7.3 +5.4...7.7 +5.4...7.8 +5.4...8.2 +5.4...8.5 +5.4...9.4 +5.4...>10.0 +5.4...>11.0 +5.4...>9.0 +5.45 +5.5 +5.5... +5.5...10.0 +5.5...10.2 +5.5...10.5 +5.5...11 +5.5...11.5 +5.5...11.7 +5.5...12 +5.5...12.5 +5.5...13.5 +5.5...5.6 +5.5...5.7 +5.5...5.8 +5.5...6 +5.5...6.0 +5.5...6.2 +5.5...6.3 +5.5...6.5 +5.5...6.7 +5.5...6.8 +5.5...7.0 +5.5...7.1 +5.5...7.2 +5.5...7.3 +5.5...7.4 +5.5...7.5 +5.5...7.6 +5.5...7.7 +5.5...7.9 +5.5...8.0 +5.5...8.1 +5.5...8.2 +5.5...8.5 +5.5...8.6 +5.5...8.7 +5.5...9.0 +5.5...9.1 +5.5...9.3 +5.5...9.5 +5.5...9.7 +5.5...<6.8 +5.5...>10 +5.5...>10.0 +5.5...>11 +5.5...>11.0 +5.5...>12 +5.5...>7.0 +5.5...>8.0 +5.5...>9.0 +5.5...>9.5 +5.55 +5.55...7.8 +5.56 +5.6 +5.6...10 +5.6...10.1 +5.6...6.0 +5.6...6.2 +5.6...6.4 +5.6...6.6 +5.6...7.0 +5.6...7.1 +5.6...7.2 +5.6...7.3 +5.6...7.4 +5.6...7.5 +5.6...7.6 +5.6...7.7 +5.6...8.0 +5.6...8.3 +5.6...8.4 +5.6...8.5 +5.6...8.75 +5.6...8.8 +5.6...9 +5.6...9.0 +5.6...9.4 +5.6...9.5 +5.6...9.6 +5.6...>11 +5.6...>7.0 +5.6...>7.5 +5.6...>8.0 +5.6...>9.0 +5.65 +5.7 +5.7... +5.7...10 +5.7...10.0 +5.7...11 +5.7...6.0 +5.7...6.2 +5.7...6.3 +5.7...6.5 +5.7...6.8 +5.7...7.0 +5.7...7.2 +5.7...7.3 +5.7...7.5 +5.7...7.6 +5.7...7.65 +5.7...7.7 +5.7...7.8 +5.7...7.9 +5.7...8.0 +5.7...8.4 +5.7...8.5 +5.7...8.7 +5.7...8.9 +5.7...9.0 +5.7...9.2 +5.7...9.4 +5.7...9.7 +5.7...>10.0 +5.7...>11 +5.7...>6.6 +5.7...>7.2 +5.7...>8.5 +5.7...>9.0 +5.73 +5.75 +5.75...7.3 +5.8 +5.8...10.0 +5.8...11 +5.8...6.0 +5.8...6.2 +5.8...6.5 +5.8...6.7 +5.8...6.8 +5.8...6.9 +5.8...7.0 +5.8...7.2 +5.8...7.3 +5.8...7.4 +5.8...7.5 +5.8...7.6 +5.8...7.7 +5.8...7.8 +5.8...8.0 +5.8...8.1 +5.8...8.2 +5.8...8.3 +5.8...8.4 +5.8...8.5 +5.8...8.6 +5.8...8.8 +5.8...9.0 +5.8...9.2 +5.8...9.3 +5.8...9.8 +5.8...>10.5 +5.8...>11 +5.8...>6.7 +5.8...>6.8 +5.8...>8.0 +5.8...>9.0 +5.85 +5.87 +5.9 +5.9... +5.9...10.2 +5.9...6.0 +5.9...6.5 +5.9...7.3 +5.9...7.5 +5.9...7.7 +5.9...8.0 +5.9...8.2 +5.9...8.3 +5.9...8.4 +5.9...8.5 +5.9...8.7 +5.9...9.0 +5.9...>7.0 +5.9...>8.5 +5.92 +5.95...8.0 +6 +6...10 +6...11 +6...11.5 +6...6.5 +6...8 +6...8.0 +6...8.5 +6...9 +6...9.2 +6...9.3 +6...>10 +6...>12 +6...>7.2 +6...>8 +6.0 +6.0 +6.0...10 +6.0...10.0 +6.0...10.2 +6.0...10.3 +6.0...10.4 +6.0...10.5 +6.0...11 +6.0...11.0 +6.0...11.5 +6.0...13 +6.0...6.2 +6.0...6.3 +6.0...6.4 +6.0...6.5 +6.0...6.6 +6.0...6.7 +6.0...6.8 +6.0...7.0 +6.0...7.2 +6.0...7.3 +6.0...7.4 +6.0...7.5 +6.0...7.6 +6.0...7.7 +6.0...7.8 +6.0...8.0 +6.0...8.1 +6.0...8.2 +6.0...8.3 +6.0...8.4 +6.0...8.5 +6.0...8.5 +6.0...8.6 +6.0...8.7 +6.0...8.9 +6.0...9.0 +6.0...9.1 +6.0...9.2 +6.0...9.3 +6.0...9.4 +6.0...9.5 +6.0...9.7 +6.0...9.8 +6.0...9.9 +6.0...>10.0 +6.0...>10.5 +6.0...>11 +6.0...>7.0 +6.0...>7.5 +6.0...>8.0 +6.0...>8.2 +6.0...>8.5 +6.0...>8.7 +6.0...>9.0 +6.0...>9.5 +6.01 +6.1 +6.1... +6.1...10 +6.1...10.8 +6.1...6.2 +6.1...6.6 +6.1...6.8 +6.1...6.9 +6.1...7.0 +6.1...7.2 +6.1...7.5 +6.1...7.6 +6.1...7.7 +6.1...7.8 +6.1...7.9 +6.1...8.0 +6.1...8.2 +6.1...8.5 +6.1...8.7 +6.1...8.9 +6.1...9.0 +6.1...9.3 +6.1...9.4 +6.1...>12 +6.1...>8.0 +6.1...>8.2 +6.1...>8.5 +6.1...>9.0 +6.1...>9.5 +6.15 +6.18 +6.2 +6.2...10 +6.2...10.0 +6.2...10.5 +6.2...11 +6.2...6.4 +6.2...6.5 +6.2...6.8 +6.2...6.9 +6.2...7.0 +6.2...7.2 +6.2...7.4 +6.2...7.5 +6.2...7.6 +6.2...7.7 +6.2...7.8 +6.2...8.0 +6.2...8.1 +6.2...8.2 +6.2...8.3 +6.2...8.4 +6.2...8.5 +6.2...8.6 +6.2...8.7 +6.2...8.8 +6.2...9.0 +6.2...9.1 +6.2...9.2 +6.2...9.5 +6.2...9.7 +6.2...9.8 +6.2...>10.5 +6.2...>11.5 +6.2...>7.1 +6.2...>7.8 +6.2...>8.0 +6.2...>9.0 +6.2...>9.5 +6.23...8.07 +6.24 +6.25 +6.25...7.5 +6.25...>9.0 +6.3 +6.3... +6.3...10.0 +6.3...10.2 +6.3...10.4 +6.3...10.5 +6.3...6.4 +6.3...6.5 +6.3...6.6 +6.3...6.7 +6.3...6.8 +6.3...7.3 +6.3...7.5 +6.3...7.7 +6.3...7.8 +6.3...7.9 +6.3...8.0 +6.3...8.2 +6.3...8.3 +6.3...8.5 +6.3...8.6 +6.3...8.7 +6.3...8.9 +6.3...9.0 +6.3...9.1 +6.3...9.2 +6.3...9.3 +6.3...9.4 +6.3...9.5 +6.3...9.7 +6.3...9.8 +6.3...>10.0 +6.3...>11 +6.3...>7.5 +6.3...>7.8 +6.3...>8.0 +6.3...>8.1 +6.3...>8.3 +6.3...>8.5 +6.3...>9.0 +6.3...>9.2 +6.35 +6.4 +6.4...7.7 +6.4...10 +6.4...10.8 +6.4...6.7 +6.4...6.8 +6.4...7.0 +6.4...7.2 +6.4...7.4 +6.4...7.5 +6.4...7.6 +6.4...7.7 +6.4...7.8 +6.4...8.0 +6.4...8.1 +6.4...8.2 +6.4...8.3 +6.4...8.4 +6.4...8.6 +6.4...8.7 +6.4...8.8 +6.4...9.0 +6.4...9.3 +6.4...>10 +6.4...>11.0 +6.4...>7.8 +6.4...>8.0 +6.4...>8.5 +6.4...>8.6 +6.4...>9.0 +6.4...>9.1 +6.4...>9.2 +6.4...>9.5 +6.42 +6.5 +6.5 +6.5... +6.5...10 +6.5...10.0 +6.5...10.2 +6.5...10.3 +6.5...10.5 +6.5...11 +6.5...11.0 +6.5...11.5 +6.5...12.0 +6.5...6.7 +6.5...6.75 +6.5...6.8 +6.5...6.9 +6.5...7.0 +6.5...7.1 +6.5...7.3 +6.5...7.4 +6.5...7.5 +6.5...7.6 +6.5...7.7 +6.5...7.8 +6.5...7.9 +6.5...8.0 +6.5...8.1 +6.5...8.2 +6.5...8.25 +6.5...8.3 +6.5...8.4 +6.5...8.5 +6.5...8.6 +6.5...8.7 +6.5...8.8 +6.5...8.9 +6.5...9 +6.5...9.0 +6.5...9.2 +6.5...9.3 +6.5...9.5 +6.5...9.7 +6.5...9.8 +6.5...>10 +6.5...>10.0 +6.5...>11 +6.5...>11.0 +6.5...>12.0 +6.5...>7.0 +6.5...>7.9 +6.5...>8.0 +6.5...>8.3 +6.5...>8.5 +6.5...>8.7 +6.5...>8.8 +6.5...>9.0 +6.5...>9.5 +6.51 +6.55 +6.6 +6.6... +6.6...7.0 +6.6...7.1 +6.6...7.3 +6.6...7.4 +6.6...7.5 +6.6...7.9 +6.6...8.3 +6.6...8.4 +6.6...8.5 +6.6...8.6 +6.6...8.7 +6.6...8.8 +6.6...8.9 +6.6...9.0 +6.6...9.4 +6.6...9.7 +6.6...9.8 +6.6...>10.0 +6.6...>10.3 +6.6...>11 +6.6...>9.0 +6.6...>9.5 +6.65...8.6 +6.67 +6.7 +6.7... +6.7...10.2 +6.7...11.5 +6.7...6.8 +6.7...6.9 +6.7...7.0 +6.7...7.3 +6.7...7.4 +6.7...7.5 +6.7...7.6 +6.7...7.8 +6.7...8.0 +6.7...8.1 +6.7...8.2 +6.7...8.3 +6.7...8.5 +6.7...8.6 +6.7...8.7 +6.7...8.8 +6.7...8.9 +6.7...9.0 +6.7...9.1 +6.7...9.2 +6.7...9.5 +6.7...9.7 +6.7...9.8 +6.7...>10 +6.7...>7.0 +6.7...>7.5 +6.7...>8.0 +6.7...>8.5 +6.7...>8.6 +6.7...>9 +6.7...>9.0 +6.7...>9.3 +6.74...8.0 +6.75 +6.75... +6.75...9.5 +6.75...>9 +6.75...>9.0 +6.75...>9.5 +6.76...8.10 +6.8 +6.8... +6.8...10 +6.8...10.0 +6.8...10.5 +6.8...6.9 +6.8...7.0 +6.8...7.1 +6.8...7.2 +6.8...7.3 +6.8...7.5 +6.8...7.6 +6.8...7.7 +6.8...7.8 +6.8...7.9 +6.8...8.0 +6.8...8.2 +6.8...8.25 +6.8...8.3 +6.8...8.5 +6.8...8.6 +6.8...8.7 +6.8...8.8 +6.8...8.9 +6.8...9.0 +6.8...9.1 +6.8...9.3 +6.8...9.4 +6.8...9.5 +6.8...9.6 +6.8...9.9 +6.8...>10.0 +6.8...>7.9 +6.8...>8.0 +6.8...>8.1 +6.8...>8.5 +6.8...>9.0 +6.8...>9.1 +6.80...>8.5 +6.81...7.53 +6.85 +6.86 +6.88 +6.9 +6.9... +6.9...10.4 +6.9...12.2 +6.9...7.0 +6.9...7.1 +6.9...7.3 +6.9...7.6 +6.9...7.9 +6.9...8.0 +6.9...8.2 +6.9...8.4 +6.9...8.5 +6.9...8.6 +6.9...8.7 +6.9...8.8 +6.9...9.0 +6.9...9.1 +6.9...9.2 +6.9...9.5 +6.9...9.7 +6.9...>7.1 +6.9...>8 +6.9...>8.5 +6.9...>9 +6.9...>9.0 +6.9...>9.3 +6.9...>9.5 +6.9...>90 +7 +7...10 +7...10.5 +7...11 +7...7.5 +7...8 +7...8.5 +7...8.8 +7...9 +7...>10 +7...>10.5 +7...>9.5 +7.0 +7.0 +7.0... +7.0...10 +7.0...10.0 +7.0...10.2 +7.0...10.3 +7.0...10.5 +7.0...10.7 +7.0...11 +7.0...11.0 +7.0...11.5 +7.0...7.2 +7.0...7.2 +7.0...7.3 +7.0...7.4 +7.0...7.5 +7.0...7.6 +7.0...7.8 +7.0...7.9 +7.0...8.0 +7.0...8.1 +7.0...8.2 +7.0...8.3 +7.0...8.4 +7.0...8.5 +7.0...8.6 +7.0...8.67 +7.0...8.7 +7.0...8.8 +7.0...8.9 +7.0...9.0 +7.0...9.1 +7.0...9.2 +7.0...9.3 +7.0...9.4 +7.0...9.5 +7.0...9.7 +7.0...9.75 +7.0...9.8 +7.0...9.9 +7.0...<8.0 +7.0...>10 +7.0...>10.0 +7.0...>10.5 +7.0...>11 +7.0...>11.5 +7.0...>7.8 +7.0...>8.5 +7.0...>8.7 +7.0...>9 +7.0...>9.0 +7.0...>9.5 +7.00 +7.01 +7.03 +7.05 +7.07 +7.1 +7.1...10.1 +7.1...10.3 +7.1...7.9 +7.1...8.1 +7.1...8.2 +7.1...8.3 +7.1...8.4 +7.1...8.6 +7.1...8.7 +7.1...8.9 +7.1...9.0 +7.1...9.2 +7.1...9.3 +7.1...9.4 +7.1...9.8 +7.1...>8.3 +7.1...>8.5 +7.1...>9.0 +7.1...>9.5 +7.10 +7.15 +7.15...8.3 +7.2 +7.2...10.0 +7.2...10.2 +7.2...10.5 +7.2...13.5 +7.2...7.3 +7.2...7.4 +7.2...7.5 +7.2...7.6 +7.2...7.8 +7.2...8.0 +7.2...8.2 +7.2...8.3 +7.2...8.6 +7.2...8.7 +7.2...8.8 +7.2...8.9 +7.2...9.0 +7.2...9.1 +7.2...9.2 +7.2...9.3 +7.2...9.5 +7.2...9.6 +7.2...9.7 +7.2...9.8 +7.2...>10 +7.2...>10.0 +7.2...>11 +7.2...>8.0 +7.2...>8.5 +7.2...>8.7 +7.2...>9.0 +7.2...>9.1 +7.2...>9.2 +7.2...>9.5 +7.24 +7.25 +7.25...7.75 +7.25...8.1 +7.25...8.25 +7.25...8.4 +7.25...8.6 +7.25...8.7 +7.25...9.25 +7.25...>10.0 +7.25...>8.0 +7.25...>8.25 +7.25...>9.0 +7.26 +7.3 +7.3... +7.3...10.2 +7.3...10.5 +7.3...11 +7.3...7.5 +7.3...7.6 +7.3...7.8 +7.3...7.9 +7.3...8.0 +7.3...8.2 +7.3...8.3 +7.3...8.4 +7.3...8.5 +7.3...8.6 +7.3...8.8 +7.3...8.9 +7.3...9.0 +7.3...9.15 +7.3...9.2 +7.3...9.4 +7.3...9.5 +7.3...9.8 +7.3...9.9 +7.3...>10 +7.3...>10.0 +7.3...>10.5 +7.3...>7.8 +7.3...>8.0 +7.3...>8.3 +7.3...>9.0 +7.3...>9.2 +7.3...>9.3 +7.3...>9.5 +7.3...>9.75 +7.3...>90 +7.32 +7.35 +7.4 +7.4... +7.4...10 +7.4...10.0 +7.4...12.1 +7.4...7.6 +7.4...7.8 +7.4...8.0 +7.4...8.2 +7.4...8.3 +7.4...8.4 +7.4...8.5 +7.4...8.6 +7.4...8.7 +7.4...8.8 +7.4...9.0 +7.4...9.2 +7.4...9.3 +7.4...9.6 +7.4...9.7 +7.4...9.8 +7.4...>10.0 +7.4...>8 +7.4...>8.5 +7.4...>8.6 +7.4...>8.7 +7.4...>9.0 +7.45 +7.5 +7.5 +7.5... +7.5...10 +7.5...10.0 +7.5...10.1 +7.5...10.2 +7.5...10.3 +7.5...10.5 +7.5...10.8 +7.5...11 +7.5...11.0 +7.5...11.5 +7.5...12.0 +7.5...7.6 +7.5...7.8 +7.5...8 +7.5...8.0 +7.5...8.1 +7.5...8.2 +7.5...8.4 +7.5...8.5 +7.5...8.6 +7.5...8.7 +7.5...9.0 +7.5...9.1 +7.5...9.2 +7.5...9.3 +7.5...9.4 +7.5...9.5 +7.5...9.6 +7.5...9.7 +7.5...9.8 +7.5...>10 +7.5...>10.0 +7.5...>10.5 +7.5...>11.0 +7.5...>8.3 +7.5...>8.5 +7.5...>8.7 +7.5...>8.75 +7.5...>9 +7.5...>9.0 +7.5...>9.2 +7.5...>9.5 +7.54 +7.55 +7.6 +7.6 +7.6...10.3 +7.6...10.5 +7.6...7.8 +7.6...7.9 +7.6...8.0 +7.6...8.2 +7.6...8.3 +7.6...8.4 +7.6...8.5 +7.6...8.6 +7.6...8.8 +7.6...9.0 +7.6...9.2 +7.6...9.3 +7.6...9.4 +7.6...9.5 +7.6...9.7 +7.6...>10.0 +7.6...>10.3 +7.6...>10.5 +7.6...>11 +7.6...>8.25 +7.6...>8.8 +7.6...>9.0 +7.6...>9.5 +7.65 +7.7... +7.7...10 +7.7...10.0 +7.7...10.3 +7.7...10.5 +7.7...11.0 +7.7...11.5 +7.7...7.8 +7.7...8.0 +7.7...8.3 +7.7...8.5 +7.7...8.7 +7.7...9.0 +7.7...9.25 +7.7...9.4 +7.7...9.5 +7.7...9.8 +7.7...>10.5 +7.7...>8.5 +7.7...>8.6 +7.7...>9.0 +7.7...>9.5 +7.72...8.7 +7.75 +7.75... +7.75...8.1 +7.78 +7.8 +7.8...10.2 +7.8...7.9 +7.8...8.0 +7.8...8.1 +7.8...8.2 +7.8...8.4 +7.8...8.5 +7.8...8.7 +7.8...8.8 +7.8...8.9 +7.8...9.0 +7.8...9.2 +7.8...9.3 +7.8...9.4 +7.8...9.6 +7.8...<9.8 +7.8...>10.0 +7.8...>8.8 +7.8...>8.9 +7.8...>9.0 +7.8...>9.1 +7.8...>9.4 +7.80 +7.85 +7.9 +7.9...10.9 +7.9...8.1 +7.9...8.2 +7.9...8.3 +7.9...8.5 +7.9...8.6 +7.9...8.9 +7.9...9.0 +7.9...9.3 +7.96 +8 +8...10 +8...10.2 +8...10.5 +8...11.0 +8...8.5 +8...9 +8...>10 +8...>11 +8.0 +8.0...10 +8.0...10.0 +8.0...10.2 +8.0...10.3 +8.0...10.5 +8.0...10.8 +8.0...11.0 +8.0...11.2 +8.0...11.7 +8.0...12.0 +8.0...8.5 +8.0...8.12 +8.0...8.2 +8.0...8.4 +8.0...8.5 +8.0...8.6 +8.0...8.7 +8.0...8.8 +8.0...8.9 +8.0...9.0 +8.0...9.2 +8.0...9.4 +8.0...9.5 +8.0...9.7 +8.0...>10 +8.0...>10.0 +8.0...>10.5 +8.0...>11 +8.0...>13.0 +8.0...>8.75 +8.0...>9.0 +8.0...>9.2 +8.0...>9.5 +8.1 +8.1...10.5 +8.1...10.9 +8.1...8.2 +8.1...8.3 +8.1...8.4 +8.1...8.6 +8.1...8.7 +8.1...8.75 +8.1...8.9 +8.1...9.1 +8.1...9.9 +8.1...>9.5 +8.13 +8.15 +8.2 +8.2... +8.2...10.2 +8.2...10.4 +8.2...8.4 +8.2...8.5 +8.2...8.8 +8.2...8.9 +8.2...9.0 +8.2...9.6 +8.2...9.8 +8.2...>10 +8.2...>10.5 +8.2...>11 +8.2...>11.0 +8.2...>8.8 +8.2...>9.3 +8.20 +8.25 +8.25...10.2 +8.27 +8.27...10 +8.28 +8.3 +8.3... +8.3...10.2 +8.3...10.3 +8.3...10.8 +8.3...8.4 +8.3...8.5 +8.3...8.6 +8.3...8.8 +8.3...9.6 +8.3...9.9 +8.3...>9.5 +8.3...>9.8 +8.35 +8.4 +8.4...10.1 +8.4...8.5 +8.4...8.6 +8.4...8.7 +8.4...8.8 +8.4...8.9 +8.4...9.0 +8.4...9.2 +8.4...9.5 +8.4...<9.8 +8.4...>10.0 +8.4...>10.5 +8.4...>9.8 +8.48 +8.5 +8.5... +8.5...10.0 +8.5...10.3 +8.5...10.5 +8.5...10.7 +8.5...10.8 +8.5...11 +8.5...11.0 +8.5...12.5 +8.5...8.6 +8.5...8.8 +8.5...9.0 +8.5...9.1 +8.5...9.2 +8.5...9.3 +8.5...9.4 +8.5...9.5 +8.5...9.9 +8.5...>10 +8.5...>11 +8.5...>11.5 +8.5...>9.5 +8.6 +8.6...10.2 +8.6...10.3 +8.6...11 +8.6...8.7 +8.6...8.8 +8.6...8.9 +8.6...9.4 +8.6...>11 +8.6...>99 +8.65 +8.7 +8.7...10.6 +8.7...11.2 +8.7...11.3 +8.7...11.7 +8.7...8.9 +8.7...9.1 +8.7...9.6 +8.7...9.8 +8.75 +8.8 +8.8...10.3 +8.8...10.7 +8.8...9.0 +8.8...9.2 +8.8...9.8 +8.8...>9.8 +8.9 +8.9...10.3 +8.9...10.4 +8.9...10.9 +8.9...11.5 +8.9...9.2 +9 +9.8 +9...10 +9...11 +9...>12 +9.0 +9.0... +9.0...10 +9.0...10.0 +9.0...10.5 +9.0...10.7 +9.0...11 +9.0...11.0 +9.0...11.1 +9.0...11.2 +9.0...11.3 +9.0...11.5 +9.0...12 +9.0...9.2 +9.0...9.5 +9.0...>10.7 +9.0...>11.0 +9.1 +9.1...10 +9.1...10.1 +9.1...9.5 +9.15 +9.2 +9.2...10 +9.2...10.8 +9.2...12 +9.2...9.3 +9.2...9.4 +9.2...9.5 +9.2...9.6 +9.25 +9.3 +9.4 +9.4...10 +9.4...12.2 +9.4...9.7 +9.5 +9.5... +9.5...10 +9.5...10.0 +9.5...10.4 +9.5...10.5 +9.5...10.6 +9.5...11.2 +9.5...9.8 +9.5...>10.2 +9.5...>10.5 +9.55 +9.6 +9.6...10 +9.6...10.2 +9.6...11.5 +9.6...>11.2 +9.63 +9.7 +9.7...11 +9.7...11.5 +9.7...9.8 +9.7...>12 +9.75 +9.76 +9.8 +9.8...10.0 +9.8...10.1 +9.8...>12.5 +9.9 +9.99 +10 +10...10.5 +10...11 +10.0 +10.0...10.4 +10.0...10.5 +10.0...11.25 +10.0...11.5 +10.0...12.5 +10.0...>12.5 +10.2 +10.2...11.8 +10.25 +10.3 +10.32 +10.4 +10.45 +10.5 +10.5...11.0 +10.5...11.5 +10.5...<13.5 +10.6 +10.7 +10.7...12.3 +10.70 +10.75 +10.8 +11 +11 +11.0 +11.3 +11.5 +11.5...12.5 +11.75 +12 +12.0 +12.2 +14 +15 +16 +16.7 +17 +18 +19 +19.8 +20 +20.0 +22 +22 +22.9 +23 +23...25 +23.7 +24 +24.0 +\N diff --git a/contrib/seg/expected/security.out b/contrib/seg/expected/security.out new file mode 100644 index 0000000..b47598d --- /dev/null +++ b/contrib/seg/expected/security.out @@ -0,0 +1,32 @@ +-- +-- Test extension script protection against search path overriding +-- +CREATE ROLE regress_seg_role; +SELECT current_database() AS datname \gset +GRANT CREATE ON DATABASE :"datname" TO regress_seg_role; +SET ROLE regress_seg_role; +CREATE SCHEMA regress_seg_schema; +CREATE FUNCTION regress_seg_schema.exfun(i int) RETURNS int AS $$ +BEGIN + CREATE EXTENSION seg VERSION '1.2'; + + CREATE FUNCTION regress_seg_schema.compare(oid, regclass) RETURNS boolean AS + 'BEGIN RAISE EXCEPTION ''overloaded compare() called by %'', current_user; END;' LANGUAGE plpgsql; + + CREATE OPERATOR = (LEFTARG = oid, RIGHTARG = regclass, PROCEDURE = regress_seg_schema.compare); + + ALTER EXTENSION seg UPDATE TO '1.3'; + + RETURN i; +END; $$ LANGUAGE plpgsql; +CREATE SCHEMA test_schema +CREATE TABLE t(i int) PARTITION BY RANGE (i) +CREATE TABLE p1 PARTITION OF t FOR VALUES FROM (1) TO (regress_seg_schema.exfun(2)); +DROP SCHEMA test_schema CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table test_schema.t +drop cascades to extension seg +drop cascades to operator test_schema.=(oid,regclass) +RESET ROLE; +DROP OWNED BY regress_seg_role; +DROP ROLE regress_seg_role; diff --git a/contrib/seg/expected/seg.out b/contrib/seg/expected/seg.out new file mode 100644 index 0000000..cd21139 --- /dev/null +++ b/contrib/seg/expected/seg.out @@ -0,0 +1,1299 @@ +-- +-- Test seg datatype +-- +CREATE EXTENSION seg; +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + amname | opcname +--------+--------- +(0 rows) + +-- +-- testing the input and output functions +-- +-- Any number +SELECT '1'::seg AS seg; + seg +----- + 1 +(1 row) + +SELECT '-1'::seg AS seg; + seg +----- + -1 +(1 row) + +SELECT '1.0'::seg AS seg; + seg +----- + 1.0 +(1 row) + +SELECT '-1.0'::seg AS seg; + seg +------ + -1.0 +(1 row) + +SELECT '1e7'::seg AS seg; + seg +------- + 1e+07 +(1 row) + +SELECT '-1e7'::seg AS seg; + seg +-------- + -1e+07 +(1 row) + +SELECT '1.0e7'::seg AS seg; + seg +--------- + 1.0e+07 +(1 row) + +SELECT '-1.0e7'::seg AS seg; + seg +---------- + -1.0e+07 +(1 row) + +SELECT '1e+7'::seg AS seg; + seg +------- + 1e+07 +(1 row) + +SELECT '-1e+7'::seg AS seg; + seg +-------- + -1e+07 +(1 row) + +SELECT '1.0e+7'::seg AS seg; + seg +--------- + 1.0e+07 +(1 row) + +SELECT '-1.0e+7'::seg AS seg; + seg +---------- + -1.0e+07 +(1 row) + +SELECT '1e-7'::seg AS seg; + seg +------- + 1e-07 +(1 row) + +SELECT '-1e-7'::seg AS seg; + seg +-------- + -1e-07 +(1 row) + +SELECT '1.0e-7'::seg AS seg; + seg +--------- + 1.0e-07 +(1 row) + +SELECT '-1.0e-7'::seg AS seg; + seg +---------- + -1.0e-07 +(1 row) + +SELECT '2e-6'::seg AS seg; + seg +------- + 2e-06 +(1 row) + +SELECT '2e-5'::seg AS seg; + seg +------- + 2e-05 +(1 row) + +SELECT '2e-4'::seg AS seg; + seg +-------- + 0.0002 +(1 row) + +SELECT '2e-3'::seg AS seg; + seg +------- + 0.002 +(1 row) + +SELECT '2e-2'::seg AS seg; + seg +------ + 0.02 +(1 row) + +SELECT '2e-1'::seg AS seg; + seg +----- + 0.2 +(1 row) + +SELECT '2e-0'::seg AS seg; + seg +----- + 2 +(1 row) + +SELECT '2e+0'::seg AS seg; + seg +----- + 2 +(1 row) + +SELECT '2e+1'::seg AS seg; + seg +----- + 2e1 +(1 row) + +SELECT '2e+2'::seg AS seg; + seg +----- + 2e2 +(1 row) + +SELECT '2e+3'::seg AS seg; + seg +----- + 2e3 +(1 row) + +SELECT '2e+4'::seg AS seg; + seg +----- + 2e4 +(1 row) + +SELECT '2e+5'::seg AS seg; + seg +------- + 2e+05 +(1 row) + +SELECT '2e+6'::seg AS seg; + seg +------- + 2e+06 +(1 row) + +-- Significant digits preserved +SELECT '1'::seg AS seg; + seg +----- + 1 +(1 row) + +SELECT '1.0'::seg AS seg; + seg +----- + 1.0 +(1 row) + +SELECT '1.00'::seg AS seg; + seg +------ + 1.00 +(1 row) + +SELECT '1.000'::seg AS seg; + seg +------- + 1.000 +(1 row) + +SELECT '1.0000'::seg AS seg; + seg +-------- + 1.0000 +(1 row) + +SELECT '1.00000'::seg AS seg; + seg +--------- + 1.00000 +(1 row) + +SELECT '1.000000'::seg AS seg; + seg +--------- + 1.00000 +(1 row) + +SELECT '0.000000120'::seg AS seg; + seg +---------- + 1.20e-07 +(1 row) + +SELECT '3.400e5'::seg AS seg; + seg +----------- + 3.400e+05 +(1 row) + +-- Digits truncated +SELECT '12.34567890123456'::seg AS seg; + seg +--------- + 12.3457 +(1 row) + +-- Same, with a very long input +SELECT '12.3456789012345600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'::seg AS seg; + seg +--------- + 12.3457 +(1 row) + +-- Numbers with certainty indicators +SELECT '~6.5'::seg AS seg; + seg +------ + ~6.5 +(1 row) + +SELECT '<6.5'::seg AS seg; + seg +------ + <6.5 +(1 row) + +SELECT '>6.5'::seg AS seg; + seg +------ + >6.5 +(1 row) + +SELECT '~ 6.5'::seg AS seg; + seg +------ + ~6.5 +(1 row) + +SELECT '< 6.5'::seg AS seg; + seg +------ + <6.5 +(1 row) + +SELECT '> 6.5'::seg AS seg; + seg +------ + >6.5 +(1 row) + +-- Open intervals +SELECT '0..'::seg AS seg; + seg +------ + 0 .. +(1 row) + +SELECT '0...'::seg AS seg; + seg +------ + 0 .. +(1 row) + +SELECT '0 ..'::seg AS seg; + seg +------ + 0 .. +(1 row) + +SELECT '0 ...'::seg AS seg; + seg +------ + 0 .. +(1 row) + +SELECT '..0'::seg AS seg; + seg +------ + .. 0 +(1 row) + +SELECT '...0'::seg AS seg; + seg +------ + .. 0 +(1 row) + +SELECT '.. 0'::seg AS seg; + seg +------ + .. 0 +(1 row) + +SELECT '... 0'::seg AS seg; + seg +------ + .. 0 +(1 row) + +-- Finite intervals +SELECT '0 .. 1'::seg AS seg; + seg +-------- + 0 .. 1 +(1 row) + +SELECT '-1 .. 0'::seg AS seg; + seg +--------- + -1 .. 0 +(1 row) + +SELECT '-1 .. 1'::seg AS seg; + seg +--------- + -1 .. 1 +(1 row) + +-- (+/-) intervals +SELECT '0(+-)1'::seg AS seg; + seg +--------- + -1 .. 1 +(1 row) + +SELECT '0(+-)1.0'::seg AS seg; + seg +------------- + -1.0 .. 1.0 +(1 row) + +SELECT '1.0(+-)0.005'::seg AS seg; + seg +---------------- + 0.995 .. 1.005 +(1 row) + +SELECT '101(+-)1'::seg AS seg; + seg +------------------ + 1.00e2 .. 1.02e2 +(1 row) + +-- incorrect number of significant digits in 99.0: +SELECT '100(+-)1'::seg AS seg; + seg +---------------- + 99.0 .. 1.01e2 +(1 row) + +-- invalid input +SELECT ''::seg AS seg; +ERROR: bad seg representation +LINE 1: SELECT ''::seg AS seg; + ^ +DETAIL: syntax error at end of input +SELECT 'ABC'::seg AS seg; +ERROR: bad seg representation +LINE 1: SELECT 'ABC'::seg AS seg; + ^ +DETAIL: syntax error at or near "A" +SELECT '1ABC'::seg AS seg; +ERROR: bad seg representation +LINE 1: SELECT '1ABC'::seg AS seg; + ^ +DETAIL: syntax error at or near "A" +SELECT '1.'::seg AS seg; +ERROR: bad seg representation +LINE 1: SELECT '1.'::seg AS seg; + ^ +DETAIL: syntax error at or near "." +SELECT '1.....'::seg AS seg; +ERROR: bad seg representation +LINE 1: SELECT '1.....'::seg AS seg; + ^ +DETAIL: syntax error at or near ".." +SELECT '.1'::seg AS seg; +ERROR: bad seg representation +LINE 1: SELECT '.1'::seg AS seg; + ^ +DETAIL: syntax error at or near "." +SELECT '1..2.'::seg AS seg; +ERROR: bad seg representation +LINE 1: SELECT '1..2.'::seg AS seg; + ^ +DETAIL: syntax error at or near "." +SELECT '1 e7'::seg AS seg; +ERROR: bad seg representation +LINE 1: SELECT '1 e7'::seg AS seg; + ^ +DETAIL: syntax error at or near "e" +SELECT '1e700'::seg AS seg; +ERROR: "1e700" is out of range for type real +LINE 1: SELECT '1e700'::seg AS seg; + ^ +-- +-- testing the operators +-- +-- equality/inequality: +-- +SELECT '24 .. 33.20'::seg = '24 .. 33.20'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '24 .. 33.20'::seg = '24 .. 33.21'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '24 .. 33.20'::seg != '24 .. 33.20'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '24 .. 33.20'::seg != '24 .. 33.21'::seg AS bool; + bool +------ + t +(1 row) + +-- overlap +-- +SELECT '1'::seg && '1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '1'::seg && '2'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 ..'::seg && '0 ..'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg && '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '..0'::seg && '0..'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '-1 .. 0.1'::seg && '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '-1 .. 0'::seg && '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '-1 .. -0.0001'::seg && '0 .. 1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 ..'::seg && '1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg && '1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg && '2'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 2'::seg && '1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '1'::seg && '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '2'::seg && '0 .. 1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '1'::seg && '0 .. 2'::seg AS bool; + bool +------ + t +(1 row) + +-- overlap on the left +-- +SELECT '1'::seg &< '0'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '1'::seg &< '1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '1'::seg &< '2'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg &< '0'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 1'::seg &< '1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg &< '2'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg &< '0 .. 0.5'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 1'::seg &< '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg &< '0 .. 2'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg &< '1 .. 2'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg &< '2 .. 3'::seg AS bool; + bool +------ + t +(1 row) + +-- overlap on the right +-- +SELECT '0'::seg &> '1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '1'::seg &> '1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '2'::seg &> '1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0'::seg &> '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '1'::seg &> '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '2'::seg &> '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 0.5'::seg &> '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg &> '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 2'::seg &> '0 .. 2'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '1 .. 2'::seg &> '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '2 .. 3'::seg &> '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +-- left +-- +SELECT '1'::seg << '0'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '1'::seg << '1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '1'::seg << '2'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg << '0'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 1'::seg << '1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 1'::seg << '2'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 1'::seg << '0 .. 0.5'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 1'::seg << '0 .. 1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 1'::seg << '0 .. 2'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 1'::seg << '1 .. 2'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 1'::seg << '2 .. 3'::seg AS bool; + bool +------ + t +(1 row) + +-- right +-- +SELECT '0'::seg >> '1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '1'::seg >> '1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '2'::seg >> '1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0'::seg >> '0 .. 1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '1'::seg >> '0 .. 1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '2'::seg >> '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. 0.5'::seg >> '0 .. 1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 1'::seg >> '0 .. 1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0 .. 2'::seg >> '0 .. 2'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '1 .. 2'::seg >> '0 .. 1'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '2 .. 3'::seg >> '0 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +-- "contained in" (the left value belongs within the interval specified in the right value): +-- +SELECT '0'::seg <@ '0'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0'::seg <@ '0 ..'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0'::seg <@ '.. 0'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0'::seg <@ '-1 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0'::seg <@ '-1 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '-1'::seg <@ '-1 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '1'::seg <@ '-1 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '-1 .. 1'::seg <@ '-1 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +-- "contains" (the left value contains the interval specified in the right value): +-- +SELECT '0'::seg @> '0'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '0 .. '::seg <@ '0'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '.. 0'::seg <@ '0'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '-1 .. 1'::seg <@ '0'::seg AS bool; + bool +------ + f +(1 row) + +SELECT '0'::seg <@ '-1 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '-1'::seg <@ '-1 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +SELECT '1'::seg <@ '-1 .. 1'::seg AS bool; + bool +------ + t +(1 row) + +-- Load some example data and build the index +-- +CREATE TABLE test_seg (s seg); +\copy test_seg from 'data/test_seg.data' +CREATE INDEX test_seg_ix ON test_seg USING gist (s); +SET enable_indexscan = false; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM test_seg WHERE s @> '11..11.3'; + QUERY PLAN +------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on test_seg + Recheck Cond: (s @> '1.1e1 .. 11.3'::seg) + -> Bitmap Index Scan on test_seg_ix + Index Cond: (s @> '1.1e1 .. 11.3'::seg) +(5 rows) + +SELECT count(*) FROM test_seg WHERE s @> '11..11.3'; + count +------- + 143 +(1 row) + +RESET enable_indexscan; +SET enable_bitmapscan = false; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM test_seg WHERE s @> '11..11.3'; + QUERY PLAN +----------------------------------------------------- + Aggregate + -> Index Only Scan using test_seg_ix on test_seg + Index Cond: (s @> '1.1e1 .. 11.3'::seg) +(3 rows) + +SELECT count(*) FROM test_seg WHERE s @> '11..11.3'; + count +------- + 143 +(1 row) + +RESET enable_bitmapscan; +-- Test sorting +SELECT * FROM test_seg WHERE s @> '11..11.3' GROUP BY s; + s +----------------- + .. 4.0e1 + .. >8.2e1 + .. 9.0e1 + <1.0 .. >13.0 + 1.3 .. 12.0 + 2.0 .. 11.5 + 2.1 .. 11.8 + <2.3 .. + >2.3 .. + 2.4 .. 11.3 + 2.5 .. 11.5 + 2.5 .. 11.8 + 2.6 .. + 2.7 .. 12.0 + <3.0 .. + 3 .. 5.8e1 + 3.1 .. 11.5 + 3.5 .. 11.5 + 3.5 .. 12.2 + <4.0 .. >1.2e1 + <4.0 .. + 4 .. 1.2e1 + 4.0 .. 11.7 + 4.0 .. 12.5 + 4.0 .. 13.0 + 4.0 .. 6.0e1 + 4.0 .. + 4.2 .. 11.5 + 4.2 .. 11.7 + <4.5 .. >1.2e1 + 4.5 .. 11.5 + 4.5 .. <1.2e1 + 4.5 .. >1.2e1 + 4.5 .. 12.5 + 4.5 .. 1.15e2 + 4.7 .. 11.8 + 4.8 .. 11.5 + 4.8 .. 11.6 + 4.8 .. 12.5 + 4.8 .. + 4.9 .. >1.2e1 + 4.9 .. + 5 .. 11.5 + 5 .. 1.2e1 + 5 .. 3.0e1 + 5.0 .. 11.4 + 5.0 .. 11.5 + 5.0 .. 11.6 + 5.0 .. 11.7 + 5.0 .. 12.0 + 5.0 .. >12.0 + 5.0 .. >1.2e1 + 5.2 .. 11.5 + 5.2 .. >1.2e1 + 5.25 .. >1.2e1 + 5.3 .. 11.5 + 5.3 .. 1.3e1 + 5.3 .. >9.0e1 + 5.3 .. + 5.4 .. + 5.5 .. 11.5 + 5.5 .. 11.7 + 5.5 .. 1.2e1 + 5.5 .. >1.2e1 + 5.5 .. 12.5 + 5.5 .. 13.5 + 5.5 .. + >5.5 .. + 5.7 .. + 5.9 .. + 6 .. 11.5 + 6 .. >1.2e1 + 6.0 .. 11.5 + 6.0 .. 1.3e1 + >6.0 .. <11.5 + 6.1 .. >1.2e1 + 6.1 .. + 6.2 .. >11.5 + 6.3 .. + 6.5 .. 11.5 + 6.5 .. 12.0 + 6.5 .. >12.0 + 6.5 .. + 6.6 .. + 6.7 .. 11.5 + 6.7 .. + 6.75 .. + 6.8 .. + 6.9 .. 12.2 + 6.9 .. >9.0e1 + 6.9 .. + <7.0 .. >11.5 + 7.0 .. 11.5 + 7.0 .. >11.5 + 7.0 .. + >7.15 .. + 7.2 .. 13.5 + 7.3 .. >9.0e1 + 7.3 .. + >7.3 .. + 7.4 .. 12.1 + 7.4 .. + 7.5 .. 11.5 + 7.5 .. 12.0 + 7.5 .. + 7.7 .. 11.5 + 7.7 .. + 7.75 .. + 8.0 .. 11.7 + 8.0 .. 12.0 + 8.0 .. >13.0 + 8.2 .. + 8.3 .. + 8.5 .. >11.5 + 8.5 .. 12.5 + 8.5 .. + 8.6 .. >9.9e1 + 8.7 .. 11.3 + 8.7 .. 11.7 + 8.9 .. 11.5 + 9 .. >1.2e1 + 9.0 .. 11.3 + 9.0 .. 11.5 + 9.0 .. 1.2e1 + 9.0 .. + 9.2 .. 1.2e1 + 9.4 .. 12.2 + <9.5 .. 1.2e1 + <9.5 .. >12.2 + 9.5 .. + 9.6 .. 11.5 + 9.7 .. 11.5 + 9.7 .. >1.2e1 + 9.8 .. >12.5 + <1.0e1 .. >11.6 + 10.0 .. 11.5 + 10.0 .. 12.5 + 10.0 .. >12.5 + 10.2 .. 11.8 + <10.5 .. 11.5 + 10.5 .. 11.5 + 10.5 .. <13.5 + 10.7 .. 12.3 +(143 rows) + +-- Test functions +SELECT seg_lower(s), seg_center(s), seg_upper(s) +FROM test_seg WHERE s @> '11.2..11.3' OR s IS NULL ORDER BY s; + seg_lower | seg_center | seg_upper +-----------+------------+----------- + -Infinity | -Infinity | 40 + -Infinity | -Infinity | 82 + -Infinity | -Infinity | 90 + 1 | 7 | 13 + 1.3 | 6.65 | 12 + 2 | 6.75 | 11.5 + 2.1 | 6.95 | 11.8 + 2.3 | Infinity | Infinity + 2.3 | Infinity | Infinity + 2.4 | 6.8500004 | 11.3 + 2.5 | 7 | 11.5 + 2.5 | 7.15 | 11.8 + 2.6 | Infinity | Infinity + 2.7 | 7.35 | 12 + 3 | Infinity | Infinity + 3 | 30.5 | 58 + 3.1 | 7.3 | 11.5 + 3.5 | 7.5 | 11.5 + 3.5 | 7.85 | 12.2 + 4 | 8 | 12 + 4 | Infinity | Infinity + 4 | 8 | 12 + 4 | 7.85 | 11.7 + 4 | 8.25 | 12.5 + 4 | 8.5 | 13 + 4 | 32 | 60 + 4 | Infinity | Infinity + 4.2 | 7.85 | 11.5 + 4.2 | 7.95 | 11.7 + 4.5 | 8.25 | 12 + 4.5 | 8 | 11.5 + 4.5 | 8.25 | 12 + 4.5 | 8.25 | 12 + 4.5 | 8.5 | 12.5 + 4.5 | 59.75 | 115 + 4.7 | 8.25 | 11.8 + 4.8 | 8.15 | 11.5 + 4.8 | 8.200001 | 11.6 + 4.8 | 8.65 | 12.5 + 4.8 | Infinity | Infinity + 4.9 | 8.45 | 12 + 4.9 | Infinity | Infinity + 5 | 8.25 | 11.5 + 5 | 8.5 | 12 + 5 | 17.5 | 30 + 5 | 8.2 | 11.4 + 5 | 8.25 | 11.5 + 5 | 8.3 | 11.6 + 5 | 8.35 | 11.7 + 5 | 8.5 | 12 + 5 | 8.5 | 12 + 5 | 8.5 | 12 + 5.2 | 8.35 | 11.5 + 5.2 | 8.6 | 12 + 5.25 | 8.625 | 12 + 5.3 | 8.4 | 11.5 + 5.3 | 9.15 | 13 + 5.3 | 47.65 | 90 + 5.3 | Infinity | Infinity + 5.4 | Infinity | Infinity + 5.5 | 8.5 | 11.5 + 5.5 | 8.6 | 11.7 + 5.5 | 8.75 | 12 + 5.5 | 8.75 | 12 + 5.5 | 9 | 12.5 + 5.5 | 9.5 | 13.5 + 5.5 | Infinity | Infinity + 5.5 | Infinity | Infinity + 5.7 | Infinity | Infinity + 5.9 | Infinity | Infinity + 6 | 8.75 | 11.5 + 6 | 9 | 12 + 6 | 8.75 | 11.5 + 6 | 9.5 | 13 + 6 | 8.75 | 11.5 + 6.1 | 9.05 | 12 + 6.1 | Infinity | Infinity + 6.2 | 8.85 | 11.5 + 6.3 | Infinity | Infinity + 6.5 | 9 | 11.5 + 6.5 | 9.25 | 12 + 6.5 | 9.25 | 12 + 6.5 | Infinity | Infinity + 6.6 | Infinity | Infinity + 6.7 | 9.1 | 11.5 + 6.7 | Infinity | Infinity + 6.75 | Infinity | Infinity + 6.8 | Infinity | Infinity + 6.9 | 9.55 | 12.2 + 6.9 | 48.45 | 90 + 6.9 | Infinity | Infinity + 7 | 9.25 | 11.5 + 7 | 9.25 | 11.5 + 7 | 9.25 | 11.5 + 7 | Infinity | Infinity + 7.15 | Infinity | Infinity + 7.2 | 10.35 | 13.5 + 7.3 | 48.65 | 90 + 7.3 | Infinity | Infinity + 7.3 | Infinity | Infinity + 7.4 | 9.75 | 12.1 + 7.4 | Infinity | Infinity + 7.5 | 9.5 | 11.5 + 7.5 | 9.75 | 12 + 7.5 | Infinity | Infinity + 7.7 | 9.6 | 11.5 + 7.7 | Infinity | Infinity + 7.75 | Infinity | Infinity + 8 | 9.85 | 11.7 + 8 | 10 | 12 + 8 | 10.5 | 13 + 8.2 | Infinity | Infinity + 8.3 | Infinity | Infinity + 8.5 | 10 | 11.5 + 8.5 | 10.5 | 12.5 + 8.5 | Infinity | Infinity + 8.6 | 53.8 | 99 + 8.7 | 10 | 11.3 + 8.7 | 10.2 | 11.7 + 8.9 | 10.2 | 11.5 + 9 | 10.5 | 12 + 9 | 10.15 | 11.3 + 9 | 10.25 | 11.5 + 9 | 10.5 | 12 + 9 | Infinity | Infinity + 9.2 | 10.6 | 12 + 9.4 | 10.799999 | 12.2 + 9.5 | 10.75 | 12 + 9.5 | 10.85 | 12.2 + 9.5 | Infinity | Infinity + 9.6 | 10.55 | 11.5 + 9.7 | 10.6 | 11.5 + 9.7 | 10.85 | 12 + 9.8 | 11.15 | 12.5 + 10 | 10.8 | 11.6 + 10 | 10.75 | 11.5 + 10 | 11.25 | 12.5 + 10 | 11.25 | 12.5 + 10.2 | 11 | 11.8 + 10.5 | 11 | 11.5 + 10.5 | 11 | 11.5 + 10.5 | 12 | 13.5 + 10.7 | 11.5 | 12.3 + | | +(144 rows) + +-- test non error throwing API +SELECT str as seg, + pg_input_is_valid(str,'seg') as ok, + errinfo.sql_error_code, + errinfo.message, + errinfo.detail, + errinfo.hint +FROM unnest(ARRAY['-1 .. 1'::text, + '100(+-)1', + '', + 'ABC', + '1 e7', + '1e700']) str, + LATERAL pg_input_error_info(str, 'seg') as errinfo; + seg | ok | sql_error_code | message | detail | hint +----------+----+----------------+---------------------------------------+------------------------------+------ + -1 .. 1 | t | | | | + 100(+-)1 | t | | | | + | f | 42601 | bad seg representation | syntax error at end of input | + ABC | f | 42601 | bad seg representation | syntax error at or near "A" | + 1 e7 | f | 42601 | bad seg representation | syntax error at or near "e" | + 1e700 | f | 22003 | "1e700" is out of range for type real | | +(6 rows) + diff --git a/contrib/seg/meson.build b/contrib/seg/meson.build new file mode 100644 index 0000000..ecde302 --- /dev/null +++ b/contrib/seg/meson.build @@ -0,0 +1,60 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +seg_sources = files( + 'seg.c', +) + +seg_scan = custom_target('segscan', + input: 'segscan.l', + output: 'segscan.c', + command: flex_cmd, +) +generated_sources += seg_scan +seg_sources += seg_scan + +seg_parse = custom_target('segparse', + input: 'segparse.y', + kwargs: bison_kw, +) +generated_sources += seg_parse.to_list() +seg_sources += seg_parse + +if host_system == 'windows' + seg_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'seg', + '--FILEDESC', 'seg - line segment data type',]) +endif + +seg = shared_module('seg', + seg_sources, + include_directories: include_directories('.'), + kwargs: contrib_mod_args, +) +contrib_targets += seg + +install_data( + 'seg.control', + 'seg--1.0--1.1.sql', + 'seg--1.1--1.2.sql', + 'seg--1.1.sql', + 'seg--1.2--1.3.sql', + 'seg--1.3--1.4.sql', + kwargs: contrib_data_args, +) + +install_headers( + 'segdata.h', + install_dir: dir_include_extension / 'seg', +) + +tests += { + 'name': 'seg', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'security', + 'seg', + ], + }, +} diff --git a/contrib/seg/seg--1.0--1.1.sql b/contrib/seg/seg--1.0--1.1.sql new file mode 100644 index 0000000..ae6cb2f --- /dev/null +++ b/contrib/seg/seg--1.0--1.1.sql @@ -0,0 +1,63 @@ +/* contrib/seg/seg--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION seg UPDATE TO '1.1'" to load this file. \quit + +-- Update procedure signatures the hard way. +-- We use to_regprocedure() so that query doesn't fail if run against 9.6beta1 definitions, +-- wherein the signatures have been updated already. In that case to_regprocedure() will +-- return NULL and no updates will happen. +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_catalog.pg_proc SET + proargtypes = pg_catalog.array_to_string(newtypes::pg_catalog.oid[], ' ')::pg_catalog.oidvector, + pronargs = pg_catalog.array_length(newtypes, 1) +FROM (VALUES +(NULL::pg_catalog.text, NULL::pg_catalog.text[]), -- establish column types +('gseg_consistent(internal,SCH.seg,int4,oid,internal)', '{internal,SCH.seg,int2,oid,internal}') +) AS update_data (oldproc, newtypestext), +LATERAL ( + SELECT array_agg(replace(typ, 'SCH', my_schema)::regtype) as newtypes FROM unnest(newtypestext) typ +) ls +WHERE oid = to_regprocedure(my_schema || '.' || replace(oldproc, 'SCH', my_schema)); + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; + +ALTER FUNCTION seg_in(cstring) PARALLEL SAFE; +ALTER FUNCTION seg_out(seg) PARALLEL SAFE; +ALTER FUNCTION seg_over_left(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_over_right(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_left(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_right(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_lt(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_le(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_gt(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_ge(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_contains(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_contained(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_overlap(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_same(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_different(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_cmp(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_union(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_inter(seg, seg) PARALLEL SAFE; +ALTER FUNCTION seg_size(seg) PARALLEL SAFE; +ALTER FUNCTION seg_center(seg) PARALLEL SAFE; +ALTER FUNCTION seg_upper(seg) PARALLEL SAFE; +ALTER FUNCTION seg_lower(seg) PARALLEL SAFE; +ALTER FUNCTION gseg_consistent(internal, seg, smallint, oid, internal) PARALLEL SAFE; +ALTER FUNCTION gseg_compress(internal) PARALLEL SAFE; +ALTER FUNCTION gseg_decompress(internal) PARALLEL SAFE; +ALTER FUNCTION gseg_penalty(internal, internal, internal) PARALLEL SAFE; +ALTER FUNCTION gseg_picksplit(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gseg_union(internal, internal) PARALLEL SAFE; +ALTER FUNCTION gseg_same(seg, seg, internal) PARALLEL SAFE; diff --git a/contrib/seg/seg--1.1--1.2.sql b/contrib/seg/seg--1.1--1.2.sql new file mode 100644 index 0000000..a6e4456 --- /dev/null +++ b/contrib/seg/seg--1.1--1.2.sql @@ -0,0 +1,14 @@ +/* contrib/seg/seg--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION seg UPDATE TO '1.2'" to load this file. \quit + +ALTER OPERATOR <= (seg, seg) SET ( + RESTRICT = scalarlesel, + JOIN = scalarlejoinsel +); + +ALTER OPERATOR >= (seg, seg) SET ( + RESTRICT = scalargesel, + JOIN = scalargejoinsel +); diff --git a/contrib/seg/seg--1.1.sql b/contrib/seg/seg--1.1.sql new file mode 100644 index 0000000..d95aabc --- /dev/null +++ b/contrib/seg/seg--1.1.sql @@ -0,0 +1,395 @@ +/* contrib/seg/seg--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION seg" to load this file. \quit + +-- Create the user-defined type for 1-D floating point intervals (seg) + +CREATE FUNCTION seg_in(cstring) +RETURNS seg +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION seg_out(seg) +RETURNS cstring +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE TYPE seg ( + INTERNALLENGTH = 12, + INPUT = seg_in, + OUTPUT = seg_out +); + +COMMENT ON TYPE seg IS +'floating point interval ''FLOAT .. FLOAT'', ''.. FLOAT'', ''FLOAT ..'' or ''FLOAT'''; + +-- +-- External C-functions for R-tree methods +-- + +-- Left/Right methods + +CREATE FUNCTION seg_over_left(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_over_left(seg, seg) IS +'overlaps or is left of'; + +CREATE FUNCTION seg_over_right(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_over_right(seg, seg) IS +'overlaps or is right of'; + +CREATE FUNCTION seg_left(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_left(seg, seg) IS +'is left of'; + +CREATE FUNCTION seg_right(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_right(seg, seg) IS +'is right of'; + + +-- Scalar comparison methods + +CREATE FUNCTION seg_lt(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_lt(seg, seg) IS +'less than'; + +CREATE FUNCTION seg_le(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_le(seg, seg) IS +'less than or equal'; + +CREATE FUNCTION seg_gt(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_gt(seg, seg) IS +'greater than'; + +CREATE FUNCTION seg_ge(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_ge(seg, seg) IS +'greater than or equal'; + +CREATE FUNCTION seg_contains(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_contains(seg, seg) IS +'contains'; + +CREATE FUNCTION seg_contained(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_contained(seg, seg) IS +'contained in'; + +CREATE FUNCTION seg_overlap(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_overlap(seg, seg) IS +'overlaps'; + +CREATE FUNCTION seg_same(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_same(seg, seg) IS +'same as'; + +CREATE FUNCTION seg_different(seg, seg) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_different(seg, seg) IS +'different'; + +-- support routines for indexing + +CREATE FUNCTION seg_cmp(seg, seg) +RETURNS int4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +COMMENT ON FUNCTION seg_cmp(seg, seg) IS 'btree comparison function'; + +CREATE FUNCTION seg_union(seg, seg) +RETURNS seg +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION seg_inter(seg, seg) +RETURNS seg +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION seg_size(seg) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +-- miscellaneous + +CREATE FUNCTION seg_center(seg) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION seg_upper(seg) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION seg_lower(seg) +RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + + +-- +-- OPERATORS +-- + +CREATE OPERATOR < ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_lt, + COMMUTATOR = '>', + NEGATOR = '>=', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR <= ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_le, + COMMUTATOR = '>=', + NEGATOR = '>', + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel +); + +CREATE OPERATOR > ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_gt, + COMMUTATOR = '<', + NEGATOR = '<=', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR >= ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_ge, + COMMUTATOR = '<=', + NEGATOR = '<', + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel +); + +CREATE OPERATOR << ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_left, + COMMUTATOR = '>>', + RESTRICT = positionsel, + JOIN = positionjoinsel +); + +CREATE OPERATOR &< ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_over_left, + RESTRICT = positionsel, + JOIN = positionjoinsel +); + +CREATE OPERATOR && ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_overlap, + COMMUTATOR = '&&', + RESTRICT = areasel, + JOIN = areajoinsel +); + +CREATE OPERATOR &> ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_over_right, + RESTRICT = positionsel, + JOIN = positionjoinsel +); + +CREATE OPERATOR >> ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_right, + COMMUTATOR = '<<', + RESTRICT = positionsel, + JOIN = positionjoinsel +); + +CREATE OPERATOR = ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_same, + COMMUTATOR = '=', + NEGATOR = '<>', + RESTRICT = eqsel, + JOIN = eqjoinsel, + MERGES +); + +CREATE OPERATOR <> ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_different, + COMMUTATOR = '<>', + NEGATOR = '=', + RESTRICT = neqsel, + JOIN = neqjoinsel +); + +CREATE OPERATOR @> ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_contains, + COMMUTATOR = '<@', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR <@ ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_contained, + COMMUTATOR = '@>', + RESTRICT = contsel, + JOIN = contjoinsel +); + +-- obsolete: +CREATE OPERATOR @ ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_contains, + COMMUTATOR = '~', + RESTRICT = contsel, + JOIN = contjoinsel +); + +CREATE OPERATOR ~ ( + LEFTARG = seg, + RIGHTARG = seg, + PROCEDURE = seg_contained, + COMMUTATOR = '@', + RESTRICT = contsel, + JOIN = contjoinsel +); + + +-- define the GiST support methods +CREATE FUNCTION gseg_consistent(internal,seg,smallint,oid,internal) +RETURNS bool +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gseg_compress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gseg_decompress(internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gseg_penalty(internal,internal,internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gseg_picksplit(internal, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gseg_union(internal, internal) +RETURNS seg +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION gseg_same(seg, seg, internal) +RETURNS internal +AS 'MODULE_PATHNAME' +LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + + +-- Create the operator classes for indexing + +CREATE OPERATOR CLASS seg_ops + DEFAULT FOR TYPE seg USING btree AS + OPERATOR 1 < , + OPERATOR 2 <= , + OPERATOR 3 = , + OPERATOR 4 >= , + OPERATOR 5 > , + FUNCTION 1 seg_cmp(seg, seg); + +CREATE OPERATOR CLASS gist_seg_ops +DEFAULT FOR TYPE seg USING gist +AS + OPERATOR 1 << , + OPERATOR 2 &< , + OPERATOR 3 && , + OPERATOR 4 &> , + OPERATOR 5 >> , + OPERATOR 6 = , + OPERATOR 7 @> , + OPERATOR 8 <@ , + OPERATOR 13 @ , + OPERATOR 14 ~ , + FUNCTION 1 gseg_consistent (internal, seg, smallint, oid, internal), + FUNCTION 2 gseg_union (internal, internal), + FUNCTION 3 gseg_compress (internal), + FUNCTION 4 gseg_decompress (internal), + FUNCTION 5 gseg_penalty (internal, internal, internal), + FUNCTION 6 gseg_picksplit (internal, internal), + FUNCTION 7 gseg_same (seg, seg, internal); diff --git a/contrib/seg/seg--1.2--1.3.sql b/contrib/seg/seg--1.2--1.3.sql new file mode 100644 index 0000000..578e989 --- /dev/null +++ b/contrib/seg/seg--1.2--1.3.sql @@ -0,0 +1,58 @@ +/* contrib/seg/seg--1.2--1.3.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION seg UPDATE TO '1.3'" to load this file. \quit + +-- +-- Get rid of unnecessary compress and decompress support functions. +-- +-- To be allowed to drop the opclass entry for a support function, +-- we must change the entry's dependency type from 'internal' to 'auto', +-- as though it were a loose member of the opfamily rather than being +-- bound into a particular opclass. There's no SQL command for that, +-- so fake it with a manual update on pg_depend. +-- +DO LANGUAGE plpgsql +$$ +DECLARE + my_schema pg_catalog.text := pg_catalog.quote_ident(pg_catalog.current_schema()); + old_path pg_catalog.text := pg_catalog.current_setting('search_path'); +BEGIN +-- for safety, transiently set search_path to just pg_catalog+pg_temp +PERFORM pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + +UPDATE pg_catalog.pg_depend +SET deptype = 'a' +WHERE classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass + AND objid = + (SELECT objid + FROM pg_catalog.pg_depend + WHERE classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass + AND refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass + AND (refobjid = (my_schema || '.gseg_compress(internal)')::pg_catalog.regprocedure)) + AND refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass + AND deptype = 'i'; + +UPDATE pg_catalog.pg_depend +SET deptype = 'a' +WHERE classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass + AND objid = + (SELECT objid + FROM pg_catalog.pg_depend + WHERE classid = 'pg_catalog.pg_amproc'::pg_catalog.regclass + AND refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass + AND (refobjid = (my_schema || '.gseg_decompress(internal)')::pg_catalog.regprocedure)) + AND refclassid = 'pg_catalog.pg_opclass'::pg_catalog.regclass + AND deptype = 'i'; + +PERFORM pg_catalog.set_config('search_path', old_path, true); +END +$$; + +ALTER OPERATOR FAMILY gist_seg_ops USING gist drop function 3 (seg); +ALTER EXTENSION seg DROP function gseg_compress(pg_catalog.internal); +DROP function gseg_compress(pg_catalog.internal); + +ALTER OPERATOR FAMILY gist_seg_ops USING gist drop function 4 (seg); +ALTER EXTENSION seg DROP function gseg_decompress(pg_catalog.internal); +DROP function gseg_decompress(pg_catalog.internal); diff --git a/contrib/seg/seg--1.3--1.4.sql b/contrib/seg/seg--1.3--1.4.sql new file mode 100644 index 0000000..13babdd --- /dev/null +++ b/contrib/seg/seg--1.3--1.4.sql @@ -0,0 +1,8 @@ +/* contrib/seg/seg--1.3--1.4.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION seg UPDATE TO '1.4'" to load this file. \quit + +-- Remove @ and ~ +DROP OPERATOR @ (seg, seg); +DROP OPERATOR ~ (seg, seg); diff --git a/contrib/seg/seg-validate.pl b/contrib/seg/seg-validate.pl new file mode 100755 index 0000000..67c0015 --- /dev/null +++ b/contrib/seg/seg-validate.pl @@ -0,0 +1,56 @@ +#!/usr/bin/perl + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +use strict; +use warnings; + +my $integer = '[+-]?[0-9]+'; +my $real = '[+-]?[0-9]+\.[0-9]+'; + +my $RANGE = '(\.\.)(\.)?'; +my $PLUMIN = q(\'\+\-\'); +my $FLOAT = "(($integer)|($real))([eE]($integer))?"; +my $EXTENSION = '<|>|~'; + +my $boundary = "($EXTENSION)?$FLOAT"; +my $deviation = $FLOAT; + +my $rule_1 = $boundary . $PLUMIN . $deviation; +my $rule_2 = $boundary . $RANGE . $boundary; +my $rule_3 = $boundary . $RANGE; +my $rule_4 = $RANGE . $boundary; +my $rule_5 = $boundary; + + +print "$rule_5\n"; +while (<>) +{ + + # s/ +//g; + if (/^($rule_1)$/) + { + print; + } + elsif (/^($rule_2)$/) + { + print; + } + elsif (/^($rule_3)$/) + { + print; + } + elsif (/^($rule_4)$/) + { + print; + } + elsif (/^($rule_5)$/) + { + print; + } + else + { + print STDERR "error in $_\n"; + } + +} diff --git a/contrib/seg/seg.c b/contrib/seg/seg.c new file mode 100644 index 0000000..7f9fc24 --- /dev/null +++ b/contrib/seg/seg.c @@ -0,0 +1,1095 @@ +/* + * contrib/seg/seg.c + * + ****************************************************************************** + This file contains routines that can be bound to a Postgres backend and + called by the backend in the process of processing queries. The calling + format for these routines is dictated by Postgres architecture. +******************************************************************************/ + +#include "postgres.h" + +#include +#include + +#include "access/gist.h" +#include "access/stratnum.h" +#include "fmgr.h" + +#include "segdata.h" + + +#define DatumGetSegP(X) ((SEG *) DatumGetPointer(X)) +#define PG_GETARG_SEG_P(n) DatumGetSegP(PG_GETARG_DATUM(n)) + + +/* +#define GIST_DEBUG +#define GIST_QUERY_DEBUG +*/ + +PG_MODULE_MAGIC; + +/* + * Auxiliary data structure for picksplit method. + */ +typedef struct +{ + float center; + OffsetNumber index; + SEG *data; +} gseg_picksplit_item; + +/* +** Input/Output routines +*/ +PG_FUNCTION_INFO_V1(seg_in); +PG_FUNCTION_INFO_V1(seg_out); +PG_FUNCTION_INFO_V1(seg_size); +PG_FUNCTION_INFO_V1(seg_lower); +PG_FUNCTION_INFO_V1(seg_upper); +PG_FUNCTION_INFO_V1(seg_center); + +/* +** GiST support methods +*/ +PG_FUNCTION_INFO_V1(gseg_consistent); +PG_FUNCTION_INFO_V1(gseg_compress); +PG_FUNCTION_INFO_V1(gseg_decompress); +PG_FUNCTION_INFO_V1(gseg_picksplit); +PG_FUNCTION_INFO_V1(gseg_penalty); +PG_FUNCTION_INFO_V1(gseg_union); +PG_FUNCTION_INFO_V1(gseg_same); +static Datum gseg_leaf_consistent(Datum key, Datum query, StrategyNumber strategy); +static Datum gseg_internal_consistent(Datum key, Datum query, StrategyNumber strategy); +static Datum gseg_binary_union(Datum r1, Datum r2, int *sizep); + + +/* +** R-tree support functions +*/ +PG_FUNCTION_INFO_V1(seg_same); +PG_FUNCTION_INFO_V1(seg_contains); +PG_FUNCTION_INFO_V1(seg_contained); +PG_FUNCTION_INFO_V1(seg_overlap); +PG_FUNCTION_INFO_V1(seg_left); +PG_FUNCTION_INFO_V1(seg_over_left); +PG_FUNCTION_INFO_V1(seg_right); +PG_FUNCTION_INFO_V1(seg_over_right); +PG_FUNCTION_INFO_V1(seg_union); +PG_FUNCTION_INFO_V1(seg_inter); +static void rt_seg_size(SEG *a, float *size); + +/* +** Various operators +*/ +PG_FUNCTION_INFO_V1(seg_cmp); +PG_FUNCTION_INFO_V1(seg_lt); +PG_FUNCTION_INFO_V1(seg_le); +PG_FUNCTION_INFO_V1(seg_gt); +PG_FUNCTION_INFO_V1(seg_ge); +PG_FUNCTION_INFO_V1(seg_different); + +/* +** Auxiliary functions +*/ +static int restore(char *result, float val, int n); + + +/***************************************************************************** + * Input/Output functions + *****************************************************************************/ + +Datum +seg_in(PG_FUNCTION_ARGS) +{ + char *str = PG_GETARG_CSTRING(0); + SEG *result = palloc(sizeof(SEG)); + + seg_scanner_init(str); + + if (seg_yyparse(result, fcinfo->context) != 0) + seg_yyerror(result, fcinfo->context, "bogus input"); + + seg_scanner_finish(); + + PG_RETURN_POINTER(result); +} + +Datum +seg_out(PG_FUNCTION_ARGS) +{ + SEG *seg = PG_GETARG_SEG_P(0); + char *result; + char *p; + + p = result = (char *) palloc(40); + + if (seg->l_ext == '>' || seg->l_ext == '<' || seg->l_ext == '~') + p += sprintf(p, "%c", seg->l_ext); + + if (seg->lower == seg->upper && seg->l_ext == seg->u_ext) + { + /* + * indicates that this interval was built by seg_in off a single point + */ + p += restore(p, seg->lower, seg->l_sigd); + } + else + { + if (seg->l_ext != '-') + { + /* print the lower boundary if exists */ + p += restore(p, seg->lower, seg->l_sigd); + p += sprintf(p, " "); + } + p += sprintf(p, ".."); + if (seg->u_ext != '-') + { + /* print the upper boundary if exists */ + p += sprintf(p, " "); + if (seg->u_ext == '>' || seg->u_ext == '<' || seg->l_ext == '~') + p += sprintf(p, "%c", seg->u_ext); + p += restore(p, seg->upper, seg->u_sigd); + } + } + + PG_RETURN_CSTRING(result); +} + +Datum +seg_center(PG_FUNCTION_ARGS) +{ + SEG *seg = PG_GETARG_SEG_P(0); + + PG_RETURN_FLOAT4(((float) seg->lower + (float) seg->upper) / 2.0); +} + +Datum +seg_lower(PG_FUNCTION_ARGS) +{ + SEG *seg = PG_GETARG_SEG_P(0); + + PG_RETURN_FLOAT4(seg->lower); +} + +Datum +seg_upper(PG_FUNCTION_ARGS) +{ + SEG *seg = PG_GETARG_SEG_P(0); + + PG_RETURN_FLOAT4(seg->upper); +} + + +/***************************************************************************** + * GiST functions + *****************************************************************************/ + +/* +** The GiST Consistent method for segments +** Should return false if for all data items x below entry, +** the predicate x op query == false, where op is the oper +** corresponding to strategy in the pg_amop table. +*/ +Datum +gseg_consistent(PG_FUNCTION_ARGS) +{ + GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + Datum query = PG_GETARG_DATUM(1); + StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2); + + /* Oid subtype = PG_GETARG_OID(3); */ + bool *recheck = (bool *) PG_GETARG_POINTER(4); + + /* All cases served by this function are exact */ + *recheck = false; + + /* + * if entry is not leaf, use gseg_internal_consistent, else use + * gseg_leaf_consistent + */ + if (GIST_LEAF(entry)) + return gseg_leaf_consistent(entry->key, query, strategy); + else + return gseg_internal_consistent(entry->key, query, strategy); +} + +/* +** The GiST Union method for segments +** returns the minimal bounding seg that encloses all the entries in entryvec +*/ +Datum +gseg_union(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + int *sizep = (int *) PG_GETARG_POINTER(1); + int numranges, + i; + Datum out = 0; + Datum tmp; + +#ifdef GIST_DEBUG + fprintf(stderr, "union\n"); +#endif + + numranges = entryvec->n; + tmp = entryvec->vector[0].key; + *sizep = sizeof(SEG); + + for (i = 1; i < numranges; i++) + { + out = gseg_binary_union(tmp, entryvec->vector[i].key, sizep); + tmp = out; + } + + PG_RETURN_DATUM(out); +} + +/* +** GiST Compress and Decompress methods for segments +** do not do anything. +*/ +Datum +gseg_compress(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(PG_GETARG_POINTER(0)); +} + +Datum +gseg_decompress(PG_FUNCTION_ARGS) +{ + PG_RETURN_POINTER(PG_GETARG_POINTER(0)); +} + +/* +** The GiST Penalty method for segments +** As in the R-tree paper, we use change in area as our penalty metric +*/ +Datum +gseg_penalty(PG_FUNCTION_ARGS) +{ + GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); + GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); + float *result = (float *) PG_GETARG_POINTER(2); + SEG *ud; + float tmp1, + tmp2; + + ud = DatumGetSegP(DirectFunctionCall2(seg_union, + origentry->key, + newentry->key)); + rt_seg_size(ud, &tmp1); + rt_seg_size(DatumGetSegP(origentry->key), &tmp2); + *result = tmp1 - tmp2; + +#ifdef GIST_DEBUG + fprintf(stderr, "penalty\n"); + fprintf(stderr, "\t%g\n", *result); +#endif + + PG_RETURN_POINTER(result); +} + +/* + * Compare function for gseg_picksplit_item: sort by center. + */ +static int +gseg_picksplit_item_cmp(const void *a, const void *b) +{ + const gseg_picksplit_item *i1 = (const gseg_picksplit_item *) a; + const gseg_picksplit_item *i2 = (const gseg_picksplit_item *) b; + + if (i1->center < i2->center) + return -1; + else if (i1->center == i2->center) + return 0; + else + return 1; +} + +/* + * The GiST PickSplit method for segments + * + * We used to use Guttman's split algorithm here, but since the data is 1-D + * it's easier and more robust to just sort the segments by center-point and + * split at the middle. + */ +Datum +gseg_picksplit(PG_FUNCTION_ARGS) +{ + GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); + GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + int i; + SEG *seg, + *seg_l, + *seg_r; + gseg_picksplit_item *sort_items; + OffsetNumber *left, + *right; + OffsetNumber maxoff; + OffsetNumber firstright; + +#ifdef GIST_DEBUG + fprintf(stderr, "picksplit\n"); +#endif + + /* Valid items in entryvec->vector[] are indexed 1..maxoff */ + maxoff = entryvec->n - 1; + + /* + * Prepare the auxiliary array and sort it. + */ + sort_items = (gseg_picksplit_item *) + palloc(maxoff * sizeof(gseg_picksplit_item)); + for (i = 1; i <= maxoff; i++) + { + seg = DatumGetSegP(entryvec->vector[i].key); + /* center calculation is done this way to avoid possible overflow */ + sort_items[i - 1].center = seg->lower * 0.5f + seg->upper * 0.5f; + sort_items[i - 1].index = i; + sort_items[i - 1].data = seg; + } + qsort(sort_items, maxoff, sizeof(gseg_picksplit_item), + gseg_picksplit_item_cmp); + + /* sort items below "firstright" will go into the left side */ + firstright = maxoff / 2; + + v->spl_left = (OffsetNumber *) palloc(maxoff * sizeof(OffsetNumber)); + v->spl_right = (OffsetNumber *) palloc(maxoff * sizeof(OffsetNumber)); + left = v->spl_left; + v->spl_nleft = 0; + right = v->spl_right; + v->spl_nright = 0; + + /* + * Emit segments to the left output page, and compute its bounding box. + */ + seg_l = (SEG *) palloc(sizeof(SEG)); + memcpy(seg_l, sort_items[0].data, sizeof(SEG)); + *left++ = sort_items[0].index; + v->spl_nleft++; + for (i = 1; i < firstright; i++) + { + Datum sortitem = PointerGetDatum(sort_items[i].data); + + seg_l = DatumGetSegP(DirectFunctionCall2(seg_union, + PointerGetDatum(seg_l), + sortitem)); + *left++ = sort_items[i].index; + v->spl_nleft++; + } + + /* + * Likewise for the right page. + */ + seg_r = (SEG *) palloc(sizeof(SEG)); + memcpy(seg_r, sort_items[firstright].data, sizeof(SEG)); + *right++ = sort_items[firstright].index; + v->spl_nright++; + for (i = firstright + 1; i < maxoff; i++) + { + Datum sortitem = PointerGetDatum(sort_items[i].data); + + seg_r = DatumGetSegP(DirectFunctionCall2(seg_union, + PointerGetDatum(seg_r), + sortitem)); + *right++ = sort_items[i].index; + v->spl_nright++; + } + + v->spl_ldatum = PointerGetDatum(seg_l); + v->spl_rdatum = PointerGetDatum(seg_r); + + PG_RETURN_POINTER(v); +} + +/* +** Equality methods +*/ +Datum +gseg_same(PG_FUNCTION_ARGS) +{ + bool *result = (bool *) PG_GETARG_POINTER(2); + + if (DirectFunctionCall2(seg_same, PG_GETARG_DATUM(0), PG_GETARG_DATUM(1))) + *result = true; + else + *result = false; + +#ifdef GIST_DEBUG + fprintf(stderr, "same: %s\n", (*result ? "TRUE" : "FALSE")); +#endif + + PG_RETURN_POINTER(result); +} + +/* +** SUPPORT ROUTINES +*/ +static Datum +gseg_leaf_consistent(Datum key, Datum query, StrategyNumber strategy) +{ + Datum retval; + +#ifdef GIST_QUERY_DEBUG + fprintf(stderr, "leaf_consistent, %d\n", strategy); +#endif + + switch (strategy) + { + case RTLeftStrategyNumber: + retval = DirectFunctionCall2(seg_left, key, query); + break; + case RTOverLeftStrategyNumber: + retval = DirectFunctionCall2(seg_over_left, key, query); + break; + case RTOverlapStrategyNumber: + retval = DirectFunctionCall2(seg_overlap, key, query); + break; + case RTOverRightStrategyNumber: + retval = DirectFunctionCall2(seg_over_right, key, query); + break; + case RTRightStrategyNumber: + retval = DirectFunctionCall2(seg_right, key, query); + break; + case RTSameStrategyNumber: + retval = DirectFunctionCall2(seg_same, key, query); + break; + case RTContainsStrategyNumber: + case RTOldContainsStrategyNumber: + retval = DirectFunctionCall2(seg_contains, key, query); + break; + case RTContainedByStrategyNumber: + case RTOldContainedByStrategyNumber: + retval = DirectFunctionCall2(seg_contained, key, query); + break; + default: + retval = false; + } + + PG_RETURN_DATUM(retval); +} + +static Datum +gseg_internal_consistent(Datum key, Datum query, StrategyNumber strategy) +{ + bool retval; + +#ifdef GIST_QUERY_DEBUG + fprintf(stderr, "internal_consistent, %d\n", strategy); +#endif + + switch (strategy) + { + case RTLeftStrategyNumber: + retval = + !DatumGetBool(DirectFunctionCall2(seg_over_right, key, query)); + break; + case RTOverLeftStrategyNumber: + retval = + !DatumGetBool(DirectFunctionCall2(seg_right, key, query)); + break; + case RTOverlapStrategyNumber: + retval = + DatumGetBool(DirectFunctionCall2(seg_overlap, key, query)); + break; + case RTOverRightStrategyNumber: + retval = + !DatumGetBool(DirectFunctionCall2(seg_left, key, query)); + break; + case RTRightStrategyNumber: + retval = + !DatumGetBool(DirectFunctionCall2(seg_over_left, key, query)); + break; + case RTSameStrategyNumber: + case RTContainsStrategyNumber: + case RTOldContainsStrategyNumber: + retval = + DatumGetBool(DirectFunctionCall2(seg_contains, key, query)); + break; + case RTContainedByStrategyNumber: + case RTOldContainedByStrategyNumber: + retval = + DatumGetBool(DirectFunctionCall2(seg_overlap, key, query)); + break; + default: + retval = false; + } + + PG_RETURN_BOOL(retval); +} + +static Datum +gseg_binary_union(Datum r1, Datum r2, int *sizep) +{ + Datum retval; + + retval = DirectFunctionCall2(seg_union, r1, r2); + *sizep = sizeof(SEG); + + return retval; +} + + +Datum +seg_contains(PG_FUNCTION_ARGS) +{ + SEG *a = PG_GETARG_SEG_P(0); + SEG *b = PG_GETARG_SEG_P(1); + + PG_RETURN_BOOL((a->lower <= b->lower) && (a->upper >= b->upper)); +} + +Datum +seg_contained(PG_FUNCTION_ARGS) +{ + Datum a = PG_GETARG_DATUM(0); + Datum b = PG_GETARG_DATUM(1); + + PG_RETURN_DATUM(DirectFunctionCall2(seg_contains, b, a)); +} + +/***************************************************************************** + * Operator class for R-tree indexing + *****************************************************************************/ + +Datum +seg_same(PG_FUNCTION_ARGS) +{ + int cmp = DatumGetInt32(DirectFunctionCall2(seg_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(cmp == 0); +} + +/* seg_overlap -- does a overlap b? + */ +Datum +seg_overlap(PG_FUNCTION_ARGS) +{ + SEG *a = PG_GETARG_SEG_P(0); + SEG *b = PG_GETARG_SEG_P(1); + + PG_RETURN_BOOL(((a->upper >= b->upper) && (a->lower <= b->upper)) || + ((b->upper >= a->upper) && (b->lower <= a->upper))); +} + +/* seg_over_left -- is the right edge of (a) located at or left of the right edge of (b)? + */ +Datum +seg_over_left(PG_FUNCTION_ARGS) +{ + SEG *a = PG_GETARG_SEG_P(0); + SEG *b = PG_GETARG_SEG_P(1); + + PG_RETURN_BOOL(a->upper <= b->upper); +} + +/* seg_left -- is (a) entirely on the left of (b)? + */ +Datum +seg_left(PG_FUNCTION_ARGS) +{ + SEG *a = PG_GETARG_SEG_P(0); + SEG *b = PG_GETARG_SEG_P(1); + + PG_RETURN_BOOL(a->upper < b->lower); +} + +/* seg_right -- is (a) entirely on the right of (b)? + */ +Datum +seg_right(PG_FUNCTION_ARGS) +{ + SEG *a = PG_GETARG_SEG_P(0); + SEG *b = PG_GETARG_SEG_P(1); + + PG_RETURN_BOOL(a->lower > b->upper); +} + +/* seg_over_right -- is the left edge of (a) located at or right of the left edge of (b)? + */ +Datum +seg_over_right(PG_FUNCTION_ARGS) +{ + SEG *a = PG_GETARG_SEG_P(0); + SEG *b = PG_GETARG_SEG_P(1); + + PG_RETURN_BOOL(a->lower >= b->lower); +} + +Datum +seg_union(PG_FUNCTION_ARGS) +{ + SEG *a = PG_GETARG_SEG_P(0); + SEG *b = PG_GETARG_SEG_P(1); + SEG *n; + + n = (SEG *) palloc(sizeof(*n)); + + /* take max of upper endpoints */ + if (a->upper > b->upper) + { + n->upper = a->upper; + n->u_sigd = a->u_sigd; + n->u_ext = a->u_ext; + } + else + { + n->upper = b->upper; + n->u_sigd = b->u_sigd; + n->u_ext = b->u_ext; + } + + /* take min of lower endpoints */ + if (a->lower < b->lower) + { + n->lower = a->lower; + n->l_sigd = a->l_sigd; + n->l_ext = a->l_ext; + } + else + { + n->lower = b->lower; + n->l_sigd = b->l_sigd; + n->l_ext = b->l_ext; + } + + PG_RETURN_POINTER(n); +} + +Datum +seg_inter(PG_FUNCTION_ARGS) +{ + SEG *a = PG_GETARG_SEG_P(0); + SEG *b = PG_GETARG_SEG_P(1); + SEG *n; + + n = (SEG *) palloc(sizeof(*n)); + + /* take min of upper endpoints */ + if (a->upper < b->upper) + { + n->upper = a->upper; + n->u_sigd = a->u_sigd; + n->u_ext = a->u_ext; + } + else + { + n->upper = b->upper; + n->u_sigd = b->u_sigd; + n->u_ext = b->u_ext; + } + + /* take max of lower endpoints */ + if (a->lower > b->lower) + { + n->lower = a->lower; + n->l_sigd = a->l_sigd; + n->l_ext = a->l_ext; + } + else + { + n->lower = b->lower; + n->l_sigd = b->l_sigd; + n->l_ext = b->l_ext; + } + + PG_RETURN_POINTER(n); +} + +static void +rt_seg_size(SEG *a, float *size) +{ + if (a == (SEG *) NULL || a->upper <= a->lower) + *size = 0.0; + else + *size = fabsf(a->upper - a->lower); +} + +Datum +seg_size(PG_FUNCTION_ARGS) +{ + SEG *seg = PG_GETARG_SEG_P(0); + + PG_RETURN_FLOAT4(fabsf(seg->upper - seg->lower)); +} + + +/***************************************************************************** + * Miscellaneous operators + *****************************************************************************/ +Datum +seg_cmp(PG_FUNCTION_ARGS) +{ + SEG *a = PG_GETARG_SEG_P(0); + SEG *b = PG_GETARG_SEG_P(1); + + /* + * First compare on lower boundary position + */ + if (a->lower < b->lower) + PG_RETURN_INT32(-1); + if (a->lower > b->lower) + PG_RETURN_INT32(1); + + /* + * a->lower == b->lower, so consider type of boundary. + * + * A '-' lower bound is < any other kind (this could only be relevant if + * -HUGE_VAL is used as a regular data value). A '<' lower bound is < any + * other kind except '-'. A '>' lower bound is > any other kind. + */ + if (a->l_ext != b->l_ext) + { + if (a->l_ext == '-') + PG_RETURN_INT32(-1); + if (b->l_ext == '-') + PG_RETURN_INT32(1); + if (a->l_ext == '<') + PG_RETURN_INT32(-1); + if (b->l_ext == '<') + PG_RETURN_INT32(1); + if (a->l_ext == '>') + PG_RETURN_INT32(1); + if (b->l_ext == '>') + PG_RETURN_INT32(-1); + } + + /* + * For other boundary types, consider # of significant digits first. + */ + if (a->l_sigd < b->l_sigd) /* (a) is blurred and is likely to include (b) */ + PG_RETURN_INT32(-1); + if (a->l_sigd > b->l_sigd) /* (a) is less blurred and is likely to be + * included in (b) */ + PG_RETURN_INT32(1); + + /* + * For same # of digits, an approximate boundary is more blurred than + * exact. + */ + if (a->l_ext != b->l_ext) + { + if (a->l_ext == '~') /* (a) is approximate, while (b) is exact */ + PG_RETURN_INT32(-1); + if (b->l_ext == '~') + PG_RETURN_INT32(1); + /* can't get here unless data is corrupt */ + elog(ERROR, "bogus lower boundary types %d %d", + (int) a->l_ext, (int) b->l_ext); + } + + /* at this point, the lower boundaries are identical */ + + /* + * First compare on upper boundary position + */ + if (a->upper < b->upper) + PG_RETURN_INT32(-1); + if (a->upper > b->upper) + PG_RETURN_INT32(1); + + /* + * a->upper == b->upper, so consider type of boundary. + * + * A '-' upper bound is > any other kind (this could only be relevant if + * HUGE_VAL is used as a regular data value). A '<' upper bound is < any + * other kind. A '>' upper bound is > any other kind except '-'. + */ + if (a->u_ext != b->u_ext) + { + if (a->u_ext == '-') + PG_RETURN_INT32(1); + if (b->u_ext == '-') + PG_RETURN_INT32(-1); + if (a->u_ext == '<') + PG_RETURN_INT32(-1); + if (b->u_ext == '<') + PG_RETURN_INT32(1); + if (a->u_ext == '>') + PG_RETURN_INT32(1); + if (b->u_ext == '>') + PG_RETURN_INT32(-1); + } + + /* + * For other boundary types, consider # of significant digits first. Note + * result here is converse of the lower-boundary case. + */ + if (a->u_sigd < b->u_sigd) /* (a) is blurred and is likely to include (b) */ + PG_RETURN_INT32(1); + if (a->u_sigd > b->u_sigd) /* (a) is less blurred and is likely to be + * included in (b) */ + PG_RETURN_INT32(-1); + + /* + * For same # of digits, an approximate boundary is more blurred than + * exact. Again, result is converse of lower-boundary case. + */ + if (a->u_ext != b->u_ext) + { + if (a->u_ext == '~') /* (a) is approximate, while (b) is exact */ + PG_RETURN_INT32(1); + if (b->u_ext == '~') + PG_RETURN_INT32(-1); + /* can't get here unless data is corrupt */ + elog(ERROR, "bogus upper boundary types %d %d", + (int) a->u_ext, (int) b->u_ext); + } + + PG_RETURN_INT32(0); +} + +Datum +seg_lt(PG_FUNCTION_ARGS) +{ + int cmp = DatumGetInt32(DirectFunctionCall2(seg_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(cmp < 0); +} + +Datum +seg_le(PG_FUNCTION_ARGS) +{ + int cmp = DatumGetInt32(DirectFunctionCall2(seg_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(cmp <= 0); +} + +Datum +seg_gt(PG_FUNCTION_ARGS) +{ + int cmp = DatumGetInt32(DirectFunctionCall2(seg_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(cmp > 0); +} + +Datum +seg_ge(PG_FUNCTION_ARGS) +{ + int cmp = DatumGetInt32(DirectFunctionCall2(seg_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(cmp >= 0); +} + + +Datum +seg_different(PG_FUNCTION_ARGS) +{ + int cmp = DatumGetInt32(DirectFunctionCall2(seg_cmp, + PG_GETARG_DATUM(0), + PG_GETARG_DATUM(1))); + + PG_RETURN_BOOL(cmp != 0); +} + + + +/***************************************************************************** + * Auxiliary functions + *****************************************************************************/ + +/* + * The purpose of this routine is to print the given floating point + * value with exactly n significant digits. Its behaviour + * is similar to %.ng except it prints 8.00 where %.ng would + * print 8. Returns the length of the string written at "result". + * + * Caller must provide a sufficiently large result buffer; 16 bytes + * should be enough for all known float implementations. + */ +static int +restore(char *result, float val, int n) +{ + char buf[25] = { + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '\0' + }; + char *p; + int exp; + int i, + dp, + sign; + + /* + * Put a cap on the number of significant digits to avoid garbage in the + * output and ensure we don't overrun the result buffer. (n should not be + * negative, but check to protect ourselves against corrupted data.) + */ + if (n <= 0) + n = FLT_DIG; + else + n = Min(n, FLT_DIG); + + /* remember the sign */ + sign = (val < 0 ? 1 : 0); + + /* print, in %e style to start with */ + sprintf(result, "%.*e", n - 1, val); + + /* find the exponent */ + p = strchr(result, 'e'); + + /* punt if we have 'inf' or similar */ + if (p == NULL) + return strlen(result); + + exp = atoi(p + 1); + if (exp == 0) + { + /* just truncate off the 'e+00' */ + *p = '\0'; + } + else + { + if (abs(exp) <= 4) + { + /* + * remove the decimal point from the mantissa and write the digits + * to the buf array + */ + for (p = result + sign, i = 10, dp = 0; *p != 'e'; p++, i++) + { + buf[i] = *p; + if (*p == '.') + { + dp = i--; /* skip the decimal point */ + } + } + if (dp == 0) + dp = i--; /* no decimal point was found in the above + * for() loop */ + + if (exp > 0) + { + if (dp - 10 + exp >= n) + { + /* + * the decimal point is behind the last significant digit; + * the digits in between must be converted to the exponent + * and the decimal point placed after the first digit + */ + exp = dp - 10 + exp - n; + buf[10 + n] = '\0'; + + /* insert the decimal point */ + if (n > 1) + { + dp = 11; + for (i = 23; i > dp; i--) + buf[i] = buf[i - 1]; + buf[dp] = '.'; + } + + /* + * adjust the exponent by the number of digits after the + * decimal point + */ + if (n > 1) + sprintf(&buf[11 + n], "e%d", exp + n - 1); + else + sprintf(&buf[11], "e%d", exp + n - 1); + + if (sign) + { + buf[9] = '-'; + strcpy(result, &buf[9]); + } + else + strcpy(result, &buf[10]); + } + else + { /* insert the decimal point */ + dp += exp; + for (i = 23; i > dp; i--) + buf[i] = buf[i - 1]; + buf[11 + n] = '\0'; + buf[dp] = '.'; + if (sign) + { + buf[9] = '-'; + strcpy(result, &buf[9]); + } + else + strcpy(result, &buf[10]); + } + } + else + { /* exp <= 0 */ + dp += exp - 1; + buf[10 + n] = '\0'; + buf[dp] = '.'; + if (sign) + { + buf[dp - 2] = '-'; + strcpy(result, &buf[dp - 2]); + } + else + strcpy(result, &buf[dp - 1]); + } + } + + /* do nothing for abs(exp) > 4; %e must be OK */ + /* just get rid of zeroes after [eE]- and +zeroes after [Ee]. */ + + /* ... this is not done yet. */ + } + return strlen(result); +} + + +/* +** Miscellany +*/ + +/* find out the number of significant digits in a string representing + * a floating point number + */ +int +significant_digits(const char *s) +{ + const char *p = s; + int n, + c, + zeroes; + + zeroes = 1; + /* skip leading zeroes and sign */ + for (c = *p; (c == '0' || c == '+' || c == '-') && c != 0; c = *(++p)); + + /* skip decimal point and following zeroes */ + for (c = *p; (c == '0' || c == '.') && c != 0; c = *(++p)) + { + if (c != '.') + zeroes++; + } + + /* count significant digits (n) */ + for (c = *p, n = 0; c != 0; c = *(++p)) + { + if (!((c >= '0' && c <= '9') || (c == '.'))) + break; + if (c != '.') + n++; + } + + if (!n) + return zeroes; + + return n; +} diff --git a/contrib/seg/seg.control b/contrib/seg/seg.control new file mode 100644 index 0000000..e2c6a47 --- /dev/null +++ b/contrib/seg/seg.control @@ -0,0 +1,6 @@ +# seg extension +comment = 'data type for representing line segments or floating-point intervals' +default_version = '1.4' +module_pathname = '$libdir/seg' +relocatable = true +trusted = true diff --git a/contrib/seg/segdata.h b/contrib/seg/segdata.h new file mode 100644 index 0000000..3d6e3e3 --- /dev/null +++ b/contrib/seg/segdata.h @@ -0,0 +1,25 @@ +/* + * contrib/seg/segdata.h + */ +typedef struct SEG +{ + float4 lower; + float4 upper; + char l_sigd; + char u_sigd; + char l_ext; + char u_ext; +} SEG; + +/* in seg.c */ +extern int significant_digits(const char *s); + +/* in segscan.l */ +extern int seg_yylex(void); +extern void seg_yyerror(SEG *result, struct Node *escontext, + const char *message); +extern void seg_scanner_init(const char *str); +extern void seg_scanner_finish(void); + +/* in segparse.y */ +extern int seg_yyparse(SEG *result, struct Node *escontext); diff --git a/contrib/seg/segparse.c b/contrib/seg/segparse.c new file mode 100644 index 0000000..e1e3f59 --- /dev/null +++ b/contrib/seg/segparse.c @@ -0,0 +1,1446 @@ +/* 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 . */ + +/* 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 seg_yyparse +#define yylex seg_yylex +#define yyerror seg_yyerror +#define yydebug seg_yydebug +#define yynerrs seg_yynerrs +#define yylval seg_yylval +#define yychar seg_yychar + +/* First part of user prologue. */ +#line 1 "segparse.y" + +/* contrib/seg/segparse.y */ + +#include "postgres.h" + +#include +#include + +#include "fmgr.h" +#include "nodes/miscnodes.h" +#include "utils/builtins.h" +#include "utils/float.h" + +#include "segdata.h" + +/* + * Bison doesn't allocate anything that needs to live across parser calls, + * so we can easily have it use palloc instead of malloc. This prevents + * memory leaks if we error out during parsing. + */ +#define YYMALLOC palloc +#define YYFREE pfree + +static bool seg_atof(char *value, float *result, struct Node *escontext); + +static int sig_digits(const char *value); + +static char strbuf[25] = { + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '\0' +}; + + +#line 115 "segparse.c" + +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (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 + +#include "segparse.h" +/* 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_SEGFLOAT = 3, /* SEGFLOAT */ + YYSYMBOL_RANGE = 4, /* RANGE */ + YYSYMBOL_PLUMIN = 5, /* PLUMIN */ + YYSYMBOL_EXTENSION = 6, /* EXTENSION */ + YYSYMBOL_YYACCEPT = 7, /* $accept */ + YYSYMBOL_range = 8, /* range */ + YYSYMBOL_boundary = 9, /* boundary */ + YYSYMBOL_deviation = 10 /* deviation */ +}; +typedef enum yysymbol_kind_t yysymbol_kind_t; + + + + +#ifdef short +# undef short +#endif + +/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure + and (if available) are included + so that the code can choose integer types of a good width. */ + +#ifndef __PTRDIFF_MAX__ +# include /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* 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 + . */ +#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 /* 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 /* 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 /* 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 /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include /* 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 /* 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 8 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 12 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 7 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 4 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 9 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 14 + +/* YYMAXUTOK -- Last valid token kind. */ +#define YYMAXUTOK 261 + + +/* 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, + 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, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6 +}; + +#if YYDEBUG + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 66, 66, 78, 96, 106, 116, 124, 136, 150 +}; +#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\"", "SEGFLOAT", "RANGE", + "PLUMIN", "EXTENSION", "$accept", "range", "boundary", "deviation", 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 +}; +#endif + +#define YYPACT_NINF (-3) + +#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[] = +{ + -1, -3, 3, 1, 8, 6, -3, -3, -3, 3, + 9, -3, -3, -3 +}; + + /* 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[] = +{ + 0, 7, 0, 0, 0, 6, 5, 8, 1, 4, + 0, 3, 9, 2 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -3, -3, -2, -3 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + 0, 4, 5, 13 +}; + + /* 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[] = +{ + 6, 0, 1, 2, 7, 3, 1, 11, 8, 3, + 9, 10, 12 +}; + +static const yytype_int8 yycheck[] = +{ + 2, -1, 3, 4, 3, 6, 3, 9, 0, 6, + 4, 5, 3 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_int8 yystos[] = +{ + 0, 3, 4, 6, 8, 9, 9, 3, 0, 4, + 5, 9, 3, 10 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_int8 yyr1[] = +{ + 0, 7, 8, 8, 8, 8, 8, 9, 9, 10 +}; + + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ +static const yytype_int8 yyr2[] = +{ + 0, 2, 3, 3, 2, 2, 1, 1, 2, 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 (result, escontext, 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 /* 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, result, escontext); \ + 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, SEG *result, struct Node *escontext) +{ + FILE *yyoutput = yyo; + YY_USE (yyoutput); + YY_USE (result); + YY_USE (escontext); + 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, SEG *result, struct Node *escontext) +{ + YYFPRINTF (yyo, "%s %s (", + yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); + + yy_symbol_value_print (yyo, yykind, yyvaluep, result, escontext); + 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, SEG *result, struct Node *escontext) +{ + 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)], result, escontext); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule, result, escontext); \ +} 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, SEG *result, struct Node *escontext) +{ + YY_USE (yyvaluep); + YY_USE (result); + YY_USE (escontext); + 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 (SEG *result, struct Node *escontext) +{ + 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: /* range: boundary PLUMIN deviation */ +#line 67 "segparse.y" + { + result->lower = (yyvsp[-2].bnd).val - (yyvsp[0].bnd).val; + result->upper = (yyvsp[-2].bnd).val + (yyvsp[0].bnd).val; + sprintf(strbuf, "%g", result->lower); + result->l_sigd = Max(sig_digits(strbuf), Max((yyvsp[-2].bnd).sigd, (yyvsp[0].bnd).sigd)); + sprintf(strbuf, "%g", result->upper); + result->u_sigd = Max(sig_digits(strbuf), Max((yyvsp[-2].bnd).sigd, (yyvsp[0].bnd).sigd)); + result->l_ext = '\0'; + result->u_ext = '\0'; + } +#line 1124 "segparse.c" + break; + + case 3: /* range: boundary RANGE boundary */ +#line 79 "segparse.y" + { + result->lower = (yyvsp[-2].bnd).val; + result->upper = (yyvsp[0].bnd).val; + if ( result->lower > result->upper ) { + errsave(escontext, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("swapped boundaries: %g is greater than %g", + result->lower, result->upper))); + + YYERROR; + } + result->l_sigd = (yyvsp[-2].bnd).sigd; + result->u_sigd = (yyvsp[0].bnd).sigd; + result->l_ext = ( (yyvsp[-2].bnd).ext ? (yyvsp[-2].bnd).ext : '\0' ); + result->u_ext = ( (yyvsp[0].bnd).ext ? (yyvsp[0].bnd).ext : '\0' ); + } +#line 1145 "segparse.c" + break; + + case 4: /* range: boundary RANGE */ +#line 97 "segparse.y" + { + result->lower = (yyvsp[-1].bnd).val; + result->upper = HUGE_VAL; + result->l_sigd = (yyvsp[-1].bnd).sigd; + result->u_sigd = 0; + result->l_ext = ( (yyvsp[-1].bnd).ext ? (yyvsp[-1].bnd).ext : '\0' ); + result->u_ext = '-'; + } +#line 1158 "segparse.c" + break; + + case 5: /* range: RANGE boundary */ +#line 107 "segparse.y" + { + result->lower = -HUGE_VAL; + result->upper = (yyvsp[0].bnd).val; + result->l_sigd = 0; + result->u_sigd = (yyvsp[0].bnd).sigd; + result->l_ext = '-'; + result->u_ext = ( (yyvsp[0].bnd).ext ? (yyvsp[0].bnd).ext : '\0' ); + } +#line 1171 "segparse.c" + break; + + case 6: /* range: boundary */ +#line 117 "segparse.y" + { + result->lower = result->upper = (yyvsp[0].bnd).val; + result->l_sigd = result->u_sigd = (yyvsp[0].bnd).sigd; + result->l_ext = result->u_ext = ( (yyvsp[0].bnd).ext ? (yyvsp[0].bnd).ext : '\0' ); + } +#line 1181 "segparse.c" + break; + + case 7: /* boundary: SEGFLOAT */ +#line 125 "segparse.y" + { + /* temp variable avoids a gcc 3.3.x bug on Sparc64 */ + float val; + + if (!seg_atof((yyvsp[0].text), &val, escontext)) + YYABORT; + + (yyval.bnd).ext = '\0'; + (yyval.bnd).sigd = sig_digits((yyvsp[0].text)); + (yyval.bnd).val = val; + } +#line 1197 "segparse.c" + break; + + case 8: /* boundary: EXTENSION SEGFLOAT */ +#line 137 "segparse.y" + { + /* temp variable avoids a gcc 3.3.x bug on Sparc64 */ + float val; + + if (!seg_atof((yyvsp[0].text), &val, escontext)) + YYABORT; + + (yyval.bnd).ext = (yyvsp[-1].text)[0]; + (yyval.bnd).sigd = sig_digits((yyvsp[0].text)); + (yyval.bnd).val = val; + } +#line 1213 "segparse.c" + break; + + case 9: /* deviation: SEGFLOAT */ +#line 151 "segparse.y" + { + /* temp variable avoids a gcc 3.3.x bug on Sparc64 */ + float val; + + if (!seg_atof((yyvsp[0].text), &val, escontext)) + YYABORT; + + (yyval.bnd).ext = '\0'; + (yyval.bnd).sigd = sig_digits((yyvsp[0].text)); + (yyval.bnd).val = val; + } +#line 1229 "segparse.c" + break; + + +#line 1233 "segparse.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 (result, escontext, 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, result, escontext); + 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, result, escontext); + 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 (result, escontext, 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, result, escontext); + } + /* 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, result, escontext); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + + return yyresult; +} + +#line 164 "segparse.y" + + + +static bool +seg_atof(char *value, float *result, struct Node *escontext) +{ + *result = float4in_internal(value, NULL, "seg", value, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + return true; +} + +static int +sig_digits(const char *value) +{ + int n = significant_digits(value); + + /* Clamp, to ensure value will fit in sigd fields */ + return Min(n, FLT_DIG); +} diff --git a/contrib/seg/segparse.h b/contrib/seg/segparse.h new file mode 100644 index 0000000..d42d22a --- /dev/null +++ b/contrib/seg/segparse.h @@ -0,0 +1,92 @@ +/* A Bison parser, made by GNU Bison 3.7.5. */ + +/* Bison interface 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 . */ + +/* 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. */ + +/* 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. */ + +#ifndef YY_SEG_YY_SEGPARSE_H_INCLUDED +# define YY_SEG_YY_SEGPARSE_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int seg_yydebug; +#endif + +/* Token kinds. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + YYEMPTY = -2, + YYEOF = 0, /* "end of file" */ + YYerror = 256, /* error */ + YYUNDEF = 257, /* "invalid token" */ + SEGFLOAT = 258, /* SEGFLOAT */ + RANGE = 259, /* RANGE */ + PLUMIN = 260, /* PLUMIN */ + EXTENSION = 261 /* EXTENSION */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +union YYSTYPE +{ +#line 45 "segparse.y" + + struct BND + { + float val; + char ext; + char sigd; + } bnd; + char *text; + +#line 80 "segparse.h" + +}; +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + +extern YYSTYPE seg_yylval; + +int seg_yyparse (SEG *result, struct Node *escontext); + +#endif /* !YY_SEG_YY_SEGPARSE_H_INCLUDED */ diff --git a/contrib/seg/segparse.y b/contrib/seg/segparse.y new file mode 100644 index 0000000..bf759db --- /dev/null +++ b/contrib/seg/segparse.y @@ -0,0 +1,183 @@ +%{ +/* contrib/seg/segparse.y */ + +#include "postgres.h" + +#include +#include + +#include "fmgr.h" +#include "nodes/miscnodes.h" +#include "utils/builtins.h" +#include "utils/float.h" + +#include "segdata.h" + +/* + * Bison doesn't allocate anything that needs to live across parser calls, + * so we can easily have it use palloc instead of malloc. This prevents + * memory leaks if we error out during parsing. + */ +#define YYMALLOC palloc +#define YYFREE pfree + +static bool seg_atof(char *value, float *result, struct Node *escontext); + +static int sig_digits(const char *value); + +static char strbuf[25] = { + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', + '0', '0', '0', '0', '\0' +}; + +%} + +/* BISON Declarations */ +%parse-param {SEG *result} +%parse-param {struct Node *escontext} +%expect 0 +%name-prefix="seg_yy" + +%union +{ + struct BND + { + float val; + char ext; + char sigd; + } bnd; + char *text; +} +%token SEGFLOAT +%token RANGE +%token PLUMIN +%token EXTENSION +%type boundary +%type deviation +%start range + +/* Grammar follows */ +%% + + +range: boundary PLUMIN deviation + { + result->lower = $1.val - $3.val; + result->upper = $1.val + $3.val; + sprintf(strbuf, "%g", result->lower); + result->l_sigd = Max(sig_digits(strbuf), Max($1.sigd, $3.sigd)); + sprintf(strbuf, "%g", result->upper); + result->u_sigd = Max(sig_digits(strbuf), Max($1.sigd, $3.sigd)); + result->l_ext = '\0'; + result->u_ext = '\0'; + } + + | boundary RANGE boundary + { + result->lower = $1.val; + result->upper = $3.val; + if ( result->lower > result->upper ) { + errsave(escontext, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("swapped boundaries: %g is greater than %g", + result->lower, result->upper))); + + YYERROR; + } + result->l_sigd = $1.sigd; + result->u_sigd = $3.sigd; + result->l_ext = ( $1.ext ? $1.ext : '\0' ); + result->u_ext = ( $3.ext ? $3.ext : '\0' ); + } + + | boundary RANGE + { + result->lower = $1.val; + result->upper = HUGE_VAL; + result->l_sigd = $1.sigd; + result->u_sigd = 0; + result->l_ext = ( $1.ext ? $1.ext : '\0' ); + result->u_ext = '-'; + } + + | RANGE boundary + { + result->lower = -HUGE_VAL; + result->upper = $2.val; + result->l_sigd = 0; + result->u_sigd = $2.sigd; + result->l_ext = '-'; + result->u_ext = ( $2.ext ? $2.ext : '\0' ); + } + + | boundary + { + result->lower = result->upper = $1.val; + result->l_sigd = result->u_sigd = $1.sigd; + result->l_ext = result->u_ext = ( $1.ext ? $1.ext : '\0' ); + } + ; + +boundary: SEGFLOAT + { + /* temp variable avoids a gcc 3.3.x bug on Sparc64 */ + float val; + + if (!seg_atof($1, &val, escontext)) + YYABORT; + + $$.ext = '\0'; + $$.sigd = sig_digits($1); + $$.val = val; + } + | EXTENSION SEGFLOAT + { + /* temp variable avoids a gcc 3.3.x bug on Sparc64 */ + float val; + + if (!seg_atof($2, &val, escontext)) + YYABORT; + + $$.ext = $1[0]; + $$.sigd = sig_digits($2); + $$.val = val; + } + ; + +deviation: SEGFLOAT + { + /* temp variable avoids a gcc 3.3.x bug on Sparc64 */ + float val; + + if (!seg_atof($1, &val, escontext)) + YYABORT; + + $$.ext = '\0'; + $$.sigd = sig_digits($1); + $$.val = val; + } + ; + +%% + + +static bool +seg_atof(char *value, float *result, struct Node *escontext) +{ + *result = float4in_internal(value, NULL, "seg", value, escontext); + if (SOFT_ERROR_OCCURRED(escontext)) + return false; + return true; +} + +static int +sig_digits(const char *value) +{ + int n = significant_digits(value); + + /* Clamp, to ensure value will fit in sigd fields */ + return Min(n, FLT_DIG); +} diff --git a/contrib/seg/segscan.c b/contrib/seg/segscan.c new file mode 100644 index 0000000..92ff09d --- /dev/null +++ b/contrib/seg/segscan.c @@ -0,0 +1,2107 @@ +#line 2 "segscan.c" +/* + * A scanner for EMP-style numeric ranges + */ +#include "postgres.h" + +#include "nodes/miscnodes.h" + +/* + * NB: include segparse.h only AFTER including segdata.h, because segdata.h + * contains the definition for SEG. + */ +#include "segdata.h" +#include "segparse.h" + +#line 17 "segscan.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define yy_create_buffer seg_yy_create_buffer +#define yy_delete_buffer seg_yy_delete_buffer +#define yy_scan_buffer seg_yy_scan_buffer +#define yy_scan_string seg_yy_scan_string +#define yy_scan_bytes seg_yy_scan_bytes +#define yy_init_buffer seg_yy_init_buffer +#define yy_flush_buffer seg_yy_flush_buffer +#define yy_load_buffer_state seg_yy_load_buffer_state +#define yy_switch_to_buffer seg_yy_switch_to_buffer +#define yypush_buffer_state seg_yypush_buffer_state +#define yypop_buffer_state seg_yypop_buffer_state +#define yyensure_buffer_stack seg_yyensure_buffer_stack +#define yy_flex_debug seg_yy_flex_debug +#define yyin seg_yyin +#define yyleng seg_yyleng +#define yylex seg_yylex +#define yylineno seg_yylineno +#define yyout seg_yyout +#define yyrestart seg_yyrestart +#define yytext seg_yytext +#define yywrap seg_yywrap +#define yyalloc seg_yyalloc +#define yyrealloc seg_yyrealloc +#define yyfree seg_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 seg_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer seg_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define seg_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer seg_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define seg_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer seg_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define seg_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string seg_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define seg_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes seg_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define seg_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer seg_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define seg_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer seg_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define seg_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state seg_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define seg_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer seg_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define seg_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state seg_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define seg_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state seg_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define seg_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack seg_yyensure_buffer_stack +#endif + +#ifdef yylex +#define seg_yylex_ALREADY_DEFINED +#else +#define yylex seg_yylex +#endif + +#ifdef yyrestart +#define seg_yyrestart_ALREADY_DEFINED +#else +#define yyrestart seg_yyrestart +#endif + +#ifdef yylex_init +#define seg_yylex_init_ALREADY_DEFINED +#else +#define yylex_init seg_yylex_init +#endif + +#ifdef yylex_init_extra +#define seg_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra seg_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define seg_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy seg_yylex_destroy +#endif + +#ifdef yyget_debug +#define seg_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug seg_yyget_debug +#endif + +#ifdef yyset_debug +#define seg_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug seg_yyset_debug +#endif + +#ifdef yyget_extra +#define seg_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra seg_yyget_extra +#endif + +#ifdef yyset_extra +#define seg_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra seg_yyset_extra +#endif + +#ifdef yyget_in +#define seg_yyget_in_ALREADY_DEFINED +#else +#define yyget_in seg_yyget_in +#endif + +#ifdef yyset_in +#define seg_yyset_in_ALREADY_DEFINED +#else +#define yyset_in seg_yyset_in +#endif + +#ifdef yyget_out +#define seg_yyget_out_ALREADY_DEFINED +#else +#define yyget_out seg_yyget_out +#endif + +#ifdef yyset_out +#define seg_yyset_out_ALREADY_DEFINED +#else +#define yyset_out seg_yyset_out +#endif + +#ifdef yyget_leng +#define seg_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng seg_yyget_leng +#endif + +#ifdef yyget_text +#define seg_yyget_text_ALREADY_DEFINED +#else +#define yyget_text seg_yyget_text +#endif + +#ifdef yyget_lineno +#define seg_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno seg_yyget_lineno +#endif + +#ifdef yyset_lineno +#define seg_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno seg_yyset_lineno +#endif + +#ifdef yywrap +#define seg_yywrap_ALREADY_DEFINED +#else +#define yywrap seg_yywrap +#endif + +#ifdef yyalloc +#define seg_yyalloc_ALREADY_DEFINED +#else +#define yyalloc seg_yyalloc +#endif + +#ifdef yyrealloc +#define seg_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc seg_yyrealloc +#endif + +#ifdef yyfree +#define seg_yyfree_ALREADY_DEFINED +#else +#define yyfree seg_yyfree +#endif + +#ifdef yytext +#define seg_yytext_ALREADY_DEFINED +#else +#define yytext seg_yytext +#endif + +#ifdef yyleng +#define seg_yyleng_ALREADY_DEFINED +#else +#define yyleng seg_yyleng +#endif + +#ifdef yyin +#define seg_yyin_ALREADY_DEFINED +#else +#define yyin seg_yyin +#endif + +#ifdef yyout +#define seg_yyout_ALREADY_DEFINED +#else +#define yyout seg_yyout +#endif + +#ifdef yy_flex_debug +#define seg_yy_flex_debug_ALREADY_DEFINED +#else +#define yy_flex_debug seg_yy_flex_debug +#endif + +#ifdef yylineno +#define seg_yylineno_ALREADY_DEFINED +#else +#define yylineno seg_yylineno +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . 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 +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 seg_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 9 +#define YY_END_OF_BUFFER 10 +/* 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[30] = + { 0, + 0, 0, 10, 8, 7, 7, 8, 8, 8, 8, + 3, 4, 5, 6, 7, 0, 0, 3, 1, 0, + 0, 0, 0, 1, 3, 0, 3, 2, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 4, 5, + 6, 1, 7, 1, 8, 9, 1, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 1, 1, 11, + 1, 12, 1, 1, 1, 1, 1, 1, 13, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 14, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static const YY_CHAR yy_meta[15] = + { 0, + 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, + 1, 1, 1, 1 + } ; + +static const flex_int16_t yy_base[31] = + { 0, + 0, 0, 40, 41, 13, 15, 32, 31, 27, 27, + 10, 41, 41, 41, 19, 27, 26, 0, 24, 22, + 21, 26, 23, 41, 14, 18, 16, 41, 41, 23 + } ; + +static const flex_int16_t yy_def[31] = + { 0, + 29, 1, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 11, 29, 29, + 30, 29, 29, 29, 29, 29, 29, 29, 0, 29 + } ; + +static const flex_int16_t yy_nxt[56] = + { 0, + 4, 5, 6, 7, 8, 4, 9, 9, 10, 11, + 12, 13, 4, 14, 15, 15, 15, 15, 20, 18, + 15, 15, 21, 25, 26, 27, 21, 27, 28, 28, + 27, 25, 24, 23, 22, 19, 18, 17, 16, 29, + 3, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29 + } ; + +static const flex_int16_t yy_chk[56] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 5, 5, 6, 6, 11, 11, + 15, 15, 11, 25, 30, 27, 25, 26, 23, 22, + 21, 20, 19, 17, 16, 10, 9, 8, 7, 3, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29 + } ; + +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 "segscan.l" + +#line 18 "segscan.l" +/* LCOV_EXCL_START */ + +/* No reason to constrain amount of data slurped */ +#define YY_READ_BUF_SIZE 16777216 + +/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ +#undef fprintf +#define fprintf(file, fmt, msg) fprintf_to_ereport(fmt, msg) + +static void +fprintf_to_ereport(const char *fmt, const char *msg) +{ + ereport(ERROR, (errmsg_internal("%s", msg))); +} + +/* Handles to the buffer that the lexer uses internally */ +static YY_BUFFER_STATE scanbufhandle; +static char *scanbuf; +#line 754 "segscan.c" +#define YY_NO_INPUT 1 +#line 756 "segscan.c" + +#define INITIAL 0 + +#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 +#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 54 "segscan.l" + + +#line 974 "segscan.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 >= 30 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_current_state != 29 ); + 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 56 "segscan.l" +seg_yylval.text = yytext; return RANGE; + YY_BREAK +case 2: +YY_RULE_SETUP +#line 57 "segscan.l" +seg_yylval.text = yytext; return PLUMIN; + YY_BREAK +case 3: +YY_RULE_SETUP +#line 58 "segscan.l" +seg_yylval.text = yytext; return SEGFLOAT; + YY_BREAK +case 4: +YY_RULE_SETUP +#line 59 "segscan.l" +seg_yylval.text = "<"; return EXTENSION; + YY_BREAK +case 5: +YY_RULE_SETUP +#line 60 "segscan.l" +seg_yylval.text = ">"; return EXTENSION; + YY_BREAK +case 6: +YY_RULE_SETUP +#line 61 "segscan.l" +seg_yylval.text = "~"; return EXTENSION; + YY_BREAK +case 7: +/* rule 7 can match eol */ +YY_RULE_SETUP +#line 62 "segscan.l" +/* discard spaces */ + YY_BREAK +case 8: +YY_RULE_SETUP +#line 63 "segscan.l" +return yytext[0]; /* alert parser of the garbage */ + YY_BREAK +case 9: +YY_RULE_SETUP +#line 65 "segscan.l" +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +#line 1073 "segscan.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 >= 30 ) + 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 >= 30 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 29); + + 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 65 "segscan.l" + + +/* LCOV_EXCL_STOP */ + +void +seg_yyerror(SEG *result, struct Node *escontext, const char *message) +{ + /* if we already reported an error, don't overwrite it */ + if (SOFT_ERROR_OCCURRED(escontext)) + return; + + if (*yytext == YY_END_OF_BUFFER_CHAR) + { + errsave(escontext, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("bad seg representation"), + /* translator: %s is typically "syntax error" */ + errdetail("%s at end of input", message))); + } + else + { + errsave(escontext, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("bad seg representation"), + /* translator: first %s is typically "syntax error" */ + errdetail("%s at or near \"%s\"", message, yytext))); + } +} + + +/* + * Called before any actual parsing is done + */ +void +seg_scanner_init(const char *str) +{ + Size slen = strlen(str); + + /* + * Might be left over after ereport() + */ + if (YY_CURRENT_BUFFER) + yy_delete_buffer(YY_CURRENT_BUFFER); + + /* + * Make a scan buffer with special termination needed by flex. + */ + scanbuf = palloc(slen + 2); + memcpy(scanbuf, str, slen); + scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR; + scanbufhandle = yy_scan_buffer(scanbuf, slen + 2); + + BEGIN(INITIAL); +} + + +/* + * Called after parsing is done to clean up after seg_scanner_init() + */ +void +seg_scanner_finish(void) +{ + yy_delete_buffer(scanbufhandle); + pfree(scanbuf); +} + diff --git a/contrib/seg/segscan.l b/contrib/seg/segscan.l new file mode 100644 index 0000000..a1e9e99 --- /dev/null +++ b/contrib/seg/segscan.l @@ -0,0 +1,129 @@ +%top{ +/* + * A scanner for EMP-style numeric ranges + */ +#include "postgres.h" + +#include "nodes/miscnodes.h" + +/* + * NB: include segparse.h only AFTER including segdata.h, because segdata.h + * contains the definition for SEG. + */ +#include "segdata.h" +#include "segparse.h" +} + +%{ +/* LCOV_EXCL_START */ + +/* No reason to constrain amount of data slurped */ +#define YY_READ_BUF_SIZE 16777216 + +/* Avoid exit() on fatal scanner errors (a bit ugly -- see yy_fatal_error) */ +#undef fprintf +#define fprintf(file, fmt, msg) fprintf_to_ereport(fmt, msg) + +static void +fprintf_to_ereport(const char *fmt, const char *msg) +{ + ereport(ERROR, (errmsg_internal("%s", msg))); +} + +/* Handles to the buffer that the lexer uses internally */ +static YY_BUFFER_STATE scanbufhandle; +static char *scanbuf; +%} + +%option 8bit +%option never-interactive +%option nodefault +%option noinput +%option nounput +%option noyywrap +%option warn +%option prefix="seg_yy" + + +range (\.\.)(\.)? +plumin (\'\+\-\')|(\(\+\-)\) +integer [+-]?[0-9]+ +real [+-]?[0-9]+\.[0-9]+ +float ({integer}|{real})([eE]{integer})? + +%% + +{range} seg_yylval.text = yytext; return RANGE; +{plumin} seg_yylval.text = yytext; return PLUMIN; +{float} seg_yylval.text = yytext; return SEGFLOAT; +\< seg_yylval.text = "<"; return EXTENSION; +\> seg_yylval.text = ">"; return EXTENSION; +\~ seg_yylval.text = "~"; return EXTENSION; +[ \t\n\r\f]+ /* discard spaces */ +. return yytext[0]; /* alert parser of the garbage */ + +%% + +/* LCOV_EXCL_STOP */ + +void +seg_yyerror(SEG *result, struct Node *escontext, const char *message) +{ + /* if we already reported an error, don't overwrite it */ + if (SOFT_ERROR_OCCURRED(escontext)) + return; + + if (*yytext == YY_END_OF_BUFFER_CHAR) + { + errsave(escontext, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("bad seg representation"), + /* translator: %s is typically "syntax error" */ + errdetail("%s at end of input", message))); + } + else + { + errsave(escontext, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("bad seg representation"), + /* translator: first %s is typically "syntax error" */ + errdetail("%s at or near \"%s\"", message, yytext))); + } +} + + +/* + * Called before any actual parsing is done + */ +void +seg_scanner_init(const char *str) +{ + Size slen = strlen(str); + + /* + * Might be left over after ereport() + */ + if (YY_CURRENT_BUFFER) + yy_delete_buffer(YY_CURRENT_BUFFER); + + /* + * Make a scan buffer with special termination needed by flex. + */ + scanbuf = palloc(slen + 2); + memcpy(scanbuf, str, slen); + scanbuf[slen] = scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR; + scanbufhandle = yy_scan_buffer(scanbuf, slen + 2); + + BEGIN(INITIAL); +} + + +/* + * Called after parsing is done to clean up after seg_scanner_init() + */ +void +seg_scanner_finish(void) +{ + yy_delete_buffer(scanbufhandle); + pfree(scanbuf); +} diff --git a/contrib/seg/sort-segments.pl b/contrib/seg/sort-segments.pl new file mode 100755 index 0000000..3cc21a3 --- /dev/null +++ b/contrib/seg/sort-segments.pl @@ -0,0 +1,30 @@ +#!/usr/bin/perl + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# this script will sort any table with the segment data type in its last column + +use strict; +use warnings; + +my @rows; + +while (<>) +{ + chomp; + push @rows, $_; +} + +foreach ( + sort { + my @ar = split("\t", $a); + my $valA = pop @ar; + $valA =~ s/[~<> ]+//g; + @ar = split("\t", $b); + my $valB = pop @ar; + $valB =~ s/[~<> ]+//g; + $valA <=> $valB + } @rows) +{ + print "$_\n"; +} diff --git a/contrib/seg/sql/security.sql b/contrib/seg/sql/security.sql new file mode 100644 index 0000000..7dfbbaa --- /dev/null +++ b/contrib/seg/sql/security.sql @@ -0,0 +1,32 @@ +-- +-- Test extension script protection against search path overriding +-- + +CREATE ROLE regress_seg_role; +SELECT current_database() AS datname \gset +GRANT CREATE ON DATABASE :"datname" TO regress_seg_role; +SET ROLE regress_seg_role; +CREATE SCHEMA regress_seg_schema; + +CREATE FUNCTION regress_seg_schema.exfun(i int) RETURNS int AS $$ +BEGIN + CREATE EXTENSION seg VERSION '1.2'; + + CREATE FUNCTION regress_seg_schema.compare(oid, regclass) RETURNS boolean AS + 'BEGIN RAISE EXCEPTION ''overloaded compare() called by %'', current_user; END;' LANGUAGE plpgsql; + + CREATE OPERATOR = (LEFTARG = oid, RIGHTARG = regclass, PROCEDURE = regress_seg_schema.compare); + + ALTER EXTENSION seg UPDATE TO '1.3'; + + RETURN i; +END; $$ LANGUAGE plpgsql; + +CREATE SCHEMA test_schema +CREATE TABLE t(i int) PARTITION BY RANGE (i) +CREATE TABLE p1 PARTITION OF t FOR VALUES FROM (1) TO (regress_seg_schema.exfun(2)); + +DROP SCHEMA test_schema CASCADE; +RESET ROLE; +DROP OWNED BY regress_seg_role; +DROP ROLE regress_seg_role; diff --git a/contrib/seg/sql/seg.sql b/contrib/seg/sql/seg.sql new file mode 100644 index 0000000..c30f1f6 --- /dev/null +++ b/contrib/seg/sql/seg.sql @@ -0,0 +1,257 @@ +-- +-- Test seg datatype +-- + +CREATE EXTENSION seg; + +-- Check whether any of our opclasses fail amvalidate +SELECT amname, opcname +FROM pg_opclass opc LEFT JOIN pg_am am ON am.oid = opcmethod +WHERE opc.oid >= 16384 AND NOT amvalidate(opc.oid); + +-- +-- testing the input and output functions +-- + +-- Any number +SELECT '1'::seg AS seg; +SELECT '-1'::seg AS seg; +SELECT '1.0'::seg AS seg; +SELECT '-1.0'::seg AS seg; +SELECT '1e7'::seg AS seg; +SELECT '-1e7'::seg AS seg; +SELECT '1.0e7'::seg AS seg; +SELECT '-1.0e7'::seg AS seg; +SELECT '1e+7'::seg AS seg; +SELECT '-1e+7'::seg AS seg; +SELECT '1.0e+7'::seg AS seg; +SELECT '-1.0e+7'::seg AS seg; +SELECT '1e-7'::seg AS seg; +SELECT '-1e-7'::seg AS seg; +SELECT '1.0e-7'::seg AS seg; +SELECT '-1.0e-7'::seg AS seg; +SELECT '2e-6'::seg AS seg; +SELECT '2e-5'::seg AS seg; +SELECT '2e-4'::seg AS seg; +SELECT '2e-3'::seg AS seg; +SELECT '2e-2'::seg AS seg; +SELECT '2e-1'::seg AS seg; +SELECT '2e-0'::seg AS seg; +SELECT '2e+0'::seg AS seg; +SELECT '2e+1'::seg AS seg; +SELECT '2e+2'::seg AS seg; +SELECT '2e+3'::seg AS seg; +SELECT '2e+4'::seg AS seg; +SELECT '2e+5'::seg AS seg; +SELECT '2e+6'::seg AS seg; + + +-- Significant digits preserved +SELECT '1'::seg AS seg; +SELECT '1.0'::seg AS seg; +SELECT '1.00'::seg AS seg; +SELECT '1.000'::seg AS seg; +SELECT '1.0000'::seg AS seg; +SELECT '1.00000'::seg AS seg; +SELECT '1.000000'::seg AS seg; +SELECT '0.000000120'::seg AS seg; +SELECT '3.400e5'::seg AS seg; + +-- Digits truncated +SELECT '12.34567890123456'::seg AS seg; + +-- Same, with a very long input +SELECT '12.3456789012345600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'::seg AS seg; + +-- Numbers with certainty indicators +SELECT '~6.5'::seg AS seg; +SELECT '<6.5'::seg AS seg; +SELECT '>6.5'::seg AS seg; +SELECT '~ 6.5'::seg AS seg; +SELECT '< 6.5'::seg AS seg; +SELECT '> 6.5'::seg AS seg; + +-- Open intervals +SELECT '0..'::seg AS seg; +SELECT '0...'::seg AS seg; +SELECT '0 ..'::seg AS seg; +SELECT '0 ...'::seg AS seg; +SELECT '..0'::seg AS seg; +SELECT '...0'::seg AS seg; +SELECT '.. 0'::seg AS seg; +SELECT '... 0'::seg AS seg; + +-- Finite intervals +SELECT '0 .. 1'::seg AS seg; +SELECT '-1 .. 0'::seg AS seg; +SELECT '-1 .. 1'::seg AS seg; + +-- (+/-) intervals +SELECT '0(+-)1'::seg AS seg; +SELECT '0(+-)1.0'::seg AS seg; +SELECT '1.0(+-)0.005'::seg AS seg; +SELECT '101(+-)1'::seg AS seg; +-- incorrect number of significant digits in 99.0: +SELECT '100(+-)1'::seg AS seg; + +-- invalid input +SELECT ''::seg AS seg; +SELECT 'ABC'::seg AS seg; +SELECT '1ABC'::seg AS seg; +SELECT '1.'::seg AS seg; +SELECT '1.....'::seg AS seg; +SELECT '.1'::seg AS seg; +SELECT '1..2.'::seg AS seg; +SELECT '1 e7'::seg AS seg; +SELECT '1e700'::seg AS seg; + +-- +-- testing the operators +-- + +-- equality/inequality: +-- +SELECT '24 .. 33.20'::seg = '24 .. 33.20'::seg AS bool; +SELECT '24 .. 33.20'::seg = '24 .. 33.21'::seg AS bool; +SELECT '24 .. 33.20'::seg != '24 .. 33.20'::seg AS bool; +SELECT '24 .. 33.20'::seg != '24 .. 33.21'::seg AS bool; + +-- overlap +-- +SELECT '1'::seg && '1'::seg AS bool; +SELECT '1'::seg && '2'::seg AS bool; +SELECT '0 ..'::seg && '0 ..'::seg AS bool; +SELECT '0 .. 1'::seg && '0 .. 1'::seg AS bool; +SELECT '..0'::seg && '0..'::seg AS bool; +SELECT '-1 .. 0.1'::seg && '0 .. 1'::seg AS bool; +SELECT '-1 .. 0'::seg && '0 .. 1'::seg AS bool; +SELECT '-1 .. -0.0001'::seg && '0 .. 1'::seg AS bool; +SELECT '0 ..'::seg && '1'::seg AS bool; +SELECT '0 .. 1'::seg && '1'::seg AS bool; +SELECT '0 .. 1'::seg && '2'::seg AS bool; +SELECT '0 .. 2'::seg && '1'::seg AS bool; +SELECT '1'::seg && '0 .. 1'::seg AS bool; +SELECT '2'::seg && '0 .. 1'::seg AS bool; +SELECT '1'::seg && '0 .. 2'::seg AS bool; + +-- overlap on the left +-- +SELECT '1'::seg &< '0'::seg AS bool; +SELECT '1'::seg &< '1'::seg AS bool; +SELECT '1'::seg &< '2'::seg AS bool; +SELECT '0 .. 1'::seg &< '0'::seg AS bool; +SELECT '0 .. 1'::seg &< '1'::seg AS bool; +SELECT '0 .. 1'::seg &< '2'::seg AS bool; +SELECT '0 .. 1'::seg &< '0 .. 0.5'::seg AS bool; +SELECT '0 .. 1'::seg &< '0 .. 1'::seg AS bool; +SELECT '0 .. 1'::seg &< '0 .. 2'::seg AS bool; +SELECT '0 .. 1'::seg &< '1 .. 2'::seg AS bool; +SELECT '0 .. 1'::seg &< '2 .. 3'::seg AS bool; + +-- overlap on the right +-- +SELECT '0'::seg &> '1'::seg AS bool; +SELECT '1'::seg &> '1'::seg AS bool; +SELECT '2'::seg &> '1'::seg AS bool; +SELECT '0'::seg &> '0 .. 1'::seg AS bool; +SELECT '1'::seg &> '0 .. 1'::seg AS bool; +SELECT '2'::seg &> '0 .. 1'::seg AS bool; +SELECT '0 .. 0.5'::seg &> '0 .. 1'::seg AS bool; +SELECT '0 .. 1'::seg &> '0 .. 1'::seg AS bool; +SELECT '0 .. 2'::seg &> '0 .. 2'::seg AS bool; +SELECT '1 .. 2'::seg &> '0 .. 1'::seg AS bool; +SELECT '2 .. 3'::seg &> '0 .. 1'::seg AS bool; + +-- left +-- +SELECT '1'::seg << '0'::seg AS bool; +SELECT '1'::seg << '1'::seg AS bool; +SELECT '1'::seg << '2'::seg AS bool; +SELECT '0 .. 1'::seg << '0'::seg AS bool; +SELECT '0 .. 1'::seg << '1'::seg AS bool; +SELECT '0 .. 1'::seg << '2'::seg AS bool; +SELECT '0 .. 1'::seg << '0 .. 0.5'::seg AS bool; +SELECT '0 .. 1'::seg << '0 .. 1'::seg AS bool; +SELECT '0 .. 1'::seg << '0 .. 2'::seg AS bool; +SELECT '0 .. 1'::seg << '1 .. 2'::seg AS bool; +SELECT '0 .. 1'::seg << '2 .. 3'::seg AS bool; + +-- right +-- +SELECT '0'::seg >> '1'::seg AS bool; +SELECT '1'::seg >> '1'::seg AS bool; +SELECT '2'::seg >> '1'::seg AS bool; +SELECT '0'::seg >> '0 .. 1'::seg AS bool; +SELECT '1'::seg >> '0 .. 1'::seg AS bool; +SELECT '2'::seg >> '0 .. 1'::seg AS bool; +SELECT '0 .. 0.5'::seg >> '0 .. 1'::seg AS bool; +SELECT '0 .. 1'::seg >> '0 .. 1'::seg AS bool; +SELECT '0 .. 2'::seg >> '0 .. 2'::seg AS bool; +SELECT '1 .. 2'::seg >> '0 .. 1'::seg AS bool; +SELECT '2 .. 3'::seg >> '0 .. 1'::seg AS bool; + + +-- "contained in" (the left value belongs within the interval specified in the right value): +-- +SELECT '0'::seg <@ '0'::seg AS bool; +SELECT '0'::seg <@ '0 ..'::seg AS bool; +SELECT '0'::seg <@ '.. 0'::seg AS bool; +SELECT '0'::seg <@ '-1 .. 1'::seg AS bool; +SELECT '0'::seg <@ '-1 .. 1'::seg AS bool; +SELECT '-1'::seg <@ '-1 .. 1'::seg AS bool; +SELECT '1'::seg <@ '-1 .. 1'::seg AS bool; +SELECT '-1 .. 1'::seg <@ '-1 .. 1'::seg AS bool; + +-- "contains" (the left value contains the interval specified in the right value): +-- +SELECT '0'::seg @> '0'::seg AS bool; +SELECT '0 .. '::seg <@ '0'::seg AS bool; +SELECT '.. 0'::seg <@ '0'::seg AS bool; +SELECT '-1 .. 1'::seg <@ '0'::seg AS bool; +SELECT '0'::seg <@ '-1 .. 1'::seg AS bool; +SELECT '-1'::seg <@ '-1 .. 1'::seg AS bool; +SELECT '1'::seg <@ '-1 .. 1'::seg AS bool; + +-- Load some example data and build the index +-- +CREATE TABLE test_seg (s seg); + +\copy test_seg from 'data/test_seg.data' + +CREATE INDEX test_seg_ix ON test_seg USING gist (s); + +SET enable_indexscan = false; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM test_seg WHERE s @> '11..11.3'; +SELECT count(*) FROM test_seg WHERE s @> '11..11.3'; +RESET enable_indexscan; + +SET enable_bitmapscan = false; +EXPLAIN (COSTS OFF) +SELECT count(*) FROM test_seg WHERE s @> '11..11.3'; +SELECT count(*) FROM test_seg WHERE s @> '11..11.3'; +RESET enable_bitmapscan; + +-- Test sorting +SELECT * FROM test_seg WHERE s @> '11..11.3' GROUP BY s; + +-- Test functions +SELECT seg_lower(s), seg_center(s), seg_upper(s) +FROM test_seg WHERE s @> '11.2..11.3' OR s IS NULL ORDER BY s; + + +-- test non error throwing API + +SELECT str as seg, + pg_input_is_valid(str,'seg') as ok, + errinfo.sql_error_code, + errinfo.message, + errinfo.detail, + errinfo.hint +FROM unnest(ARRAY['-1 .. 1'::text, + '100(+-)1', + '', + 'ABC', + '1 e7', + '1e700']) str, + LATERAL pg_input_error_info(str, 'seg') as errinfo; diff --git a/contrib/sepgsql/.gitignore b/contrib/sepgsql/.gitignore new file mode 100644 index 0000000..31613e0 --- /dev/null +++ b/contrib/sepgsql/.gitignore @@ -0,0 +1,7 @@ +/sepgsql.sql +/sepgsql-regtest.fc +/sepgsql-regtest.if +/sepgsql-regtest.pp +/tmp +# Generated subdirectories +/results/ diff --git a/contrib/sepgsql/Makefile b/contrib/sepgsql/Makefile new file mode 100644 index 0000000..afca75b --- /dev/null +++ b/contrib/sepgsql/Makefile @@ -0,0 +1,33 @@ +# contrib/sepgsql/Makefile + +MODULE_big = sepgsql +OBJS = \ + $(WIN32RES) \ + database.o \ + dml.o \ + hooks.o \ + label.o \ + proc.o \ + relation.o \ + schema.o \ + selinux.o \ + uavc.o +DATA_built = sepgsql.sql +PGFILEDESC = "sepgsql - SELinux integration" + +# Note: because we don't tell the Makefile there are any regression tests, +# we have to clean those result files explicitly +EXTRA_CLEAN = -r $(pg_regress_clean_files) tmp/ *.pp sepgsql-regtest.if sepgsql-regtest.fc + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/sepgsql +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +SHLIB_LINK += -lselinux diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c new file mode 100644 index 0000000..4f5662e --- /dev/null +++ b/contrib/sepgsql/database.c @@ -0,0 +1,216 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/database.c + * + * Routines corresponding to database objects + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/dependency.h" +#include "catalog/pg_database.h" +#include "commands/dbcommands.h" +#include "commands/seclabel.h" +#include "sepgsql.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/snapmgr.h" + +/* + * sepgsql_database_post_create + * + * This routine assigns a default security label on a newly defined + * database, and check permission needed for its creation. + */ +void +sepgsql_database_post_create(Oid databaseId, const char *dtemplate) +{ + Relation rel; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple tuple; + char *tcontext; + char *ncontext; + ObjectAddress object; + Form_pg_database datForm; + StringInfoData audit_name; + + /* + * Oid of the source database is not saved in pg_database catalog, so we + * collect its identifier using contextual information. If NULL, its + * default is "template1" according to createdb(). + */ + if (!dtemplate) + dtemplate = "template1"; + + object.classId = DatabaseRelationId; + object.objectId = get_database_oid(dtemplate, false); + object.objectSubId = 0; + + tcontext = sepgsql_get_label(object.classId, + object.objectId, + object.objectSubId); + + /* + * check db_database:{getattr} permission + */ + initStringInfo(&audit_name); + appendStringInfoString(&audit_name, quote_identifier(dtemplate)); + sepgsql_avc_check_perms_label(tcontext, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__GETATTR, + audit_name.data, + true); + + /* + * Compute a default security label of the newly created database based on + * a pair of security label of client and source database. + * + * XXX - upcoming version of libselinux supports to take object name to + * handle special treatment on default security label. + */ + rel = table_open(DatabaseRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_database_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(databaseId)); + + sscan = systable_beginscan(rel, DatabaseOidIndexId, true, + SnapshotSelf, 1, &skey); + tuple = systable_getnext(sscan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for database %u", databaseId); + + datForm = (Form_pg_database) GETSTRUCT(tuple); + + ncontext = sepgsql_compute_create(sepgsql_get_client_label(), + tcontext, + SEPG_CLASS_DB_DATABASE, + NameStr(datForm->datname)); + + /* + * check db_database:{create} permission + */ + resetStringInfo(&audit_name); + appendStringInfoString(&audit_name, + quote_identifier(NameStr(datForm->datname))); + sepgsql_avc_check_perms_label(ncontext, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__CREATE, + audit_name.data, + true); + + systable_endscan(sscan); + table_close(rel, AccessShareLock); + + /* + * Assign the default security label on the new database + */ + object.classId = DatabaseRelationId; + object.objectId = databaseId; + object.objectSubId = 0; + + SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); + + pfree(ncontext); + pfree(tcontext); +} + +/* + * sepgsql_database_drop + * + * It checks privileges to drop the supplied database + */ +void +sepgsql_database_drop(Oid databaseId) +{ + ObjectAddress object; + char *audit_name; + + /* + * check db_database:{drop} permission + */ + object.classId = DatabaseRelationId; + object.objectId = databaseId; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__DROP, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_database_post_alter + * + * It checks privileges to alter the supplied database + */ +void +sepgsql_database_setattr(Oid databaseId) +{ + ObjectAddress object; + char *audit_name; + + /* + * check db_database:{setattr} permission + */ + object.classId = DatabaseRelationId; + object.objectId = databaseId; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__SETATTR, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_database_relabel + * + * It checks privileges to relabel the supplied database with the `seclabel' + */ +void +sepgsql_database_relabel(Oid databaseId, const char *seclabel) +{ + ObjectAddress object; + char *audit_name; + + object.classId = DatabaseRelationId; + object.objectId = databaseId; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + /* + * check db_database:{setattr relabelfrom} permission + */ + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__SETATTR | + SEPG_DB_DATABASE__RELABELFROM, + audit_name, + true); + + /* + * check db_database:{relabelto} permission + */ + sepgsql_avc_check_perms_label(seclabel, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__RELABELTO, + audit_name, + true); + pfree(audit_name); +} diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c new file mode 100644 index 0000000..8c8f6f1 --- /dev/null +++ b/contrib/sepgsql/dml.c @@ -0,0 +1,359 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/dml.c + * + * Routines to handle DML permission checks + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/tupdesc.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/heap.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "commands/seclabel.h" +#include "commands/tablecmds.h" +#include "executor/executor.h" +#include "nodes/bitmapset.h" +#include "parser/parsetree.h" +#include "sepgsql.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +/* + * fixup_whole_row_references + * + * When user references a whole-row Var, it is equivalent to referencing + * all the user columns (not system columns). So, we need to fix up the + * given bitmapset, if it contains a whole-row reference. + */ +static Bitmapset * +fixup_whole_row_references(Oid relOid, Bitmapset *columns) +{ + Bitmapset *result; + HeapTuple tuple; + AttrNumber natts; + AttrNumber attno; + int index; + + /* if no whole-row references, nothing to do */ + index = InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber; + if (!bms_is_member(index, columns)) + return columns; + + /* obtain number of attributes */ + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relOid); + natts = ((Form_pg_class) GETSTRUCT(tuple))->relnatts; + ReleaseSysCache(tuple); + + /* remove bit 0 from column set, add in all the non-dropped columns */ + result = bms_copy(columns); + result = bms_del_member(result, index); + + for (attno = 1; attno <= natts; attno++) + { + tuple = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relOid), + Int16GetDatum(attno)); + if (!HeapTupleIsValid(tuple)) + continue; /* unexpected case, should we error? */ + + if (!((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped) + { + index = attno - FirstLowInvalidHeapAttributeNumber; + result = bms_add_member(result, index); + } + + ReleaseSysCache(tuple); + } + return result; +} + +/* + * fixup_inherited_columns + * + * When user is querying on a table with children, it implicitly accesses + * child tables also. So, we also need to check security label of child + * tables and columns, but there is no guarantee attribute numbers are + * same between the parent and children. + * It returns a bitmapset which contains attribute number of the child + * table based on the given bitmapset of the parent. + */ +static Bitmapset * +fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns) +{ + Bitmapset *result = NULL; + int index; + + /* + * obviously, no need to do anything here + */ + if (parentId == childId) + return columns; + + index = -1; + while ((index = bms_next_member(columns, index)) >= 0) + { + /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */ + AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber; + char *attname; + + /* + * whole-row-reference shall be fixed-up later + */ + if (attno == InvalidAttrNumber) + { + result = bms_add_member(result, index); + continue; + } + + attname = get_attname(parentId, attno, false); + attno = get_attnum(childId, attname); + if (attno == InvalidAttrNumber) + elog(ERROR, "cache lookup failed for attribute %s of relation %u", + attname, childId); + + result = bms_add_member(result, + attno - FirstLowInvalidHeapAttributeNumber); + + pfree(attname); + } + + return result; +} + +/* + * check_relation_privileges + * + * It actually checks required permissions on a certain relation + * and its columns. + */ +static bool +check_relation_privileges(Oid relOid, + Bitmapset *selected, + Bitmapset *inserted, + Bitmapset *updated, + uint32 required, + bool abort_on_violation) +{ + ObjectAddress object; + char *audit_name; + Bitmapset *columns; + int index; + char relkind = get_rel_relkind(relOid); + bool result = true; + + /* + * Hardwired Policies: SE-PostgreSQL enforces - clients cannot modify + * system catalogs using DMLs - clients cannot reference/modify toast + * relations using DMLs + */ + if (sepgsql_getenforce() > 0) + { + if ((required & (SEPG_DB_TABLE__UPDATE | + SEPG_DB_TABLE__INSERT | + SEPG_DB_TABLE__DELETE)) != 0 && + IsCatalogRelationOid(relOid)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: hardwired security policy violation"))); + + if (relkind == RELKIND_TOASTVALUE) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: hardwired security policy violation"))); + } + + /* + * Check permissions on the relation + */ + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + switch (relkind) + { + case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: + result = sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_TABLE, + required, + audit_name, + abort_on_violation); + break; + + case RELKIND_SEQUENCE: + Assert((required & ~SEPG_DB_TABLE__SELECT) == 0); + + if (required & SEPG_DB_TABLE__SELECT) + result = sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SEQUENCE, + SEPG_DB_SEQUENCE__GET_VALUE, + audit_name, + abort_on_violation); + break; + + case RELKIND_VIEW: + result = sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_VIEW, + SEPG_DB_VIEW__EXPAND, + audit_name, + abort_on_violation); + break; + + default: + /* nothing to be checked */ + break; + } + pfree(audit_name); + + /* + * Only columns owned by relations shall be checked + */ + if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) + return true; + + /* + * Check permissions on the columns + */ + selected = fixup_whole_row_references(relOid, selected); + inserted = fixup_whole_row_references(relOid, inserted); + updated = fixup_whole_row_references(relOid, updated); + columns = bms_union(selected, bms_union(inserted, updated)); + + index = -1; + while ((index = bms_next_member(columns, index)) >= 0) + { + AttrNumber attnum; + uint32 column_perms = 0; + + if (bms_is_member(index, selected)) + column_perms |= SEPG_DB_COLUMN__SELECT; + if (bms_is_member(index, inserted)) + { + if (required & SEPG_DB_TABLE__INSERT) + column_perms |= SEPG_DB_COLUMN__INSERT; + } + if (bms_is_member(index, updated)) + { + if (required & SEPG_DB_TABLE__UPDATE) + column_perms |= SEPG_DB_COLUMN__UPDATE; + } + if (column_perms == 0) + continue; + + /* obtain column's permission */ + attnum = index + FirstLowInvalidHeapAttributeNumber; + + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = attnum; + audit_name = getObjectDescription(&object, false); + + result = sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_COLUMN, + column_perms, + audit_name, + abort_on_violation); + pfree(audit_name); + + if (!result) + return result; + } + return true; +} + +/* + * sepgsql_dml_privileges + * + * Entrypoint of the DML permission checks + */ +bool +sepgsql_dml_privileges(List *rangeTbls, List *rteperminfos, + bool abort_on_violation) +{ + ListCell *lr; + + foreach(lr, rteperminfos) + { + RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, lr); + uint32 required = 0; + List *tableIds; + ListCell *li; + + /* + * Find out required permissions + */ + if (perminfo->requiredPerms & ACL_SELECT) + required |= SEPG_DB_TABLE__SELECT; + if (perminfo->requiredPerms & ACL_INSERT) + required |= SEPG_DB_TABLE__INSERT; + if (perminfo->requiredPerms & ACL_UPDATE) + { + if (!bms_is_empty(perminfo->updatedCols)) + required |= SEPG_DB_TABLE__UPDATE; + else + required |= SEPG_DB_TABLE__LOCK; + } + if (perminfo->requiredPerms & ACL_DELETE) + required |= SEPG_DB_TABLE__DELETE; + + /* + * Skip, if nothing to be checked + */ + if (required == 0) + continue; + + /* + * If this RangeTblEntry is also supposed to reference inherited + * tables, we need to check security label of the child tables. So, we + * expand rte->relid into list of OIDs of inheritance hierarchy, then + * checker routine will be invoked for each relations. + */ + if (!perminfo->inh) + tableIds = list_make1_oid(perminfo->relid); + else + tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL); + + foreach(li, tableIds) + { + Oid tableOid = lfirst_oid(li); + Bitmapset *selectedCols; + Bitmapset *insertedCols; + Bitmapset *updatedCols; + + /* + * child table has different attribute numbers, so we need to fix + * up them. + */ + selectedCols = fixup_inherited_columns(perminfo->relid, tableOid, + perminfo->selectedCols); + insertedCols = fixup_inherited_columns(perminfo->relid, tableOid, + perminfo->insertedCols); + updatedCols = fixup_inherited_columns(perminfo->relid, tableOid, + perminfo->updatedCols); + + /* + * check permissions on individual tables + */ + if (!check_relation_privileges(tableOid, + selectedCols, + insertedCols, + updatedCols, + required, abort_on_violation)) + return false; + } + list_free(tableIds); + } + return true; +} diff --git a/contrib/sepgsql/expected/alter.out b/contrib/sepgsql/expected/alter.out new file mode 100644 index 0000000..c604cc7 --- /dev/null +++ b/contrib/sepgsql/expected/alter.out @@ -0,0 +1,316 @@ +-- +-- Test for various ALTER statements +-- +-- clean-up in case a prior regression run failed +SET client_min_messages TO 'warning'; +DROP DATABASE IF EXISTS sepgsql_test_regression_1; +DROP DATABASE IF EXISTS sepgsql_test_regression; +DROP USER IF EXISTS regress_sepgsql_test_user; +RESET client_min_messages; +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +---------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 +(1 row) + +-- +-- CREATE Objects to be altered (with debug_audit being silent) +-- +CREATE DATABASE sepgsql_test_regression_1; +CREATE USER regress_sepgsql_test_user; +CREATE SCHEMA regtest_schema_1; +CREATE SCHEMA regtest_schema_2; +GRANT ALL ON SCHEMA regtest_schema_1 TO public; +GRANT ALL ON SCHEMA regtest_schema_2 TO public; +SET search_path = regtest_schema_1, regtest_schema_2, public; +CREATE TABLE regtest_table_1 (a int, b text); +CREATE TABLE regtest_table_2 (c text) inherits (regtest_table_1); +CREATE TABLE regtest_table_3 (x int primary key, y text); +--- +-- partitioned table parent +CREATE TABLE regtest_ptable_1 (o int, p text) PARTITION BY RANGE (o); +-- partitioned table children +CREATE TABLE regtest_ptable_1_ones PARTITION OF regtest_ptable_1 FOR VALUES FROM ('0') TO ('10'); +CREATE TABLE regtest_ptable_1_tens PARTITION OF regtest_ptable_1 FOR VALUES FROM ('10') TO ('100'); +--- +CREATE SEQUENCE regtest_seq_1; +CREATE VIEW regtest_view_1 AS SELECT * FROM regtest_table_1 WHERE a > 0; +CREATE FUNCTION regtest_func_1 (text) RETURNS bool + AS 'BEGIN RETURN true; END' LANGUAGE 'plpgsql'; +-- switch on debug_audit +SET sepgsql.debug_audit = true; +SET client_min_messages = LOG; +-- +-- ALTER xxx OWNER TO +-- +-- XXX: It should take db_xxx:{setattr} permission checks even if +-- owner is not actually changed. +-- +ALTER DATABASE sepgsql_test_regression_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="sepgsql_test_regression_1" permissive=0 +ALTER DATABASE sepgsql_test_regression_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="sepgsql_test_regression_1" permissive=0 +ALTER SCHEMA regtest_schema_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +ALTER SCHEMA regtest_schema_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +ALTER TABLE regtest_table_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="public" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_1.regtest_table_1" permissive=0 +ALTER TABLE regtest_table_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_1.regtest_table_1" permissive=0 +ALTER TABLE regtest_ptable_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_1.regtest_ptable_1" permissive=0 +ALTER TABLE regtest_ptable_1_ones OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_1.regtest_ptable_1_ones" permissive=0 +ALTER SEQUENCE regtest_seq_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema_1.regtest_seq_1" permissive=0 +ALTER SEQUENCE regtest_seq_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema_1.regtest_seq_1" permissive=0 +ALTER VIEW regtest_view_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema_1.regtest_view_1" permissive=0 +ALTER VIEW regtest_view_1 OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema_1.regtest_view_1" permissive=0 +ALTER FUNCTION regtest_func_1(text) OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="regtest_schema_1.regtest_func_1(pg_catalog.text)" permissive=0 +ALTER FUNCTION regtest_func_1(text) OWNER TO regress_sepgsql_test_user; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="regtest_schema_1.regtest_func_1(pg_catalog.text)" permissive=0 +-- +-- ALTER xxx SET SCHEMA +-- +ALTER TABLE regtest_table_1 SET SCHEMA regtest_schema_2; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_1.regtest_table_1" permissive=0 +ALTER TABLE regtest_ptable_1 SET SCHEMA regtest_schema_2; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_1.regtest_ptable_1" permissive=0 +ALTER TABLE regtest_ptable_1_ones SET SCHEMA regtest_schema_2; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_1.regtest_ptable_1_ones" permissive=0 +ALTER SEQUENCE regtest_seq_1 SET SCHEMA regtest_schema_2; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema_1.regtest_seq_1" permissive=0 +ALTER VIEW regtest_view_1 SET SCHEMA regtest_schema_2; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema_1.regtest_view_1" permissive=0 +ALTER FUNCTION regtest_func_1(text) SET SCHEMA regtest_schema_2; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="regtest_schema_1.regtest_func_1(pg_catalog.text)" permissive=0 +-- +-- ALTER xxx RENAME TO +-- +ALTER DATABASE sepgsql_test_regression_1 RENAME TO sepgsql_test_regression; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="sepgsql_test_regression_1" permissive=0 +ALTER SCHEMA regtest_schema_1 RENAME TO regtest_schema; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_1" permissive=0 +ALTER TABLE regtest_table_1 RENAME TO regtest_table; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="public" permissive=0 +LOG: SELinux: allowed { add_name remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_table_1" permissive=0 +--- +-- partitioned table parent +ALTER TABLE regtest_ptable_1 RENAME TO regtest_ptable; +LOG: SELinux: allowed { add_name remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_ptable_1" permissive=0 +-- partitioned table child +ALTER TABLE regtest_ptable_1_ones RENAME TO regtest_table_part; +LOG: SELinux: allowed { add_name remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_ptable_1_ones" permissive=0 +--- +ALTER SEQUENCE regtest_seq_1 RENAME TO regtest_seq; +LOG: SELinux: allowed { add_name remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema_2.regtest_seq_1" permissive=0 +ALTER VIEW regtest_view_1 RENAME TO regtest_view; +LOG: SELinux: allowed { add_name remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema_2.regtest_view_1" permissive=0 +ALTER FUNCTION regtest_func_1(text) RENAME TO regtest_func; +LOG: SELinux: allowed { add_name remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="regtest_schema_2.regtest_func_1(pg_catalog.text)" permissive=0 +SET search_path = regtest_schema, regtest_schema_2, public; +-- +-- misc ALTER commands +-- +ALTER DATABASE sepgsql_test_regression CONNECTION LIMIT 999; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="sepgsql_test_regression" permissive=0 +ALTER DATABASE sepgsql_test_regression SET search_path TO regtest_schema, public; -- not supported yet +ALTER TABLE regtest_table ADD COLUMN d float; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="public" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: ALTER TABLE regtest_table ADD COLUMN d float; + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table.d" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.d" permissive=0 +ALTER TABLE regtest_table DROP COLUMN d; +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.d" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table.d" permissive=0 +ALTER TABLE regtest_table ALTER b SET DEFAULT 'abcd'; -- not supported yet +ALTER TABLE regtest_table ALTER b SET DEFAULT 'XYZ'; -- not supported yet +ALTER TABLE regtest_table ALTER b DROP DEFAULT; -- not supported yet +ALTER TABLE regtest_table ALTER b SET NOT NULL; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table.b" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.b" permissive=0 +ALTER TABLE regtest_table ALTER b DROP NOT NULL; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table.b" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.b" permissive=0 +ALTER TABLE regtest_table ALTER b SET STATISTICS -1; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table.b" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.b" permissive=0 +ALTER TABLE regtest_table ALTER b SET (n_distinct = 999); +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table.b" permissive=0 +ALTER TABLE regtest_table ALTER b SET STORAGE PLAIN; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table.b" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.b" permissive=0 +ALTER TABLE regtest_table ADD CONSTRAINT test_fk FOREIGN KEY (a) REFERENCES regtest_table_3(x); -- not supported +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_3" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column x of table regtest_table_3" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_table" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column a of table regtest_table" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema_2" permissive=0 +LINE 1: SELECT fk."a" FROM ONLY "regtest_schema_2"."regtest_table" f... + ^ +QUERY: SELECT fk."a" FROM ONLY "regtest_schema_2"."regtest_table" fk LEFT OUTER JOIN ONLY "regtest_schema"."regtest_table_3" pk ON ( pk."x" OPERATOR(pg_catalog.=) fk."a") WHERE pk."x" IS NULL AND (fk."a" IS NOT NULL) +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LINE 1: ...schema_2"."regtest_table" fk LEFT OUTER JOIN ONLY "regtest_s... + ^ +QUERY: SELECT fk."a" FROM ONLY "regtest_schema_2"."regtest_table" fk LEFT OUTER JOIN ONLY "regtest_schema"."regtest_table_3" pk ON ( pk."x" OPERATOR(pg_catalog.=) fk."a") WHERE pk."x" IS NULL AND (fk."a" IS NOT NULL) +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: ..."regtest_schema"."regtest_table_3" pk ON ( pk."x" OPERATOR(p... + ^ +QUERY: SELECT fk."a" FROM ONLY "regtest_schema_2"."regtest_table" fk LEFT OUTER JOIN ONLY "regtest_schema"."regtest_table_3" pk ON ( pk."x" OPERATOR(pg_catalog.=) fk."a") WHERE pk."x" IS NULL AND (fk."a" IS NOT NULL) +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_table" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column a of table regtest_table" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_3" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column x of table regtest_table_3" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4eq(integer,integer)" permissive=0 +ALTER TABLE regtest_table ADD CONSTRAINT test_ck CHECK (b like '%abc%') NOT VALID; -- not supported +ALTER TABLE regtest_table VALIDATE CONSTRAINT test_ck; -- not supported +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 +ALTER TABLE regtest_table DROP CONSTRAINT test_ck; -- not supported +CREATE TRIGGER regtest_test_trig BEFORE UPDATE ON regtest_table + FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); +ALTER TABLE regtest_table DISABLE TRIGGER regtest_test_trig; -- not supported +ALTER TABLE regtest_table ENABLE TRIGGER regtest_test_trig; -- not supported +CREATE RULE regtest_test_rule AS ON INSERT TO regtest_table_3 DO ALSO NOTHING; +ALTER TABLE regtest_table_3 DISABLE RULE regtest_test_rule; -- not supported +ALTER TABLE regtest_table_3 ENABLE RULE regtest_test_rule; -- not supported +ALTER TABLE regtest_table SET (fillfactor = 75); +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_table" permissive=0 +ALTER TABLE regtest_table RESET (fillfactor); +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_table" permissive=0 +ALTER TABLE regtest_table_2 NO INHERIT regtest_table; -- not supported +ALTER TABLE regtest_table_2 INHERIT regtest_table; -- not supported +ALTER TABLE regtest_table SET TABLESPACE pg_default; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_table" permissive=0 +--- +-- partitioned table parent +ALTER TABLE regtest_ptable ADD COLUMN d float; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: ALTER TABLE regtest_ptable ADD COLUMN d float; + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_ptable.d" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.d" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_1_tens.d" permissive=0 +ALTER TABLE regtest_ptable DROP COLUMN d; +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.d" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_1_tens.d" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_ptable.d" permissive=0 +ALTER TABLE regtest_ptable ALTER p SET DEFAULT 'abcd'; -- not supported by sepgsql +ALTER TABLE regtest_ptable ALTER p SET DEFAULT 'XYZ'; -- not supported by sepgsql +ALTER TABLE regtest_ptable ALTER p DROP DEFAULT; -- not supported by sepgsql +ALTER TABLE regtest_ptable ALTER p SET NOT NULL; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_ptable.p" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_1_tens.p" permissive=0 +ALTER TABLE regtest_ptable ALTER p DROP NOT NULL; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_ptable.p" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_1_tens.p" permissive=0 +ALTER TABLE regtest_ptable ALTER p SET STATISTICS -1; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_ptable.p" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_1_tens.p" permissive=0 +ALTER TABLE regtest_ptable ALTER p SET (n_distinct = 999); +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_ptable.p" permissive=0 +ALTER TABLE regtest_ptable ALTER p SET STORAGE PLAIN; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_ptable.p" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_1_tens.p" permissive=0 +ALTER TABLE regtest_ptable ADD CONSTRAINT test_ck CHECK (p like '%abc%') NOT VALID; -- not supported by sepgsql +ALTER TABLE regtest_ptable DROP CONSTRAINT test_ck; -- not supported by sepgsql +ALTER TABLE regtest_ptable SET TABLESPACE pg_default; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_ptable" permissive=0 +-- partitioned table child +ALTER TABLE regtest_table_part ALTER p SET DEFAULT 'abcd'; -- not supported by sepgsql +ALTER TABLE regtest_table_part ALTER p SET DEFAULT 'XYZ'; -- not supported by sepgsql +ALTER TABLE regtest_table_part ALTER p DROP DEFAULT; -- not supported by sepgsql +ALTER TABLE regtest_table_part ALTER p SET NOT NULL; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0 +ALTER TABLE regtest_table_part ALTER p DROP NOT NULL; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0 +ALTER TABLE regtest_table_part ALTER p SET STATISTICS -1; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0 +ALTER TABLE regtest_table_part ALTER p SET (n_distinct = 999); +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0 +ALTER TABLE regtest_table_part ALTER p SET STORAGE PLAIN; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema_2.regtest_table_part.p" permissive=0 +ALTER TABLE regtest_table_part ADD CONSTRAINT test_ck CHECK (p like '%abc%') NOT VALID; -- not supported by sepgsql +ALTER TABLE regtest_table_part VALIDATE CONSTRAINT test_ck; -- not supported by sepgsql +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 +ALTER TABLE regtest_table_part DROP CONSTRAINT test_ck; -- not supported by sepgsql +CREATE TRIGGER regtest_part_test_trig BEFORE UPDATE ON regtest_table_part + FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); +ALTER TABLE regtest_table_part DISABLE TRIGGER regtest_part_test_trig; -- not supported by sepgsql +ALTER TABLE regtest_table_part ENABLE TRIGGER regtest_part_test_trig; -- not supported by sepgsql +ALTER TABLE regtest_table_part SET (fillfactor = 75); +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_table_part" permissive=0 +ALTER TABLE regtest_table_part RESET (fillfactor); +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_table_part" permissive=0 +ALTER TABLE regtest_table_part SET TABLESPACE pg_default; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema_2.regtest_table_part" permissive=0 +--- +ALTER VIEW regtest_view SET (security_barrier); +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema_2.regtest_view" permissive=0 +ALTER SEQUENCE regtest_seq INCREMENT BY 10 START WITH 1000; +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema_2.regtest_seq" permissive=0 +-- +-- clean-up objects +-- +RESET sepgsql.debug_audit; +RESET client_min_messages; +DROP DATABASE sepgsql_test_regression; +DROP SCHEMA regtest_schema CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table regtest_table_2 +drop cascades to table regtest_table_3 +drop cascades to constraint test_fk on table regtest_table +drop cascades to table regtest_ptable_1_tens +DROP SCHEMA regtest_schema_2 CASCADE; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table regtest_table +drop cascades to table regtest_ptable +drop cascades to sequence regtest_seq +drop cascades to view regtest_view +drop cascades to function regtest_func(text) +DROP USER regress_sepgsql_test_user; diff --git a/contrib/sepgsql/expected/ddl.out b/contrib/sepgsql/expected/ddl.out new file mode 100644 index 0000000..15d2b9c --- /dev/null +++ b/contrib/sepgsql/expected/ddl.out @@ -0,0 +1,538 @@ +-- +-- Regression Test for DDL of Object Permission Checks +-- +-- clean-up in case a prior regression run failed +SET client_min_messages TO 'warning'; +DROP DATABASE IF EXISTS sepgsql_test_regression; +DROP USER IF EXISTS regress_sepgsql_test_user; +RESET client_min_messages; +-- confirm required permissions using audit messages +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +---------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 +(1 row) + +SET sepgsql.debug_audit = true; +SET client_min_messages = LOG; +-- +-- CREATE Permission checks +-- +CREATE DATABASE sepgsql_test_regression; +LOG: SELinux: allowed { getattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_db_t:s0 tclass=db_database name="template1" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="sepgsql_test_regression" permissive=0 +CREATE USER regress_sepgsql_test_user; +CREATE SCHEMA regtest_schema; +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +GRANT ALL ON SCHEMA regtest_schema TO regress_sepgsql_test_user; +SET search_path = regtest_schema, public; +CREATE TABLE regtest_table (x serial primary key, y text); +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LINE 1: CREATE TABLE regtest_table (x serial primary key, y text); + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="public" permissive=0 +LINE 1: CREATE TABLE regtest_table (x serial primary key, y text); + ^ +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_table_x_seq" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.x" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.y" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_table_x_seq" permissive=0 +ALTER TABLE regtest_table ADD COLUMN z int; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: ALTER TABLE regtest_table ADD COLUMN z int; + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.z" permissive=0 +CREATE TABLE regtest_table_2 (a int); +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: CREATE TABLE regtest_table_2 (a int); + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_2" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.a" permissive=0 +CREATE TABLE regtest_ptable (a int) PARTITION BY RANGE (a); +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: CREATE TABLE regtest_ptable (a int) PARTITION BY RANGE (a); + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.a" permissive=0 +CREATE TABLE regtest_ptable_ones PARTITION OF regtest_ptable FOR VALUES FROM ('0') TO ('10'); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_ones" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.a" permissive=0 +CREATE TABLE regtest_ptable_tens PARTITION OF regtest_ptable FOR VALUES FROM ('10') TO ('100'); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_tens" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.a" permissive=0 +ALTER TABLE regtest_ptable ADD COLUMN q int; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: ALTER TABLE regtest_ptable ADD COLUMN q int; + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.q" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.q" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.q" permissive=0 +-- corresponding toast table should not have label and permission checks +ALTER TABLE regtest_table_2 ADD COLUMN b text; +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.b" permissive=0 +-- VACUUM FULL internally create a new table and swap them later. +VACUUM FULL regtest_table; +VACUUM FULL regtest_ptable; +CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100; +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema.regtest_view" permissive=0 +CREATE VIEW regtest_pview AS SELECT * FROM regtest_ptable WHERE a < 99; +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema.regtest_pview" permissive=0 +CREATE SEQUENCE regtest_seq; +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_seq" permissive=0 +CREATE TYPE regtest_comptype AS (a int, b text); +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +CREATE FUNCTION regtest_func(text,int[]) RETURNS bool LANGUAGE plpgsql + AS 'BEGIN RAISE NOTICE ''regtest_func => %'', $1; RETURN true; END'; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="regtest_schema.regtest_func(pg_catalog.text,integer[])" permissive=0 +CREATE AGGREGATE regtest_agg ( + sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond1 = '0' +); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="regtest_schema.regtest_agg(integer)" permissive=0 +-- CREATE objects owned by others +SET SESSION AUTHORIZATION regress_sepgsql_test_user; +SET search_path = regtest_schema, public; +CREATE TABLE regtest_table_3 (x int, y serial); +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LINE 1: CREATE TABLE regtest_table_3 (x int, y serial); + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="public" permissive=0 +LINE 1: CREATE TABLE regtest_table_3 (x int, y serial); + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: CREATE TABLE regtest_table_3 (x int, y serial); + ^ +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_table_3_y_seq" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_3" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.x" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.y" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_table_3_y_seq" permissive=0 +CREATE TABLE regtest_ptable_3 (o int, p serial) PARTITION BY RANGE (o); +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: CREATE TABLE regtest_ptable_3 (o int, p serial) PARTITION BY... + ^ +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_ptable_3_p_seq" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_3" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.o" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.p" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_ptable_3_p_seq" permissive=0 +CREATE TABLE regtest_ptable_3_ones PARTITION OF regtest_ptable_3 FOR VALUES FROM ('0') to ('10'); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_3_ones" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.o" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.p" permissive=0 +CREATE TABLE regtest_ptable_3_tens PARTITION OF regtest_ptable_3 FOR VALUES FROM ('10') to ('100'); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_3_tens" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.o" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.p" permissive=0 +CREATE VIEW regtest_view_2 AS SELECT * FROM regtest_table_3 WHERE x < y; +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema.regtest_view_2" permissive=0 +CREATE VIEW regtest_pview_2 AS SELECT * FROM regtest_ptable_3 WHERE o < p; +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema.regtest_pview_2" permissive=0 +CREATE FUNCTION regtest_func_2(int) RETURNS bool LANGUAGE plpgsql + AS 'BEGIN RETURN $1 * $1 < 100; END'; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="regtest_schema.regtest_func_2(integer)" permissive=0 +RESET SESSION AUTHORIZATION; +-- +-- ALTER and CREATE/DROP extra attribute permissions +-- +CREATE TABLE regtest_table_4 (x int primary key, y int, z int); +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LINE 1: CREATE TABLE regtest_table_4 (x int primary key, y int, z in... + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="public" permissive=0 +LINE 1: CREATE TABLE regtest_table_4 (x int primary key, y int, z in... + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: CREATE TABLE regtest_table_4 (x int primary key, y int, z in... + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: ...REATE TABLE regtest_table_4 (x int primary key, y int, z int... + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: ...ATE TABLE regtest_table_4 (x int primary key, y int, z int); + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.x" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.y" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.z" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +CREATE INDEX regtest_index_tbl4_y ON regtest_table_4(y); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +CREATE INDEX regtest_index_tbl4_z ON regtest_table_4(z); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +ALTER TABLE regtest_table_4 ALTER COLUMN y TYPE float; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.y" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.float8(integer)" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +DROP INDEX regtest_index_tbl4_y; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +ALTER TABLE regtest_table_4 + ADD CONSTRAINT regtest_tbl4_con EXCLUDE USING btree (z WITH =); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +DROP TABLE regtest_table_4 CASCADE; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_4" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.x" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.y" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_4.z" permissive=0 +-- For partitioned tables +CREATE TABLE regtest_ptable_4 (x int, y int, z int) PARTITION BY RANGE (x); +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: CREATE TABLE regtest_ptable_4 (x int, y int, z int) PARTITIO... + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: CREATE TABLE regtest_ptable_4 (x int, y int, z int) PARTITIO... + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LINE 1: CREATE TABLE regtest_ptable_4 (x int, y int, z int) PARTITIO... + ^ +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.x" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.y" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.z" permissive=0 +CREATE TABLE regtest_ptable_4_ones PARTITION OF regtest_ptable_4 FOR VALUES FROM ('0') TO ('10'); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4_ones" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.tableoid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.cmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.xmax" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.cmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.xmin" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.ctid" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.x" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.y" permissive=0 +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.z" permissive=0 +CREATE INDEX regtest_pindex_tbl4_y ON regtest_ptable_4_ones(y); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4_ones" permissive=0 +CREATE INDEX regtest_pindex_tbl4_z ON regtest_ptable_4_ones(z); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4_ones" permissive=0 +ALTER TABLE regtest_ptable_4 ALTER COLUMN y TYPE float; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.y" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.y" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.float8(integer)" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4_ones" permissive=0 +DROP INDEX regtest_pindex_tbl4_y; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4_ones" permissive=0 +ALTER TABLE regtest_ptable_4_ones + ADD CONSTRAINT regtest_ptbl4_con EXCLUDE USING btree (z WITH =); +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4_ones" permissive=0 +DROP TABLE regtest_ptable_4 CASCADE; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4_ones" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4_ones" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4_ones" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.x" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.y" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4_ones.z" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_4" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.x" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.y" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_4.z" permissive=0 +-- +-- DROP Permission checks (with clean-up) +-- +DROP FUNCTION regtest_func(text,int[]); +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="regtest_schema.regtest_func(pg_catalog.text,integer[])" permissive=0 +DROP AGGREGATE regtest_agg(int); +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="pg_catalog" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="regtest_schema.regtest_agg(integer)" permissive=0 +DROP SEQUENCE regtest_seq; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_seq" permissive=0 +DROP VIEW regtest_view; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema.regtest_view" permissive=0 +ALTER TABLE regtest_table DROP COLUMN y; +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.y" permissive=0 +ALTER TABLE regtest_ptable DROP COLUMN q CASCADE; +NOTICE: drop cascades to view regtest_pview +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.q" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.q" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema.regtest_pview" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.q" permissive=0 +DROP TABLE regtest_table; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { setattr } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_table_x_seq" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.x" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table.z" permissive=0 +DROP TABLE regtest_ptable CASCADE; +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_tens" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_tens.a" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_ones" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_ones.a" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable.a" permissive=0 +DROP OWNED BY regress_sepgsql_test_user; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="public" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="regtest_schema.regtest_func_2(integer)" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema.regtest_pview_2" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="regtest_schema.regtest_view_2" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_3_tens" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.o" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_tens.p" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_3_ones" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.o" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3_ones.p" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_ptable_3_p_seq" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_ptable_3" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.o" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_ptable_3.p" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="regtest_schema.regtest_table_3_y_seq" permissive=0 +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_3" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.x" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_3.y" permissive=0 +DROP DATABASE sepgsql_test_regression; +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="sepgsql_test_regression" permissive=0 +DROP USER regress_sepgsql_test_user; +DROP SCHEMA IF EXISTS regtest_schema CASCADE; +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { search } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=system_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="public" permissive=0 +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table regtest_table_2 +drop cascades to type regtest_comptype +LOG: SELinux: allowed { remove_name } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="regtest_schema.regtest_table_2" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.tableoid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.cmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.xmax" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.cmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.xmin" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.ctid" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.a" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="regtest_schema.regtest_table_2.b" permissive=0 +LOG: SELinux: allowed { drop } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="regtest_schema" permissive=0 diff --git a/contrib/sepgsql/expected/dml.out b/contrib/sepgsql/expected/dml.out new file mode 100644 index 0000000..6d5b1c1 --- /dev/null +++ b/contrib/sepgsql/expected/dml.out @@ -0,0 +1,399 @@ +-- +-- Regression Test for DML Permissions +-- +-- +-- Setup +-- +CREATE TABLE t1 (a int, junk int, b text); +SECURITY LABEL ON TABLE t1 IS 'system_u:object_r:sepgsql_table_t:s0'; +ALTER TABLE t1 DROP COLUMN junk; +INSERT INTO t1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc'); +CREATE TABLE t2 (x int, y text); +SECURITY LABEL ON TABLE t2 IS 'system_u:object_r:sepgsql_ro_table_t:s0'; +INSERT INTO t2 VALUES (1, 'xxx'), (2, 'yyy'), (3, 'zzz'); +CREATE TABLE t3 (s int, t text); +SECURITY LABEL ON TABLE t3 IS 'system_u:object_r:sepgsql_fixed_table_t:s0'; +INSERT INTO t3 VALUES (1, 'sss'), (2, 'ttt'), (3, 'uuu'); +CREATE TABLE t4 (m int, junk int, n text); +SECURITY LABEL ON TABLE t4 IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +ALTER TABLE t4 DROP COLUMN junk; +INSERT INTO t4 VALUES (1, 'mmm'), (2, 'nnn'), (3, 'ooo'); +CREATE TABLE t5 (e text, f text, g text); +SECURITY LABEL ON TABLE t5 IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t5.e IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t5.f IS 'system_u:object_r:sepgsql_ro_table_t:s0'; +SECURITY LABEL ON COLUMN t5.g IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +--- +-- partitioned table parent +CREATE TABLE t1p (o int, p text, q text) PARTITION BY RANGE (o); +SECURITY LABEL ON TABLE t1p IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t1p.o IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t1p.p IS 'system_u:object_r:sepgsql_ro_table_t:s0'; +SECURITY LABEL ON COLUMN t1p.q IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +-- partitioned table children +CREATE TABLE t1p_ones PARTITION OF t1p FOR VALUES FROM ('0') TO ('10'); +SECURITY LABEL ON COLUMN t1p_ones.o IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t1p_ones.p IS 'system_u:object_r:sepgsql_ro_table_t:s0'; +SECURITY LABEL ON COLUMN t1p_ones.q IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +CREATE TABLE t1p_tens PARTITION OF t1p FOR VALUES FROM ('10') TO ('100'); +SECURITY LABEL ON COLUMN t1p_tens.o IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t1p_tens.p IS 'system_u:object_r:sepgsql_ro_table_t:s0'; +SECURITY LABEL ON COLUMN t1p_tens.q IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +--- +CREATE TABLE customer (cid int primary key, cname text, ccredit text); +SECURITY LABEL ON COLUMN customer.ccredit IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +INSERT INTO customer VALUES (1, 'Taro', '1111-2222-3333-4444'), + (2, 'Hanako', '5555-6666-7777-8888'); +CREATE FUNCTION customer_credit(int) RETURNS text + AS 'SELECT regexp_replace(ccredit, ''-[0-9]+$'', ''-????'') FROM customer WHERE cid = $1' + LANGUAGE sql; +SECURITY LABEL ON FUNCTION customer_credit(int) + IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; +SELECT objtype, objname, label FROM pg_seclabels + WHERE provider = 'selinux' + AND objtype in ('table', 'column') + AND objname in ('t1', 't2', 't3', 't4', + 't5', 't5.e', 't5.f', 't5.g', + 't1p', 't1p.o', 't1p.p', 't1p.q', + 't1p_ones', 't1p_ones.o', 't1p_ones.p', 't1p_ones.q', + 't1p_tens', 't1p_tens.o', 't1p_tens.p', 't1p_tens.q') +ORDER BY objname COLLATE "C"; + objtype | objname | label +---------+------------+--------------------------------------------- + table | t1 | system_u:object_r:sepgsql_table_t:s0 + table | t1p | system_u:object_r:sepgsql_table_t:s0 + column | t1p.o | system_u:object_r:sepgsql_table_t:s0 + column | t1p.p | system_u:object_r:sepgsql_ro_table_t:s0 + column | t1p.q | system_u:object_r:sepgsql_secret_table_t:s0 + table | t1p_ones | unconfined_u:object_r:sepgsql_table_t:s0 + column | t1p_ones.o | system_u:object_r:sepgsql_table_t:s0 + column | t1p_ones.p | system_u:object_r:sepgsql_ro_table_t:s0 + column | t1p_ones.q | system_u:object_r:sepgsql_secret_table_t:s0 + table | t1p_tens | unconfined_u:object_r:sepgsql_table_t:s0 + column | t1p_tens.o | system_u:object_r:sepgsql_table_t:s0 + column | t1p_tens.p | system_u:object_r:sepgsql_ro_table_t:s0 + column | t1p_tens.q | system_u:object_r:sepgsql_secret_table_t:s0 + table | t2 | system_u:object_r:sepgsql_ro_table_t:s0 + table | t3 | system_u:object_r:sepgsql_fixed_table_t:s0 + table | t4 | system_u:object_r:sepgsql_secret_table_t:s0 + table | t5 | system_u:object_r:sepgsql_table_t:s0 + column | t5.e | system_u:object_r:sepgsql_table_t:s0 + column | t5.f | system_u:object_r:sepgsql_ro_table_t:s0 + column | t5.g | system_u:object_r:sepgsql_secret_table_t:s0 +(20 rows) + +CREATE SCHEMA my_schema_1; +CREATE TABLE my_schema_1.ts1 (a int, b text); +CREATE TABLE my_schema_1.pts1 (o int, p text) PARTITION BY RANGE (o); +CREATE TABLE my_schema_1.pts1_ones PARTITION OF my_schema_1.pts1 FOR VALUES FROM ('0') to ('10'); +CREATE SCHEMA my_schema_2; +CREATE TABLE my_schema_2.ts2 (x int, y text); +CREATE TABLE my_schema_2.pts2 (o int, p text) PARTITION BY RANGE (o); +CREATE TABLE my_schema_2.pts2_tens PARTITION OF my_schema_2.pts2 FOR VALUES FROM ('10') to ('100'); +SECURITY LABEL ON SCHEMA my_schema_2 + IS 'system_u:object_r:sepgsql_regtest_invisible_schema_t:s0'; +-- Hardwired Rules +UPDATE pg_attribute SET attisdropped = true + WHERE attrelid = 't5'::regclass AND attname = 'f'; -- failed +ERROR: SELinux: hardwired security policy violation +-- +-- Simple DML statements +-- +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 +(1 row) + +SELECT * FROM t1; -- ok + a | b +---+----- + 1 | aaa + 2 | bbb + 3 | ccc +(3 rows) + +SELECT * FROM t2; -- ok + x | y +---+----- + 1 | xxx + 2 | yyy + 3 | zzz +(3 rows) + +SELECT * FROM t3; -- ok + s | t +---+----- + 1 | sss + 2 | ttt + 3 | uuu +(3 rows) + +SELECT * FROM t4; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM t5; -- failed +ERROR: SELinux: security policy violation +SELECT e,f FROM t5; -- ok + e | f +---+--- +(0 rows) + +SELECT (t1.*)::record FROM t1; -- ok + t1 +--------- + (1,aaa) + (2,bbb) + (3,ccc) +(3 rows) + +SELECT (t4.*)::record FROM t4; -- failed +ERROR: SELinux: security policy violation +--- +-- partitioned table parent +SELECT * FROM t1p; -- failed +ERROR: SELinux: security policy violation +SELECT o,p FROM t1p; -- ok + o | p +---+--- +(0 rows) + +--partitioned table children +SELECT * FROM t1p_ones; -- failed +ERROR: SELinux: security policy violation +SELECT o FROM t1p_ones; -- ok + o +--- +(0 rows) + +SELECT o,p FROM t1p_ones; -- ok + o | p +---+--- +(0 rows) + +SELECT * FROM t1p_tens; -- failed +ERROR: SELinux: security policy violation +SELECT o FROM t1p_tens; -- ok + o +--- +(0 rows) + +SELECT o,p FROM t1p_tens; -- ok + o | p +---+--- +(0 rows) + +--- +SELECT * FROM customer; -- failed +ERROR: SELinux: security policy violation +SELECT cid, cname, customer_credit(cid) FROM customer; -- ok + cid | cname | customer_credit +-----+--------+--------------------- + 1 | Taro | 1111-2222-3333-???? + 2 | Hanako | 5555-6666-7777-???? +(2 rows) + +SELECT count(*) FROM t5; -- ok + count +------- + 0 +(1 row) + +SELECT count(*) FROM t5 WHERE g IS NULL; -- failed +ERROR: SELinux: security policy violation +--- +-- partitioned table parent +SELECT count(*) FROM t1p; -- ok + count +------- + 0 +(1 row) + +SELECT count(*) FROM t1p WHERE q IS NULL; -- failed +ERROR: SELinux: security policy violation +-- partitioned table children +SELECT count(*) FROM t1p_ones; -- ok + count +------- + 0 +(1 row) + +SELECT count(*) FROM t1p_ones WHERE q IS NULL; -- failed +ERROR: SELinux: security policy violation +SELECT count(*) FROM t1p_tens; -- ok + count +------- + 0 +(1 row) + +SELECT count(*) FROM t1p_tens WHERE q IS NULL; -- failed +ERROR: SELinux: security policy violation +--- +INSERT INTO t1 VALUES (4, 'abc'); -- ok +INSERT INTO t2 VALUES (4, 'xyz'); -- failed +ERROR: SELinux: security policy violation +INSERT INTO t3 VALUES (4, 'stu'); -- ok +INSERT INTO t4 VALUES (4, 'mno'); -- failed +ERROR: SELinux: security policy violation +INSERT INTO t5 VALUES (1,2,3); -- failed +ERROR: SELinux: security policy violation +INSERT INTO t5 (e,f) VALUES ('abc', 'def'); -- failed +ERROR: SELinux: security policy violation +INSERT INTO t5 (e) VALUES ('abc'); -- ok +--- +-- partitioned table parent +INSERT INTO t1p (o,p) VALUES (9, 'mno'); -- failed +ERROR: SELinux: security policy violation +INSERT INTO t1p (o) VALUES (9); -- ok +INSERT INTO t1p (o,p) VALUES (99, 'pqr'); -- failed +ERROR: SELinux: security policy violation +INSERT INTO t1p (o) VALUES (99); -- ok +-- partitioned table children +INSERT INTO t1p_ones (o,p) VALUES (9, 'mno'); -- failed +ERROR: SELinux: security policy violation +INSERT INTO t1p_ones (o) VALUES (9); -- ok +INSERT INTO t1p_tens (o,p) VALUES (99, 'pqr'); -- failed +ERROR: SELinux: security policy violation +INSERT INTO t1p_tens (o) VALUES (99); -- ok +--- +UPDATE t1 SET b = b || '_upd'; -- ok +UPDATE t2 SET y = y || '_upd'; -- failed +ERROR: SELinux: security policy violation +UPDATE t3 SET t = t || '_upd'; -- failed +ERROR: SELinux: security policy violation +UPDATE t4 SET n = n || '_upd'; -- failed +ERROR: SELinux: security policy violation +UPDATE t5 SET e = 'xyz'; -- ok +UPDATE t5 SET e = f || '_upd'; -- ok +UPDATE t5 SET e = g || '_upd'; -- failed +ERROR: SELinux: security policy violation +--- +-- partitioned table parent +UPDATE t1p SET o = 9 WHERE o < 10; -- ok +UPDATE t1p SET o = 99 WHERE o >= 10; -- ok +UPDATE t1p SET o = ascii(COALESCE(p,'upd'))%10 WHERE o < 10; -- ok +UPDATE t1p SET o = ascii(COALESCE(q,'upd'))%100 WHERE o >= 10; -- failed +ERROR: SELinux: security policy violation +-- partitioned table children +UPDATE t1p_ones SET o = 9; -- ok +UPDATE t1p_ones SET o = ascii(COALESCE(p,'upd'))%10; -- ok +UPDATE t1p_ones SET o = ascii(COALESCE(q,'upd'))%10; -- failed +ERROR: SELinux: security policy violation +UPDATE t1p_tens SET o = 99; -- ok +UPDATE t1p_tens SET o = ascii(COALESCE(p,'upd'))%100; -- ok +UPDATE t1p_tens SET o = ascii(COALESCE(q,'upd'))%100; -- failed +ERROR: SELinux: security policy violation +--- +DELETE FROM t1; -- ok +DELETE FROM t2; -- failed +ERROR: SELinux: security policy violation +DELETE FROM t3; -- failed +ERROR: SELinux: security policy violation +DELETE FROM t4; -- failed +ERROR: SELinux: security policy violation +DELETE FROM t5; -- ok +DELETE FROM t5 WHERE f IS NULL; -- ok +DELETE FROM t5 WHERE g IS NULL; -- failed +ERROR: SELinux: security policy violation +--- +-- partitioned table parent +DELETE FROM t1p; -- ok +DELETE FROM t1p WHERE p IS NULL; -- ok +DELETE FROM t1p WHERE q IS NULL; -- failed +ERROR: SELinux: security policy violation +-- partitioned table children +DELETE FROM t1p_ones WHERE p IS NULL; -- ok +DELETE FROM t1p_ones WHERE q IS NULL; -- failed; +ERROR: SELinux: security policy violation +DELETE FROM t1p_tens WHERE p IS NULL; -- ok +DELETE FROM t1p_tens WHERE q IS NULL; -- failed +ERROR: SELinux: security policy violation +--- +-- +-- COPY TO/FROM statements +-- +COPY t1 TO '/dev/null'; -- ok +COPY t2 TO '/dev/null'; -- ok +COPY t3 TO '/dev/null'; -- ok +COPY t4 TO '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t5 TO '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t5(e,f) TO '/dev/null'; -- ok +--- +-- partitioned table parent +COPY (SELECT * FROM t1p) TO '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY (SELECT (o,p) FROM t1p) TO '/dev/null'; -- ok +-- partitioned table children +COPY t1p_ones TO '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t1p_ones(o,p) TO '/dev/null'; -- ok +COPY t1p_tens TO '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t1p_tens(o,p) TO '/dev/null'; -- ok +--- +COPY t1 FROM '/dev/null'; -- ok +COPY t2 FROM '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t3 FROM '/dev/null'; -- ok +COPY t4 FROM '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t5 FROM '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t5 (e,f) FROM '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t5 (e) FROM '/dev/null'; -- ok +--- +-- partitioned table parent +COPY t1p FROM '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t1p (o) FROM '/dev/null'; -- ok +-- partitioned table children +COPY t1p_ones FROM '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t1p_ones (o) FROM '/dev/null'; -- ok +COPY t1p_tens FROM '/dev/null'; -- failed +ERROR: SELinux: security policy violation +COPY t1p_tens (o) FROM '/dev/null'; -- ok +--- +-- +-- Schema search path +-- +SET search_path = my_schema_1, my_schema_2, public; +SELECT * FROM ts1; -- ok + a | b +---+--- +(0 rows) + +SELECT * FROM ts2; -- failed (relation not found) +ERROR: relation "ts2" does not exist +LINE 1: SELECT * FROM ts2; + ^ +SELECT * FROM my_schema_2.ts2; -- failed (policy violation) +ERROR: SELinux: security policy violation +LINE 1: SELECT * FROM my_schema_2.ts2; + ^ +-- +-- Clean up +-- +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +--------------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 +(1 row) + +DROP TABLE IF EXISTS t1 CASCADE; +DROP TABLE IF EXISTS t2 CASCADE; +DROP TABLE IF EXISTS t3 CASCADE; +DROP TABLE IF EXISTS t4 CASCADE; +DROP TABLE IF EXISTS t5 CASCADE; +DROP TABLE IF EXISTS t1p CASCADE; +DROP TABLE IF EXISTS customer CASCADE; +DROP SCHEMA IF EXISTS my_schema_1 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table my_schema_1.ts1 +drop cascades to table my_schema_1.pts1 +DROP SCHEMA IF EXISTS my_schema_2 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table my_schema_2.ts2 +drop cascades to table my_schema_2.pts2 diff --git a/contrib/sepgsql/expected/label.out b/contrib/sepgsql/expected/label.out new file mode 100644 index 0000000..a086667 --- /dev/null +++ b/contrib/sepgsql/expected/label.out @@ -0,0 +1,611 @@ +-- +-- Regression Tests for Label Management +-- +-- +-- Setup +-- +CREATE TABLE t1 (a int, b text); +INSERT INTO t1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc'); +CREATE TABLE t2 AS SELECT * FROM t1 WHERE a % 2 = 0; +CREATE FUNCTION f1 () RETURNS text + AS 'SELECT sepgsql_getcon()' + LANGUAGE sql; +CREATE FUNCTION f2 () RETURNS text + AS 'SELECT sepgsql_getcon()' + LANGUAGE sql; +SECURITY LABEL ON FUNCTION f2() + IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; +CREATE FUNCTION f3 () RETURNS text + AS 'BEGIN + RAISE EXCEPTION ''an exception from f3()''; + RETURN NULL; + END;' LANGUAGE plpgsql; +SECURITY LABEL ON FUNCTION f3() + IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; +CREATE FUNCTION f4 () RETURNS text + AS 'SELECT sepgsql_getcon()' + LANGUAGE sql; +SECURITY LABEL ON FUNCTION f4() + IS 'system_u:object_r:sepgsql_nosuch_trusted_proc_exec_t:s0'; +CREATE FUNCTION f5 (text) RETURNS bool + AS 'SELECT sepgsql_setcon($1)' + LANGUAGE sql; +SECURITY LABEL ON FUNCTION f5(text) + IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0'; +CREATE TABLE auth_tbl(uname text, credential text, label text); +INSERT INTO auth_tbl + VALUES ('foo', 'acbd18db4cc2f85cedef654fccc4a4d8', 'sepgsql_regtest_foo_t:s0'), + ('var', 'b2145aac704ce76dbe1ac7adac535b23', 'sepgsql_regtest_var_t:s0'), + ('baz', 'b2145aac704ce76dbe1ac7adac535b23', 'sepgsql_regtest_baz_t:s0'); +SECURITY LABEL ON TABLE auth_tbl + IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +CREATE FUNCTION auth_func(text, text) RETURNS bool + LANGUAGE sql + AS 'SELECT sepgsql_setcon(regexp_replace(sepgsql_getcon(), ''_r:.*$'', ''_r:'' || label)) + FROM auth_tbl WHERE uname = $1 AND credential = $2'; +SECURITY LABEL ON FUNCTION auth_func(text,text) + IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0'; +CREATE TABLE foo_tbl(a int, b text); +INSERT INTO foo_tbl VALUES (1, 'aaa'), (2,'bbb'), (3,'ccc'), (4,'ddd'); +SECURITY LABEL ON TABLE foo_tbl + IS 'system_u:object_r:sepgsql_regtest_foo_table_t:s0'; +CREATE TABLE var_tbl(x int, y text); +INSERT INTO var_tbl VALUES (2,'xxx'), (3,'yyy'), (4,'zzz'), (5,'xyz'); +SECURITY LABEL ON TABLE var_tbl + IS 'system_u:object_r:sepgsql_regtest_var_table_t:s0'; +CREATE TABLE foo_ptbl(o int, p text) PARTITION BY RANGE (o); +CREATE TABLE foo_ptbl_ones PARTITION OF foo_ptbl FOR VALUES FROM ('0') TO ('10'); +CREATE TABLE foo_ptbl_tens PARTITION OF foo_ptbl FOR VALUES FROM ('10') TO ('100'); +INSERT INTO foo_ptbl VALUES (0, 'aaa'), (9,'bbb'), (10,'ccc'), (99,'ddd'); +SECURITY LABEL ON TABLE foo_ptbl + IS 'system_u:object_r:sepgsql_regtest_foo_table_t:s0'; +CREATE TABLE var_ptbl(q int, r text) PARTITION BY RANGE (q); +CREATE TABLE var_ptbl_ones PARTITION OF var_ptbl FOR VALUES FROM ('0') TO ('10'); +CREATE TABLE var_ptbl_tens PARTITION OF var_ptbl FOR VALUES FROM ('10') TO ('100'); +INSERT INTO var_ptbl VALUES (0,'xxx'), (9,'yyy'), (10,'zzz'), (99,'xyz'); +SECURITY LABEL ON TABLE var_ptbl + IS 'system_u:object_r:sepgsql_regtest_var_table_t:s0'; +-- +-- Tests for default labeling behavior +-- +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 +(1 row) + +CREATE TABLE t3 (s int, t text); +INSERT INTO t3 VALUES (1, 'sss'), (2, 'ttt'), (3, 'uuu'); +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +---------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_dba_t:s0 +(1 row) + +CREATE TABLE t4 (m int, n text); +INSERT INTO t4 VALUES (1,'mmm'), (2,'nnn'), (3,'ooo'); +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 +(1 row) + +CREATE TABLE tpart (o int, p text) PARTITION BY RANGE (o); +CREATE TABLE tpart_ones PARTITION OF tpart FOR VALUES FROM ('0') TO ('10'); +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +---------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_dba_t:s0 +(1 row) + +CREATE TABLE tpart_tens PARTITION OF tpart FOR VALUES FROM ('10') TO ('100'); +INSERT INTO tpart VALUES (0, 'aaa'); +INSERT INTO tpart VALUES (9, 'bbb'); +INSERT INTO tpart VALUES (99, 'ccc'); +SELECT objtype, objname, label FROM pg_seclabels + WHERE provider = 'selinux' AND objtype = 'table' AND objname in ('t1', 't2', 't3', + 'tpart', + 'tpart_ones', + 'tpart_tens') + ORDER BY objname COLLATE "C" ASC; + objtype | objname | label +---------+------------+----------------------------------------------- + table | t1 | unconfined_u:object_r:sepgsql_table_t:s0 + table | t2 | unconfined_u:object_r:sepgsql_table_t:s0 + table | t3 | unconfined_u:object_r:user_sepgsql_table_t:s0 + table | tpart | unconfined_u:object_r:user_sepgsql_table_t:s0 + table | tpart_ones | unconfined_u:object_r:user_sepgsql_table_t:s0 + table | tpart_tens | unconfined_u:object_r:sepgsql_table_t:s0 +(6 rows) + +SELECT objtype, objname, label FROM pg_seclabels + WHERE provider = 'selinux' AND objtype = 'column' AND (objname like 't3.%' + OR objname like 't4.%' + OR objname like 'tpart.%' + OR objname like 'tpart_ones.%' + OR objname like 'tpart_tens.%') + ORDER BY objname COLLATE "C" ASC; + objtype | objname | label +---------+---------------------+----------------------------------------------- + column | t3.cmax | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | t3.cmin | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | t3.ctid | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | t3.s | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | t3.t | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | t3.tableoid | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | t3.xmax | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | t3.xmin | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | t4.cmax | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | t4.cmin | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | t4.ctid | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | t4.m | unconfined_u:object_r:sepgsql_table_t:s0 + column | t4.n | unconfined_u:object_r:sepgsql_table_t:s0 + column | t4.tableoid | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | t4.xmax | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | t4.xmin | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | tpart.cmax | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart.cmin | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart.ctid | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart.o | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart.p | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart.tableoid | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart.xmax | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart.xmin | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart_ones.cmax | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart_ones.cmin | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart_ones.ctid | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart_ones.o | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart_ones.p | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart_ones.tableoid | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart_ones.xmax | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart_ones.xmin | unconfined_u:object_r:user_sepgsql_table_t:s0 + column | tpart_tens.cmax | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | tpart_tens.cmin | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | tpart_tens.ctid | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | tpart_tens.o | unconfined_u:object_r:sepgsql_table_t:s0 + column | tpart_tens.p | unconfined_u:object_r:sepgsql_table_t:s0 + column | tpart_tens.tableoid | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | tpart_tens.xmax | unconfined_u:object_r:sepgsql_sysobj_t:s0 + column | tpart_tens.xmin | unconfined_u:object_r:sepgsql_sysobj_t:s0 +(40 rows) + +-- +-- Tests for SECURITY LABEL +-- +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +---------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_dba_t:s0 +(1 row) + +SECURITY LABEL ON TABLE t1 + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok +SECURITY LABEL ON TABLE t2 + IS 'invalid security context'; -- be failed +ERROR: SELinux: invalid security label: "invalid security context" +SECURITY LABEL ON COLUMN t2 + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- be failed +ERROR: column name must be qualified +SECURITY LABEL ON COLUMN t2.b + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok +SECURITY LABEL ON TABLE tpart + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok +SECURITY LABEL ON TABLE tpart + IS 'invalid security context'; -- failed +ERROR: SELinux: invalid security label: "invalid security context" +SECURITY LABEL ON COLUMN tpart + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- failed +ERROR: column name must be qualified +SECURITY LABEL ON COLUMN tpart.o + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok +-- +-- Tests for Trusted Procedures +-- +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 +(1 row) + +SET sepgsql.debug_audit = true; +SET client_min_messages = log; +SELECT f1(); -- normal procedure +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=unconfined_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="public.f1()" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.sepgsql_getcon()" permissive=0 + f1 +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 +(1 row) + +SELECT f2(); -- trusted procedure +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=system_u:object_r:sepgsql_trusted_proc_exec_t:s0 tclass=db_procedure name="public.f2()" permissive=0 +LOG: SELinux: allowed { entrypoint } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=system_u:object_r:sepgsql_trusted_proc_exec_t:s0 tclass=db_procedure name="function f2()" permissive=0 +LOG: SELinux: allowed { transition } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=unconfined_u:unconfined_r:sepgsql_trusted_proc_t:s0 tclass=process permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_trusted_proc_t:s0 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.sepgsql_getcon()" permissive=0 + f2 +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_trusted_proc_t:s0 +(1 row) + +SELECT f3(); -- trusted procedure that raises an error +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=system_u:object_r:sepgsql_trusted_proc_exec_t:s0 tclass=db_procedure name="public.f3()" permissive=0 +LOG: SELinux: allowed { entrypoint } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=system_u:object_r:sepgsql_trusted_proc_exec_t:s0 tclass=db_procedure name="function f3()" permissive=0 +LOG: SELinux: allowed { transition } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=unconfined_u:unconfined_r:sepgsql_trusted_proc_t:s0 tclass=process permissive=0 +ERROR: an exception from f3() +CONTEXT: PL/pgSQL function f3() line 2 at RAISE +SELECT f4(); -- failed on domain transition +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=system_u:object_r:sepgsql_nosuch_trusted_proc_exec_t:s0 tclass=db_procedure name="public.f4()" permissive=0 +LOG: SELinux: allowed { entrypoint } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=system_u:object_r:sepgsql_nosuch_trusted_proc_exec_t:s0 tclass=db_procedure name="function f4()" permissive=0 +LOG: SELinux: denied { transition } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=unconfined_u:unconfined_r:sepgsql_regtest_nosuch_t:s0 tclass=process permissive=0 +ERROR: SELinux: security policy violation +SELECT sepgsql_getcon(); -- client's label must be restored +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.sepgsql_getcon()" permissive=0 + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 +(1 row) + +-- +-- Test for Dynamic Domain Transition +-- +-- validation of transaction aware dynamic-transition +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +----------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c25 +(1 row) + +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c15'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c15 +(1 row) + +SELECT sepgsql_setcon(NULL); -- failed to reset +ERROR: SELinux: security policy violation +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c15 +(1 row) + +BEGIN; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c12'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c12 +(1 row) + +SAVEPOINT svpt_1; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c9'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +---------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c9 +(1 row) + +SAVEPOINT svpt_2; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c6'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +---------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c6 +(1 row) + +SAVEPOINT svpt_3; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c3'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +---------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c3 +(1 row) + +ROLLBACK TO SAVEPOINT svpt_2; +SELECT sepgsql_getcon(); -- should be 's0:c0.c9' + sepgsql_getcon +---------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c9 +(1 row) + +ROLLBACK TO SAVEPOINT svpt_1; +SELECT sepgsql_getcon(); -- should be 's0:c0.c12' + sepgsql_getcon +----------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c12 +(1 row) + +ABORT; +SELECT sepgsql_getcon(); -- should be 's0:c0.c15' + sepgsql_getcon +----------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c15 +(1 row) + +BEGIN; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c8'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +---------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c8 +(1 row) + +SAVEPOINT svpt_1; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c4'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +---------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c4 +(1 row) + +ROLLBACK TO SAVEPOINT svpt_1; +SELECT sepgsql_getcon(); -- should be 's0:c0.c8' + sepgsql_getcon +---------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c8 +(1 row) + +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c6'); + sepgsql_setcon +---------------- + t +(1 row) + +COMMIT; +SELECT sepgsql_getcon(); -- should be 's0:c0.c6' + sepgsql_getcon +---------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c6 +(1 row) + +-- sepgsql_regtest_user_t is not available dynamic-transition, +-- unless sepgsql_setcon() is called inside of trusted-procedure +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +------------------------------------------------------------ + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15 +(1 row) + +-- sepgsql_regtest_user_t has no permission to switch current label +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0'); -- failed +ERROR: SELinux: security policy violation +SELECT sepgsql_getcon(); + sepgsql_getcon +------------------------------------------------------------ + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15 +(1 row) + +-- trusted procedure allows to switch, but unavailable to override MCS rules +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7'); -- OK + f5 +---- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7 +(1 row) + +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c31'); -- Failed +ERROR: SELinux: security policy violation +CONTEXT: SQL function "f5" statement 1 +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7 +(1 row) + +SELECT f5(NULL); -- Failed +ERROR: SELinux: security policy violation +CONTEXT: SQL function "f5" statement 1 +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7 +(1 row) + +BEGIN; +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c3'); -- OK + f5 +---- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c3 +(1 row) + +ABORT; +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7 +(1 row) + +-- +-- Test for simulation of typical connection pooling server +-- +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_pool_t:s0 +(1 row) + +-- we shouldn't allow to switch client label without trusted procedure +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_foo_t:s0'); +ERROR: SELinux: security policy violation +SELECT * FROM auth_tbl; -- failed, no permission to reference +ERROR: SELinux: security policy violation +-- switch to "foo" +SELECT auth_func('foo', 'acbd18db4cc2f85cedef654fccc4a4d8'); + auth_func +----------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +---------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_foo_t:s0 +(1 row) + +SELECT * FROM foo_tbl; -- OK + a | b +---+----- + 1 | aaa + 2 | bbb + 3 | ccc + 4 | ddd +(4 rows) + +SELECT * FROM foo_ptbl; -- OK + o | p +----+----- + 0 | aaa + 9 | bbb + 10 | ccc + 99 | ddd +(4 rows) + +SELECT * FROM var_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM var_ptbl; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM auth_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT sepgsql_setcon(NULL); -- end of session + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_pool_t:s0 +(1 row) + +-- the pooler cannot touch these tables directly +SELECT * FROM foo_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM foo_ptbl; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM var_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM var_ptbl; -- failed +ERROR: SELinux: security policy violation +-- switch to "var" +SELECT auth_func('var', 'b2145aac704ce76dbe1ac7adac535b23'); + auth_func +----------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +---------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_var_t:s0 +(1 row) + +SELECT * FROM foo_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM foo_ptbl; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM var_tbl; -- OK + x | y +---+----- + 2 | xxx + 3 | yyy + 4 | zzz + 5 | xyz +(4 rows) + +SELECT * FROM var_ptbl; -- OK + q | r +----+----- + 0 | xxx + 9 | yyy + 10 | zzz + 99 | xyz +(4 rows) + +SELECT * FROM auth_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT sepgsql_setcon(NULL); -- end of session + sepgsql_setcon +---------------- + t +(1 row) + +-- misc checks +SELECT auth_func('var', 'invalid credential'); -- not works + auth_func +----------- + +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_pool_t:s0 +(1 row) + +-- +-- Clean up +-- +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +--------------------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 +(1 row) + +DROP TABLE IF EXISTS t1 CASCADE; +DROP TABLE IF EXISTS t2 CASCADE; +DROP TABLE IF EXISTS t3 CASCADE; +DROP TABLE IF EXISTS t4 CASCADE; +DROP TABLE IF EXISTS tpart CASCADE; +DROP FUNCTION IF EXISTS f1() CASCADE; +DROP FUNCTION IF EXISTS f2() CASCADE; +DROP FUNCTION IF EXISTS f3() CASCADE; +DROP FUNCTION IF EXISTS f4() CASCADE; +DROP FUNCTION IF EXISTS f5(text) CASCADE; diff --git a/contrib/sepgsql/expected/misc.out b/contrib/sepgsql/expected/misc.out new file mode 100644 index 0000000..73e65d1 --- /dev/null +++ b/contrib/sepgsql/expected/misc.out @@ -0,0 +1,231 @@ +-- +-- Regression Test for Misc Permission Checks +-- +LOAD '$libdir/sepgsql'; -- failed +ERROR: SELinux: LOAD is not permitted +-- +-- Permissions to execute functions +-- +CREATE TABLE t1 (x int, y text); +INSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(1,100) x); +CREATE TABLE t1p (o int, p text) PARTITION BY RANGE (o); +CREATE TABLE t1p_ones PARTITION OF t1p FOR VALUES FROM ('0') TO ('10'); +CREATE TABLE t1p_tens PARTITION OF t1p FOR VALUES FROM ('10') TO ('100'); +INSERT INTO t1p (SELECT x, md5(x::text) FROM generate_series(0,99) x); +SET sepgsql.debug_audit = on; +SET client_min_messages = log; +-- regular function and operators +SELECT * FROM t1 WHERE x > 50 AND y like '%64%'; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column x of table t1" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column y of table t1" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4gt(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 + x | y +-----+---------------------------------- + 77 | 28dd2c7955ce926456240b2ff0100bde + 89 | 7647966b7343c29048673252e490f736 + 90 | 8613985ec49eb8f757ae6439e879bb2a + 91 | 54229abfcfa5649e7003b83dd4755294 + 99 | ac627ab1ccbdb62ec96e702f07f6425b + 100 | f899139df5e1059396431415e770c6dd +(6 rows) + +SELECT * FROM t1p WHERE o > 50 AND p like '%64%'; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column p of table t1p" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column p of table t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_tens" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_tens" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column p of table t1p_tens" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4gt(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 + o | p +----+---------------------------------- + 77 | 28dd2c7955ce926456240b2ff0100bde + 89 | 7647966b7343c29048673252e490f736 + 90 | 8613985ec49eb8f757ae6439e879bb2a + 91 | 54229abfcfa5649e7003b83dd4755294 + 99 | ac627ab1ccbdb62ec96e702f07f6425b +(5 rows) + +SELECT * FROM t1p_ones WHERE o > 50 AND p like '%64%'; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column p of table t1p_ones" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4gt(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 + o | p +---+--- +(0 rows) + +SELECT * FROM t1p_tens WHERE o > 50 AND p like '%64%'; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_tens" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_tens" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column p of table t1p_tens" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4gt(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 + o | p +----+---------------------------------- + 77 | 28dd2c7955ce926456240b2ff0100bde + 89 | 7647966b7343c29048673252e490f736 + 90 | 8613985ec49eb8f757ae6439e879bb2a + 91 | 54229abfcfa5649e7003b83dd4755294 + 99 | ac627ab1ccbdb62ec96e702f07f6425b +(5 rows) + +-- aggregate function +SELECT MIN(x), AVG(x) FROM t1; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column x of table t1" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.min(integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4smaller(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.avg(integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int8_avg(bigint[])" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4_avg_accum(bigint[],integer)" permissive=0 + min | avg +-----+--------------------- + 1 | 50.5000000000000000 +(1 row) + +SELECT MIN(o), AVG(o) FROM t1p; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_tens" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_tens" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.min(integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4smaller(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.avg(integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int8_avg(bigint[])" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4_avg_accum(bigint[],integer)" permissive=0 + min | avg +-----+--------------------- + 0 | 49.5000000000000000 +(1 row) + +SELECT MIN(o), AVG(o) FROM t1p_ones; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_ones" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.min(integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4smaller(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.avg(integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int8_avg(bigint[])" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4_avg_accum(bigint[],integer)" permissive=0 + min | avg +-----+-------------------- + 0 | 4.5000000000000000 +(1 row) + +SELECT MIN(o), AVG(o) FROM t1p_tens; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_tens" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_tens" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.min(integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4smaller(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.avg(integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int8_avg(bigint[])" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4_avg_accum(bigint[],integer)" permissive=0 + min | avg +-----+--------------------- + 10 | 54.5000000000000000 +(1 row) + +-- window function +SELECT row_number() OVER (order by x), * FROM t1 WHERE y like '%86%'; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column x of table t1" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column y of table t1" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4eq(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.row_number()" permissive=0 + row_number | x | y +------------+----+---------------------------------- + 1 | 2 | c81e728d9d4c2f636f067f89cc14862c + 2 | 17 | 70efdf2ec9b086079795c442636b55fb + 3 | 22 | b6d767d2f8ed5d21a44b0e5886680cb9 + 4 | 27 | 02e74f10e0327ad868d138f2b4fdd6f0 + 5 | 33 | 182be0c5cdcd5072bb1864cdee4d3d6e + 6 | 43 | 17e62166fc8586dfa4d1bc0e1742c08b + 7 | 54 | a684eceee76fc522773286a895bc8436 + 8 | 73 | d2ddea18f00665ce8623e36bd4e3c7c5 + 9 | 76 | fbd7939d674997cdb4692d34de8633c4 + 10 | 89 | 7647966b7343c29048673252e490f736 + 11 | 90 | 8613985ec49eb8f757ae6439e879bb2a + 12 | 94 | f4b9ec30ad9f68f89b29639786cb62ef +(12 rows) + +SELECT row_number() OVER (order by o), * FROM t1p WHERE p like '%86%'; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column p of table t1p" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column p of table t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_tens" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_tens" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column p of table t1p_tens" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4eq(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.row_number()" permissive=0 + row_number | o | p +------------+----+---------------------------------- + 1 | 2 | c81e728d9d4c2f636f067f89cc14862c + 2 | 17 | 70efdf2ec9b086079795c442636b55fb + 3 | 22 | b6d767d2f8ed5d21a44b0e5886680cb9 + 4 | 27 | 02e74f10e0327ad868d138f2b4fdd6f0 + 5 | 33 | 182be0c5cdcd5072bb1864cdee4d3d6e + 6 | 43 | 17e62166fc8586dfa4d1bc0e1742c08b + 7 | 54 | a684eceee76fc522773286a895bc8436 + 8 | 73 | d2ddea18f00665ce8623e36bd4e3c7c5 + 9 | 76 | fbd7939d674997cdb4692d34de8633c4 + 10 | 89 | 7647966b7343c29048673252e490f736 + 11 | 90 | 8613985ec49eb8f757ae6439e879bb2a + 12 | 94 | f4b9ec30ad9f68f89b29639786cb62ef +(12 rows) + +SELECT row_number() OVER (order by o), * FROM t1p_ones WHERE p like '%86%'; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_ones" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column p of table t1p_ones" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4eq(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.row_number()" permissive=0 + row_number | o | p +------------+---+---------------------------------- + 1 | 2 | c81e728d9d4c2f636f067f89cc14862c +(1 row) + +SELECT row_number() OVER (order by o), * FROM t1p_tens WHERE p like '%86%'; +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="public.t1p_tens" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column o of table t1p_tens" permissive=0 +LOG: SELinux: allowed { select } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="column p of table t1p_tens" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.textlike(pg_catalog.text,pg_catalog.text)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.int4eq(integer,integer)" permissive=0 +LOG: SELinux: allowed { execute } scontext=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 tcontext=system_u:object_r:sepgsql_proc_exec_t:s0 tclass=db_procedure name="pg_catalog.row_number()" permissive=0 + row_number | o | p +------------+----+---------------------------------- + 1 | 17 | 70efdf2ec9b086079795c442636b55fb + 2 | 22 | b6d767d2f8ed5d21a44b0e5886680cb9 + 3 | 27 | 02e74f10e0327ad868d138f2b4fdd6f0 + 4 | 33 | 182be0c5cdcd5072bb1864cdee4d3d6e + 5 | 43 | 17e62166fc8586dfa4d1bc0e1742c08b + 6 | 54 | a684eceee76fc522773286a895bc8436 + 7 | 73 | d2ddea18f00665ce8623e36bd4e3c7c5 + 8 | 76 | fbd7939d674997cdb4692d34de8633c4 + 9 | 89 | 7647966b7343c29048673252e490f736 + 10 | 90 | 8613985ec49eb8f757ae6439e879bb2a + 11 | 94 | f4b9ec30ad9f68f89b29639786cb62ef +(11 rows) + +RESET sepgsql.debug_audit; +RESET client_min_messages; +-- +-- Cleanup +-- +DROP TABLE IF EXISTS t1 CASCADE; +DROP TABLE IF EXISTS t1p CASCADE; diff --git a/contrib/sepgsql/expected/truncate.out b/contrib/sepgsql/expected/truncate.out new file mode 100644 index 0000000..e2cabd7 --- /dev/null +++ b/contrib/sepgsql/expected/truncate.out @@ -0,0 +1,46 @@ +-- +-- Regression Test for TRUNCATE +-- +-- +-- Setup +-- +CREATE TABLE julio_claudians (name text, birth_date date); +SECURITY LABEL ON TABLE julio_claudians IS 'system_u:object_r:sepgsql_regtest_foo_table_t:s0'; +INSERT INTO julio_claudians VALUES ('Augustus', 'September 23, 63 BC'), ('Tiberius', 'November 16, 42 BC'), ('Caligula', 'August 31, 0012'), ('Claudius', 'August 1, 0010'), ('Nero', 'December 15, 0037'); +CREATE TABLE flavians (name text, birth_date date); +SECURITY LABEL ON TABLE flavians IS 'system_u:object_r:sepgsql_table_t:s0'; +INSERT INTO flavians VALUES ('Vespasian', 'November 17, 0009'), ('Titus', 'December 30, 0039'), ('Domitian', 'October 24, 0051'); +SELECT * from julio_claudians; + name | birth_date +----------+--------------- + Augustus | 09-23-0063 BC + Tiberius | 11-16-0042 BC + Caligula | 08-31-0012 + Claudius | 08-01-0010 + Nero | 12-15-0037 +(5 rows) + +SELECT * from flavians; + name | birth_date +-----------+------------ + Vespasian | 11-17-0009 + Titus | 12-30-0039 + Domitian | 10-24-0051 +(3 rows) + +TRUNCATE TABLE julio_claudians; -- ok +TRUNCATE TABLE flavians; -- failed +ERROR: SELinux: security policy violation +SELECT * from julio_claudians; + name | birth_date +------+------------ +(0 rows) + +SELECT * from flavians; + name | birth_date +-----------+------------ + Vespasian | 11-17-0009 + Titus | 12-30-0039 + Domitian | 10-24-0051 +(3 rows) + diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c new file mode 100644 index 0000000..fa73476 --- /dev/null +++ b/contrib/sepgsql/hooks.c @@ -0,0 +1,483 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/hooks.c + * + * Entrypoints of the hooks in PostgreSQL, and dispatches the callbacks. + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/dependency.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_class.h" +#include "catalog/pg_database.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_proc.h" +#include "commands/seclabel.h" +#include "executor/executor.h" +#include "fmgr.h" +#include "miscadmin.h" +#include "sepgsql.h" +#include "tcop/utility.h" +#include "utils/guc.h" +#include "utils/queryenvironment.h" + +PG_MODULE_MAGIC; + +/* + * Declarations + */ + +/* + * Saved hook entries (if stacked) + */ +static object_access_hook_type next_object_access_hook = NULL; +static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL; +static ProcessUtility_hook_type next_ProcessUtility_hook = NULL; + +/* + * Contextual information on DDL commands + */ +typedef struct +{ + NodeTag cmdtype; + + /* + * Name of the template database given by users on CREATE DATABASE + * command. Elsewhere (including the case of default) NULL. + */ + const char *createdb_dtemplate; +} sepgsql_context_info_t; + +static sepgsql_context_info_t sepgsql_context_info; + +/* + * GUC: sepgsql.permissive = (on|off) + */ +static bool sepgsql_permissive = false; + +bool +sepgsql_get_permissive(void) +{ + return sepgsql_permissive; +} + +/* + * GUC: sepgsql.debug_audit = (on|off) + */ +static bool sepgsql_debug_audit = false; + +bool +sepgsql_get_debug_audit(void) +{ + return sepgsql_debug_audit; +} + +/* + * sepgsql_object_access + * + * Entrypoint of the object_access_hook. This routine performs as + * a dispatcher of invocation based on access type and object classes. + */ +static void +sepgsql_object_access(ObjectAccessType access, + Oid classId, + Oid objectId, + int subId, + void *arg) +{ + if (next_object_access_hook) + (*next_object_access_hook) (access, classId, objectId, subId, arg); + + switch (access) + { + case OAT_POST_CREATE: + { + ObjectAccessPostCreate *pc_arg = arg; + bool is_internal; + + is_internal = pc_arg ? pc_arg->is_internal : false; + + switch (classId) + { + case DatabaseRelationId: + Assert(!is_internal); + sepgsql_database_post_create(objectId, + sepgsql_context_info.createdb_dtemplate); + break; + + case NamespaceRelationId: + Assert(!is_internal); + sepgsql_schema_post_create(objectId); + break; + + case RelationRelationId: + if (subId == 0) + { + /* + * The cases in which we want to apply permission + * checks on creation of a new relation correspond + * to direct user invocation. For internal uses, + * that is creation of toast tables, index rebuild + * or ALTER TABLE commands, we need neither + * assignment of security labels nor permission + * checks. + */ + if (is_internal) + break; + + sepgsql_relation_post_create(objectId); + } + else + sepgsql_attribute_post_create(objectId, subId); + break; + + case ProcedureRelationId: + Assert(!is_internal); + sepgsql_proc_post_create(objectId); + break; + + default: + /* Ignore unsupported object classes */ + break; + } + } + break; + + case OAT_DROP: + { + ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg; + + /* + * No need to apply permission checks on object deletion due + * to internal cleanups; such as removal of temporary database + * object on session closed. + */ + if ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL) != 0) + break; + + switch (classId) + { + case DatabaseRelationId: + sepgsql_database_drop(objectId); + break; + + case NamespaceRelationId: + sepgsql_schema_drop(objectId); + break; + + case RelationRelationId: + if (subId == 0) + sepgsql_relation_drop(objectId); + else + sepgsql_attribute_drop(objectId, subId); + break; + + case ProcedureRelationId: + sepgsql_proc_drop(objectId); + break; + + default: + /* Ignore unsupported object classes */ + break; + } + } + break; + + case OAT_TRUNCATE: + { + switch (classId) + { + case RelationRelationId: + sepgsql_relation_truncate(objectId); + break; + default: + /* Ignore unsupported object classes */ + break; + } + } + break; + + case OAT_POST_ALTER: + { + ObjectAccessPostAlter *pa_arg = arg; + bool is_internal = pa_arg->is_internal; + + switch (classId) + { + case DatabaseRelationId: + Assert(!is_internal); + sepgsql_database_setattr(objectId); + break; + + case NamespaceRelationId: + Assert(!is_internal); + sepgsql_schema_setattr(objectId); + break; + + case RelationRelationId: + if (subId == 0) + { + /* + * A case when we don't want to apply permission + * check is that relation is internally altered + * without user's intention. E.g, no need to check + * on toast table/index to be renamed at end of + * the table rewrites. + */ + if (is_internal) + break; + + sepgsql_relation_setattr(objectId); + } + else + sepgsql_attribute_setattr(objectId, subId); + break; + + case ProcedureRelationId: + Assert(!is_internal); + sepgsql_proc_setattr(objectId); + break; + + default: + /* Ignore unsupported object classes */ + break; + } + } + break; + + case OAT_NAMESPACE_SEARCH: + { + ObjectAccessNamespaceSearch *ns_arg = arg; + + /* + * If stacked extension already decided not to allow users to + * search this schema, we just stick with that decision. + */ + if (!ns_arg->result) + break; + + Assert(classId == NamespaceRelationId); + Assert(ns_arg->result); + ns_arg->result + = sepgsql_schema_search(objectId, + ns_arg->ereport_on_violation); + } + break; + + case OAT_FUNCTION_EXECUTE: + { + Assert(classId == ProcedureRelationId); + sepgsql_proc_execute(objectId); + } + break; + + default: + elog(ERROR, "unexpected object access type: %d", (int) access); + break; + } +} + +/* + * sepgsql_exec_check_perms + * + * Entrypoint of DML permissions + */ +static bool +sepgsql_exec_check_perms(List *rangeTbls, List *rteperminfos, bool abort) +{ + /* + * If security provider is stacking and one of them replied 'false' at + * least, we don't need to check any more. + */ + if (next_exec_check_perms_hook && + !(*next_exec_check_perms_hook) (rangeTbls, rteperminfos, abort)) + return false; + + if (!sepgsql_dml_privileges(rangeTbls, rteperminfos, abort)) + return false; + + return true; +} + +/* + * sepgsql_utility_command + * + * It tries to rough-grained control on utility commands; some of them can + * break whole of the things if nefarious user would use. + */ +static void +sepgsql_utility_command(PlannedStmt *pstmt, + const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc) +{ + Node *parsetree = pstmt->utilityStmt; + sepgsql_context_info_t saved_context_info = sepgsql_context_info; + ListCell *cell; + + PG_TRY(); + { + /* + * Check command tag to avoid nefarious operations, and save the + * current contextual information to determine whether we should apply + * permission checks here, or not. + */ + sepgsql_context_info.cmdtype = nodeTag(parsetree); + + switch (nodeTag(parsetree)) + { + case T_CreatedbStmt: + + /* + * We hope to reference name of the source database, but it + * does not appear in system catalog. So, we save it here. + */ + foreach(cell, ((CreatedbStmt *) parsetree)->options) + { + DefElem *defel = (DefElem *) lfirst(cell); + + if (strcmp(defel->defname, "template") == 0) + { + sepgsql_context_info.createdb_dtemplate + = strVal(defel->arg); + break; + } + } + break; + + case T_LoadStmt: + + /* + * We reject LOAD command across the board on enforcing mode, + * because a binary module can arbitrarily override hooks. + */ + if (sepgsql_getenforce()) + { + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: LOAD is not permitted"))); + } + break; + default: + + /* + * Right now we don't check any other utility commands, + * because it needs more detailed information to make access + * control decision here, but we don't want to have two parse + * and analyze routines individually. + */ + break; + } + + if (next_ProcessUtility_hook) + (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + else + standard_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + } + PG_FINALLY(); + { + sepgsql_context_info = saved_context_info; + } + PG_END_TRY(); +} + +/* + * Module load/unload callback + */ +void +_PG_init(void) +{ + /* + * We allow to load the SE-PostgreSQL module on single-user-mode or + * shared_preload_libraries settings only. + */ + if (IsUnderPostmaster) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("sepgsql must be loaded via shared_preload_libraries"))); + + /* + * Check availability of SELinux on the platform. If disabled, we cannot + * activate any SE-PostgreSQL features, and we have to skip rest of + * initialization. + */ + if (is_selinux_enabled() < 1) + { + sepgsql_set_mode(SEPGSQL_MODE_DISABLED); + return; + } + + /* + * sepgsql.permissive = (on|off) + * + * This variable controls performing mode of SE-PostgreSQL on user's + * session. + */ + DefineCustomBoolVariable("sepgsql.permissive", + "Turn on/off permissive mode in SE-PostgreSQL", + NULL, + &sepgsql_permissive, + false, + PGC_SIGHUP, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + /* + * sepgsql.debug_audit = (on|off) + * + * This variable allows users to turn on/off audit logs on access control + * decisions, independent from auditallow/auditdeny setting in the + * security policy. We intend to use this option for debugging purpose. + */ + DefineCustomBoolVariable("sepgsql.debug_audit", + "Turn on/off debug audit messages", + NULL, + &sepgsql_debug_audit, + false, + PGC_USERSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("sepgsql"); + + /* Initialize userspace access vector cache */ + sepgsql_avc_init(); + + /* Initialize security label of the client and related stuff */ + sepgsql_init_client_label(); + + /* Security label provider hook */ + register_label_provider(SEPGSQL_LABEL_TAG, + sepgsql_object_relabel); + + /* Object access hook */ + next_object_access_hook = object_access_hook; + object_access_hook = sepgsql_object_access; + + /* DML permission check */ + next_exec_check_perms_hook = ExecutorCheckPerms_hook; + ExecutorCheckPerms_hook = sepgsql_exec_check_perms; + + /* ProcessUtility hook */ + next_ProcessUtility_hook = ProcessUtility_hook; + ProcessUtility_hook = sepgsql_utility_command; + + /* init contextual info */ + memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info)); +} diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c new file mode 100644 index 0000000..38ff406 --- /dev/null +++ b/contrib/sepgsql/label.c @@ -0,0 +1,916 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/label.c + * + * Routines to support SELinux labels (security context) + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/table.h" +#include "access/xact.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_class.h" +#include "catalog/pg_database.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_proc.h" +#include "commands/dbcommands.h" +#include "commands/seclabel.h" +#include "libpq/auth.h" +#include "libpq/libpq-be.h" +#include "miscadmin.h" +#include "sepgsql.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" + +/* + * Saved hook entries (if stacked) + */ +static ClientAuthentication_hook_type next_client_auth_hook = NULL; +static needs_fmgr_hook_type next_needs_fmgr_hook = NULL; +static fmgr_hook_type next_fmgr_hook = NULL; + +/* + * client_label_* + * + * security label of the database client. Initially the client security label + * is equal to client_label_peer, and can be changed by one or more calls to + * sepgsql_setcon(), and also be temporarily overridden during execution of a + * trusted-procedure. + * + * sepgsql_setcon() is a transaction-aware operation; a (sub-)transaction + * rollback should also rollback the current client security label. Therefore + * we use the list client_label_pending of pending_label to keep track of which + * labels were set during the (sub-)transactions. + */ +static char *client_label_peer = NULL; /* set by getpeercon(3) */ +static List *client_label_pending = NIL; /* pending list being set by + * sepgsql_setcon() */ +static char *client_label_committed = NULL; /* set by sepgsql_setcon(), and + * already committed */ +static char *client_label_func = NULL; /* set by trusted procedure */ + +typedef struct +{ + SubTransactionId subid; + char *label; +} pending_label; + +/* + * sepgsql_get_client_label + * + * Returns the current security label of the client. All code should use this + * routine to get the current label, instead of referring to the client_label_* + * variables above. + */ +char * +sepgsql_get_client_label(void) +{ + /* trusted procedure client label override */ + if (client_label_func) + return client_label_func; + + /* uncommitted sepgsql_setcon() value */ + if (client_label_pending) + { + pending_label *plabel = llast(client_label_pending); + + if (plabel->label) + return plabel->label; + } + else if (client_label_committed) + return client_label_committed; /* set by sepgsql_setcon() committed */ + + /* default label */ + Assert(client_label_peer != NULL); + return client_label_peer; +} + +/* + * sepgsql_set_client_label + * + * This routine tries to switch the current security label of the client, and + * checks related permissions. The supplied new label shall be added to the + * client_label_pending list, then saved at transaction-commit time to ensure + * transaction-awareness. + */ +static void +sepgsql_set_client_label(const char *new_label) +{ + const char *tcontext; + MemoryContext oldcxt; + pending_label *plabel; + + /* Reset to the initial client label, if NULL */ + if (!new_label) + tcontext = client_label_peer; + else + { + if (security_check_context_raw(new_label) < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("SELinux: invalid security label: \"%s\"", + new_label))); + tcontext = new_label; + } + + /* Check process:{setcurrent} permission. */ + sepgsql_avc_check_perms_label(sepgsql_get_client_label(), + SEPG_CLASS_PROCESS, + SEPG_PROCESS__SETCURRENT, + NULL, + true); + /* Check process:{dyntransition} permission. */ + sepgsql_avc_check_perms_label(tcontext, + SEPG_CLASS_PROCESS, + SEPG_PROCESS__DYNTRANSITION, + NULL, + true); + + /* + * Append the supplied new_label on the pending list until the current + * transaction is committed. + */ + oldcxt = MemoryContextSwitchTo(CurTransactionContext); + + plabel = palloc0(sizeof(pending_label)); + plabel->subid = GetCurrentSubTransactionId(); + if (new_label) + plabel->label = pstrdup(new_label); + client_label_pending = lappend(client_label_pending, plabel); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * sepgsql_xact_callback + * + * A callback routine of transaction commit/abort/prepare. Commit or abort + * changes in the client_label_pending list. + */ +static void +sepgsql_xact_callback(XactEvent event, void *arg) +{ + if (event == XACT_EVENT_COMMIT) + { + if (client_label_pending != NIL) + { + pending_label *plabel = llast(client_label_pending); + char *new_label; + + if (plabel->label) + new_label = MemoryContextStrdup(TopMemoryContext, + plabel->label); + else + new_label = NULL; + + if (client_label_committed) + pfree(client_label_committed); + + client_label_committed = new_label; + + /* + * XXX - Note that items of client_label_pending are allocated on + * CurTransactionContext, thus, all acquired memory region shall + * be released implicitly. + */ + client_label_pending = NIL; + } + } + else if (event == XACT_EVENT_ABORT) + client_label_pending = NIL; +} + +/* + * sepgsql_subxact_callback + * + * A callback routine of sub-transaction start/abort/commit. Releases all + * security labels that are set within the sub-transaction that is aborted. + */ +static void +sepgsql_subxact_callback(SubXactEvent event, SubTransactionId mySubid, + SubTransactionId parentSubid, void *arg) +{ + ListCell *cell; + + if (event == SUBXACT_EVENT_ABORT_SUB) + { + foreach(cell, client_label_pending) + { + pending_label *plabel = lfirst(cell); + + if (plabel->subid == mySubid) + client_label_pending + = foreach_delete_current(client_label_pending, cell); + } + } +} + +/* + * sepgsql_client_auth + * + * Entrypoint of the client authentication hook. + * It switches the client label according to getpeercon(), and the current + * performing mode according to the GUC setting. + */ +static void +sepgsql_client_auth(Port *port, int status) +{ + if (next_client_auth_hook) + (*next_client_auth_hook) (port, status); + + /* + * In the case when authentication failed, the supplied socket shall be + * closed soon, so we don't need to do anything here. + */ + if (status != STATUS_OK) + return; + + /* + * Getting security label of the peer process using API of libselinux. + */ + if (getpeercon_raw(port->sock, &client_label_peer) < 0) + ereport(FATAL, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: unable to get peer label: %m"))); + + /* + * Switch the current performing mode from INTERNAL to either DEFAULT or + * PERMISSIVE. + */ + if (sepgsql_get_permissive()) + sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE); + else + sepgsql_set_mode(SEPGSQL_MODE_DEFAULT); +} + +/* + * sepgsql_needs_fmgr_hook + * + * It informs the core whether the supplied function is trusted procedure, + * or not. If true, sepgsql_fmgr_hook shall be invoked at start, end, and + * abort time of function invocation. + */ +static bool +sepgsql_needs_fmgr_hook(Oid functionId) +{ + ObjectAddress object; + + if (next_needs_fmgr_hook && + (*next_needs_fmgr_hook) (functionId)) + return true; + + /* + * SELinux needs the function to be called via security_definer wrapper, + * if this invocation will take a domain-transition. We call these + * functions as trusted-procedure, if the security policy has a rule that + * switches security label of the client on execution. + */ + if (sepgsql_avc_trusted_proc(functionId) != NULL) + return true; + + /* + * Even if not a trusted-procedure, this function should not be inlined + * unless the client has db_procedure:{execute} permission. Please note + * that it shall be actually failed later because of same reason with + * ACL_EXECUTE. + */ + object.classId = ProcedureRelationId; + object.objectId = functionId; + object.objectSubId = 0; + if (!sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_PROCEDURE, + SEPG_DB_PROCEDURE__EXECUTE | + SEPG_DB_PROCEDURE__ENTRYPOINT, + SEPGSQL_AVC_NOAUDIT, false)) + return true; + + return false; +} + +/* + * sepgsql_fmgr_hook + * + * It switches security label of the client on execution of trusted + * procedures. + */ +static void +sepgsql_fmgr_hook(FmgrHookEventType event, + FmgrInfo *flinfo, Datum *private) +{ + struct + { + char *old_label; + char *new_label; + Datum next_private; + } *stack; + + switch (event) + { + case FHET_START: + stack = (void *) DatumGetPointer(*private); + if (!stack) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt); + stack = palloc(sizeof(*stack)); + stack->old_label = NULL; + stack->new_label = sepgsql_avc_trusted_proc(flinfo->fn_oid); + stack->next_private = 0; + + MemoryContextSwitchTo(oldcxt); + + /* + * process:transition permission between old and new label, + * when user tries to switch security label of the client on + * execution of trusted procedure. + * + * Also, db_procedure:entrypoint permission should be checked + * whether this procedure can perform as an entrypoint of the + * trusted procedure, or not. Note that db_procedure:execute + * permission shall be checked individually. + */ + if (stack->new_label) + { + ObjectAddress object; + + object.classId = ProcedureRelationId; + object.objectId = flinfo->fn_oid; + object.objectSubId = 0; + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_PROCEDURE, + SEPG_DB_PROCEDURE__ENTRYPOINT, + getObjectDescription(&object, false), + true); + + sepgsql_avc_check_perms_label(stack->new_label, + SEPG_CLASS_PROCESS, + SEPG_PROCESS__TRANSITION, + NULL, true); + } + *private = PointerGetDatum(stack); + } + Assert(!stack->old_label); + if (stack->new_label) + { + stack->old_label = client_label_func; + client_label_func = stack->new_label; + } + if (next_fmgr_hook) + (*next_fmgr_hook) (event, flinfo, &stack->next_private); + break; + + case FHET_END: + case FHET_ABORT: + stack = (void *) DatumGetPointer(*private); + + if (next_fmgr_hook) + (*next_fmgr_hook) (event, flinfo, &stack->next_private); + + if (stack->new_label) + { + client_label_func = stack->old_label; + stack->old_label = NULL; + } + break; + + default: + elog(ERROR, "unexpected event type: %d", (int) event); + break; + } +} + +/* + * sepgsql_init_client_label + * + * Initializes the client security label and sets up related hooks for client + * label management. + */ +void +sepgsql_init_client_label(void) +{ + /* + * Set up dummy client label. + * + * XXX - note that PostgreSQL launches background worker process like + * autovacuum without authentication steps. So, we initialize sepgsql_mode + * with SEPGSQL_MODE_INTERNAL, and client_label with the security context + * of server process. Later, it also launches background of user session. + * In this case, the process is always hooked on post-authentication, and + * we can initialize the sepgsql_mode and client_label correctly. + */ + if (getcon_raw(&client_label_peer) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: failed to get server security label: %m"))); + + /* Client authentication hook */ + next_client_auth_hook = ClientAuthentication_hook; + ClientAuthentication_hook = sepgsql_client_auth; + + /* Trusted procedure hooks */ + next_needs_fmgr_hook = needs_fmgr_hook; + needs_fmgr_hook = sepgsql_needs_fmgr_hook; + + next_fmgr_hook = fmgr_hook; + fmgr_hook = sepgsql_fmgr_hook; + + /* Transaction/Sub-transaction callbacks */ + RegisterXactCallback(sepgsql_xact_callback, NULL); + RegisterSubXactCallback(sepgsql_subxact_callback, NULL); +} + +/* + * sepgsql_get_label + * + * It returns a security context of the specified database object. + * If unlabeled or incorrectly labeled, the system "unlabeled" label + * shall be returned. + */ +char * +sepgsql_get_label(Oid classId, Oid objectId, int32 subId) +{ + ObjectAddress object; + char *label; + + object.classId = classId; + object.objectId = objectId; + object.objectSubId = subId; + + label = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG); + if (!label || security_check_context_raw(label)) + { + char *unlabeled; + + if (security_get_initial_context_raw("unlabeled", &unlabeled) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: failed to get initial security label: %m"))); + PG_TRY(); + { + label = pstrdup(unlabeled); + } + PG_FINALLY(); + { + freecon(unlabeled); + } + PG_END_TRY(); + } + return label; +} + +/* + * sepgsql_object_relabel + * + * An entrypoint of SECURITY LABEL statement + */ +void +sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel) +{ + /* + * validate format of the supplied security label, if it is security + * context of selinux. + */ + if (seclabel && + security_check_context_raw(seclabel) < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("SELinux: invalid security label: \"%s\"", seclabel))); + + /* + * Do actual permission checks for each object classes + */ + switch (object->classId) + { + case DatabaseRelationId: + sepgsql_database_relabel(object->objectId, seclabel); + break; + + case NamespaceRelationId: + sepgsql_schema_relabel(object->objectId, seclabel); + break; + + case RelationRelationId: + if (object->objectSubId == 0) + sepgsql_relation_relabel(object->objectId, + seclabel); + else + sepgsql_attribute_relabel(object->objectId, + object->objectSubId, + seclabel); + break; + + case ProcedureRelationId: + sepgsql_proc_relabel(object->objectId, seclabel); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("sepgsql provider does not support labels on %s", + getObjectTypeDescription(object, false)))); + break; + } +} + +/* + * TEXT sepgsql_getcon(VOID) + * + * It returns the security label of the client. + */ +PG_FUNCTION_INFO_V1(sepgsql_getcon); +Datum +sepgsql_getcon(PG_FUNCTION_ARGS) +{ + char *client_label; + + if (!sepgsql_is_enabled()) + PG_RETURN_NULL(); + + client_label = sepgsql_get_client_label(); + + PG_RETURN_TEXT_P(cstring_to_text(client_label)); +} + +/* + * BOOL sepgsql_setcon(TEXT) + * + * It switches the security label of the client. + */ +PG_FUNCTION_INFO_V1(sepgsql_setcon); +Datum +sepgsql_setcon(PG_FUNCTION_ARGS) +{ + const char *new_label; + + if (PG_ARGISNULL(0)) + new_label = NULL; + else + new_label = TextDatumGetCString(PG_GETARG_DATUM(0)); + + sepgsql_set_client_label(new_label); + + PG_RETURN_BOOL(true); +} + +/* + * TEXT sepgsql_mcstrans_in(TEXT) + * + * It translate the given qualified MLS/MCS range into raw format + * when mcstrans daemon is working. + */ +PG_FUNCTION_INFO_V1(sepgsql_mcstrans_in); +Datum +sepgsql_mcstrans_in(PG_FUNCTION_ARGS) +{ + text *label = PG_GETARG_TEXT_PP(0); + char *raw_label; + char *result; + + if (!sepgsql_is_enabled()) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("sepgsql is not enabled"))); + + if (selinux_trans_to_raw_context(text_to_cstring(label), + &raw_label) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: could not translate security label: %m"))); + + PG_TRY(); + { + result = pstrdup(raw_label); + } + PG_FINALLY(); + { + freecon(raw_label); + } + PG_END_TRY(); + + PG_RETURN_TEXT_P(cstring_to_text(result)); +} + +/* + * TEXT sepgsql_mcstrans_out(TEXT) + * + * It translate the given raw MLS/MCS range into qualified format + * when mcstrans daemon is working. + */ +PG_FUNCTION_INFO_V1(sepgsql_mcstrans_out); +Datum +sepgsql_mcstrans_out(PG_FUNCTION_ARGS) +{ + text *label = PG_GETARG_TEXT_PP(0); + char *qual_label; + char *result; + + if (!sepgsql_is_enabled()) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("sepgsql is not currently enabled"))); + + if (selinux_raw_to_trans_context(text_to_cstring(label), + &qual_label) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: could not translate security label: %m"))); + + PG_TRY(); + { + result = pstrdup(qual_label); + } + PG_FINALLY(); + { + freecon(qual_label); + } + PG_END_TRY(); + + PG_RETURN_TEXT_P(cstring_to_text(result)); +} + +/* + * quote_object_name + * + * Concatenate as many of the given strings as aren't NULL, with dots between. + * Quote any of the strings that wouldn't be valid identifiers otherwise. + */ +static char * +quote_object_name(const char *src1, const char *src2, + const char *src3, const char *src4) +{ + StringInfoData result; + + initStringInfo(&result); + if (src1) + appendStringInfoString(&result, quote_identifier(src1)); + if (src2) + appendStringInfo(&result, ".%s", quote_identifier(src2)); + if (src3) + appendStringInfo(&result, ".%s", quote_identifier(src3)); + if (src4) + appendStringInfo(&result, ".%s", quote_identifier(src4)); + return result.data; +} + +/* + * exec_object_restorecon + * + * This routine is a helper called by sepgsql_restorecon; it set up + * initial security labels of database objects within the supplied + * catalog OID. + */ +static void +exec_object_restorecon(struct selabel_handle *sehnd, Oid catalogId) +{ + Relation rel; + SysScanDesc sscan; + HeapTuple tuple; + char *database_name = get_database_name(MyDatabaseId); + char *namespace_name; + Oid namespace_id; + char *relation_name; + + /* + * Open the target catalog. We don't want to allow writable accesses by + * other session during initial labeling. + */ + rel = table_open(catalogId, AccessShareLock); + + sscan = systable_beginscan(rel, InvalidOid, false, + NULL, 0, NULL); + while (HeapTupleIsValid(tuple = systable_getnext(sscan))) + { + Form_pg_database datForm; + Form_pg_namespace nspForm; + Form_pg_class relForm; + Form_pg_attribute attForm; + Form_pg_proc proForm; + char *objname; + int objtype = 1234; + ObjectAddress object; + char *context; + + /* + * The way to determine object name depends on object classes. So, any + * branches set up `objtype', `objname' and `object' here. + */ + switch (catalogId) + { + case DatabaseRelationId: + datForm = (Form_pg_database) GETSTRUCT(tuple); + + objtype = SELABEL_DB_DATABASE; + + objname = quote_object_name(NameStr(datForm->datname), + NULL, NULL, NULL); + + object.classId = DatabaseRelationId; + object.objectId = datForm->oid; + object.objectSubId = 0; + break; + + case NamespaceRelationId: + nspForm = (Form_pg_namespace) GETSTRUCT(tuple); + + objtype = SELABEL_DB_SCHEMA; + + objname = quote_object_name(database_name, + NameStr(nspForm->nspname), + NULL, NULL); + + object.classId = NamespaceRelationId; + object.objectId = nspForm->oid; + object.objectSubId = 0; + break; + + case RelationRelationId: + relForm = (Form_pg_class) GETSTRUCT(tuple); + + if (relForm->relkind == RELKIND_RELATION || + relForm->relkind == RELKIND_PARTITIONED_TABLE) + objtype = SELABEL_DB_TABLE; + else if (relForm->relkind == RELKIND_SEQUENCE) + objtype = SELABEL_DB_SEQUENCE; + else if (relForm->relkind == RELKIND_VIEW) + objtype = SELABEL_DB_VIEW; + else + continue; /* no need to assign security label */ + + namespace_name = get_namespace_name(relForm->relnamespace); + objname = quote_object_name(database_name, + namespace_name, + NameStr(relForm->relname), + NULL); + pfree(namespace_name); + + object.classId = RelationRelationId; + object.objectId = relForm->oid; + object.objectSubId = 0; + break; + + case AttributeRelationId: + attForm = (Form_pg_attribute) GETSTRUCT(tuple); + + if (get_rel_relkind(attForm->attrelid) != RELKIND_RELATION && + get_rel_relkind(attForm->attrelid) != RELKIND_PARTITIONED_TABLE) + continue; /* no need to assign security label */ + + objtype = SELABEL_DB_COLUMN; + + namespace_id = get_rel_namespace(attForm->attrelid); + namespace_name = get_namespace_name(namespace_id); + relation_name = get_rel_name(attForm->attrelid); + objname = quote_object_name(database_name, + namespace_name, + relation_name, + NameStr(attForm->attname)); + pfree(namespace_name); + pfree(relation_name); + + object.classId = RelationRelationId; + object.objectId = attForm->attrelid; + object.objectSubId = attForm->attnum; + break; + + case ProcedureRelationId: + proForm = (Form_pg_proc) GETSTRUCT(tuple); + + objtype = SELABEL_DB_PROCEDURE; + + namespace_name = get_namespace_name(proForm->pronamespace); + objname = quote_object_name(database_name, + namespace_name, + NameStr(proForm->proname), + NULL); + pfree(namespace_name); + + object.classId = ProcedureRelationId; + object.objectId = proForm->oid; + object.objectSubId = 0; + break; + + default: + elog(ERROR, "unexpected catalog id: %u", catalogId); + objname = NULL; /* for compiler quiet */ + break; + } + + if (selabel_lookup_raw(sehnd, &context, objname, objtype) == 0) + { + PG_TRY(); + { + /* + * Check SELinux permission to relabel the fetched object, + * then do the actual relabeling. + */ + sepgsql_object_relabel(&object, context); + + SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, context); + } + PG_FINALLY(); + { + freecon(context); + } + PG_END_TRY(); + } + else if (errno == ENOENT) + ereport(WARNING, + (errmsg("SELinux: no initial label assigned for %s (type=%d), skipping", + objname, objtype))); + else + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: could not determine initial security label for %s (type=%d): %m", objname, objtype))); + + pfree(objname); + } + systable_endscan(sscan); + + table_close(rel, NoLock); +} + +/* + * BOOL sepgsql_restorecon(TEXT specfile) + * + * This function tries to assign initial security labels on all the object + * within the current database, according to the system setting. + * It is typically invoked by sepgsql-install script just after initdb, to + * assign initial security labels. + * + * If @specfile is not NULL, it uses explicitly specified specfile, instead + * of the system default. + */ +PG_FUNCTION_INFO_V1(sepgsql_restorecon); +Datum +sepgsql_restorecon(PG_FUNCTION_ARGS) +{ + struct selabel_handle *sehnd; + struct selinux_opt seopts; + + /* + * SELinux has to be enabled on the running platform. + */ + if (!sepgsql_is_enabled()) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("sepgsql is not currently enabled"))); + + /* + * Check DAC permission. Only superuser can set up initial security + * labels, like root-user in filesystems + */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: must be superuser to restore initial contexts"))); + + /* + * Open selabel_lookup(3) stuff. It provides a set of mapping between an + * initial security label and object class/name due to the system setting. + */ + if (PG_ARGISNULL(0)) + { + seopts.type = SELABEL_OPT_UNUSED; + seopts.value = NULL; + } + else + { + seopts.type = SELABEL_OPT_PATH; + seopts.value = TextDatumGetCString(PG_GETARG_DATUM(0)); + } + sehnd = selabel_open(SELABEL_CTX_DB, &seopts, 1); + if (!sehnd) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: failed to initialize labeling handle: %m"))); + PG_TRY(); + { + exec_object_restorecon(sehnd, DatabaseRelationId); + exec_object_restorecon(sehnd, NamespaceRelationId); + exec_object_restorecon(sehnd, RelationRelationId); + exec_object_restorecon(sehnd, AttributeRelationId); + exec_object_restorecon(sehnd, ProcedureRelationId); + } + PG_FINALLY(); + { + selabel_close(sehnd); + } + PG_END_TRY(); + + PG_RETURN_BOOL(true); +} diff --git a/contrib/sepgsql/launcher b/contrib/sepgsql/launcher new file mode 100755 index 0000000..7954549 --- /dev/null +++ b/contrib/sepgsql/launcher @@ -0,0 +1,52 @@ +#!/bin/sh +# +# A wrapper script to launch psql command in regression test +# +# Copyright (c) 2010-2023, PostgreSQL Global Development Group +# +# ------------------------------------------------------------------------- + +if [ $# -lt 1 ]; then + echo "usage: `basename $0` [options...]" + exit 1 +fi + +RUNCON=`which runcon` +if [ ! -e "$RUNCON" ]; then + echo "runcon command is not found" + exit 1 +fi + +# +# Read SQL from stdin +# +TEMP=`mktemp` +CONTEXT="unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255" + +while IFS='\\n' read LINE +do + if echo "$LINE" | grep -q "^-- @SECURITY-CONTEXT="; then + if [ -s "$TEMP" ]; then + if [ -n "$CONTEXT" ]; then + "$RUNCON" "$CONTEXT" $* < "$TEMP" + else + $* < $TEMP + fi + truncate -s0 $TEMP + fi + CONTEXT=`echo "$LINE" | sed 's/^-- @SECURITY-CONTEXT=//g'` + LINE="SELECT sepgsql_getcon(); -- confirm client privilege" + fi + echo "$LINE" >> $TEMP +done + +if [ -s "$TEMP" ]; then + if [ -n "$CONTEXT" ]; then + "$RUNCON" "$CONTEXT" $* < "$TEMP" + else + $* < $TEMP + fi +fi + +# cleanup temp file +rm -f $TEMP diff --git a/contrib/sepgsql/meson.build b/contrib/sepgsql/meson.build new file mode 100644 index 0000000..c8e518b --- /dev/null +++ b/contrib/sepgsql/meson.build @@ -0,0 +1,43 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not selinux.found() + subdir_done() +endif + +sepgsql_sources = files( + 'database.c', + 'dml.c', + 'hooks.c', + 'label.c', + 'proc.c', + 'relation.c', + 'schema.c', + 'selinux.c', + 'uavc.c', +) + +if host_system == 'windows' + sepgsql_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'sepgsql', + '--FILEDESC', 'sepgsql - SELinux integration',]) +endif + +sepgsql = shared_module('sepgsql', + sepgsql_sources, + c_pch: pch_postgres_h, + kwargs: contrib_mod_args + { + 'dependencies': [selinux, contrib_mod_args['dependencies']], + } +) +contrib_targets += sepgsql + +contrib_targets += custom_target('sepgsql.sql', + input: 'sepgsql.sql.in', + output: 'sepgsql.sql', + command: [sed, '-e', 's,MODULE_PATHNAME,$libdir/sepgsql,g', '@INPUT@'], + capture: true, + install: true, + install_dir: contrib_data_args['install_dir'], +) + +# TODO: implement sepgsql tests diff --git a/contrib/sepgsql/proc.c b/contrib/sepgsql/proc.c new file mode 100644 index 0000000..2182034 --- /dev/null +++ b/contrib/sepgsql/proc.c @@ -0,0 +1,333 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/proc.c + * + * Routines corresponding to procedure objects + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/dependency.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "commands/seclabel.h" +#include "lib/stringinfo.h" +#include "sepgsql.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +/* + * sepgsql_proc_post_create + * + * This routine assigns a default security label on a newly defined + * procedure. + */ +void +sepgsql_proc_post_create(Oid functionId) +{ + Relation rel; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple tuple; + char *nsp_name; + char *scontext; + char *tcontext; + char *ncontext; + uint32 required; + int i; + StringInfoData audit_name; + ObjectAddress object; + Form_pg_proc proForm; + + /* + * Fetch namespace of the new procedure. Because pg_proc entry is not + * visible right now, we need to scan the catalog using SnapshotSelf. + */ + rel = table_open(ProcedureRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_proc_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(functionId)); + + sscan = systable_beginscan(rel, ProcedureOidIndexId, true, + SnapshotSelf, 1, &skey); + + tuple = systable_getnext(sscan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for function %u", functionId); + + proForm = (Form_pg_proc) GETSTRUCT(tuple); + + /* + * check db_schema:{add_name} permission of the namespace + */ + object.classId = NamespaceRelationId; + object.objectId = proForm->pronamespace; + object.objectSubId = 0; + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__ADD_NAME, + getObjectIdentity(&object, false), + true); + + /* + * XXX - db_language:{implement} also should be checked here + */ + + + /* + * Compute a default security label when we create a new procedure object + * under the specified namespace. + */ + scontext = sepgsql_get_client_label(); + tcontext = sepgsql_get_label(NamespaceRelationId, + proForm->pronamespace, 0); + ncontext = sepgsql_compute_create(scontext, tcontext, + SEPG_CLASS_DB_PROCEDURE, + NameStr(proForm->proname)); + + /* + * check db_procedure:{create (install)} permission + */ + initStringInfo(&audit_name); + nsp_name = get_namespace_name(proForm->pronamespace); + appendStringInfo(&audit_name, "%s(", + quote_qualified_identifier(nsp_name, NameStr(proForm->proname))); + for (i = 0; i < proForm->pronargs; i++) + { + if (i > 0) + appendStringInfoChar(&audit_name, ','); + + object.classId = TypeRelationId; + object.objectId = proForm->proargtypes.values[i]; + object.objectSubId = 0; + appendStringInfoString(&audit_name, getObjectIdentity(&object, false)); + } + appendStringInfoChar(&audit_name, ')'); + + required = SEPG_DB_PROCEDURE__CREATE; + if (proForm->proleakproof) + required |= SEPG_DB_PROCEDURE__INSTALL; + + sepgsql_avc_check_perms_label(ncontext, + SEPG_CLASS_DB_PROCEDURE, + required, + audit_name.data, + true); + + /* + * Assign the default security label on a new procedure + */ + object.classId = ProcedureRelationId; + object.objectId = functionId; + object.objectSubId = 0; + SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); + + /* + * Cleanup + */ + systable_endscan(sscan); + table_close(rel, AccessShareLock); + + pfree(audit_name.data); + pfree(tcontext); + pfree(ncontext); +} + +/* + * sepgsql_proc_drop + * + * It checks privileges to drop the supplied function. + */ +void +sepgsql_proc_drop(Oid functionId) +{ + ObjectAddress object; + char *audit_name; + + /* + * check db_schema:{remove_name} permission + */ + object.classId = NamespaceRelationId; + object.objectId = get_func_namespace(functionId); + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__REMOVE_NAME, + audit_name, + true); + pfree(audit_name); + + /* + * check db_procedure:{drop} permission + */ + object.classId = ProcedureRelationId; + object.objectId = functionId; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_PROCEDURE, + SEPG_DB_PROCEDURE__DROP, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_proc_relabel + * + * It checks privileges to relabel the supplied function + * by the `seclabel'. + */ +void +sepgsql_proc_relabel(Oid functionId, const char *seclabel) +{ + ObjectAddress object; + char *audit_name; + + object.classId = ProcedureRelationId; + object.objectId = functionId; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + /* + * check db_procedure:{setattr relabelfrom} permission + */ + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_PROCEDURE, + SEPG_DB_PROCEDURE__SETATTR | + SEPG_DB_PROCEDURE__RELABELFROM, + audit_name, + true); + + /* + * check db_procedure:{relabelto} permission + */ + sepgsql_avc_check_perms_label(seclabel, + SEPG_CLASS_DB_PROCEDURE, + SEPG_DB_PROCEDURE__RELABELTO, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_proc_setattr + * + * It checks privileges to alter the supplied function. + */ +void +sepgsql_proc_setattr(Oid functionId) +{ + Relation rel; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple oldtup; + HeapTuple newtup; + Form_pg_proc oldform; + Form_pg_proc newform; + uint32 required; + ObjectAddress object; + char *audit_name; + + /* + * Fetch newer catalog + */ + rel = table_open(ProcedureRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_proc_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(functionId)); + + sscan = systable_beginscan(rel, ProcedureOidIndexId, true, + SnapshotSelf, 1, &skey); + newtup = systable_getnext(sscan); + if (!HeapTupleIsValid(newtup)) + elog(ERROR, "could not find tuple for function %u", functionId); + newform = (Form_pg_proc) GETSTRUCT(newtup); + + /* + * Fetch older catalog + */ + oldtup = SearchSysCache1(PROCOID, ObjectIdGetDatum(functionId)); + if (!HeapTupleIsValid(oldtup)) + elog(ERROR, "cache lookup failed for function %u", functionId); + oldform = (Form_pg_proc) GETSTRUCT(oldtup); + + /* + * Does this ALTER command takes operation to namespace? + */ + if (newform->pronamespace != oldform->pronamespace) + { + sepgsql_schema_remove_name(oldform->pronamespace); + sepgsql_schema_add_name(oldform->pronamespace); + } + if (strcmp(NameStr(newform->proname), NameStr(oldform->proname)) != 0) + sepgsql_schema_rename(oldform->pronamespace); + + /* + * check db_procedure:{setattr (install)} permission + */ + required = SEPG_DB_PROCEDURE__SETATTR; + if (!oldform->proleakproof && newform->proleakproof) + required |= SEPG_DB_PROCEDURE__INSTALL; + + object.classId = ProcedureRelationId; + object.objectId = functionId; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_PROCEDURE, + required, + audit_name, + true); + /* cleanups */ + pfree(audit_name); + + ReleaseSysCache(oldtup); + systable_endscan(sscan); + table_close(rel, AccessShareLock); +} + +/* + * sepgsql_proc_execute + * + * It checks privileges to execute the supplied function + */ +void +sepgsql_proc_execute(Oid functionId) +{ + ObjectAddress object; + char *audit_name; + + /* + * check db_procedure:{execute} permission + */ + object.classId = ProcedureRelationId; + object.objectId = functionId; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_PROCEDURE, + SEPG_DB_PROCEDURE__EXECUTE, + audit_name, + true); + pfree(audit_name); +} diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c new file mode 100644 index 0000000..4653a98 --- /dev/null +++ b/contrib/sepgsql/relation.c @@ -0,0 +1,773 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/relation.c + * + * Routines corresponding to relation/attribute objects + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/dependency.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_class.h" +#include "catalog/pg_namespace.h" +#include "commands/seclabel.h" +#include "lib/stringinfo.h" +#include "sepgsql.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +static void sepgsql_index_modify(Oid indexOid); + +/* + * sepgsql_attribute_post_create + * + * This routine assigns a default security label on a newly defined + * column, using ALTER TABLE ... ADD COLUMN. + * Note that this routine is not invoked in the case of CREATE TABLE, + * although it also defines columns in addition to table. + */ +void +sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum) +{ + Relation rel; + ScanKeyData skey[2]; + SysScanDesc sscan; + HeapTuple tuple; + char *scontext; + char *tcontext; + char *ncontext; + ObjectAddress object; + Form_pg_attribute attForm; + StringInfoData audit_name; + char relkind = get_rel_relkind(relOid); + + /* + * Only attributes within regular relations or partition relations have + * individual security labels. + */ + if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) + return; + + /* + * Compute a default security label of the new column underlying the + * specified relation, and check permission to create it. + */ + rel = table_open(AttributeRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_attribute_attrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relOid)); + ScanKeyInit(&skey[1], + Anum_pg_attribute_attnum, + BTEqualStrategyNumber, F_INT2EQ, + Int16GetDatum(attnum)); + + sscan = systable_beginscan(rel, AttributeRelidNumIndexId, true, + SnapshotSelf, 2, &skey[0]); + + tuple = systable_getnext(sscan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for column %d of relation %u", + attnum, relOid); + + attForm = (Form_pg_attribute) GETSTRUCT(tuple); + + scontext = sepgsql_get_client_label(); + tcontext = sepgsql_get_label(RelationRelationId, relOid, 0); + ncontext = sepgsql_compute_create(scontext, tcontext, + SEPG_CLASS_DB_COLUMN, + NameStr(attForm->attname)); + + /* + * check db_column:{create} permission + */ + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = 0; + + initStringInfo(&audit_name); + appendStringInfo(&audit_name, "%s.%s", + getObjectIdentity(&object, false), + quote_identifier(NameStr(attForm->attname))); + sepgsql_avc_check_perms_label(ncontext, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_COLUMN__CREATE, + audit_name.data, + true); + + /* + * Assign the default security label on a new procedure + */ + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = attnum; + SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); + + systable_endscan(sscan); + table_close(rel, AccessShareLock); + + pfree(tcontext); + pfree(ncontext); +} + +/* + * sepgsql_attribute_drop + * + * It checks privileges to drop the supplied column. + */ +void +sepgsql_attribute_drop(Oid relOid, AttrNumber attnum) +{ + ObjectAddress object; + char *audit_name; + char relkind = get_rel_relkind(relOid); + + if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) + return; + + /* + * check db_column:{drop} permission + */ + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = attnum; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_COLUMN__DROP, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_attribute_relabel + * + * It checks privileges to relabel the supplied column + * by the `seclabel'. + */ +void +sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum, + const char *seclabel) +{ + ObjectAddress object; + char *audit_name; + char relkind = get_rel_relkind(relOid); + + if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot set security label on non-regular columns"))); + + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = attnum; + audit_name = getObjectIdentity(&object, false); + + /* + * check db_column:{setattr relabelfrom} permission + */ + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_COLUMN__SETATTR | + SEPG_DB_COLUMN__RELABELFROM, + audit_name, + true); + + /* + * check db_column:{relabelto} permission + */ + sepgsql_avc_check_perms_label(seclabel, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_PROCEDURE__RELABELTO, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_attribute_setattr + * + * It checks privileges to alter the supplied column. + */ +void +sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum) +{ + ObjectAddress object; + char *audit_name; + char relkind = get_rel_relkind(relOid); + + if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) + return; + + /* + * check db_column:{setattr} permission + */ + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = attnum; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_COLUMN__SETATTR, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_relation_post_create + * + * The post creation hook of relation/attribute + */ +void +sepgsql_relation_post_create(Oid relOid) +{ + Relation rel; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple tuple; + Form_pg_class classForm; + ObjectAddress object; + uint16_t tclass; + char *scontext; /* subject */ + char *tcontext; /* schema */ + char *rcontext; /* relation */ + char *ccontext; /* column */ + char *nsp_name; + StringInfoData audit_name; + + /* + * Fetch catalog record of the new relation. Because pg_class entry is not + * visible right now, we need to scan the catalog using SnapshotSelf. + */ + rel = table_open(RelationRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_class_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relOid)); + + sscan = systable_beginscan(rel, ClassOidIndexId, true, + SnapshotSelf, 1, &skey); + + tuple = systable_getnext(sscan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for relation %u", relOid); + + classForm = (Form_pg_class) GETSTRUCT(tuple); + + /* ignore indexes on toast tables */ + if (classForm->relkind == RELKIND_INDEX && + classForm->relnamespace == PG_TOAST_NAMESPACE) + goto out; + + /* + * check db_schema:{add_name} permission of the namespace + */ + object.classId = NamespaceRelationId; + object.objectId = classForm->relnamespace; + object.objectSubId = 0; + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__ADD_NAME, + getObjectIdentity(&object, false), + true); + + switch (classForm->relkind) + { + case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: + tclass = SEPG_CLASS_DB_TABLE; + break; + case RELKIND_SEQUENCE: + tclass = SEPG_CLASS_DB_SEQUENCE; + break; + case RELKIND_VIEW: + tclass = SEPG_CLASS_DB_VIEW; + break; + case RELKIND_INDEX: + /* deal with indexes specially; no need for tclass */ + sepgsql_index_modify(relOid); + goto out; + default: + /* ignore other relkinds */ + goto out; + } + + /* + * Compute a default security label when we create a new relation object + * under the specified namespace. + */ + scontext = sepgsql_get_client_label(); + tcontext = sepgsql_get_label(NamespaceRelationId, + classForm->relnamespace, 0); + rcontext = sepgsql_compute_create(scontext, tcontext, tclass, + NameStr(classForm->relname)); + + /* + * check db_xxx:{create} permission + */ + nsp_name = get_namespace_name(classForm->relnamespace); + initStringInfo(&audit_name); + appendStringInfo(&audit_name, "%s.%s", + quote_identifier(nsp_name), + quote_identifier(NameStr(classForm->relname))); + sepgsql_avc_check_perms_label(rcontext, + tclass, + SEPG_DB_DATABASE__CREATE, + audit_name.data, + true); + + /* + * Assign the default security label on the new regular or partitioned + * relation. + */ + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = 0; + SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext); + + /* + * We also assign a default security label on columns of a new table. + */ + if (classForm->relkind == RELKIND_RELATION || + classForm->relkind == RELKIND_PARTITIONED_TABLE) + { + Relation arel; + ScanKeyData akey; + SysScanDesc ascan; + HeapTuple atup; + Form_pg_attribute attForm; + + arel = table_open(AttributeRelationId, AccessShareLock); + + ScanKeyInit(&akey, + Anum_pg_attribute_attrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relOid)); + + ascan = systable_beginscan(arel, AttributeRelidNumIndexId, true, + SnapshotSelf, 1, &akey); + + while (HeapTupleIsValid(atup = systable_getnext(ascan))) + { + attForm = (Form_pg_attribute) GETSTRUCT(atup); + + resetStringInfo(&audit_name); + appendStringInfo(&audit_name, "%s.%s.%s", + quote_identifier(nsp_name), + quote_identifier(NameStr(classForm->relname)), + quote_identifier(NameStr(attForm->attname))); + + ccontext = sepgsql_compute_create(scontext, + rcontext, + SEPG_CLASS_DB_COLUMN, + NameStr(attForm->attname)); + + /* + * check db_column:{create} permission + */ + sepgsql_avc_check_perms_label(ccontext, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_COLUMN__CREATE, + audit_name.data, + true); + + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = attForm->attnum; + SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext); + + pfree(ccontext); + } + systable_endscan(ascan); + table_close(arel, AccessShareLock); + } + pfree(rcontext); + +out: + systable_endscan(sscan); + table_close(rel, AccessShareLock); +} + +/* + * sepgsql_relation_drop + * + * It checks privileges to drop the supplied relation. + */ +void +sepgsql_relation_drop(Oid relOid) +{ + ObjectAddress object; + char *audit_name; + uint16_t tclass = 0; + char relkind = get_rel_relkind(relOid); + + switch (relkind) + { + case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: + tclass = SEPG_CLASS_DB_TABLE; + break; + case RELKIND_SEQUENCE: + tclass = SEPG_CLASS_DB_SEQUENCE; + break; + case RELKIND_VIEW: + tclass = SEPG_CLASS_DB_VIEW; + break; + case RELKIND_INDEX: + /* ignore indexes on toast tables */ + if (get_rel_namespace(relOid) == PG_TOAST_NAMESPACE) + return; + /* other indexes are handled specially below; no need for tclass */ + break; + default: + /* ignore other relkinds */ + return; + } + + /* + * check db_schema:{remove_name} permission + */ + object.classId = NamespaceRelationId; + object.objectId = get_rel_namespace(relOid); + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__REMOVE_NAME, + audit_name, + true); + pfree(audit_name); + + /* deal with indexes specially */ + if (relkind == RELKIND_INDEX) + { + sepgsql_index_modify(relOid); + return; + } + + /* + * check db_table/sequence/view:{drop} permission + */ + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + tclass, + SEPG_DB_TABLE__DROP, + audit_name, + true); + pfree(audit_name); + + /* + * check db_column:{drop} permission + */ + if (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE) + { + Form_pg_attribute attForm; + CatCList *attrList; + HeapTuple atttup; + int i; + + attrList = SearchSysCacheList1(ATTNUM, ObjectIdGetDatum(relOid)); + for (i = 0; i < attrList->n_members; i++) + { + atttup = &attrList->members[i]->tuple; + attForm = (Form_pg_attribute) GETSTRUCT(atttup); + + if (attForm->attisdropped) + continue; + + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = attForm->attnum; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_COLUMN__DROP, + audit_name, + true); + pfree(audit_name); + } + ReleaseCatCacheList(attrList); + } +} + +/* + * sepgsql_relation_truncate + * + * Check privileges to TRUNCATE the supplied relation. + */ +void +sepgsql_relation_truncate(Oid relOid) +{ + ObjectAddress object; + char *audit_name; + uint16_t tclass = 0; + char relkind = get_rel_relkind(relOid); + + switch (relkind) + { + case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: + tclass = SEPG_CLASS_DB_TABLE; + break; + default: + /* ignore other relkinds */ + return; + } + + /* + * check db_table:{truncate} permission + */ + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + tclass, + SEPG_DB_TABLE__TRUNCATE, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_relation_relabel + * + * It checks privileges to relabel the supplied relation by the `seclabel'. + */ +void +sepgsql_relation_relabel(Oid relOid, const char *seclabel) +{ + ObjectAddress object; + char *audit_name; + char relkind = get_rel_relkind(relOid); + uint16_t tclass = 0; + + if (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE) + tclass = SEPG_CLASS_DB_TABLE; + else if (relkind == RELKIND_SEQUENCE) + tclass = SEPG_CLASS_DB_SEQUENCE; + else if (relkind == RELKIND_VIEW) + tclass = SEPG_CLASS_DB_VIEW; + else + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot set security labels on relations except " + "for tables, sequences or views"))); + + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + /* + * check db_xxx:{setattr relabelfrom} permission + */ + sepgsql_avc_check_perms(&object, + tclass, + SEPG_DB_TABLE__SETATTR | + SEPG_DB_TABLE__RELABELFROM, + audit_name, + true); + + /* + * check db_xxx:{relabelto} permission + */ + sepgsql_avc_check_perms_label(seclabel, + tclass, + SEPG_DB_TABLE__RELABELTO, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_relation_setattr + * + * It checks privileges to set attribute of the supplied relation + */ +void +sepgsql_relation_setattr(Oid relOid) +{ + Relation rel; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple oldtup; + HeapTuple newtup; + Form_pg_class oldform; + Form_pg_class newform; + ObjectAddress object; + char *audit_name; + uint16_t tclass; + + switch (get_rel_relkind(relOid)) + { + case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: + tclass = SEPG_CLASS_DB_TABLE; + break; + case RELKIND_SEQUENCE: + tclass = SEPG_CLASS_DB_SEQUENCE; + break; + case RELKIND_VIEW: + tclass = SEPG_CLASS_DB_VIEW; + break; + case RELKIND_INDEX: + /* deal with indexes specially */ + sepgsql_index_modify(relOid); + return; + default: + /* other relkinds don't need additional work */ + return; + } + + /* + * Fetch newer catalog + */ + rel = table_open(RelationRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_class_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relOid)); + + sscan = systable_beginscan(rel, ClassOidIndexId, true, + SnapshotSelf, 1, &skey); + + newtup = systable_getnext(sscan); + if (!HeapTupleIsValid(newtup)) + elog(ERROR, "could not find tuple for relation %u", relOid); + newform = (Form_pg_class) GETSTRUCT(newtup); + + /* + * Fetch older catalog + */ + oldtup = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); + if (!HeapTupleIsValid(oldtup)) + elog(ERROR, "cache lookup failed for relation %u", relOid); + oldform = (Form_pg_class) GETSTRUCT(oldtup); + + /* + * Does this ALTER command takes operation to namespace? + */ + if (newform->relnamespace != oldform->relnamespace) + { + sepgsql_schema_remove_name(oldform->relnamespace); + sepgsql_schema_add_name(newform->relnamespace); + } + if (strcmp(NameStr(newform->relname), NameStr(oldform->relname)) != 0) + sepgsql_schema_rename(oldform->relnamespace); + + /* + * XXX - In the future version, db_tuple:{use} of system catalog entry + * shall be checked, if tablespace configuration is changed. + */ + + /* + * check db_xxx:{setattr} permission + */ + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + tclass, + SEPG_DB_TABLE__SETATTR, + audit_name, + true); + pfree(audit_name); + + ReleaseSysCache(oldtup); + systable_endscan(sscan); + table_close(rel, AccessShareLock); +} + +/* + * sepgsql_relation_setattr_extra + * + * It checks permission of the relation being referenced by extra attributes, + * such as pg_index entries. Like core PostgreSQL, sepgsql also does not deal + * with such entries as individual "objects", thus, modification of these + * entries shall be considered as setting an attribute of the underlying + * relation. + */ +static void +sepgsql_relation_setattr_extra(Relation catalog, + Oid catindex_id, + Oid extra_oid, + AttrNumber anum_relation_id, + AttrNumber anum_extra_id) +{ + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple tuple; + Datum datum; + bool isnull; + + ScanKeyInit(&skey, anum_extra_id, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extra_oid)); + + sscan = systable_beginscan(catalog, catindex_id, true, + SnapshotSelf, 1, &skey); + tuple = systable_getnext(sscan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for object %u in catalog \"%s\"", + extra_oid, RelationGetRelationName(catalog)); + + datum = heap_getattr(tuple, anum_relation_id, + RelationGetDescr(catalog), &isnull); + Assert(!isnull); + + sepgsql_relation_setattr(DatumGetObjectId(datum)); + + systable_endscan(sscan); +} + +/* + * sepgsql_index_modify + * Handle index create, update, drop + * + * Unlike other relation kinds, indexes do not have their own security labels, + * so instead of doing checks directly, treat them as extra attributes of their + * owning tables; so check 'setattr' permissions on the table. + */ +static void +sepgsql_index_modify(Oid indexOid) +{ + Relation catalog = table_open(IndexRelationId, AccessShareLock); + + /* check db_table:{setattr} permission of the table being indexed */ + sepgsql_relation_setattr_extra(catalog, + IndexRelidIndexId, + indexOid, + Anum_pg_index_indrelid, + Anum_pg_index_indexrelid); + table_close(catalog, AccessShareLock); +} diff --git a/contrib/sepgsql/schema.c b/contrib/sepgsql/schema.c new file mode 100644 index 0000000..9c77f20 --- /dev/null +++ b/contrib/sepgsql/schema.c @@ -0,0 +1,235 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/schema.c + * + * Routines corresponding to schema objects + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/table.h" +#include "catalog/dependency.h" +#include "catalog/pg_database.h" +#include "catalog/pg_namespace.h" +#include "commands/seclabel.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "sepgsql.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/snapmgr.h" + +/* + * sepgsql_schema_post_create + * + * This routine assigns a default security label on a newly defined + * schema. + */ +void +sepgsql_schema_post_create(Oid namespaceId) +{ + Relation rel; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple tuple; + char *tcontext; + char *ncontext; + const char *nsp_name; + ObjectAddress object; + Form_pg_namespace nspForm; + StringInfoData audit_name; + + /* + * Compute a default security label when we create a new schema object + * under the working database. + * + * XXX - upcoming version of libselinux supports to take object name to + * handle special treatment on default security label; such as special + * label on "pg_temp" schema. + */ + rel = table_open(NamespaceRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_namespace_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(namespaceId)); + + sscan = systable_beginscan(rel, NamespaceOidIndexId, true, + SnapshotSelf, 1, &skey); + tuple = systable_getnext(sscan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for namespace %u", namespaceId); + + nspForm = (Form_pg_namespace) GETSTRUCT(tuple); + nsp_name = NameStr(nspForm->nspname); + if (strncmp(nsp_name, "pg_temp_", 8) == 0) + nsp_name = "pg_temp"; + else if (strncmp(nsp_name, "pg_toast_temp_", 14) == 0) + nsp_name = "pg_toast_temp"; + + tcontext = sepgsql_get_label(DatabaseRelationId, MyDatabaseId, 0); + ncontext = sepgsql_compute_create(sepgsql_get_client_label(), + tcontext, + SEPG_CLASS_DB_SCHEMA, + nsp_name); + + /* + * check db_schema:{create} + */ + initStringInfo(&audit_name); + appendStringInfoString(&audit_name, quote_identifier(nsp_name)); + sepgsql_avc_check_perms_label(ncontext, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__CREATE, + audit_name.data, + true); + systable_endscan(sscan); + table_close(rel, AccessShareLock); + + /* + * Assign the default security label on a new procedure + */ + object.classId = NamespaceRelationId; + object.objectId = namespaceId; + object.objectSubId = 0; + SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); + + pfree(ncontext); + pfree(tcontext); +} + +/* + * sepgsql_schema_drop + * + * It checks privileges to drop the supplied schema object. + */ +void +sepgsql_schema_drop(Oid namespaceId) +{ + ObjectAddress object; + char *audit_name; + + /* + * check db_schema:{drop} permission + */ + object.classId = NamespaceRelationId; + object.objectId = namespaceId; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__DROP, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_schema_relabel + * + * It checks privileges to relabel the supplied schema + * by the `seclabel'. + */ +void +sepgsql_schema_relabel(Oid namespaceId, const char *seclabel) +{ + ObjectAddress object; + char *audit_name; + + object.classId = NamespaceRelationId; + object.objectId = namespaceId; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + /* + * check db_schema:{setattr relabelfrom} permission + */ + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__SETATTR | + SEPG_DB_SCHEMA__RELABELFROM, + audit_name, + true); + + /* + * check db_schema:{relabelto} permission + */ + sepgsql_avc_check_perms_label(seclabel, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__RELABELTO, + audit_name, + true); + pfree(audit_name); +} + +/* + * sepgsql_schema_check_perms + * + * utility routine to check db_schema:{xxx} permissions + */ +static bool +check_schema_perms(Oid namespaceId, uint32 required, bool abort_on_violation) +{ + ObjectAddress object; + char *audit_name; + bool result; + + object.classId = NamespaceRelationId; + object.objectId = namespaceId; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + + result = sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SCHEMA, + required, + audit_name, + abort_on_violation); + pfree(audit_name); + + return result; +} + +/* db_schema:{setattr} permission */ +void +sepgsql_schema_setattr(Oid namespaceId) +{ + check_schema_perms(namespaceId, SEPG_DB_SCHEMA__SETATTR, true); +} + +/* db_schema:{search} permission */ +bool +sepgsql_schema_search(Oid namespaceId, bool abort_on_violation) +{ + return check_schema_perms(namespaceId, + SEPG_DB_SCHEMA__SEARCH, + abort_on_violation); +} + +void +sepgsql_schema_add_name(Oid namespaceId) +{ + check_schema_perms(namespaceId, SEPG_DB_SCHEMA__ADD_NAME, true); +} + +void +sepgsql_schema_remove_name(Oid namespaceId) +{ + check_schema_perms(namespaceId, SEPG_DB_SCHEMA__REMOVE_NAME, true); +} + +void +sepgsql_schema_rename(Oid namespaceId) +{ + check_schema_perms(namespaceId, + SEPG_DB_SCHEMA__ADD_NAME | + SEPG_DB_SCHEMA__REMOVE_NAME, + true); +} diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c new file mode 100644 index 0000000..cd6784c --- /dev/null +++ b/contrib/sepgsql/selinux.c @@ -0,0 +1,888 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/selinux.c + * + * Interactions between userspace and selinux in kernelspace, + * using libselinux api. + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "lib/stringinfo.h" + +#include "sepgsql.h" + +/* + * selinux_catalog + * + * This mapping table enables to translate the name of object classes and + * access vectors to/from their own codes. + * When we ask SELinux whether the required privileges are allowed or not, + * we use security_compute_av(3). It needs us to represent object classes + * and access vectors using 'external' codes defined in the security policy. + * It is determined in the runtime, not build time. So, it needs an internal + * service to translate object class/access vectors which we want to check + * into the code which kernel want to be given. + */ +static struct +{ + const char *class_name; + uint16 class_code; + struct + { + const char *av_name; + uint32 av_code; + } av[32]; +} selinux_catalog[] = + +{ + { + "process", SEPG_CLASS_PROCESS, + { + { + "transition", SEPG_PROCESS__TRANSITION + }, + { + "dyntransition", SEPG_PROCESS__DYNTRANSITION + }, + { + "setcurrent", SEPG_PROCESS__SETCURRENT + }, + { + NULL, 0UL + } + } + }, + { + "file", SEPG_CLASS_FILE, + { + { + "read", SEPG_FILE__READ + }, + { + "write", SEPG_FILE__WRITE + }, + { + "create", SEPG_FILE__CREATE + }, + { + "getattr", SEPG_FILE__GETATTR + }, + { + "unlink", SEPG_FILE__UNLINK + }, + { + "rename", SEPG_FILE__RENAME + }, + { + "append", SEPG_FILE__APPEND + }, + { + NULL, 0UL + } + } + }, + { + "dir", SEPG_CLASS_DIR, + { + { + "read", SEPG_DIR__READ + }, + { + "write", SEPG_DIR__WRITE + }, + { + "create", SEPG_DIR__CREATE + }, + { + "getattr", SEPG_DIR__GETATTR + }, + { + "unlink", SEPG_DIR__UNLINK + }, + { + "rename", SEPG_DIR__RENAME + }, + { + "search", SEPG_DIR__SEARCH + }, + { + "add_name", SEPG_DIR__ADD_NAME + }, + { + "remove_name", SEPG_DIR__REMOVE_NAME + }, + { + "rmdir", SEPG_DIR__RMDIR + }, + { + "reparent", SEPG_DIR__REPARENT + }, + { + NULL, 0UL + } + } + }, + { + "lnk_file", SEPG_CLASS_LNK_FILE, + { + { + "read", SEPG_LNK_FILE__READ + }, + { + "write", SEPG_LNK_FILE__WRITE + }, + { + "create", SEPG_LNK_FILE__CREATE + }, + { + "getattr", SEPG_LNK_FILE__GETATTR + }, + { + "unlink", SEPG_LNK_FILE__UNLINK + }, + { + "rename", SEPG_LNK_FILE__RENAME + }, + { + NULL, 0UL + } + } + }, + { + "chr_file", SEPG_CLASS_CHR_FILE, + { + { + "read", SEPG_CHR_FILE__READ + }, + { + "write", SEPG_CHR_FILE__WRITE + }, + { + "create", SEPG_CHR_FILE__CREATE + }, + { + "getattr", SEPG_CHR_FILE__GETATTR + }, + { + "unlink", SEPG_CHR_FILE__UNLINK + }, + { + "rename", SEPG_CHR_FILE__RENAME + }, + { + NULL, 0UL + } + } + }, + { + "blk_file", SEPG_CLASS_BLK_FILE, + { + { + "read", SEPG_BLK_FILE__READ + }, + { + "write", SEPG_BLK_FILE__WRITE + }, + { + "create", SEPG_BLK_FILE__CREATE + }, + { + "getattr", SEPG_BLK_FILE__GETATTR + }, + { + "unlink", SEPG_BLK_FILE__UNLINK + }, + { + "rename", SEPG_BLK_FILE__RENAME + }, + { + NULL, 0UL + } + } + }, + { + "sock_file", SEPG_CLASS_SOCK_FILE, + { + { + "read", SEPG_SOCK_FILE__READ + }, + { + "write", SEPG_SOCK_FILE__WRITE + }, + { + "create", SEPG_SOCK_FILE__CREATE + }, + { + "getattr", SEPG_SOCK_FILE__GETATTR + }, + { + "unlink", SEPG_SOCK_FILE__UNLINK + }, + { + "rename", SEPG_SOCK_FILE__RENAME + }, + { + NULL, 0UL + } + } + }, + { + "fifo_file", SEPG_CLASS_FIFO_FILE, + { + { + "read", SEPG_FIFO_FILE__READ + }, + { + "write", SEPG_FIFO_FILE__WRITE + }, + { + "create", SEPG_FIFO_FILE__CREATE + }, + { + "getattr", SEPG_FIFO_FILE__GETATTR + }, + { + "unlink", SEPG_FIFO_FILE__UNLINK + }, + { + "rename", SEPG_FIFO_FILE__RENAME + }, + { + NULL, 0UL + } + } + }, + { + "db_database", SEPG_CLASS_DB_DATABASE, + { + { + "create", SEPG_DB_DATABASE__CREATE + }, + { + "drop", SEPG_DB_DATABASE__DROP + }, + { + "getattr", SEPG_DB_DATABASE__GETATTR + }, + { + "setattr", SEPG_DB_DATABASE__SETATTR + }, + { + "relabelfrom", SEPG_DB_DATABASE__RELABELFROM + }, + { + "relabelto", SEPG_DB_DATABASE__RELABELTO + }, + { + "access", SEPG_DB_DATABASE__ACCESS + }, + { + "load_module", SEPG_DB_DATABASE__LOAD_MODULE + }, + { + NULL, 0UL + }, + } + }, + { + "db_schema", SEPG_CLASS_DB_SCHEMA, + { + { + "create", SEPG_DB_SCHEMA__CREATE + }, + { + "drop", SEPG_DB_SCHEMA__DROP + }, + { + "getattr", SEPG_DB_SCHEMA__GETATTR + }, + { + "setattr", SEPG_DB_SCHEMA__SETATTR + }, + { + "relabelfrom", SEPG_DB_SCHEMA__RELABELFROM + }, + { + "relabelto", SEPG_DB_SCHEMA__RELABELTO + }, + { + "search", SEPG_DB_SCHEMA__SEARCH + }, + { + "add_name", SEPG_DB_SCHEMA__ADD_NAME + }, + { + "remove_name", SEPG_DB_SCHEMA__REMOVE_NAME + }, + { + NULL, 0UL + }, + } + }, + { + "db_table", SEPG_CLASS_DB_TABLE, + { + { + "create", SEPG_DB_TABLE__CREATE + }, + { + "drop", SEPG_DB_TABLE__DROP + }, + { + "getattr", SEPG_DB_TABLE__GETATTR + }, + { + "setattr", SEPG_DB_TABLE__SETATTR + }, + { + "relabelfrom", SEPG_DB_TABLE__RELABELFROM + }, + { + "relabelto", SEPG_DB_TABLE__RELABELTO + }, + { + "select", SEPG_DB_TABLE__SELECT + }, + { + "update", SEPG_DB_TABLE__UPDATE + }, + { + "insert", SEPG_DB_TABLE__INSERT + }, + { + "delete", SEPG_DB_TABLE__DELETE + }, + { + "lock", SEPG_DB_TABLE__LOCK + }, + { + "truncate", SEPG_DB_TABLE__TRUNCATE + }, + { + NULL, 0UL + }, + } + }, + { + "db_sequence", SEPG_CLASS_DB_SEQUENCE, + { + { + "create", SEPG_DB_SEQUENCE__CREATE + }, + { + "drop", SEPG_DB_SEQUENCE__DROP + }, + { + "getattr", SEPG_DB_SEQUENCE__GETATTR + }, + { + "setattr", SEPG_DB_SEQUENCE__SETATTR + }, + { + "relabelfrom", SEPG_DB_SEQUENCE__RELABELFROM + }, + { + "relabelto", SEPG_DB_SEQUENCE__RELABELTO + }, + { + "get_value", SEPG_DB_SEQUENCE__GET_VALUE + }, + { + "next_value", SEPG_DB_SEQUENCE__NEXT_VALUE + }, + { + "set_value", SEPG_DB_SEQUENCE__SET_VALUE + }, + { + NULL, 0UL + }, + } + }, + { + "db_procedure", SEPG_CLASS_DB_PROCEDURE, + { + { + "create", SEPG_DB_PROCEDURE__CREATE + }, + { + "drop", SEPG_DB_PROCEDURE__DROP + }, + { + "getattr", SEPG_DB_PROCEDURE__GETATTR + }, + { + "setattr", SEPG_DB_PROCEDURE__SETATTR + }, + { + "relabelfrom", SEPG_DB_PROCEDURE__RELABELFROM + }, + { + "relabelto", SEPG_DB_PROCEDURE__RELABELTO + }, + { + "execute", SEPG_DB_PROCEDURE__EXECUTE + }, + { + "entrypoint", SEPG_DB_PROCEDURE__ENTRYPOINT + }, + { + "install", SEPG_DB_PROCEDURE__INSTALL + }, + { + NULL, 0UL + }, + } + }, + { + "db_column", SEPG_CLASS_DB_COLUMN, + { + { + "create", SEPG_DB_COLUMN__CREATE + }, + { + "drop", SEPG_DB_COLUMN__DROP + }, + { + "getattr", SEPG_DB_COLUMN__GETATTR + }, + { + "setattr", SEPG_DB_COLUMN__SETATTR + }, + { + "relabelfrom", SEPG_DB_COLUMN__RELABELFROM + }, + { + "relabelto", SEPG_DB_COLUMN__RELABELTO + }, + { + "select", SEPG_DB_COLUMN__SELECT + }, + { + "update", SEPG_DB_COLUMN__UPDATE + }, + { + "insert", SEPG_DB_COLUMN__INSERT + }, + { + NULL, 0UL + }, + } + }, + { + "db_tuple", SEPG_CLASS_DB_TUPLE, + { + { + "relabelfrom", SEPG_DB_TUPLE__RELABELFROM + }, + { + "relabelto", SEPG_DB_TUPLE__RELABELTO + }, + { + "select", SEPG_DB_TUPLE__SELECT + }, + { + "update", SEPG_DB_TUPLE__UPDATE + }, + { + "insert", SEPG_DB_TUPLE__INSERT + }, + { + "delete", SEPG_DB_TUPLE__DELETE + }, + { + NULL, 0UL + }, + } + }, + { + "db_blob", SEPG_CLASS_DB_BLOB, + { + { + "create", SEPG_DB_BLOB__CREATE + }, + { + "drop", SEPG_DB_BLOB__DROP + }, + { + "getattr", SEPG_DB_BLOB__GETATTR + }, + { + "setattr", SEPG_DB_BLOB__SETATTR + }, + { + "relabelfrom", SEPG_DB_BLOB__RELABELFROM + }, + { + "relabelto", SEPG_DB_BLOB__RELABELTO + }, + { + "read", SEPG_DB_BLOB__READ + }, + { + "write", SEPG_DB_BLOB__WRITE + }, + { + "import", SEPG_DB_BLOB__IMPORT + }, + { + "export", SEPG_DB_BLOB__EXPORT + }, + { + NULL, 0UL + }, + } + }, + { + "db_language", SEPG_CLASS_DB_LANGUAGE, + { + { + "create", SEPG_DB_LANGUAGE__CREATE + }, + { + "drop", SEPG_DB_LANGUAGE__DROP + }, + { + "getattr", SEPG_DB_LANGUAGE__GETATTR + }, + { + "setattr", SEPG_DB_LANGUAGE__SETATTR + }, + { + "relabelfrom", SEPG_DB_LANGUAGE__RELABELFROM + }, + { + "relabelto", SEPG_DB_LANGUAGE__RELABELTO + }, + { + "implement", SEPG_DB_LANGUAGE__IMPLEMENT + }, + { + "execute", SEPG_DB_LANGUAGE__EXECUTE + }, + { + NULL, 0UL + }, + } + }, + { + "db_view", SEPG_CLASS_DB_VIEW, + { + { + "create", SEPG_DB_VIEW__CREATE + }, + { + "drop", SEPG_DB_VIEW__DROP + }, + { + "getattr", SEPG_DB_VIEW__GETATTR + }, + { + "setattr", SEPG_DB_VIEW__SETATTR + }, + { + "relabelfrom", SEPG_DB_VIEW__RELABELFROM + }, + { + "relabelto", SEPG_DB_VIEW__RELABELTO + }, + { + "expand", SEPG_DB_VIEW__EXPAND + }, + { + NULL, 0UL + }, + } + }, +}; + +/* + * sepgsql_mode + * + * SEPGSQL_MODE_DISABLED: Disabled on runtime + * SEPGSQL_MODE_DEFAULT: Same as system settings + * SEPGSQL_MODE_PERMISSIVE: Always permissive mode + * SEPGSQL_MODE_INTERNAL: Same as permissive, except for no audit logs + */ +static int sepgsql_mode = SEPGSQL_MODE_INTERNAL; + +/* + * sepgsql_is_enabled + */ +bool +sepgsql_is_enabled(void) +{ + return (sepgsql_mode != SEPGSQL_MODE_DISABLED); +} + +/* + * sepgsql_get_mode + */ +int +sepgsql_get_mode(void) +{ + return sepgsql_mode; +} + +/* + * sepgsql_set_mode + */ +int +sepgsql_set_mode(int new_mode) +{ + int old_mode = sepgsql_mode; + + sepgsql_mode = new_mode; + + return old_mode; +} + +/* + * sepgsql_getenforce + * + * It returns whether the current working mode tries to enforce access + * control decision, or not. It shall be enforced when sepgsql_mode is + * SEPGSQL_MODE_DEFAULT and system is running in enforcing mode. + */ +bool +sepgsql_getenforce(void) +{ + if (sepgsql_mode == SEPGSQL_MODE_DEFAULT && + selinux_status_getenforce() > 0) + return true; + + return false; +} + +/* + * sepgsql_audit_log + * + * It generates a security audit record. It writes out audit records + * into standard PG's logfile. + * + * SELinux can control what should be audited and should not using + * "auditdeny" and "auditallow" rules in the security policy. In the + * default, all the access violations are audited, and all the access + * allowed are not audited. But we can set up the security policy, so + * we can have exceptions. So, it is necessary to follow the suggestion + * come from the security policy. (av_decision.auditallow and auditdeny) + * + * Security audit is an important feature, because it enables us to check + * what was happen if we have a security incident. In fact, ISO/IEC15408 + * defines several security functionalities for audit features. + */ +void +sepgsql_audit_log(bool denied, + bool enforcing, + const char *scontext, + const char *tcontext, + uint16 tclass, + uint32 audited, + const char *audit_name) +{ + StringInfoData buf; + const char *class_name; + const char *av_name; + int i; + + /* lookup name of the object class */ + Assert(tclass < SEPG_CLASS_MAX); + class_name = selinux_catalog[tclass].class_name; + + /* lookup name of the permissions */ + initStringInfo(&buf); + appendStringInfo(&buf, "%s {", + (denied ? "denied" : "allowed")); + for (i = 0; selinux_catalog[tclass].av[i].av_name; i++) + { + if (audited & (1UL << i)) + { + av_name = selinux_catalog[tclass].av[i].av_name; + appendStringInfo(&buf, " %s", av_name); + } + } + appendStringInfoString(&buf, " }"); + + /* + * Call external audit module, if loaded + */ + appendStringInfo(&buf, " scontext=%s tcontext=%s tclass=%s", + scontext, tcontext, class_name); + if (audit_name) + appendStringInfo(&buf, " name=\"%s\"", audit_name); + + if (enforcing) + appendStringInfoString(&buf, " permissive=0"); + else + appendStringInfoString(&buf, " permissive=1"); + + ereport(LOG, (errmsg("SELinux: %s", buf.data))); +} + +/* + * sepgsql_compute_avd + * + * It actually asks SELinux what permissions are allowed on a pair of + * the security contexts and object class. It also returns what permissions + * should be audited on access violation or allowed. + * In most cases, subject's security context (scontext) is a client, and + * target security context (tcontext) is a database object. + * + * The access control decision shall be set on the given av_decision. + * The av_decision.allowed has a bitmask of SEPG___ + * to suggest a set of allowed actions in this object class. + */ +void +sepgsql_compute_avd(const char *scontext, + const char *tcontext, + uint16 tclass, + struct av_decision *avd) +{ + const char *tclass_name; + security_class_t tclass_ex; + struct av_decision avd_ex; + int i, + deny_unknown = security_deny_unknown(); + + /* Get external code of the object class */ + Assert(tclass < SEPG_CLASS_MAX); + Assert(tclass == selinux_catalog[tclass].class_code); + + tclass_name = selinux_catalog[tclass].class_name; + tclass_ex = string_to_security_class(tclass_name); + + if (tclass_ex == 0) + { + /* + * If the current security policy does not support permissions + * corresponding to database objects, we fill up them with dummy data. + * If security_deny_unknown() returns positive value, undefined + * permissions should be denied. Otherwise, allowed + */ + avd->allowed = (security_deny_unknown() > 0 ? 0 : ~0); + avd->auditallow = 0U; + avd->auditdeny = ~0U; + avd->flags = 0; + + return; + } + + /* + * Ask SELinux what is allowed set of permissions on a pair of the + * security contexts and the given object class. + */ + if (security_compute_av_flags_raw(scontext, + tcontext, + tclass_ex, 0, &avd_ex) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux could not compute av_decision: " + "scontext=%s tcontext=%s tclass=%s: %m", + scontext, tcontext, tclass_name))); + + /* + * SELinux returns its access control decision as a set of permissions + * represented in external code which depends on run-time environment. So, + * we need to translate it to the internal representation before returning + * results for the caller. + */ + memset(avd, 0, sizeof(struct av_decision)); + + for (i = 0; selinux_catalog[tclass].av[i].av_name; i++) + { + access_vector_t av_code_ex; + const char *av_name = selinux_catalog[tclass].av[i].av_name; + uint32 av_code = selinux_catalog[tclass].av[i].av_code; + + av_code_ex = string_to_av_perm(tclass_ex, av_name); + if (av_code_ex == 0) + { + /* fill up undefined permissions */ + if (!deny_unknown) + avd->allowed |= av_code; + avd->auditdeny |= av_code; + + continue; + } + + if (avd_ex.allowed & av_code_ex) + avd->allowed |= av_code; + if (avd_ex.auditallow & av_code_ex) + avd->auditallow |= av_code; + if (avd_ex.auditdeny & av_code_ex) + avd->auditdeny |= av_code; + } +} + +/* + * sepgsql_compute_create + * + * It returns a default security context to be assigned on a new database + * object. SELinux compute it based on a combination of client, upper object + * which owns the new object and object class. + * + * For example, when a client (staff_u:staff_r:staff_t:s0) tries to create + * a new table within a schema (system_u:object_r:sepgsql_schema_t:s0), + * SELinux looks-up its security policy. If it has a special rule on the + * combination of these security contexts and object class (db_table), + * it returns the security context suggested by the special rule. + * Otherwise, it returns the security context of schema, as is. + * + * We expect the caller already applies sanity/validation checks on the + * given security context. + * + * scontext: security context of the subject (mostly, peer process). + * tcontext: security context of the upper database object. + * tclass: class code (SEPG_CLASS_*) of the new object in creation + */ +char * +sepgsql_compute_create(const char *scontext, + const char *tcontext, + uint16 tclass, + const char *objname) +{ + char *ncontext; + security_class_t tclass_ex; + const char *tclass_name; + char *result; + + /* Get external code of the object class */ + Assert(tclass < SEPG_CLASS_MAX); + + tclass_name = selinux_catalog[tclass].class_name; + tclass_ex = string_to_security_class(tclass_name); + + /* + * Ask SELinux what is the default context for the given object class on a + * pair of security contexts + */ + if (security_compute_create_name_raw(scontext, + tcontext, + tclass_ex, + objname, + &ncontext) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux could not compute a new context: " + "scontext=%s tcontext=%s tclass=%s: %m", + scontext, tcontext, tclass_name))); + + /* + * libselinux returns malloc()'ed string, so we need to copy it on the + * palloc()'ed region. + */ + PG_TRY(); + { + result = pstrdup(ncontext); + } + PG_FINALLY(); + { + freecon(ncontext); + } + PG_END_TRY(); + + return result; +} diff --git a/contrib/sepgsql/sepgsql-regtest.te b/contrib/sepgsql/sepgsql-regtest.te new file mode 100644 index 0000000..569c4da --- /dev/null +++ b/contrib/sepgsql/sepgsql-regtest.te @@ -0,0 +1,261 @@ +policy_module(sepgsql-regtest, 1.08) + +gen_require(` + all_userspace_class_perms +') + +## +##

+## Allow to launch regression test of SE-PostgreSQL +## Don't switch to TRUE in normal cases +##

+##
+gen_tunable(sepgsql_regression_test_mode, false) + +# +# Type definitions for regression test +# +type sepgsql_regtest_trusted_proc_exec_t; +postgresql_procedure_object(sepgsql_regtest_trusted_proc_exec_t) +type sepgsql_nosuch_trusted_proc_exec_t; +postgresql_procedure_object(sepgsql_nosuch_trusted_proc_exec_t) + +type sepgsql_regtest_invisible_schema_t; +postgresql_schema_object(sepgsql_regtest_invisible_schema_t); + +# +# Test domains for self defined unconfined / superuser +# +role sepgsql_regtest_superuser_r; +userdom_base_user_template(sepgsql_regtest_superuser) +userdom_manage_home_role(sepgsql_regtest_superuser_r, sepgsql_regtest_superuser_t) +userdom_exec_user_home_content_files(sepgsql_regtest_superuser_t) +userdom_write_user_tmp_sockets(sepgsql_regtest_superuser_t) + +auth_read_passwd(sepgsql_regtest_superuser_t) + +optional_policy(` + postgresql_stream_connect(sepgsql_regtest_superuser_t) + postgresql_unconfined(sepgsql_regtest_superuser_t) +') +optional_policy(` + unconfined_stream_connect(sepgsql_regtest_superuser_t) + unconfined_rw_pipes(sepgsql_regtest_superuser_t) +') +optional_policy(` + gen_require(` + attribute sepgsql_client_type; + ') + allow sepgsql_regtest_superuser_t self : process { setcurrent }; + allow sepgsql_regtest_superuser_t { self sepgsql_client_type } : process { dyntransition }; +') + +# Type transition rules +allow sepgsql_regtest_user_t sepgsql_regtest_dba_t : process { transition }; +type_transition sepgsql_regtest_user_t sepgsql_regtest_trusted_proc_exec_t:process sepgsql_regtest_dba_t; +type_transition sepgsql_regtest_user_t sepgsql_nosuch_trusted_proc_exec_t:process sepgsql_regtest_nosuch_t; + +# +# Test domains for database administrators +# +role sepgsql_regtest_dba_r; +userdom_base_user_template(sepgsql_regtest_dba) +userdom_manage_home_role(sepgsql_regtest_dba_r, sepgsql_regtest_dba_t) +userdom_exec_user_home_content_files(sepgsql_regtest_dba_t) +userdom_write_user_tmp_sockets(sepgsql_regtest_user_t) + +auth_read_passwd(sepgsql_regtest_dba_t) + +optional_policy(` + postgresql_admin(sepgsql_regtest_dba_t, sepgsql_regtest_dba_r) + postgresql_stream_connect(sepgsql_regtest_dba_t) +') +optional_policy(` + unconfined_stream_connect(sepgsql_regtest_dba_t) + unconfined_rw_pipes(sepgsql_regtest_dba_t) +') + +# Type transition rules +allow sepgsql_regtest_dba_t self : process { setcurrent }; +allow sepgsql_regtest_dba_t sepgsql_regtest_user_t : process { dyntransition }; +allow sepgsql_regtest_dba_t sepgsql_regtest_foo_t : process { dyntransition }; +allow sepgsql_regtest_dba_t sepgsql_regtest_var_t : process { dyntransition }; + +# special rule for system columns +optional_policy(` + gen_require(` + attribute sepgsql_table_type; + type sepgsql_sysobj_t; + ') + type_transition sepgsql_regtest_dba_t sepgsql_table_type:db_column sepgsql_sysobj_t "ctid"; + type_transition sepgsql_regtest_dba_t sepgsql_table_type:db_column sepgsql_sysobj_t "oid"; + type_transition sepgsql_regtest_dba_t sepgsql_table_type:db_column sepgsql_sysobj_t "xmin"; + type_transition sepgsql_regtest_dba_t sepgsql_table_type:db_column sepgsql_sysobj_t "xmax"; + type_transition sepgsql_regtest_dba_t sepgsql_table_type:db_column sepgsql_sysobj_t "cmin"; + type_transition sepgsql_regtest_dba_t sepgsql_table_type:db_column sepgsql_sysobj_t "cmax"; + type_transition sepgsql_regtest_dba_t sepgsql_table_type:db_column sepgsql_sysobj_t "tableoid"; +') + +# +# Dummy domain for unpriv users +# +role sepgsql_regtest_user_r; +userdom_base_user_template(sepgsql_regtest_user) +userdom_manage_home_role(sepgsql_regtest_user_r, sepgsql_regtest_user_t) +userdom_exec_user_home_content_files(sepgsql_regtest_user_t) +userdom_write_user_tmp_sockets(sepgsql_regtest_user_t) + +auth_read_passwd(sepgsql_regtest_user_t) + +optional_policy(` + postgresql_role(sepgsql_regtest_user_r, sepgsql_regtest_user_t) + postgresql_stream_connect(sepgsql_regtest_user_t) +') +optional_policy(` + unconfined_stream_connect(sepgsql_regtest_user_t) + unconfined_rw_pipes(sepgsql_regtest_user_t) +') +# Type transition rules +allow sepgsql_regtest_user_t sepgsql_regtest_dba_t : process { transition }; +type_transition sepgsql_regtest_user_t sepgsql_regtest_trusted_proc_exec_t:process sepgsql_regtest_dba_t; +type_transition sepgsql_regtest_user_t sepgsql_nosuch_trusted_proc_exec_t:process sepgsql_regtest_nosuch_t; + +# +# Dummy domain for (virtual) connection pooler software +# +# XXX - this test scenario assumes sepgsql_regtest_pool_t domain performs +# as a typical connection pool server; that switches the client label of +# this session prior to any user queries. The sepgsql_regtest_(foo|var)_t +# is allowed to access its own table types, but not allowed to reference +# other's one. +# +role sepgsql_regtest_pool_r; +userdom_base_user_template(sepgsql_regtest_pool) +userdom_manage_home_role(sepgsql_regtest_pool_r, sepgsql_regtest_pool_t) +userdom_exec_user_home_content_files(sepgsql_regtest_pool_t) +userdom_write_user_tmp_sockets(sepgsql_regtest_pool_t) + +auth_read_passwd(sepgsql_regtest_pool_t) + +type sepgsql_regtest_foo_t; +type sepgsql_regtest_var_t; +type sepgsql_regtest_foo_table_t; +type sepgsql_regtest_var_table_t; + +allow sepgsql_regtest_foo_t sepgsql_regtest_foo_table_t:db_table { getattr select update insert delete lock }; +allow sepgsql_regtest_foo_t sepgsql_regtest_foo_table_t:db_column { getattr select update insert }; +allow sepgsql_regtest_foo_t sepgsql_regtest_foo_table_t:db_tuple { select update insert delete }; + +allow sepgsql_regtest_var_t sepgsql_regtest_var_table_t:db_table { getattr select update insert delete lock }; +allow sepgsql_regtest_var_t sepgsql_regtest_var_table_t:db_column { getattr select update insert }; +allow sepgsql_regtest_var_t sepgsql_regtest_var_table_t:db_tuple { select update insert delete }; + +optional_policy(` + gen_require(` + class db_table { truncate }; + ') + + allow sepgsql_regtest_superuser_t sepgsql_regtest_foo_table_t:db_table { truncate }; +') + +optional_policy(` + gen_require(` + role unconfined_r; + ') + postgresql_role(unconfined_r, sepgsql_regtest_foo_t) + postgresql_role(unconfined_r, sepgsql_regtest_var_t) + postgresql_table_object(sepgsql_regtest_foo_table_t) + postgresql_table_object(sepgsql_regtest_var_table_t) +') +optional_policy(` + postgresql_stream_connect(sepgsql_regtest_pool_t) + postgresql_role(sepgsql_regtest_pool_r, sepgsql_regtest_pool_t) +') +optional_policy(` + unconfined_stream_connect(sepgsql_regtest_pool_t) + unconfined_rw_pipes(sepgsql_regtest_pool_t) +') +# type transitions +allow sepgsql_regtest_pool_t self:process { setcurrent }; +allow sepgsql_regtest_pool_t sepgsql_regtest_dba_t:process { transition }; +type_transition sepgsql_regtest_pool_t sepgsql_regtest_trusted_proc_exec_t:process sepgsql_regtest_dba_t; + +allow { sepgsql_regtest_foo_t sepgsql_regtest_var_t } self:process { setcurrent }; +allow { sepgsql_regtest_foo_t sepgsql_regtest_var_t } sepgsql_regtest_pool_t:process { dyntransition }; + +# +# Dummy domain for non-exist users +# +role sepgsql_regtest_nosuch_r; +userdom_base_user_template(sepgsql_regtest_nosuch) +optional_policy(` + postgresql_role(sepgsql_regtest_nosuch_r, sepgsql_regtest_nosuch_t) +') + +# +# Rules to launch psql in the dummy domains +# +optional_policy(` + gen_require(` + role unconfined_r; + type unconfined_t; + type sepgsql_trusted_proc_t; + ') + tunable_policy(`sepgsql_regression_test_mode',` + allow unconfined_t self : process { setcurrent dyntransition }; + allow unconfined_t sepgsql_regtest_dba_t : process { transition dyntransition }; + allow unconfined_t sepgsql_regtest_superuser_t : process { transition dyntransition }; + allow unconfined_t sepgsql_regtest_user_t : process { transition dyntransition }; + allow unconfined_t sepgsql_regtest_pool_t : process { transition dyntransition }; + ') + role unconfined_r types sepgsql_regtest_dba_t; + role unconfined_r types sepgsql_regtest_superuser_t; + role unconfined_r types sepgsql_regtest_user_t; + role unconfined_r types sepgsql_regtest_nosuch_t; + role unconfined_r types sepgsql_trusted_proc_t; + + role unconfined_r types sepgsql_regtest_pool_t; + role unconfined_r types sepgsql_regtest_foo_t; + role unconfined_r types sepgsql_regtest_var_t; +') + +# +# Rule to make MCS policy work on regression test +# +# NOTE: MCS (multi category security) policy was enabled by default, to +# allow DAC style access control, in the previous selinux policy. +# However, its definition was changed later, then a limited number of +# applications are restricted by MCS policy, for container features +# mainly. The rules below enables MCS policy for domains of regression +# test also, even if base security policy does not apply. If base policy +# is old and MCS is enabled in default, rules below does nothing. +# +optional_policy(` + gen_require(` + type sepgsql_trusted_proc_t; + ') + mcs_constrained(sepgsql_regtest_dba_t) + mcs_constrained(sepgsql_regtest_superuser_t) + mcs_constrained(sepgsql_regtest_user_t) + mcs_constrained(sepgsql_regtest_nosuch_t) + mcs_constrained(sepgsql_trusted_proc_t) + + mcs_constrained(sepgsql_regtest_pool_t) + mcs_constrained(sepgsql_regtest_foo_t) + mcs_constrained(sepgsql_regtest_var_t) +') + +# +# Rule to execute original trusted procedures +# +# These rules intends to allow any valid client types to launch trusted- +# procedures (including ones causes domain transition to invalid domain) +# being labeled as sepgsql_regtest_trusted_proc_exec_t and +# sepgsql_nosuch_trusted_proc_exec_t. +# +optional_policy(` + gen_require(` + attribute sepgsql_client_type; + ') + allow sepgsql_client_type { sepgsql_regtest_trusted_proc_exec_t sepgsql_nosuch_trusted_proc_exec_t }:db_procedure { getattr execute entrypoint }; +') diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h new file mode 100644 index 0000000..00164aa --- /dev/null +++ b/contrib/sepgsql/sepgsql.h @@ -0,0 +1,324 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/sepgsql.h + * + * Definitions corresponding to SE-PostgreSQL + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#ifndef SEPGSQL_H +#define SEPGSQL_H + +#include "catalog/objectaddress.h" +#include "fmgr.h" + +#include +#include + +/* + * SE-PostgreSQL Label Tag + */ +#define SEPGSQL_LABEL_TAG "selinux" + +/* + * SE-PostgreSQL performing mode + */ +#define SEPGSQL_MODE_DEFAULT 1 +#define SEPGSQL_MODE_PERMISSIVE 2 +#define SEPGSQL_MODE_INTERNAL 3 +#define SEPGSQL_MODE_DISABLED 4 + +/* + * Internally used code of object classes + */ +#define SEPG_CLASS_PROCESS 0 +#define SEPG_CLASS_FILE 1 +#define SEPG_CLASS_DIR 2 +#define SEPG_CLASS_LNK_FILE 3 +#define SEPG_CLASS_CHR_FILE 4 +#define SEPG_CLASS_BLK_FILE 5 +#define SEPG_CLASS_SOCK_FILE 6 +#define SEPG_CLASS_FIFO_FILE 7 +#define SEPG_CLASS_DB_DATABASE 8 +#define SEPG_CLASS_DB_SCHEMA 9 +#define SEPG_CLASS_DB_TABLE 10 +#define SEPG_CLASS_DB_SEQUENCE 11 +#define SEPG_CLASS_DB_PROCEDURE 12 +#define SEPG_CLASS_DB_COLUMN 13 +#define SEPG_CLASS_DB_TUPLE 14 +#define SEPG_CLASS_DB_BLOB 15 +#define SEPG_CLASS_DB_LANGUAGE 16 +#define SEPG_CLASS_DB_VIEW 17 +#define SEPG_CLASS_MAX 18 + +/* + * Internally used code of access vectors + */ +#define SEPG_PROCESS__TRANSITION (1<<0) +#define SEPG_PROCESS__DYNTRANSITION (1<<1) +#define SEPG_PROCESS__SETCURRENT (1<<2) + +#define SEPG_FILE__READ (1<<0) +#define SEPG_FILE__WRITE (1<<1) +#define SEPG_FILE__CREATE (1<<2) +#define SEPG_FILE__GETATTR (1<<3) +#define SEPG_FILE__UNLINK (1<<4) +#define SEPG_FILE__RENAME (1<<5) +#define SEPG_FILE__APPEND (1<<6) + +#define SEPG_DIR__READ (SEPG_FILE__READ) +#define SEPG_DIR__WRITE (SEPG_FILE__WRITE) +#define SEPG_DIR__CREATE (SEPG_FILE__CREATE) +#define SEPG_DIR__GETATTR (SEPG_FILE__GETATTR) +#define SEPG_DIR__UNLINK (SEPG_FILE__UNLINK) +#define SEPG_DIR__RENAME (SEPG_FILE__RENAME) +#define SEPG_DIR__SEARCH (1<<6) +#define SEPG_DIR__ADD_NAME (1<<7) +#define SEPG_DIR__REMOVE_NAME (1<<8) +#define SEPG_DIR__RMDIR (1<<9) +#define SEPG_DIR__REPARENT (1<<10) + +#define SEPG_LNK_FILE__READ (SEPG_FILE__READ) +#define SEPG_LNK_FILE__WRITE (SEPG_FILE__WRITE) +#define SEPG_LNK_FILE__CREATE (SEPG_FILE__CREATE) +#define SEPG_LNK_FILE__GETATTR (SEPG_FILE__GETATTR) +#define SEPG_LNK_FILE__UNLINK (SEPG_FILE__UNLINK) +#define SEPG_LNK_FILE__RENAME (SEPG_FILE__RENAME) + +#define SEPG_CHR_FILE__READ (SEPG_FILE__READ) +#define SEPG_CHR_FILE__WRITE (SEPG_FILE__WRITE) +#define SEPG_CHR_FILE__CREATE (SEPG_FILE__CREATE) +#define SEPG_CHR_FILE__GETATTR (SEPG_FILE__GETATTR) +#define SEPG_CHR_FILE__UNLINK (SEPG_FILE__UNLINK) +#define SEPG_CHR_FILE__RENAME (SEPG_FILE__RENAME) + +#define SEPG_BLK_FILE__READ (SEPG_FILE__READ) +#define SEPG_BLK_FILE__WRITE (SEPG_FILE__WRITE) +#define SEPG_BLK_FILE__CREATE (SEPG_FILE__CREATE) +#define SEPG_BLK_FILE__GETATTR (SEPG_FILE__GETATTR) +#define SEPG_BLK_FILE__UNLINK (SEPG_FILE__UNLINK) +#define SEPG_BLK_FILE__RENAME (SEPG_FILE__RENAME) + +#define SEPG_SOCK_FILE__READ (SEPG_FILE__READ) +#define SEPG_SOCK_FILE__WRITE (SEPG_FILE__WRITE) +#define SEPG_SOCK_FILE__CREATE (SEPG_FILE__CREATE) +#define SEPG_SOCK_FILE__GETATTR (SEPG_FILE__GETATTR) +#define SEPG_SOCK_FILE__UNLINK (SEPG_FILE__UNLINK) +#define SEPG_SOCK_FILE__RENAME (SEPG_FILE__RENAME) + +#define SEPG_FIFO_FILE__READ (SEPG_FILE__READ) +#define SEPG_FIFO_FILE__WRITE (SEPG_FILE__WRITE) +#define SEPG_FIFO_FILE__CREATE (SEPG_FILE__CREATE) +#define SEPG_FIFO_FILE__GETATTR (SEPG_FILE__GETATTR) +#define SEPG_FIFO_FILE__UNLINK (SEPG_FILE__UNLINK) +#define SEPG_FIFO_FILE__RENAME (SEPG_FILE__RENAME) + +#define SEPG_DB_DATABASE__CREATE (1<<0) +#define SEPG_DB_DATABASE__DROP (1<<1) +#define SEPG_DB_DATABASE__GETATTR (1<<2) +#define SEPG_DB_DATABASE__SETATTR (1<<3) +#define SEPG_DB_DATABASE__RELABELFROM (1<<4) +#define SEPG_DB_DATABASE__RELABELTO (1<<5) +#define SEPG_DB_DATABASE__ACCESS (1<<6) +#define SEPG_DB_DATABASE__LOAD_MODULE (1<<7) + +#define SEPG_DB_SCHEMA__CREATE (SEPG_DB_DATABASE__CREATE) +#define SEPG_DB_SCHEMA__DROP (SEPG_DB_DATABASE__DROP) +#define SEPG_DB_SCHEMA__GETATTR (SEPG_DB_DATABASE__GETATTR) +#define SEPG_DB_SCHEMA__SETATTR (SEPG_DB_DATABASE__SETATTR) +#define SEPG_DB_SCHEMA__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) +#define SEPG_DB_SCHEMA__RELABELTO (SEPG_DB_DATABASE__RELABELTO) +#define SEPG_DB_SCHEMA__SEARCH (1<<6) +#define SEPG_DB_SCHEMA__ADD_NAME (1<<7) +#define SEPG_DB_SCHEMA__REMOVE_NAME (1<<8) + +#define SEPG_DB_TABLE__CREATE (SEPG_DB_DATABASE__CREATE) +#define SEPG_DB_TABLE__DROP (SEPG_DB_DATABASE__DROP) +#define SEPG_DB_TABLE__GETATTR (SEPG_DB_DATABASE__GETATTR) +#define SEPG_DB_TABLE__SETATTR (SEPG_DB_DATABASE__SETATTR) +#define SEPG_DB_TABLE__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) +#define SEPG_DB_TABLE__RELABELTO (SEPG_DB_DATABASE__RELABELTO) +#define SEPG_DB_TABLE__SELECT (1<<6) +#define SEPG_DB_TABLE__UPDATE (1<<7) +#define SEPG_DB_TABLE__INSERT (1<<8) +#define SEPG_DB_TABLE__DELETE (1<<9) +#define SEPG_DB_TABLE__LOCK (1<<10) +#define SEPG_DB_TABLE__TRUNCATE (1<<11) + +#define SEPG_DB_SEQUENCE__CREATE (SEPG_DB_DATABASE__CREATE) +#define SEPG_DB_SEQUENCE__DROP (SEPG_DB_DATABASE__DROP) +#define SEPG_DB_SEQUENCE__GETATTR (SEPG_DB_DATABASE__GETATTR) +#define SEPG_DB_SEQUENCE__SETATTR (SEPG_DB_DATABASE__SETATTR) +#define SEPG_DB_SEQUENCE__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) +#define SEPG_DB_SEQUENCE__RELABELTO (SEPG_DB_DATABASE__RELABELTO) +#define SEPG_DB_SEQUENCE__GET_VALUE (1<<6) +#define SEPG_DB_SEQUENCE__NEXT_VALUE (1<<7) +#define SEPG_DB_SEQUENCE__SET_VALUE (1<<8) + +#define SEPG_DB_PROCEDURE__CREATE (SEPG_DB_DATABASE__CREATE) +#define SEPG_DB_PROCEDURE__DROP (SEPG_DB_DATABASE__DROP) +#define SEPG_DB_PROCEDURE__GETATTR (SEPG_DB_DATABASE__GETATTR) +#define SEPG_DB_PROCEDURE__SETATTR (SEPG_DB_DATABASE__SETATTR) +#define SEPG_DB_PROCEDURE__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) +#define SEPG_DB_PROCEDURE__RELABELTO (SEPG_DB_DATABASE__RELABELTO) +#define SEPG_DB_PROCEDURE__EXECUTE (1<<6) +#define SEPG_DB_PROCEDURE__ENTRYPOINT (1<<7) +#define SEPG_DB_PROCEDURE__INSTALL (1<<8) + +#define SEPG_DB_COLUMN__CREATE (SEPG_DB_DATABASE__CREATE) +#define SEPG_DB_COLUMN__DROP (SEPG_DB_DATABASE__DROP) +#define SEPG_DB_COLUMN__GETATTR (SEPG_DB_DATABASE__GETATTR) +#define SEPG_DB_COLUMN__SETATTR (SEPG_DB_DATABASE__SETATTR) +#define SEPG_DB_COLUMN__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) +#define SEPG_DB_COLUMN__RELABELTO (SEPG_DB_DATABASE__RELABELTO) +#define SEPG_DB_COLUMN__SELECT (1<<6) +#define SEPG_DB_COLUMN__UPDATE (1<<7) +#define SEPG_DB_COLUMN__INSERT (1<<8) + +#define SEPG_DB_TUPLE__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) +#define SEPG_DB_TUPLE__RELABELTO (SEPG_DB_DATABASE__RELABELTO) +#define SEPG_DB_TUPLE__SELECT (SEPG_DB_DATABASE__GETATTR) +#define SEPG_DB_TUPLE__UPDATE (SEPG_DB_DATABASE__SETATTR) +#define SEPG_DB_TUPLE__INSERT (SEPG_DB_DATABASE__CREATE) +#define SEPG_DB_TUPLE__DELETE (SEPG_DB_DATABASE__DROP) + +#define SEPG_DB_BLOB__CREATE (SEPG_DB_DATABASE__CREATE) +#define SEPG_DB_BLOB__DROP (SEPG_DB_DATABASE__DROP) +#define SEPG_DB_BLOB__GETATTR (SEPG_DB_DATABASE__GETATTR) +#define SEPG_DB_BLOB__SETATTR (SEPG_DB_DATABASE__SETATTR) +#define SEPG_DB_BLOB__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) +#define SEPG_DB_BLOB__RELABELTO (SEPG_DB_DATABASE__RELABELTO) +#define SEPG_DB_BLOB__READ (1<<6) +#define SEPG_DB_BLOB__WRITE (1<<7) +#define SEPG_DB_BLOB__IMPORT (1<<8) +#define SEPG_DB_BLOB__EXPORT (1<<9) + +#define SEPG_DB_LANGUAGE__CREATE (SEPG_DB_DATABASE__CREATE) +#define SEPG_DB_LANGUAGE__DROP (SEPG_DB_DATABASE__DROP) +#define SEPG_DB_LANGUAGE__GETATTR (SEPG_DB_DATABASE__GETATTR) +#define SEPG_DB_LANGUAGE__SETATTR (SEPG_DB_DATABASE__SETATTR) +#define SEPG_DB_LANGUAGE__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) +#define SEPG_DB_LANGUAGE__RELABELTO (SEPG_DB_DATABASE__RELABELTO) +#define SEPG_DB_LANGUAGE__IMPLEMENT (1<<6) +#define SEPG_DB_LANGUAGE__EXECUTE (1<<7) + +#define SEPG_DB_VIEW__CREATE (SEPG_DB_DATABASE__CREATE) +#define SEPG_DB_VIEW__DROP (SEPG_DB_DATABASE__DROP) +#define SEPG_DB_VIEW__GETATTR (SEPG_DB_DATABASE__GETATTR) +#define SEPG_DB_VIEW__SETATTR (SEPG_DB_DATABASE__SETATTR) +#define SEPG_DB_VIEW__RELABELFROM (SEPG_DB_DATABASE__RELABELFROM) +#define SEPG_DB_VIEW__RELABELTO (SEPG_DB_DATABASE__RELABELTO) +#define SEPG_DB_VIEW__EXPAND (1<<6) + +/* + * hooks.c + */ +extern bool sepgsql_get_permissive(void); +extern bool sepgsql_get_debug_audit(void); + +/* + * selinux.c + */ +extern bool sepgsql_is_enabled(void); +extern int sepgsql_get_mode(void); +extern int sepgsql_set_mode(int new_mode); +extern bool sepgsql_getenforce(void); + +extern void sepgsql_audit_log(bool denied, + bool enforcing, + const char *scontext, + const char *tcontext, + uint16 tclass, + uint32 audited, + const char *audit_name); + +extern void sepgsql_compute_avd(const char *scontext, + const char *tcontext, + uint16 tclass, + struct av_decision *avd); + +extern char *sepgsql_compute_create(const char *scontext, + const char *tcontext, + uint16 tclass, + const char *objname); + +/* + * uavc.c + */ +#define SEPGSQL_AVC_NOAUDIT ((void *)(-1)) +extern bool sepgsql_avc_check_perms_label(const char *tcontext, + uint16 tclass, + uint32 required, + const char *audit_name, + bool abort_on_violation); +extern bool sepgsql_avc_check_perms(const ObjectAddress *tobject, + uint16 tclass, + uint32 required, + const char *audit_name, + bool abort_on_violation); +extern char *sepgsql_avc_trusted_proc(Oid functionId); +extern void sepgsql_avc_init(void); + +/* + * label.c + */ +extern char *sepgsql_get_client_label(void); +extern void sepgsql_init_client_label(void); +extern char *sepgsql_get_label(Oid classId, Oid objectId, int32 subId); + +extern void sepgsql_object_relabel(const ObjectAddress *object, + const char *seclabel); + +/* + * dml.c + */ +extern bool sepgsql_dml_privileges(List *rangeTabls, List *rteperminfos, + bool abort_on_violation); + +/* + * database.c + */ +extern void sepgsql_database_post_create(Oid databaseId, + const char *dtemplate); +extern void sepgsql_database_drop(Oid databaseId); +extern void sepgsql_database_relabel(Oid databaseId, const char *seclabel); +extern void sepgsql_database_setattr(Oid databaseId); + +/* + * schema.c + */ +extern void sepgsql_schema_post_create(Oid namespaceId); +extern void sepgsql_schema_drop(Oid namespaceId); +extern void sepgsql_schema_relabel(Oid namespaceId, const char *seclabel); +extern void sepgsql_schema_setattr(Oid namespaceId); +extern bool sepgsql_schema_search(Oid namespaceId, bool abort_on_violation); +extern void sepgsql_schema_add_name(Oid namespaceId); +extern void sepgsql_schema_remove_name(Oid namespaceId); +extern void sepgsql_schema_rename(Oid namespaceId); + +/* + * relation.c + */ +extern void sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum); +extern void sepgsql_attribute_drop(Oid relOid, AttrNumber attnum); +extern void sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum, + const char *seclabel); +extern void sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum); +extern void sepgsql_relation_post_create(Oid relOid); +extern void sepgsql_relation_drop(Oid relOid); +extern void sepgsql_relation_truncate(Oid relOid); +extern void sepgsql_relation_relabel(Oid relOid, const char *seclabel); +extern void sepgsql_relation_setattr(Oid relOid); + +/* + * proc.c + */ +extern void sepgsql_proc_post_create(Oid functionId); +extern void sepgsql_proc_drop(Oid functionId); +extern void sepgsql_proc_relabel(Oid functionId, const char *seclabel); +extern void sepgsql_proc_setattr(Oid functionId); +extern void sepgsql_proc_execute(Oid functionId); + +#endif /* SEPGSQL_H */ diff --git a/contrib/sepgsql/sepgsql.sql.in b/contrib/sepgsql/sepgsql.sql.in new file mode 100644 index 0000000..917d12d --- /dev/null +++ b/contrib/sepgsql/sepgsql.sql.in @@ -0,0 +1,37 @@ +-- +-- contrib/sepgsql/sepgsql.sql +-- +-- [Step to install] +-- +-- 1. Run initdb +-- to set up a new database cluster. +-- +-- 2. Edit $PGDATA/postgresql.conf +-- to add 'MODULE_PATHNAME' to shared_preload_libraries. +-- +-- Example) +-- shared_preload_libraries = 'MODULE_PATHNAME' +-- +-- 3. Run this script for each databases +-- This script installs corresponding functions, and assigns initial +-- security labels on target database objects. +-- It can be run both single-user mode and multi-user mode, according +-- to your preference. +-- +-- Example) +-- $ for DBNAME in template0 template1 postgres; \ +-- do \ +-- postgres --single -F -c exit_on_error=true -D $PGDATA $DBNAME \ +-- < /path/to/script/sepgsql.sql > /dev/null \ +-- done +-- +-- 4. Start postmaster, +-- if you initialized the database in single-user mode. +-- +LOAD 'MODULE_PATHNAME'; +CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_getcon() RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_getcon' LANGUAGE C; +CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_setcon(text) RETURNS bool AS 'MODULE_PATHNAME', 'sepgsql_setcon' LANGUAGE C; +CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_mcstrans_in(text) RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_mcstrans_in' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_mcstrans_out(text) RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_mcstrans_out' LANGUAGE C STRICT; +CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_restorecon(text) RETURNS bool AS 'MODULE_PATHNAME', 'sepgsql_restorecon' LANGUAGE C; +SELECT sepgsql_restorecon(NULL); diff --git a/contrib/sepgsql/sql/alter.sql b/contrib/sepgsql/sql/alter.sql new file mode 100644 index 0000000..f114449 --- /dev/null +++ b/contrib/sepgsql/sql/alter.sql @@ -0,0 +1,197 @@ +-- +-- Test for various ALTER statements +-- + +-- clean-up in case a prior regression run failed +SET client_min_messages TO 'warning'; +DROP DATABASE IF EXISTS sepgsql_test_regression_1; +DROP DATABASE IF EXISTS sepgsql_test_regression; +DROP USER IF EXISTS regress_sepgsql_test_user; +RESET client_min_messages; + +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 + +-- +-- CREATE Objects to be altered (with debug_audit being silent) +-- +CREATE DATABASE sepgsql_test_regression_1; + +CREATE USER regress_sepgsql_test_user; + +CREATE SCHEMA regtest_schema_1; +CREATE SCHEMA regtest_schema_2; + +GRANT ALL ON SCHEMA regtest_schema_1 TO public; +GRANT ALL ON SCHEMA regtest_schema_2 TO public; + +SET search_path = regtest_schema_1, regtest_schema_2, public; + +CREATE TABLE regtest_table_1 (a int, b text); + +CREATE TABLE regtest_table_2 (c text) inherits (regtest_table_1); + +CREATE TABLE regtest_table_3 (x int primary key, y text); + +--- +-- partitioned table parent +CREATE TABLE regtest_ptable_1 (o int, p text) PARTITION BY RANGE (o); + +-- partitioned table children +CREATE TABLE regtest_ptable_1_ones PARTITION OF regtest_ptable_1 FOR VALUES FROM ('0') TO ('10'); +CREATE TABLE regtest_ptable_1_tens PARTITION OF regtest_ptable_1 FOR VALUES FROM ('10') TO ('100'); +--- + +CREATE SEQUENCE regtest_seq_1; + +CREATE VIEW regtest_view_1 AS SELECT * FROM regtest_table_1 WHERE a > 0; + +CREATE FUNCTION regtest_func_1 (text) RETURNS bool + AS 'BEGIN RETURN true; END' LANGUAGE 'plpgsql'; + +-- switch on debug_audit +SET sepgsql.debug_audit = true; +SET client_min_messages = LOG; + +-- +-- ALTER xxx OWNER TO +-- +-- XXX: It should take db_xxx:{setattr} permission checks even if +-- owner is not actually changed. +-- +ALTER DATABASE sepgsql_test_regression_1 OWNER TO regress_sepgsql_test_user; +ALTER DATABASE sepgsql_test_regression_1 OWNER TO regress_sepgsql_test_user; +ALTER SCHEMA regtest_schema_1 OWNER TO regress_sepgsql_test_user; +ALTER SCHEMA regtest_schema_1 OWNER TO regress_sepgsql_test_user; +ALTER TABLE regtest_table_1 OWNER TO regress_sepgsql_test_user; +ALTER TABLE regtest_table_1 OWNER TO regress_sepgsql_test_user; +ALTER TABLE regtest_ptable_1 OWNER TO regress_sepgsql_test_user; +ALTER TABLE regtest_ptable_1_ones OWNER TO regress_sepgsql_test_user; +ALTER SEQUENCE regtest_seq_1 OWNER TO regress_sepgsql_test_user; +ALTER SEQUENCE regtest_seq_1 OWNER TO regress_sepgsql_test_user; +ALTER VIEW regtest_view_1 OWNER TO regress_sepgsql_test_user; +ALTER VIEW regtest_view_1 OWNER TO regress_sepgsql_test_user; +ALTER FUNCTION regtest_func_1(text) OWNER TO regress_sepgsql_test_user; +ALTER FUNCTION regtest_func_1(text) OWNER TO regress_sepgsql_test_user; + +-- +-- ALTER xxx SET SCHEMA +-- +ALTER TABLE regtest_table_1 SET SCHEMA regtest_schema_2; +ALTER TABLE regtest_ptable_1 SET SCHEMA regtest_schema_2; +ALTER TABLE regtest_ptable_1_ones SET SCHEMA regtest_schema_2; +ALTER SEQUENCE regtest_seq_1 SET SCHEMA regtest_schema_2; +ALTER VIEW regtest_view_1 SET SCHEMA regtest_schema_2; +ALTER FUNCTION regtest_func_1(text) SET SCHEMA regtest_schema_2; + +-- +-- ALTER xxx RENAME TO +-- +ALTER DATABASE sepgsql_test_regression_1 RENAME TO sepgsql_test_regression; +ALTER SCHEMA regtest_schema_1 RENAME TO regtest_schema; +ALTER TABLE regtest_table_1 RENAME TO regtest_table; + +--- +-- partitioned table parent +ALTER TABLE regtest_ptable_1 RENAME TO regtest_ptable; +-- partitioned table child +ALTER TABLE regtest_ptable_1_ones RENAME TO regtest_table_part; +--- + +ALTER SEQUENCE regtest_seq_1 RENAME TO regtest_seq; +ALTER VIEW regtest_view_1 RENAME TO regtest_view; +ALTER FUNCTION regtest_func_1(text) RENAME TO regtest_func; + +SET search_path = regtest_schema, regtest_schema_2, public; + +-- +-- misc ALTER commands +-- +ALTER DATABASE sepgsql_test_regression CONNECTION LIMIT 999; +ALTER DATABASE sepgsql_test_regression SET search_path TO regtest_schema, public; -- not supported yet + +ALTER TABLE regtest_table ADD COLUMN d float; +ALTER TABLE regtest_table DROP COLUMN d; +ALTER TABLE regtest_table ALTER b SET DEFAULT 'abcd'; -- not supported yet +ALTER TABLE regtest_table ALTER b SET DEFAULT 'XYZ'; -- not supported yet +ALTER TABLE regtest_table ALTER b DROP DEFAULT; -- not supported yet +ALTER TABLE regtest_table ALTER b SET NOT NULL; +ALTER TABLE regtest_table ALTER b DROP NOT NULL; +ALTER TABLE regtest_table ALTER b SET STATISTICS -1; +ALTER TABLE regtest_table ALTER b SET (n_distinct = 999); +ALTER TABLE regtest_table ALTER b SET STORAGE PLAIN; +ALTER TABLE regtest_table ADD CONSTRAINT test_fk FOREIGN KEY (a) REFERENCES regtest_table_3(x); -- not supported +ALTER TABLE regtest_table ADD CONSTRAINT test_ck CHECK (b like '%abc%') NOT VALID; -- not supported +ALTER TABLE regtest_table VALIDATE CONSTRAINT test_ck; -- not supported +ALTER TABLE regtest_table DROP CONSTRAINT test_ck; -- not supported + +CREATE TRIGGER regtest_test_trig BEFORE UPDATE ON regtest_table + FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); + +ALTER TABLE regtest_table DISABLE TRIGGER regtest_test_trig; -- not supported +ALTER TABLE regtest_table ENABLE TRIGGER regtest_test_trig; -- not supported + +CREATE RULE regtest_test_rule AS ON INSERT TO regtest_table_3 DO ALSO NOTHING; +ALTER TABLE regtest_table_3 DISABLE RULE regtest_test_rule; -- not supported +ALTER TABLE regtest_table_3 ENABLE RULE regtest_test_rule; -- not supported + +ALTER TABLE regtest_table SET (fillfactor = 75); +ALTER TABLE regtest_table RESET (fillfactor); +ALTER TABLE regtest_table_2 NO INHERIT regtest_table; -- not supported +ALTER TABLE regtest_table_2 INHERIT regtest_table; -- not supported +ALTER TABLE regtest_table SET TABLESPACE pg_default; + +--- +-- partitioned table parent +ALTER TABLE regtest_ptable ADD COLUMN d float; +ALTER TABLE regtest_ptable DROP COLUMN d; +ALTER TABLE regtest_ptable ALTER p SET DEFAULT 'abcd'; -- not supported by sepgsql +ALTER TABLE regtest_ptable ALTER p SET DEFAULT 'XYZ'; -- not supported by sepgsql +ALTER TABLE regtest_ptable ALTER p DROP DEFAULT; -- not supported by sepgsql +ALTER TABLE regtest_ptable ALTER p SET NOT NULL; +ALTER TABLE regtest_ptable ALTER p DROP NOT NULL; +ALTER TABLE regtest_ptable ALTER p SET STATISTICS -1; +ALTER TABLE regtest_ptable ALTER p SET (n_distinct = 999); +ALTER TABLE regtest_ptable ALTER p SET STORAGE PLAIN; +ALTER TABLE regtest_ptable ADD CONSTRAINT test_ck CHECK (p like '%abc%') NOT VALID; -- not supported by sepgsql +ALTER TABLE regtest_ptable DROP CONSTRAINT test_ck; -- not supported by sepgsql + +ALTER TABLE regtest_ptable SET TABLESPACE pg_default; + +-- partitioned table child +ALTER TABLE regtest_table_part ALTER p SET DEFAULT 'abcd'; -- not supported by sepgsql +ALTER TABLE regtest_table_part ALTER p SET DEFAULT 'XYZ'; -- not supported by sepgsql +ALTER TABLE regtest_table_part ALTER p DROP DEFAULT; -- not supported by sepgsql +ALTER TABLE regtest_table_part ALTER p SET NOT NULL; +ALTER TABLE regtest_table_part ALTER p DROP NOT NULL; +ALTER TABLE regtest_table_part ALTER p SET STATISTICS -1; +ALTER TABLE regtest_table_part ALTER p SET (n_distinct = 999); +ALTER TABLE regtest_table_part ALTER p SET STORAGE PLAIN; +ALTER TABLE regtest_table_part ADD CONSTRAINT test_ck CHECK (p like '%abc%') NOT VALID; -- not supported by sepgsql +ALTER TABLE regtest_table_part VALIDATE CONSTRAINT test_ck; -- not supported by sepgsql +ALTER TABLE regtest_table_part DROP CONSTRAINT test_ck; -- not supported by sepgsql + +CREATE TRIGGER regtest_part_test_trig BEFORE UPDATE ON regtest_table_part + FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); + +ALTER TABLE regtest_table_part DISABLE TRIGGER regtest_part_test_trig; -- not supported by sepgsql +ALTER TABLE regtest_table_part ENABLE TRIGGER regtest_part_test_trig; -- not supported by sepgsql + +ALTER TABLE regtest_table_part SET (fillfactor = 75); +ALTER TABLE regtest_table_part RESET (fillfactor); + +ALTER TABLE regtest_table_part SET TABLESPACE pg_default; +--- + +ALTER VIEW regtest_view SET (security_barrier); + +ALTER SEQUENCE regtest_seq INCREMENT BY 10 START WITH 1000; + +-- +-- clean-up objects +-- +RESET sepgsql.debug_audit; +RESET client_min_messages; +DROP DATABASE sepgsql_test_regression; +DROP SCHEMA regtest_schema CASCADE; +DROP SCHEMA regtest_schema_2 CASCADE; +DROP USER regress_sepgsql_test_user; diff --git a/contrib/sepgsql/sql/ddl.sql b/contrib/sepgsql/sql/ddl.sql new file mode 100644 index 0000000..3deadb6 --- /dev/null +++ b/contrib/sepgsql/sql/ddl.sql @@ -0,0 +1,125 @@ +-- +-- Regression Test for DDL of Object Permission Checks +-- + +-- clean-up in case a prior regression run failed +SET client_min_messages TO 'warning'; +DROP DATABASE IF EXISTS sepgsql_test_regression; +DROP USER IF EXISTS regress_sepgsql_test_user; +RESET client_min_messages; + +-- confirm required permissions using audit messages +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0 +SET sepgsql.debug_audit = true; +SET client_min_messages = LOG; + +-- +-- CREATE Permission checks +-- +CREATE DATABASE sepgsql_test_regression; + +CREATE USER regress_sepgsql_test_user; + +CREATE SCHEMA regtest_schema; + +GRANT ALL ON SCHEMA regtest_schema TO regress_sepgsql_test_user; + +SET search_path = regtest_schema, public; + +CREATE TABLE regtest_table (x serial primary key, y text); + +ALTER TABLE regtest_table ADD COLUMN z int; + +CREATE TABLE regtest_table_2 (a int); + +CREATE TABLE regtest_ptable (a int) PARTITION BY RANGE (a); +CREATE TABLE regtest_ptable_ones PARTITION OF regtest_ptable FOR VALUES FROM ('0') TO ('10'); +CREATE TABLE regtest_ptable_tens PARTITION OF regtest_ptable FOR VALUES FROM ('10') TO ('100'); + +ALTER TABLE regtest_ptable ADD COLUMN q int; + +-- corresponding toast table should not have label and permission checks +ALTER TABLE regtest_table_2 ADD COLUMN b text; + +-- VACUUM FULL internally create a new table and swap them later. +VACUUM FULL regtest_table; +VACUUM FULL regtest_ptable; + +CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100; +CREATE VIEW regtest_pview AS SELECT * FROM regtest_ptable WHERE a < 99; + +CREATE SEQUENCE regtest_seq; + +CREATE TYPE regtest_comptype AS (a int, b text); + +CREATE FUNCTION regtest_func(text,int[]) RETURNS bool LANGUAGE plpgsql + AS 'BEGIN RAISE NOTICE ''regtest_func => %'', $1; RETURN true; END'; + +CREATE AGGREGATE regtest_agg ( + sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond1 = '0' +); + +-- CREATE objects owned by others +SET SESSION AUTHORIZATION regress_sepgsql_test_user; + +SET search_path = regtest_schema, public; + +CREATE TABLE regtest_table_3 (x int, y serial); +CREATE TABLE regtest_ptable_3 (o int, p serial) PARTITION BY RANGE (o); +CREATE TABLE regtest_ptable_3_ones PARTITION OF regtest_ptable_3 FOR VALUES FROM ('0') to ('10'); +CREATE TABLE regtest_ptable_3_tens PARTITION OF regtest_ptable_3 FOR VALUES FROM ('10') to ('100'); + +CREATE VIEW regtest_view_2 AS SELECT * FROM regtest_table_3 WHERE x < y; +CREATE VIEW regtest_pview_2 AS SELECT * FROM regtest_ptable_3 WHERE o < p; + +CREATE FUNCTION regtest_func_2(int) RETURNS bool LANGUAGE plpgsql + AS 'BEGIN RETURN $1 * $1 < 100; END'; + +RESET SESSION AUTHORIZATION; + +-- +-- ALTER and CREATE/DROP extra attribute permissions +-- +CREATE TABLE regtest_table_4 (x int primary key, y int, z int); +CREATE INDEX regtest_index_tbl4_y ON regtest_table_4(y); +CREATE INDEX regtest_index_tbl4_z ON regtest_table_4(z); +ALTER TABLE regtest_table_4 ALTER COLUMN y TYPE float; +DROP INDEX regtest_index_tbl4_y; +ALTER TABLE regtest_table_4 + ADD CONSTRAINT regtest_tbl4_con EXCLUDE USING btree (z WITH =); +DROP TABLE regtest_table_4 CASCADE; + +-- For partitioned tables +CREATE TABLE regtest_ptable_4 (x int, y int, z int) PARTITION BY RANGE (x); +CREATE TABLE regtest_ptable_4_ones PARTITION OF regtest_ptable_4 FOR VALUES FROM ('0') TO ('10'); + +CREATE INDEX regtest_pindex_tbl4_y ON regtest_ptable_4_ones(y); +CREATE INDEX regtest_pindex_tbl4_z ON regtest_ptable_4_ones(z); +ALTER TABLE regtest_ptable_4 ALTER COLUMN y TYPE float; +DROP INDEX regtest_pindex_tbl4_y; +ALTER TABLE regtest_ptable_4_ones + ADD CONSTRAINT regtest_ptbl4_con EXCLUDE USING btree (z WITH =); +DROP TABLE regtest_ptable_4 CASCADE; + +-- +-- DROP Permission checks (with clean-up) +-- + +DROP FUNCTION regtest_func(text,int[]); +DROP AGGREGATE regtest_agg(int); + +DROP SEQUENCE regtest_seq; +DROP VIEW regtest_view; + +ALTER TABLE regtest_table DROP COLUMN y; + +ALTER TABLE regtest_ptable DROP COLUMN q CASCADE; + +DROP TABLE regtest_table; +DROP TABLE regtest_ptable CASCADE; + +DROP OWNED BY regress_sepgsql_test_user; + +DROP DATABASE sepgsql_test_regression; +DROP USER regress_sepgsql_test_user; +DROP SCHEMA IF EXISTS regtest_schema CASCADE; diff --git a/contrib/sepgsql/sql/dml.sql b/contrib/sepgsql/sql/dml.sql new file mode 100644 index 0000000..4a47b4a --- /dev/null +++ b/contrib/sepgsql/sql/dml.sql @@ -0,0 +1,257 @@ +-- +-- Regression Test for DML Permissions +-- + +-- +-- Setup +-- +CREATE TABLE t1 (a int, junk int, b text); +SECURITY LABEL ON TABLE t1 IS 'system_u:object_r:sepgsql_table_t:s0'; +ALTER TABLE t1 DROP COLUMN junk; +INSERT INTO t1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc'); + +CREATE TABLE t2 (x int, y text); +SECURITY LABEL ON TABLE t2 IS 'system_u:object_r:sepgsql_ro_table_t:s0'; +INSERT INTO t2 VALUES (1, 'xxx'), (2, 'yyy'), (3, 'zzz'); + +CREATE TABLE t3 (s int, t text); +SECURITY LABEL ON TABLE t3 IS 'system_u:object_r:sepgsql_fixed_table_t:s0'; +INSERT INTO t3 VALUES (1, 'sss'), (2, 'ttt'), (3, 'uuu'); + +CREATE TABLE t4 (m int, junk int, n text); +SECURITY LABEL ON TABLE t4 IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +ALTER TABLE t4 DROP COLUMN junk; +INSERT INTO t4 VALUES (1, 'mmm'), (2, 'nnn'), (3, 'ooo'); + +CREATE TABLE t5 (e text, f text, g text); +SECURITY LABEL ON TABLE t5 IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t5.e IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t5.f IS 'system_u:object_r:sepgsql_ro_table_t:s0'; +SECURITY LABEL ON COLUMN t5.g IS 'system_u:object_r:sepgsql_secret_table_t:s0'; + +--- +-- partitioned table parent +CREATE TABLE t1p (o int, p text, q text) PARTITION BY RANGE (o); +SECURITY LABEL ON TABLE t1p IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t1p.o IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t1p.p IS 'system_u:object_r:sepgsql_ro_table_t:s0'; +SECURITY LABEL ON COLUMN t1p.q IS 'system_u:object_r:sepgsql_secret_table_t:s0'; + +-- partitioned table children +CREATE TABLE t1p_ones PARTITION OF t1p FOR VALUES FROM ('0') TO ('10'); +SECURITY LABEL ON COLUMN t1p_ones.o IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t1p_ones.p IS 'system_u:object_r:sepgsql_ro_table_t:s0'; +SECURITY LABEL ON COLUMN t1p_ones.q IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +CREATE TABLE t1p_tens PARTITION OF t1p FOR VALUES FROM ('10') TO ('100'); +SECURITY LABEL ON COLUMN t1p_tens.o IS 'system_u:object_r:sepgsql_table_t:s0'; +SECURITY LABEL ON COLUMN t1p_tens.p IS 'system_u:object_r:sepgsql_ro_table_t:s0'; +SECURITY LABEL ON COLUMN t1p_tens.q IS 'system_u:object_r:sepgsql_secret_table_t:s0'; + +--- + +CREATE TABLE customer (cid int primary key, cname text, ccredit text); +SECURITY LABEL ON COLUMN customer.ccredit IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +INSERT INTO customer VALUES (1, 'Taro', '1111-2222-3333-4444'), + (2, 'Hanako', '5555-6666-7777-8888'); +CREATE FUNCTION customer_credit(int) RETURNS text + AS 'SELECT regexp_replace(ccredit, ''-[0-9]+$'', ''-????'') FROM customer WHERE cid = $1' + LANGUAGE sql; +SECURITY LABEL ON FUNCTION customer_credit(int) + IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; + +SELECT objtype, objname, label FROM pg_seclabels + WHERE provider = 'selinux' + AND objtype in ('table', 'column') + AND objname in ('t1', 't2', 't3', 't4', + 't5', 't5.e', 't5.f', 't5.g', + 't1p', 't1p.o', 't1p.p', 't1p.q', + 't1p_ones', 't1p_ones.o', 't1p_ones.p', 't1p_ones.q', + 't1p_tens', 't1p_tens.o', 't1p_tens.p', 't1p_tens.q') +ORDER BY objname COLLATE "C"; + +CREATE SCHEMA my_schema_1; +CREATE TABLE my_schema_1.ts1 (a int, b text); +CREATE TABLE my_schema_1.pts1 (o int, p text) PARTITION BY RANGE (o); +CREATE TABLE my_schema_1.pts1_ones PARTITION OF my_schema_1.pts1 FOR VALUES FROM ('0') to ('10'); + +CREATE SCHEMA my_schema_2; +CREATE TABLE my_schema_2.ts2 (x int, y text); +CREATE TABLE my_schema_2.pts2 (o int, p text) PARTITION BY RANGE (o); +CREATE TABLE my_schema_2.pts2_tens PARTITION OF my_schema_2.pts2 FOR VALUES FROM ('10') to ('100'); + +SECURITY LABEL ON SCHEMA my_schema_2 + IS 'system_u:object_r:sepgsql_regtest_invisible_schema_t:s0'; + +-- Hardwired Rules +UPDATE pg_attribute SET attisdropped = true + WHERE attrelid = 't5'::regclass AND attname = 'f'; -- failed + +-- +-- Simple DML statements +-- +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 + +SELECT * FROM t1; -- ok +SELECT * FROM t2; -- ok +SELECT * FROM t3; -- ok +SELECT * FROM t4; -- failed +SELECT * FROM t5; -- failed +SELECT e,f FROM t5; -- ok +SELECT (t1.*)::record FROM t1; -- ok +SELECT (t4.*)::record FROM t4; -- failed + +--- +-- partitioned table parent +SELECT * FROM t1p; -- failed +SELECT o,p FROM t1p; -- ok +--partitioned table children +SELECT * FROM t1p_ones; -- failed +SELECT o FROM t1p_ones; -- ok +SELECT o,p FROM t1p_ones; -- ok +SELECT * FROM t1p_tens; -- failed +SELECT o FROM t1p_tens; -- ok +SELECT o,p FROM t1p_tens; -- ok +--- + +SELECT * FROM customer; -- failed +SELECT cid, cname, customer_credit(cid) FROM customer; -- ok + +SELECT count(*) FROM t5; -- ok +SELECT count(*) FROM t5 WHERE g IS NULL; -- failed + +--- +-- partitioned table parent +SELECT count(*) FROM t1p; -- ok +SELECT count(*) FROM t1p WHERE q IS NULL; -- failed +-- partitioned table children +SELECT count(*) FROM t1p_ones; -- ok +SELECT count(*) FROM t1p_ones WHERE q IS NULL; -- failed +SELECT count(*) FROM t1p_tens; -- ok +SELECT count(*) FROM t1p_tens WHERE q IS NULL; -- failed +--- + +INSERT INTO t1 VALUES (4, 'abc'); -- ok +INSERT INTO t2 VALUES (4, 'xyz'); -- failed +INSERT INTO t3 VALUES (4, 'stu'); -- ok +INSERT INTO t4 VALUES (4, 'mno'); -- failed +INSERT INTO t5 VALUES (1,2,3); -- failed +INSERT INTO t5 (e,f) VALUES ('abc', 'def'); -- failed +INSERT INTO t5 (e) VALUES ('abc'); -- ok + +--- +-- partitioned table parent +INSERT INTO t1p (o,p) VALUES (9, 'mno'); -- failed +INSERT INTO t1p (o) VALUES (9); -- ok +INSERT INTO t1p (o,p) VALUES (99, 'pqr'); -- failed +INSERT INTO t1p (o) VALUES (99); -- ok +-- partitioned table children +INSERT INTO t1p_ones (o,p) VALUES (9, 'mno'); -- failed +INSERT INTO t1p_ones (o) VALUES (9); -- ok +INSERT INTO t1p_tens (o,p) VALUES (99, 'pqr'); -- failed +INSERT INTO t1p_tens (o) VALUES (99); -- ok +--- + +UPDATE t1 SET b = b || '_upd'; -- ok +UPDATE t2 SET y = y || '_upd'; -- failed +UPDATE t3 SET t = t || '_upd'; -- failed +UPDATE t4 SET n = n || '_upd'; -- failed +UPDATE t5 SET e = 'xyz'; -- ok +UPDATE t5 SET e = f || '_upd'; -- ok +UPDATE t5 SET e = g || '_upd'; -- failed + +--- +-- partitioned table parent +UPDATE t1p SET o = 9 WHERE o < 10; -- ok +UPDATE t1p SET o = 99 WHERE o >= 10; -- ok +UPDATE t1p SET o = ascii(COALESCE(p,'upd'))%10 WHERE o < 10; -- ok +UPDATE t1p SET o = ascii(COALESCE(q,'upd'))%100 WHERE o >= 10; -- failed +-- partitioned table children +UPDATE t1p_ones SET o = 9; -- ok +UPDATE t1p_ones SET o = ascii(COALESCE(p,'upd'))%10; -- ok +UPDATE t1p_ones SET o = ascii(COALESCE(q,'upd'))%10; -- failed +UPDATE t1p_tens SET o = 99; -- ok +UPDATE t1p_tens SET o = ascii(COALESCE(p,'upd'))%100; -- ok +UPDATE t1p_tens SET o = ascii(COALESCE(q,'upd'))%100; -- failed +--- + +DELETE FROM t1; -- ok +DELETE FROM t2; -- failed +DELETE FROM t3; -- failed +DELETE FROM t4; -- failed +DELETE FROM t5; -- ok +DELETE FROM t5 WHERE f IS NULL; -- ok +DELETE FROM t5 WHERE g IS NULL; -- failed + +--- +-- partitioned table parent +DELETE FROM t1p; -- ok +DELETE FROM t1p WHERE p IS NULL; -- ok +DELETE FROM t1p WHERE q IS NULL; -- failed +-- partitioned table children +DELETE FROM t1p_ones WHERE p IS NULL; -- ok +DELETE FROM t1p_ones WHERE q IS NULL; -- failed; +DELETE FROM t1p_tens WHERE p IS NULL; -- ok +DELETE FROM t1p_tens WHERE q IS NULL; -- failed +--- + +-- +-- COPY TO/FROM statements +-- +COPY t1 TO '/dev/null'; -- ok +COPY t2 TO '/dev/null'; -- ok +COPY t3 TO '/dev/null'; -- ok +COPY t4 TO '/dev/null'; -- failed +COPY t5 TO '/dev/null'; -- failed +COPY t5(e,f) TO '/dev/null'; -- ok + +--- +-- partitioned table parent +COPY (SELECT * FROM t1p) TO '/dev/null'; -- failed +COPY (SELECT (o,p) FROM t1p) TO '/dev/null'; -- ok +-- partitioned table children +COPY t1p_ones TO '/dev/null'; -- failed +COPY t1p_ones(o,p) TO '/dev/null'; -- ok +COPY t1p_tens TO '/dev/null'; -- failed +COPY t1p_tens(o,p) TO '/dev/null'; -- ok +--- + +COPY t1 FROM '/dev/null'; -- ok +COPY t2 FROM '/dev/null'; -- failed +COPY t3 FROM '/dev/null'; -- ok +COPY t4 FROM '/dev/null'; -- failed +COPY t5 FROM '/dev/null'; -- failed +COPY t5 (e,f) FROM '/dev/null'; -- failed +COPY t5 (e) FROM '/dev/null'; -- ok + +--- +-- partitioned table parent +COPY t1p FROM '/dev/null'; -- failed +COPY t1p (o) FROM '/dev/null'; -- ok +-- partitioned table children +COPY t1p_ones FROM '/dev/null'; -- failed +COPY t1p_ones (o) FROM '/dev/null'; -- ok +COPY t1p_tens FROM '/dev/null'; -- failed +COPY t1p_tens (o) FROM '/dev/null'; -- ok +--- + +-- +-- Schema search path +-- +SET search_path = my_schema_1, my_schema_2, public; +SELECT * FROM ts1; -- ok +SELECT * FROM ts2; -- failed (relation not found) +SELECT * FROM my_schema_2.ts2; -- failed (policy violation) + +-- +-- Clean up +-- +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 +DROP TABLE IF EXISTS t1 CASCADE; +DROP TABLE IF EXISTS t2 CASCADE; +DROP TABLE IF EXISTS t3 CASCADE; +DROP TABLE IF EXISTS t4 CASCADE; +DROP TABLE IF EXISTS t5 CASCADE; +DROP TABLE IF EXISTS t1p CASCADE; +DROP TABLE IF EXISTS customer CASCADE; +DROP SCHEMA IF EXISTS my_schema_1 CASCADE; +DROP SCHEMA IF EXISTS my_schema_2 CASCADE; diff --git a/contrib/sepgsql/sql/label.sql b/contrib/sepgsql/sql/label.sql new file mode 100644 index 0000000..76e261b --- /dev/null +++ b/contrib/sepgsql/sql/label.sql @@ -0,0 +1,294 @@ +-- +-- Regression Tests for Label Management +-- + +-- +-- Setup +-- +CREATE TABLE t1 (a int, b text); +INSERT INTO t1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc'); +CREATE TABLE t2 AS SELECT * FROM t1 WHERE a % 2 = 0; + +CREATE FUNCTION f1 () RETURNS text + AS 'SELECT sepgsql_getcon()' + LANGUAGE sql; + +CREATE FUNCTION f2 () RETURNS text + AS 'SELECT sepgsql_getcon()' + LANGUAGE sql; +SECURITY LABEL ON FUNCTION f2() + IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; + +CREATE FUNCTION f3 () RETURNS text + AS 'BEGIN + RAISE EXCEPTION ''an exception from f3()''; + RETURN NULL; + END;' LANGUAGE plpgsql; +SECURITY LABEL ON FUNCTION f3() + IS 'system_u:object_r:sepgsql_trusted_proc_exec_t:s0'; + +CREATE FUNCTION f4 () RETURNS text + AS 'SELECT sepgsql_getcon()' + LANGUAGE sql; +SECURITY LABEL ON FUNCTION f4() + IS 'system_u:object_r:sepgsql_nosuch_trusted_proc_exec_t:s0'; + +CREATE FUNCTION f5 (text) RETURNS bool + AS 'SELECT sepgsql_setcon($1)' + LANGUAGE sql; +SECURITY LABEL ON FUNCTION f5(text) + IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0'; + +CREATE TABLE auth_tbl(uname text, credential text, label text); +INSERT INTO auth_tbl + VALUES ('foo', 'acbd18db4cc2f85cedef654fccc4a4d8', 'sepgsql_regtest_foo_t:s0'), + ('var', 'b2145aac704ce76dbe1ac7adac535b23', 'sepgsql_regtest_var_t:s0'), + ('baz', 'b2145aac704ce76dbe1ac7adac535b23', 'sepgsql_regtest_baz_t:s0'); +SECURITY LABEL ON TABLE auth_tbl + IS 'system_u:object_r:sepgsql_secret_table_t:s0'; + +CREATE FUNCTION auth_func(text, text) RETURNS bool + LANGUAGE sql + AS 'SELECT sepgsql_setcon(regexp_replace(sepgsql_getcon(), ''_r:.*$'', ''_r:'' || label)) + FROM auth_tbl WHERE uname = $1 AND credential = $2'; +SECURITY LABEL ON FUNCTION auth_func(text,text) + IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0'; + +CREATE TABLE foo_tbl(a int, b text); +INSERT INTO foo_tbl VALUES (1, 'aaa'), (2,'bbb'), (3,'ccc'), (4,'ddd'); +SECURITY LABEL ON TABLE foo_tbl + IS 'system_u:object_r:sepgsql_regtest_foo_table_t:s0'; + +CREATE TABLE var_tbl(x int, y text); +INSERT INTO var_tbl VALUES (2,'xxx'), (3,'yyy'), (4,'zzz'), (5,'xyz'); +SECURITY LABEL ON TABLE var_tbl + IS 'system_u:object_r:sepgsql_regtest_var_table_t:s0'; + +CREATE TABLE foo_ptbl(o int, p text) PARTITION BY RANGE (o); +CREATE TABLE foo_ptbl_ones PARTITION OF foo_ptbl FOR VALUES FROM ('0') TO ('10'); +CREATE TABLE foo_ptbl_tens PARTITION OF foo_ptbl FOR VALUES FROM ('10') TO ('100'); + +INSERT INTO foo_ptbl VALUES (0, 'aaa'), (9,'bbb'), (10,'ccc'), (99,'ddd'); +SECURITY LABEL ON TABLE foo_ptbl + IS 'system_u:object_r:sepgsql_regtest_foo_table_t:s0'; + +CREATE TABLE var_ptbl(q int, r text) PARTITION BY RANGE (q); +CREATE TABLE var_ptbl_ones PARTITION OF var_ptbl FOR VALUES FROM ('0') TO ('10'); +CREATE TABLE var_ptbl_tens PARTITION OF var_ptbl FOR VALUES FROM ('10') TO ('100'); + +INSERT INTO var_ptbl VALUES (0,'xxx'), (9,'yyy'), (10,'zzz'), (99,'xyz'); +SECURITY LABEL ON TABLE var_ptbl + IS 'system_u:object_r:sepgsql_regtest_var_table_t:s0'; + +-- +-- Tests for default labeling behavior +-- +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 +CREATE TABLE t3 (s int, t text); +INSERT INTO t3 VALUES (1, 'sss'), (2, 'ttt'), (3, 'uuu'); + +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_dba_t:s0 +CREATE TABLE t4 (m int, n text); +INSERT INTO t4 VALUES (1,'mmm'), (2,'nnn'), (3,'ooo'); + +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 +CREATE TABLE tpart (o int, p text) PARTITION BY RANGE (o); + +CREATE TABLE tpart_ones PARTITION OF tpart FOR VALUES FROM ('0') TO ('10'); +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_dba_t:s0 +CREATE TABLE tpart_tens PARTITION OF tpart FOR VALUES FROM ('10') TO ('100'); + +INSERT INTO tpart VALUES (0, 'aaa'); +INSERT INTO tpart VALUES (9, 'bbb'); +INSERT INTO tpart VALUES (99, 'ccc'); + +SELECT objtype, objname, label FROM pg_seclabels + WHERE provider = 'selinux' AND objtype = 'table' AND objname in ('t1', 't2', 't3', + 'tpart', + 'tpart_ones', + 'tpart_tens') + ORDER BY objname COLLATE "C" ASC; +SELECT objtype, objname, label FROM pg_seclabels + WHERE provider = 'selinux' AND objtype = 'column' AND (objname like 't3.%' + OR objname like 't4.%' + OR objname like 'tpart.%' + OR objname like 'tpart_ones.%' + OR objname like 'tpart_tens.%') + ORDER BY objname COLLATE "C" ASC; + +-- +-- Tests for SECURITY LABEL +-- +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_dba_t:s0 +SECURITY LABEL ON TABLE t1 + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok +SECURITY LABEL ON TABLE t2 + IS 'invalid security context'; -- be failed +SECURITY LABEL ON COLUMN t2 + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- be failed +SECURITY LABEL ON COLUMN t2.b + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok +SECURITY LABEL ON TABLE tpart + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok +SECURITY LABEL ON TABLE tpart + IS 'invalid security context'; -- failed +SECURITY LABEL ON COLUMN tpart + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- failed +SECURITY LABEL ON COLUMN tpart.o + IS 'system_u:object_r:sepgsql_ro_table_t:s0'; -- ok + +-- +-- Tests for Trusted Procedures +-- +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 +SET sepgsql.debug_audit = true; +SET client_min_messages = log; +SELECT f1(); -- normal procedure +SELECT f2(); -- trusted procedure +SELECT f3(); -- trusted procedure that raises an error +SELECT f4(); -- failed on domain transition +SELECT sepgsql_getcon(); -- client's label must be restored + +-- +-- Test for Dynamic Domain Transition +-- + +-- validation of transaction aware dynamic-transition +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c25 +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c15'); +SELECT sepgsql_getcon(); + +SELECT sepgsql_setcon(NULL); -- failed to reset +SELECT sepgsql_getcon(); + +BEGIN; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c12'); +SELECT sepgsql_getcon(); + +SAVEPOINT svpt_1; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c9'); +SELECT sepgsql_getcon(); + +SAVEPOINT svpt_2; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c6'); +SELECT sepgsql_getcon(); + +SAVEPOINT svpt_3; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c3'); +SELECT sepgsql_getcon(); + +ROLLBACK TO SAVEPOINT svpt_2; +SELECT sepgsql_getcon(); -- should be 's0:c0.c9' + +ROLLBACK TO SAVEPOINT svpt_1; +SELECT sepgsql_getcon(); -- should be 's0:c0.c12' + +ABORT; +SELECT sepgsql_getcon(); -- should be 's0:c0.c15' + +BEGIN; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c8'); +SELECT sepgsql_getcon(); + +SAVEPOINT svpt_1; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c4'); +SELECT sepgsql_getcon(); + +ROLLBACK TO SAVEPOINT svpt_1; +SELECT sepgsql_getcon(); -- should be 's0:c0.c8' +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0:c0.c6'); + +COMMIT; +SELECT sepgsql_getcon(); -- should be 's0:c0.c6' + +-- sepgsql_regtest_user_t is not available dynamic-transition, +-- unless sepgsql_setcon() is called inside of trusted-procedure +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15 + +-- sepgsql_regtest_user_t has no permission to switch current label +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0'); -- failed +SELECT sepgsql_getcon(); + +-- trusted procedure allows to switch, but unavailable to override MCS rules +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7'); -- OK +SELECT sepgsql_getcon(); + +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c31'); -- Failed +SELECT sepgsql_getcon(); + +SELECT f5(NULL); -- Failed +SELECT sepgsql_getcon(); + +BEGIN; +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c3'); -- OK +SELECT sepgsql_getcon(); + +ABORT; +SELECT sepgsql_getcon(); + +-- +-- Test for simulation of typical connection pooling server +-- +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_pool_t:s0 + +-- we shouldn't allow to switch client label without trusted procedure +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_foo_t:s0'); + +SELECT * FROM auth_tbl; -- failed, no permission to reference + +-- switch to "foo" +SELECT auth_func('foo', 'acbd18db4cc2f85cedef654fccc4a4d8'); + +SELECT sepgsql_getcon(); + +SELECT * FROM foo_tbl; -- OK +SELECT * FROM foo_ptbl; -- OK + +SELECT * FROM var_tbl; -- failed +SELECT * FROM var_ptbl; -- failed + +SELECT * FROM auth_tbl; -- failed + +SELECT sepgsql_setcon(NULL); -- end of session +SELECT sepgsql_getcon(); + +-- the pooler cannot touch these tables directly +SELECT * FROM foo_tbl; -- failed +SELECT * FROM foo_ptbl; -- failed + +SELECT * FROM var_tbl; -- failed +SELECT * FROM var_ptbl; -- failed + +-- switch to "var" +SELECT auth_func('var', 'b2145aac704ce76dbe1ac7adac535b23'); + +SELECT sepgsql_getcon(); + +SELECT * FROM foo_tbl; -- failed +SELECT * FROM foo_ptbl; -- failed + +SELECT * FROM var_tbl; -- OK +SELECT * FROM var_ptbl; -- OK + +SELECT * FROM auth_tbl; -- failed + +SELECT sepgsql_setcon(NULL); -- end of session + +-- misc checks +SELECT auth_func('var', 'invalid credential'); -- not works +SELECT sepgsql_getcon(); + +-- +-- Clean up +-- +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_superuser_t:s0-s0:c0.c255 +DROP TABLE IF EXISTS t1 CASCADE; +DROP TABLE IF EXISTS t2 CASCADE; +DROP TABLE IF EXISTS t3 CASCADE; +DROP TABLE IF EXISTS t4 CASCADE; +DROP TABLE IF EXISTS tpart CASCADE; +DROP FUNCTION IF EXISTS f1() CASCADE; +DROP FUNCTION IF EXISTS f2() CASCADE; +DROP FUNCTION IF EXISTS f3() CASCADE; +DROP FUNCTION IF EXISTS f4() CASCADE; +DROP FUNCTION IF EXISTS f5(text) CASCADE; diff --git a/contrib/sepgsql/sql/misc.sql b/contrib/sepgsql/sql/misc.sql new file mode 100644 index 0000000..bd5b6e2 --- /dev/null +++ b/contrib/sepgsql/sql/misc.sql @@ -0,0 +1,45 @@ +-- +-- Regression Test for Misc Permission Checks +-- + +LOAD '$libdir/sepgsql'; -- failed + +-- +-- Permissions to execute functions +-- +CREATE TABLE t1 (x int, y text); +INSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(1,100) x); + +CREATE TABLE t1p (o int, p text) PARTITION BY RANGE (o); +CREATE TABLE t1p_ones PARTITION OF t1p FOR VALUES FROM ('0') TO ('10'); +CREATE TABLE t1p_tens PARTITION OF t1p FOR VALUES FROM ('10') TO ('100'); +INSERT INTO t1p (SELECT x, md5(x::text) FROM generate_series(0,99) x); + +SET sepgsql.debug_audit = on; +SET client_min_messages = log; + +-- regular function and operators +SELECT * FROM t1 WHERE x > 50 AND y like '%64%'; +SELECT * FROM t1p WHERE o > 50 AND p like '%64%'; +SELECT * FROM t1p_ones WHERE o > 50 AND p like '%64%'; +SELECT * FROM t1p_tens WHERE o > 50 AND p like '%64%'; + +-- aggregate function +SELECT MIN(x), AVG(x) FROM t1; +SELECT MIN(o), AVG(o) FROM t1p; +SELECT MIN(o), AVG(o) FROM t1p_ones; +SELECT MIN(o), AVG(o) FROM t1p_tens; + +-- window function +SELECT row_number() OVER (order by x), * FROM t1 WHERE y like '%86%'; +SELECT row_number() OVER (order by o), * FROM t1p WHERE p like '%86%'; +SELECT row_number() OVER (order by o), * FROM t1p_ones WHERE p like '%86%'; +SELECT row_number() OVER (order by o), * FROM t1p_tens WHERE p like '%86%'; + +RESET sepgsql.debug_audit; +RESET client_min_messages; +-- +-- Cleanup +-- +DROP TABLE IF EXISTS t1 CASCADE; +DROP TABLE IF EXISTS t1p CASCADE; diff --git a/contrib/sepgsql/sql/truncate.sql b/contrib/sepgsql/sql/truncate.sql new file mode 100644 index 0000000..3748a1b --- /dev/null +++ b/contrib/sepgsql/sql/truncate.sql @@ -0,0 +1,24 @@ +-- +-- Regression Test for TRUNCATE +-- + +-- +-- Setup +-- +CREATE TABLE julio_claudians (name text, birth_date date); +SECURITY LABEL ON TABLE julio_claudians IS 'system_u:object_r:sepgsql_regtest_foo_table_t:s0'; +INSERT INTO julio_claudians VALUES ('Augustus', 'September 23, 63 BC'), ('Tiberius', 'November 16, 42 BC'), ('Caligula', 'August 31, 0012'), ('Claudius', 'August 1, 0010'), ('Nero', 'December 15, 0037'); + +CREATE TABLE flavians (name text, birth_date date); +SECURITY LABEL ON TABLE flavians IS 'system_u:object_r:sepgsql_table_t:s0'; + +INSERT INTO flavians VALUES ('Vespasian', 'November 17, 0009'), ('Titus', 'December 30, 0039'), ('Domitian', 'October 24, 0051'); + +SELECT * from julio_claudians; +SELECT * from flavians; + +TRUNCATE TABLE julio_claudians; -- ok +TRUNCATE TABLE flavians; -- failed + +SELECT * from julio_claudians; +SELECT * from flavians; diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql new file mode 100755 index 0000000..3a29556 --- /dev/null +++ b/contrib/sepgsql/test_sepgsql @@ -0,0 +1,306 @@ +#!/bin/sh +# +# Run the sepgsql regression tests, after making a lot of environmental checks +# to try to ensure that the SELinux environment is set up appropriately and +# the database is configured correctly. +# +# Note that this must be run against an installed Postgres database. +# There's no equivalent of "make check", and that wouldn't be terribly useful +# since much of the value is in checking that you installed sepgsql into +# your database correctly. +# +# This must be run in the contrib/sepgsql directory of a Postgres build tree. +# + +PG_BINDIR=`pg_config --bindir` + +# we must move to contrib/sepgsql directory to run pg_regress correctly +cd `dirname $0` + +echo +echo "============== checking selinux environment ==============" + +# matchpathcon must be present to assess whether the installation environment +# is OK. +echo -n "checking for matchpathcon ... " +if ! matchpathcon -n . >/dev/null 2>&1; then + echo "not found" + echo "" + echo "The matchpathcon command must be available." + echo "Please install it or update your PATH to include it" + echo "(it is typically in '/usr/sbin', which might not be in your PATH)." + echo "matchpathcon is typically included in the libselinux-utils package." + exit 1 +fi +echo "ok" + +# runcon must be present to launch psql using the correct environment +echo -n "checking for runcon ... " +if ! runcon --help >/dev/null 2>&1; then + echo "not found" + echo "" + echo "The runcon command must be available." + echo "runcon is typically included in the coreutils package." + echo "" + exit 1 +fi +echo "ok" + +# check sestatus too, since that lives in yet another package +echo -n "checking for sestatus ... " +if ! sestatus >/dev/null 2>&1; then + echo "not found" + echo "" + echo "The sestatus command must be available." + echo "sestatus is typically included in the policycoreutils package." + echo "" + exit 1 +fi +echo "ok" + +# check that the user is running in the unconfined_t domain +echo -n "checking current user domain ... " +DOMAIN=`id -Z 2>/dev/null | sed 's/:/ /g' | awk '{print $3}'` +echo ${DOMAIN:-failed} +if [ "${DOMAIN}" != unconfined_t ]; then + echo "" + echo "The regression tests must be launched from the unconfined_t domain." + echo "" + echo "The unconfined_t domain is typically the default domain for user" + echo "shell processes. If the default has been changed on your system," + echo "you can revert the changes like this:" + echo "" + echo " \$ sudo semanage login -d `whoami`" + echo "" + echo "Or, you can add a setting to log in using the unconfined_t domain:" + echo "" + echo " \$ sudo semanage login -a -s unconfined_u -r s0-s0:c0.c255 `whoami`" + echo "" + exit 1 +fi + +# SELinux must be configured in enforcing mode +echo -n "checking selinux operating mode ... " +CURRENT_MODE=`LANG=C sestatus | grep '^Current mode:' | awk '{print $3}'` +echo ${CURRENT_MODE:-failed} +if [ "${CURRENT_MODE}" = enforcing ]; then + : OK +elif [ "${CURRENT_MODE}" = permissive -o "${CURRENT_MODE}" = disabled ]; then + echo "" + echo "Before running the regression tests, SELinux must be enabled and" + echo "must be running in enforcing mode." + echo "" + echo "If SELinux is currently running in permissive mode, you can" + echo "switch to enforcing mode using the 'setenforce' command." + echo + echo " \$ sudo setenforce 1" + echo "" + echo "The system default setting is configured in /etc/selinux/config," + echo "or using a kernel boot parameter." + echo "" + exit 1 +else + echo "" + echo "Unable to determine the current selinux operating mode. Please" + echo "verify that the sestatus command is installed and in your PATH." + echo "" + exit 1 +fi + +# 'sepgsql-regtest' policy module must be loaded +echo -n "checking for sepgsql-regtest policy ... " +SELINUX_MNT=`LANG=C sestatus | grep '^SELinuxfs mount:' | awk '{print $3}'` +if [ "$SELINUX_MNT" = "" ]; then + echo "failed" + echo "" + echo "Unable to find SELinuxfs mount point." + echo "" + echo "The sestatus command should report the location where SELinuxfs" + echo "is mounted, but did not do so." + echo "" + exit 1 +fi +if [ ! -e "${SELINUX_MNT}/booleans/sepgsql_regression_test_mode" ]; then + echo "failed" + echo "" + echo "The 'sepgsql-regtest' policy module appears not to be installed." + echo "Without this policy installed, the regression tests will fail." + echo "You can install this module using the following commands:" + echo "" + echo " \$ make -f /usr/share/selinux/devel/Makefile" + echo " \$ sudo semodule -u sepgsql-regtest.pp" + echo "" + echo "To confirm that the policy package is installed, use this command:" + echo "" + echo " \$ sudo semodule -l | grep sepgsql" + echo "" + exit 1 +fi +echo "ok" + +# Verify that sepgsql_regression_test_mode is active. +echo -n "checking whether policy is enabled ... " +POLICY_STATUS=`getsebool sepgsql_regression_test_mode | awk '{print $3}'` +echo ${POLICY_STATUS:-failed} +if [ "${POLICY_STATUS}" != on ]; then + echo "" + echo "The SELinux boolean 'sepgsql_regression_test_mode' must be" + echo "turned on in order to enable the rules necessary to run the" + echo "regression tests." + echo "" + if [ "${POLICY_STATUS}" = "" ]; then + echo "We attempted to determine the state of this Boolean using" + echo "'getsebool', but that command did not produce the expected" + echo "output. Please verify that getsebool is available and in" + echo "your PATH." + else + echo "You can turn on this variable using the following commands:" + echo "" + echo " \$ sudo setsebool sepgsql_regression_test_mode on" + echo "" + echo "For security reasons, it is suggested that you turn off this" + echo "variable when regression testing is complete and the associated" + echo "rules are no longer needed." + fi + echo "" + exit 1 +fi +POLICY_STATUS=`getsebool sepgsql_enable_users_ddl | awk '{print $3}'` +echo ${POLICY_STATUS:-failed} +if [ "${POLICY_STATUS}" != on ]; then + echo "" + echo "The SELinux boolean 'sepgsql_enable_users_ddl' must be" + echo "turned on in order to enable the rules necessary to run" + echo "the regression tests." + echo "" + if [ "${POLICY_STATUS}" = "" ]; then + echo "We attempted to determine the state of this Boolean using" + echo "'getsebool', but that command did not produce the expected" + echo "output. Please verify that getsebool is available and in" + echo "your PATH." + else + echo "You can turn on this variable using the following commands:" + echo "" + echo " \$ sudo setsebool sepgsql_enable_users_ddl on" + echo "" + echo "For security reasons, it is suggested that you turn off this" + echo "variable when regression testing is complete, unless you" + echo "don't want to allow unprivileged users DDL commands." + fi + echo "" + exit 1 +fi + +# 'psql' command must be executable from test domain +echo -n "checking whether we can run psql ... " +CMD_PSQL="${PG_BINDIR}/psql" +if [ ! -e "${CMD_PSQL}" ]; then + echo "not found" + echo + echo "${CMD_PSQL} was not found." + echo "Check your PostgreSQL installation." + echo + exit 1 +fi +runcon -t sepgsql_regtest_user_t "${CMD_PSQL}" --help >& /dev/null +if [ $? -ne 0 ]; then + echo "failed" + echo + echo "${CMD_PSQL} must be executable from the" + echo "sepgsql_regtest_user_t domain. That domain has restricted privileges" + echo "compared to unconfined_t, so the problem may be the psql file's" + echo "SELinux label. Try" + echo + PSQL_T=`matchpathcon -n "${CMD_PSQL}" | sed 's/:/ /g' | awk '{print $3}'` + if [ "${PSQL_T}" = "user_home_t" ]; then + # Installation appears to be in /home directory + echo " \$ sudo restorecon -R ${PG_BINDIR}" + echo + echo "Or, using chcon" + echo + echo " \$ sudo chcon -t user_home_t ${CMD_PSQL}" + else + echo " \$ sudo restorecon -R ${PG_BINDIR}" + echo + echo "Or, using chcon" + echo + echo " \$ sudo chcon -t bin_t ${CMD_PSQL}" + fi + echo + exit 1 +fi +echo "ok" + +# loadable module must be installed and not configured to permissive mode +echo -n "checking sepgsql installation ... " +VAL="`${CMD_PSQL} -X -t -c 'SHOW sepgsql.permissive' template1 2>/dev/null`" +RETVAL="$?" +if [ $RETVAL -eq 2 ]; then + echo "failed" + echo "" + echo "Could not connect to the database server." + echo "Please check your PostgreSQL installation." + echo "" + exit 1 +elif [ $RETVAL -ne 0 ]; then + echo "failed" + echo "" + echo "The sepgsql module does not appear to be loaded. Please verify" + echo "that the 'shared_preload_libraries' setting in postgresql.conf" + echo "includes 'sepgsql', and then restart the server." + echo "" + echo "See Installation section of the contrib/sepgsql documentation." + echo "" + exit 1 +elif ! echo "$VAL" | grep -q 'off$'; then + echo "failed" + echo "" + echo "The parameter 'sepgsql.permissive' is set to 'on'. It must be" + echo "turned off before running the regression tests." + echo "" + exit 1 +fi +echo "ok" + +# template1 database must be labeled +# NOTE: this test is wrong; we really ought to be checking template0. +# But we can't connect to that without extra pushups, and it's not worth it. +echo -n "checking for labels in template1 ... " +NUM=`${CMD_PSQL} -XAt -c 'SELECT count(*) FROM pg_catalog.pg_seclabel' template1 2>/dev/null` +if [ -z "${NUM}" ]; then + echo "failed" + echo "" + echo "In order to test sepgsql, initial labels must be assigned within" + echo "the 'template1' database. These labels will be copied into the" + echo "regression test database." + echo "" + echo "See Installation section of the contrib/sepgsql documentation." + echo "" + exit 1 +fi +echo "found ${NUM}" + +# +# checking complete - let's run the tests +# + +echo +echo "============== running sepgsql regression tests ==============" + +tests="label dml ddl alter misc" + +# Check if the truncate permission exists in the loaded policy, and if so, +# run the truncate test +# +# Testing the TRUNCATE regression test can be done by manually adding +# the permission with CIL if necessary: +# sudo semodule -cE base +# sudo sed -i -E 's/(class db_table.*?) \)/\1 truncate\)/' base.cil +# sudo semodule -i base.cil + +if [ -f /sys/fs/selinux/class/db_table/perms/truncate ]; then + tests+=" truncate" +fi + +make REGRESS="$tests" REGRESS_OPTS="--launcher ./launcher" installcheck +# exit with the exit code provided by "make" diff --git a/contrib/sepgsql/uavc.c b/contrib/sepgsql/uavc.c new file mode 100644 index 0000000..6e3a892 --- /dev/null +++ b/contrib/sepgsql/uavc.c @@ -0,0 +1,521 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/uavc.c + * + * Implementation of userspace access vector cache; that enables to cache + * access control decisions recently used, and reduce number of kernel + * invocations to avoid unnecessary performance hit. + * + * Copyright (c) 2011-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_proc.h" +#include "commands/seclabel.h" +#include "common/hashfn.h" +#include "sepgsql.h" +#include "storage/ipc.h" +#include "utils/guc.h" +#include "utils/memutils.h" + +/* + * avc_cache + * + * It enables to cache access control decision (and behavior on execution of + * trusted procedure, db_procedure class only) for a particular pair of + * security labels and object class in userspace. + */ +typedef struct +{ + uint32 hash; /* hash value of this cache entry */ + char *scontext; /* security context of the subject */ + char *tcontext; /* security context of the target */ + uint16 tclass; /* object class of the target */ + + uint32 allowed; /* permissions to be allowed */ + uint32 auditallow; /* permissions to be audited on allowed */ + uint32 auditdeny; /* permissions to be audited on denied */ + + bool permissive; /* true, if permissive rule */ + bool hot_cache; /* true, if recently referenced */ + bool tcontext_is_valid; + /* true, if tcontext is valid */ + char *ncontext; /* temporary scontext on execution of trusted + * procedure, or NULL elsewhere */ +} avc_cache; + +/* + * Declaration of static variables + */ +#define AVC_NUM_SLOTS 512 +#define AVC_NUM_RECLAIM 16 +#define AVC_DEF_THRESHOLD 384 + +static MemoryContext avc_mem_cxt; +static List *avc_slots[AVC_NUM_SLOTS]; /* avc's hash buckets */ +static int avc_num_caches; /* number of caches currently used */ +static int avc_lru_hint; /* index of the buckets to be reclaimed next */ +static int avc_threshold; /* threshold to launch cache-reclaiming */ +static char *avc_unlabeled; /* system 'unlabeled' label */ + +/* + * Hash function + */ +static uint32 +sepgsql_avc_hash(const char *scontext, const char *tcontext, uint16 tclass) +{ + return hash_any((const unsigned char *) scontext, strlen(scontext)) + ^ hash_any((const unsigned char *) tcontext, strlen(tcontext)) + ^ tclass; +} + +/* + * Reset all the avc caches + */ +static void +sepgsql_avc_reset(void) +{ + MemoryContextReset(avc_mem_cxt); + + memset(avc_slots, 0, sizeof(List *) * AVC_NUM_SLOTS); + avc_num_caches = 0; + avc_lru_hint = 0; + avc_unlabeled = NULL; +} + +/* + * Reclaim caches recently unreferenced + */ +static void +sepgsql_avc_reclaim(void) +{ + ListCell *cell; + int index; + + while (avc_num_caches >= avc_threshold - AVC_NUM_RECLAIM) + { + index = avc_lru_hint; + + foreach(cell, avc_slots[index]) + { + avc_cache *cache = lfirst(cell); + + if (!cache->hot_cache) + { + avc_slots[index] + = foreach_delete_current(avc_slots[index], cell); + + pfree(cache->scontext); + pfree(cache->tcontext); + if (cache->ncontext) + pfree(cache->ncontext); + pfree(cache); + + avc_num_caches--; + } + else + { + cache->hot_cache = false; + } + } + avc_lru_hint = (avc_lru_hint + 1) % AVC_NUM_SLOTS; + } +} + +/* ------------------------------------------------------------------------- + * + * sepgsql_avc_check_valid + * + * This function checks whether the cached entries are still valid. If + * the security policy has been reloaded (or any other events that requires + * resetting userspace caches has occurred) since the last reference to + * the access vector cache, we must flush the cache. + * + * Access control decisions must be atomic, but multiple system calls may + * be required to make a decision; thus, when referencing the access vector + * cache, we must loop until we complete without an intervening cache flush + * event. In practice, looping even once should be very rare. Callers should + * do something like this: + * + * sepgsql_avc_check_valid(); + * do { + * : + * + * : + * } while (!sepgsql_avc_check_valid()) + * + * ------------------------------------------------------------------------- + */ +static bool +sepgsql_avc_check_valid(void) +{ + if (selinux_status_updated() > 0) + { + sepgsql_avc_reset(); + + return false; + } + return true; +} + +/* + * sepgsql_avc_unlabeled + * + * Returns an alternative label to be applied when no label or an invalid + * label would otherwise be assigned. + */ +static char * +sepgsql_avc_unlabeled(void) +{ + if (!avc_unlabeled) + { + char *unlabeled; + + if (security_get_initial_context_raw("unlabeled", &unlabeled) < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: failed to get initial security label: %m"))); + PG_TRY(); + { + avc_unlabeled = MemoryContextStrdup(avc_mem_cxt, unlabeled); + } + PG_FINALLY(); + { + freecon(unlabeled); + } + PG_END_TRY(); + } + return avc_unlabeled; +} + +/* + * sepgsql_avc_compute + * + * A fallback path, when cache mishit. It asks SELinux its access control + * decision for the supplied pair of security context and object class. + */ +static avc_cache * +sepgsql_avc_compute(const char *scontext, const char *tcontext, uint16 tclass) +{ + char *ucontext = NULL; + char *ncontext = NULL; + MemoryContext oldctx; + avc_cache *cache; + uint32 hash; + int index; + struct av_decision avd; + + hash = sepgsql_avc_hash(scontext, tcontext, tclass); + index = hash % AVC_NUM_SLOTS; + + /* + * Validation check of the supplied security context. Because it always + * invoke system-call, frequent check should be avoided. Unless security + * policy is reloaded, validation status shall be kept, so we also cache + * whether the supplied security context was valid, or not. + */ + if (security_check_context_raw(tcontext) != 0) + ucontext = sepgsql_avc_unlabeled(); + + /* + * Ask SELinux its access control decision + */ + if (!ucontext) + sepgsql_compute_avd(scontext, tcontext, tclass, &avd); + else + sepgsql_compute_avd(scontext, ucontext, tclass, &avd); + + /* + * It also caches a security label to be switched when a client labeled as + * 'scontext' executes a procedure labeled as 'tcontext', not only access + * control decision on the procedure. The security label to be switched + * shall be computed uniquely on a pair of 'scontext' and 'tcontext', + * thus, it is reasonable to cache the new label on avc, and enables to + * reduce unnecessary system calls. It shall be referenced at + * sepgsql_needs_fmgr_hook to check whether the supplied function is a + * trusted procedure, or not. + */ + if (tclass == SEPG_CLASS_DB_PROCEDURE) + { + if (!ucontext) + ncontext = sepgsql_compute_create(scontext, tcontext, + SEPG_CLASS_PROCESS, NULL); + else + ncontext = sepgsql_compute_create(scontext, ucontext, + SEPG_CLASS_PROCESS, NULL); + if (strcmp(scontext, ncontext) == 0) + { + pfree(ncontext); + ncontext = NULL; + } + } + + /* + * Set up an avc_cache object + */ + oldctx = MemoryContextSwitchTo(avc_mem_cxt); + + cache = palloc0(sizeof(avc_cache)); + + cache->hash = hash; + cache->scontext = pstrdup(scontext); + cache->tcontext = pstrdup(tcontext); + cache->tclass = tclass; + + cache->allowed = avd.allowed; + cache->auditallow = avd.auditallow; + cache->auditdeny = avd.auditdeny; + cache->hot_cache = true; + if (avd.flags & SELINUX_AVD_FLAGS_PERMISSIVE) + cache->permissive = true; + if (!ucontext) + cache->tcontext_is_valid = true; + if (ncontext) + cache->ncontext = pstrdup(ncontext); + + avc_num_caches++; + + if (avc_num_caches > avc_threshold) + sepgsql_avc_reclaim(); + + avc_slots[index] = lcons(cache, avc_slots[index]); + + MemoryContextSwitchTo(oldctx); + + return cache; +} + +/* + * sepgsql_avc_lookup + * + * Look up a cache entry that matches the supplied security contexts and + * object class. If not found, create a new cache entry. + */ +static avc_cache * +sepgsql_avc_lookup(const char *scontext, const char *tcontext, uint16 tclass) +{ + avc_cache *cache; + ListCell *cell; + uint32 hash; + int index; + + hash = sepgsql_avc_hash(scontext, tcontext, tclass); + index = hash % AVC_NUM_SLOTS; + + foreach(cell, avc_slots[index]) + { + cache = lfirst(cell); + + if (cache->hash == hash && + cache->tclass == tclass && + strcmp(cache->tcontext, tcontext) == 0 && + strcmp(cache->scontext, scontext) == 0) + { + cache->hot_cache = true; + return cache; + } + } + /* not found, so insert a new cache */ + return sepgsql_avc_compute(scontext, tcontext, tclass); +} + +/* + * sepgsql_avc_check_perms(_label) + * + * It returns 'true', if the security policy suggested to allow the required + * permissions. Otherwise, it returns 'false' or raises an error according + * to the 'abort_on_violation' argument. + * The 'tobject' and 'tclass' identify the target object being referenced, + * and 'required' is a bitmask of permissions (SEPG_*__*) defined for each + * object classes. + * The 'audit_name' is the object name (optional). If SEPGSQL_AVC_NOAUDIT + * was supplied, it means to skip all the audit messages. + */ +bool +sepgsql_avc_check_perms_label(const char *tcontext, + uint16 tclass, uint32 required, + const char *audit_name, + bool abort_on_violation) +{ + char *scontext = sepgsql_get_client_label(); + avc_cache *cache; + uint32 denied; + uint32 audited; + bool result; + + sepgsql_avc_check_valid(); + do + { + result = true; + + /* + * If the target object is unlabeled, we perform the check using the + * label supplied by sepgsql_avc_unlabeled(). + */ + if (tcontext) + cache = sepgsql_avc_lookup(scontext, tcontext, tclass); + else + cache = sepgsql_avc_lookup(scontext, + sepgsql_avc_unlabeled(), tclass); + + denied = required & ~cache->allowed; + + /* + * Compute permissions to be audited + */ + if (sepgsql_get_debug_audit()) + audited = (denied ? (denied & ~0) : (required & ~0)); + else + audited = denied ? (denied & cache->auditdeny) + : (required & cache->auditallow); + + if (denied) + { + /* + * In permissive mode or permissive domain, violated permissions + * shall be audited to the log files at once, and then implicitly + * allowed to avoid a flood of access denied logs, because the + * purpose of permissive mode/domain is to collect a violation log + * that will make it possible to fix up the security policy. + */ + if (!sepgsql_getenforce() || cache->permissive) + cache->allowed |= required; + else + result = false; + } + } while (!sepgsql_avc_check_valid()); + + /* + * In the case when we have something auditable actions here, + * sepgsql_audit_log shall be called with text representation of security + * labels for both of subject and object. It records this access + * violation, so DBA will be able to find out unexpected security problems + * later. + */ + if (audited != 0 && + audit_name != SEPGSQL_AVC_NOAUDIT && + sepgsql_get_mode() != SEPGSQL_MODE_INTERNAL) + { + sepgsql_audit_log(denied != 0, + (sepgsql_getenforce() && !cache->permissive), + cache->scontext, + cache->tcontext_is_valid ? + cache->tcontext : sepgsql_avc_unlabeled(), + cache->tclass, + audited, + audit_name); + } + + if (abort_on_violation && !result) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: security policy violation"))); + + return result; +} + +bool +sepgsql_avc_check_perms(const ObjectAddress *tobject, + uint16 tclass, uint32 required, + const char *audit_name, + bool abort_on_violation) +{ + char *tcontext = GetSecurityLabel(tobject, SEPGSQL_LABEL_TAG); + bool rc; + + rc = sepgsql_avc_check_perms_label(tcontext, + tclass, required, + audit_name, abort_on_violation); + if (tcontext) + pfree(tcontext); + + return rc; +} + +/* + * sepgsql_avc_trusted_proc + * + * If the supplied function OID is configured as a trusted procedure, this + * function will return a security label to be used during the execution of + * that function. Otherwise, it returns NULL. + */ +char * +sepgsql_avc_trusted_proc(Oid functionId) +{ + char *scontext = sepgsql_get_client_label(); + char *tcontext; + ObjectAddress tobject; + avc_cache *cache; + + tobject.classId = ProcedureRelationId; + tobject.objectId = functionId; + tobject.objectSubId = 0; + tcontext = GetSecurityLabel(&tobject, SEPGSQL_LABEL_TAG); + + sepgsql_avc_check_valid(); + do + { + if (tcontext) + cache = sepgsql_avc_lookup(scontext, tcontext, + SEPG_CLASS_DB_PROCEDURE); + else + cache = sepgsql_avc_lookup(scontext, sepgsql_avc_unlabeled(), + SEPG_CLASS_DB_PROCEDURE); + } while (!sepgsql_avc_check_valid()); + + return cache->ncontext; +} + +/* + * sepgsql_avc_exit + * + * Clean up userspace AVC on process exit. + */ +static void +sepgsql_avc_exit(int code, Datum arg) +{ + selinux_status_close(); +} + +/* + * sepgsql_avc_init + * + * Initialize the userspace AVC. This should be called from _PG_init. + */ +void +sepgsql_avc_init(void) +{ + int rc; + + /* + * All the avc stuff shall be allocated in avc_mem_cxt + */ + avc_mem_cxt = AllocSetContextCreate(TopMemoryContext, + "userspace access vector cache", + ALLOCSET_DEFAULT_SIZES); + memset(avc_slots, 0, sizeof(avc_slots)); + avc_num_caches = 0; + avc_lru_hint = 0; + avc_threshold = AVC_DEF_THRESHOLD; + + /* + * SELinux allows to mmap(2) its kernel status page in read-only mode to + * inform userspace applications its status updating (such as policy + * reloading) without system-call invocations. This feature is only + * supported in Linux-2.6.38 or later, however, libselinux provides a + * fallback mode to know its status using netlink sockets. + */ + rc = selinux_status_open(1); + if (rc < 0) + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("SELinux: could not open selinux status : %m"))); + else if (rc > 0) + ereport(LOG, + (errmsg("SELinux: kernel status page uses fallback mode"))); + + /* Arrange to close selinux status page on process exit. */ + on_proc_exit(sepgsql_avc_exit, 0); +} diff --git a/contrib/spi/Makefile b/contrib/spi/Makefile new file mode 100644 index 0000000..c9c34ff --- /dev/null +++ b/contrib/spi/Makefile @@ -0,0 +1,28 @@ +# contrib/spi/Makefile + +MODULES = autoinc insert_username moddatetime refint + +EXTENSION = autoinc insert_username moddatetime refint + +DATA = autoinc--1.0.sql \ + insert_username--1.0.sql \ + moddatetime--1.0.sql \ + refint--1.0.sql +PGFILEDESC = "spi - examples of using SPI and triggers" + +DOCS = $(addsuffix .example, $(MODULES)) + +# this is needed for the regression tests; +# comment out if you want a quieter refint package for other uses +PG_CPPFLAGS = -DREFINT_VERBOSE + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/spi +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/spi/autoinc--1.0.sql b/contrib/spi/autoinc--1.0.sql new file mode 100644 index 0000000..22721e0 --- /dev/null +++ b/contrib/spi/autoinc--1.0.sql @@ -0,0 +1,9 @@ +/* contrib/spi/autoinc--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION autoinc" to load this file. \quit + +CREATE FUNCTION autoinc() +RETURNS trigger +AS 'MODULE_PATHNAME' +LANGUAGE C; diff --git a/contrib/spi/autoinc.c b/contrib/spi/autoinc.c new file mode 100644 index 0000000..8bf7422 --- /dev/null +++ b/contrib/spi/autoinc.c @@ -0,0 +1,127 @@ +/* + * contrib/spi/autoinc.c + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "commands/sequence.h" +#include "commands/trigger.h" +#include "executor/spi.h" +#include "utils/builtins.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(autoinc); + +Datum +autoinc(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + Trigger *trigger; /* to get trigger name */ + int nargs; /* # of arguments */ + int *chattrs; /* attnums of attributes to change */ + int chnattrs = 0; /* # of above */ + Datum *newvals; /* vals of above */ + bool *newnulls; /* null flags for above */ + char **args; /* arguments */ + char *relname; /* triggered relation name */ + Relation rel; /* triggered relation */ + HeapTuple rettuple = NULL; + TupleDesc tupdesc; /* tuple description */ + bool isnull; + int i; + + if (!CALLED_AS_TRIGGER(fcinfo)) + /* internal error */ + elog(ERROR, "not fired by trigger manager"); + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "must be fired for row"); + if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "must be fired before event"); + + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + rettuple = trigdata->tg_trigtuple; + else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + rettuple = trigdata->tg_newtuple; + else + /* internal error */ + elog(ERROR, "cannot process DELETE events"); + + rel = trigdata->tg_relation; + relname = SPI_getrelname(rel); + + trigger = trigdata->tg_trigger; + + nargs = trigger->tgnargs; + if (nargs <= 0 || nargs % 2 != 0) + /* internal error */ + elog(ERROR, "autoinc (%s): even number gt 0 of arguments was expected", relname); + + args = trigger->tgargs; + tupdesc = rel->rd_att; + + chattrs = (int *) palloc(nargs / 2 * sizeof(int)); + newvals = (Datum *) palloc(nargs / 2 * sizeof(Datum)); + newnulls = (bool *) palloc(nargs / 2 * sizeof(bool)); + + for (i = 0; i < nargs;) + { + int attnum = SPI_fnumber(tupdesc, args[i]); + int32 val; + Datum seqname; + + if (attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), + errmsg("\"%s\" has no attribute \"%s\"", + relname, args[i]))); + + if (SPI_gettypeid(tupdesc, attnum) != INT4OID) + ereport(ERROR, + (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), + errmsg("attribute \"%s\" of \"%s\" must be type INT4", + args[i], relname))); + + val = DatumGetInt32(SPI_getbinval(rettuple, tupdesc, attnum, &isnull)); + + if (!isnull && val != 0) + { + i += 2; + continue; + } + + i++; + chattrs[chnattrs] = attnum; + seqname = CStringGetTextDatum(args[i]); + newvals[chnattrs] = DirectFunctionCall1(nextval, seqname); + /* nextval now returns int64; coerce down to int32 */ + newvals[chnattrs] = Int32GetDatum((int32) DatumGetInt64(newvals[chnattrs])); + if (DatumGetInt32(newvals[chnattrs]) == 0) + { + newvals[chnattrs] = DirectFunctionCall1(nextval, seqname); + newvals[chnattrs] = Int32GetDatum((int32) DatumGetInt64(newvals[chnattrs])); + } + newnulls[chnattrs] = false; + pfree(DatumGetTextPP(seqname)); + chnattrs++; + i++; + } + + if (chnattrs > 0) + { + rettuple = heap_modify_tuple_by_cols(rettuple, tupdesc, + chnattrs, chattrs, + newvals, newnulls); + } + + pfree(relname); + pfree(chattrs); + pfree(newvals); + pfree(newnulls); + + return PointerGetDatum(rettuple); +} diff --git a/contrib/spi/autoinc.control b/contrib/spi/autoinc.control new file mode 100644 index 0000000..1d7a8e5 --- /dev/null +++ b/contrib/spi/autoinc.control @@ -0,0 +1,5 @@ +# autoinc extension +comment = 'functions for autoincrementing fields' +default_version = '1.0' +module_pathname = '$libdir/autoinc' +relocatable = true diff --git a/contrib/spi/autoinc.example b/contrib/spi/autoinc.example new file mode 100644 index 0000000..08880ce --- /dev/null +++ b/contrib/spi/autoinc.example @@ -0,0 +1,35 @@ +DROP SEQUENCE next_id; +DROP TABLE ids; + +CREATE SEQUENCE next_id START -2 MINVALUE -2; + +CREATE TABLE ids ( + id int4, + idesc text +); + +CREATE TRIGGER ids_nextid + BEFORE INSERT OR UPDATE ON ids + FOR EACH ROW + EXECUTE PROCEDURE autoinc (id, next_id); + +INSERT INTO ids VALUES (0, 'first (-2 ?)'); +INSERT INTO ids VALUES (null, 'second (-1 ?)'); +INSERT INTO ids(idesc) VALUES ('third (1 ?!)'); + +SELECT * FROM ids; + +UPDATE ids SET id = null, idesc = 'first: -2 --> 2' + WHERE idesc = 'first (-2 ?)'; +UPDATE ids SET id = 0, idesc = 'second: -1 --> 3' + WHERE id = -1; +UPDATE ids SET id = 4, idesc = 'third: 1 --> 4' + WHERE id = 1; + +SELECT * FROM ids; + +SELECT 'Wasn''t it 4 ?' as nextval, nextval ('next_id') as value; + +insert into ids (idesc) select textcat (idesc, '. Copy.') from ids; + +SELECT * FROM ids; diff --git a/contrib/spi/insert_username--1.0.sql b/contrib/spi/insert_username--1.0.sql new file mode 100644 index 0000000..0deb0bf --- /dev/null +++ b/contrib/spi/insert_username--1.0.sql @@ -0,0 +1,9 @@ +/* contrib/spi/insert_username--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION insert_username" to load this file. \quit + +CREATE FUNCTION insert_username() +RETURNS trigger +AS 'MODULE_PATHNAME' +LANGUAGE C; diff --git a/contrib/spi/insert_username.c b/contrib/spi/insert_username.c new file mode 100644 index 0000000..a2e1747 --- /dev/null +++ b/contrib/spi/insert_username.c @@ -0,0 +1,92 @@ +/* + * contrib/spi/insert_username.c + * + * insert user name in response to a trigger + * usage: insert_username (column_name) + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "commands/trigger.h" +#include "executor/spi.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(insert_username); + +Datum +insert_username(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + Trigger *trigger; /* to get trigger name */ + int nargs; /* # of arguments */ + Datum newval; /* new value of column */ + bool newnull; /* null flag */ + char **args; /* arguments */ + char *relname; /* triggered relation name */ + Relation rel; /* triggered relation */ + HeapTuple rettuple = NULL; + TupleDesc tupdesc; /* tuple description */ + int attnum; + + /* sanity checks from autoinc.c */ + if (!CALLED_AS_TRIGGER(fcinfo)) + /* internal error */ + elog(ERROR, "insert_username: not fired by trigger manager"); + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "insert_username: must be fired for row"); + if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "insert_username: must be fired before event"); + + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + rettuple = trigdata->tg_trigtuple; + else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + rettuple = trigdata->tg_newtuple; + else + /* internal error */ + elog(ERROR, "insert_username: cannot process DELETE events"); + + rel = trigdata->tg_relation; + relname = SPI_getrelname(rel); + + trigger = trigdata->tg_trigger; + + nargs = trigger->tgnargs; + if (nargs != 1) + /* internal error */ + elog(ERROR, "insert_username (%s): one argument was expected", relname); + + args = trigger->tgargs; + tupdesc = rel->rd_att; + + attnum = SPI_fnumber(tupdesc, args[0]); + + if (attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), + errmsg("\"%s\" has no attribute \"%s\"", relname, args[0]))); + + if (SPI_gettypeid(tupdesc, attnum) != TEXTOID) + ereport(ERROR, + (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), + errmsg("attribute \"%s\" of \"%s\" must be type TEXT", + args[0], relname))); + + /* create fields containing name */ + newval = CStringGetTextDatum(GetUserNameFromId(GetUserId(), false)); + newnull = false; + + /* construct new tuple */ + rettuple = heap_modify_tuple_by_cols(rettuple, tupdesc, + 1, &attnum, &newval, &newnull); + + pfree(relname); + + return PointerGetDatum(rettuple); +} diff --git a/contrib/spi/insert_username.control b/contrib/spi/insert_username.control new file mode 100644 index 0000000..9d11064 --- /dev/null +++ b/contrib/spi/insert_username.control @@ -0,0 +1,5 @@ +# insert_username extension +comment = 'functions for tracking who changed a table' +default_version = '1.0' +module_pathname = '$libdir/insert_username' +relocatable = true diff --git a/contrib/spi/insert_username.example b/contrib/spi/insert_username.example new file mode 100644 index 0000000..2c1eeb0 --- /dev/null +++ b/contrib/spi/insert_username.example @@ -0,0 +1,20 @@ +DROP TABLE username_test; + +CREATE TABLE username_test ( + name text, + username text not null +); + +CREATE TRIGGER insert_usernames + BEFORE INSERT OR UPDATE ON username_test + FOR EACH ROW + EXECUTE PROCEDURE insert_username (username); + +INSERT INTO username_test VALUES ('nothing'); +INSERT INTO username_test VALUES ('null', null); +INSERT INTO username_test VALUES ('empty string', ''); +INSERT INTO username_test VALUES ('space', ' '); +INSERT INTO username_test VALUES ('tab', ' '); +INSERT INTO username_test VALUES ('name', 'name'); + +SELECT * FROM username_test; diff --git a/contrib/spi/meson.build b/contrib/spi/meson.build new file mode 100644 index 0000000..a80e2c8 --- /dev/null +++ b/contrib/spi/meson.build @@ -0,0 +1,92 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +autoinc_sources = files( + 'autoinc.c', +) + +if host_system == 'windows' + autoinc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'autoinc', + '--FILEDESC', 'spi - examples of using SPI and triggers',]) +endif + +autoinc = shared_module('autoinc', + autoinc_sources, + kwargs: contrib_mod_args, +) +contrib_targets += autoinc + +install_data('autoinc.control', 'autoinc--1.0.sql', + kwargs: contrib_data_args, +) + + +insert_username_sources = files( + 'insert_username.c', +) + +if host_system == 'windows' + insert_username_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'insert_username', + '--FILEDESC', 'spi - examples of using SPI and triggers',]) +endif + +insert_username = shared_module('insert_username', + insert_username_sources, + kwargs: contrib_mod_args, +) +contrib_targets += insert_username + +install_data( + 'insert_username.control', + 'insert_username--1.0.sql', + kwargs: contrib_data_args, +) + + +moddatetime_sources = files( + 'moddatetime.c', +) + +if host_system == 'windows' + moddatetime_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'moddatetime', + '--FILEDESC', 'spi - examples of using SPI and triggers',]) +endif + +moddatetime = shared_module('moddatetime', + moddatetime_sources, + kwargs: contrib_mod_args, +) +contrib_targets += moddatetime + +install_data( + 'moddatetime.control', + 'moddatetime--1.0.sql', + kwargs: contrib_data_args, +) + +# this is needed for the regression tests; +# comment out if you want a quieter refint package for other uses +refint_cflags = ['-DREFINT_VERBOSE'] + +refint_sources = files( + 'refint.c', +) + +if host_system == 'windows' + refint_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'refint', + '--FILEDESC', 'spi - examples of using SPI and triggers',]) +endif + +refint = shared_module('refint', + refint_sources, + c_args: refint_cflags, + kwargs: contrib_mod_args, +) +contrib_targets += refint + +install_data('refint.control', 'refint--1.0.sql', + kwargs: contrib_data_args, +) diff --git a/contrib/spi/moddatetime--1.0.sql b/contrib/spi/moddatetime--1.0.sql new file mode 100644 index 0000000..2ee61b8 --- /dev/null +++ b/contrib/spi/moddatetime--1.0.sql @@ -0,0 +1,9 @@ +/* contrib/spi/moddatetime--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION moddatetime" to load this file. \quit + +CREATE FUNCTION moddatetime() +RETURNS trigger +AS 'MODULE_PATHNAME' +LANGUAGE C; diff --git a/contrib/spi/moddatetime.c b/contrib/spi/moddatetime.c new file mode 100644 index 0000000..3eb7004 --- /dev/null +++ b/contrib/spi/moddatetime.c @@ -0,0 +1,130 @@ +/* +moddatetime.c + +contrib/spi/moddatetime.c + +What is this? +It is a function to be called from a trigger for the purpose of updating +a modification datetime stamp in a record when that record is UPDATEd. + +Credits +This is 95%+ based on autoinc.c, which I used as a starting point as I do +not really know what I am doing. I also had help from +Jan Wieck who told me about the timestamp_in("now") function. +OH, me, I'm Terry Mackintosh +*/ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "commands/trigger.h" +#include "executor/spi.h" +#include "utils/builtins.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(moddatetime); + +Datum +moddatetime(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + Trigger *trigger; /* to get trigger name */ + int nargs; /* # of arguments */ + int attnum; /* positional number of field to change */ + Oid atttypid; /* type OID of field to change */ + Datum newdt; /* The current datetime. */ + bool newdtnull; /* null flag for it */ + char **args; /* arguments */ + char *relname; /* triggered relation name */ + Relation rel; /* triggered relation */ + HeapTuple rettuple = NULL; + TupleDesc tupdesc; /* tuple description */ + + if (!CALLED_AS_TRIGGER(fcinfo)) + /* internal error */ + elog(ERROR, "moddatetime: not fired by trigger manager"); + + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "moddatetime: must be fired for row"); + + if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "moddatetime: must be fired before event"); + + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "moddatetime: cannot process INSERT events"); + else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + rettuple = trigdata->tg_newtuple; + else + /* internal error */ + elog(ERROR, "moddatetime: cannot process DELETE events"); + + rel = trigdata->tg_relation; + relname = SPI_getrelname(rel); + + trigger = trigdata->tg_trigger; + + nargs = trigger->tgnargs; + + if (nargs != 1) + /* internal error */ + elog(ERROR, "moddatetime (%s): A single argument was expected", relname); + + args = trigger->tgargs; + /* must be the field layout? */ + tupdesc = rel->rd_att; + + /* + * This gets the position in the tuple of the field we want. args[0] being + * the name of the field to update, as passed in from the trigger. + */ + attnum = SPI_fnumber(tupdesc, args[0]); + + /* + * This is where we check to see if the field we are supposed to update + * even exists. + */ + if (attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), + errmsg("\"%s\" has no attribute \"%s\"", + relname, args[0]))); + + /* + * Check the target field has an allowed type, and get the current + * datetime as a value of that type. + */ + atttypid = SPI_gettypeid(tupdesc, attnum); + if (atttypid == TIMESTAMPOID) + newdt = DirectFunctionCall3(timestamp_in, + CStringGetDatum("now"), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + else if (atttypid == TIMESTAMPTZOID) + newdt = DirectFunctionCall3(timestamptz_in, + CStringGetDatum("now"), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + else + { + ereport(ERROR, + (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), + errmsg("attribute \"%s\" of \"%s\" must be type TIMESTAMP or TIMESTAMPTZ", + args[0], relname))); + newdt = (Datum) 0; /* keep compiler quiet */ + } + newdtnull = false; + + /* Replace the attnum'th column with newdt */ + rettuple = heap_modify_tuple_by_cols(rettuple, tupdesc, + 1, &attnum, &newdt, &newdtnull); + + /* Clean up */ + pfree(relname); + + return PointerGetDatum(rettuple); +} diff --git a/contrib/spi/moddatetime.control b/contrib/spi/moddatetime.control new file mode 100644 index 0000000..93dfac5 --- /dev/null +++ b/contrib/spi/moddatetime.control @@ -0,0 +1,5 @@ +# moddatetime extension +comment = 'functions for tracking last modification time' +default_version = '1.0' +module_pathname = '$libdir/moddatetime' +relocatable = true diff --git a/contrib/spi/moddatetime.example b/contrib/spi/moddatetime.example new file mode 100644 index 0000000..65af388 --- /dev/null +++ b/contrib/spi/moddatetime.example @@ -0,0 +1,27 @@ +DROP TABLE mdt; + +CREATE TABLE mdt ( + id int4, + idesc text, + moddate timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +CREATE TRIGGER mdt_moddatetime + BEFORE UPDATE ON mdt + FOR EACH ROW + EXECUTE PROCEDURE moddatetime (moddate); + +INSERT INTO mdt VALUES (1, 'first'); +INSERT INTO mdt VALUES (2, 'second'); +INSERT INTO mdt VALUES (3, 'third'); + +SELECT * FROM mdt; + +UPDATE mdt SET id = 4 + WHERE id = 1; +UPDATE mdt SET id = 5 + WHERE id = 2; +UPDATE mdt SET id = 6 + WHERE id = 3; + +SELECT * FROM mdt; diff --git a/contrib/spi/refint--1.0.sql b/contrib/spi/refint--1.0.sql new file mode 100644 index 0000000..faf797c --- /dev/null +++ b/contrib/spi/refint--1.0.sql @@ -0,0 +1,14 @@ +/* contrib/spi/refint--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION refint" to load this file. \quit + +CREATE FUNCTION check_primary_key() +RETURNS trigger +AS 'MODULE_PATHNAME' +LANGUAGE C; + +CREATE FUNCTION check_foreign_key() +RETURNS trigger +AS 'MODULE_PATHNAME' +LANGUAGE C; diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c new file mode 100644 index 0000000..18062eb --- /dev/null +++ b/contrib/spi/refint.c @@ -0,0 +1,654 @@ +/* + * contrib/spi/refint.c + * + * + * refint.c -- set of functions to define referential integrity + * constraints using general triggers. + */ +#include "postgres.h" + +#include + +#include "commands/trigger.h" +#include "executor/spi.h" +#include "utils/builtins.h" +#include "utils/memutils.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +typedef struct +{ + char *ident; + int nplans; + SPIPlanPtr *splan; +} EPlan; + +static EPlan *FPlans = NULL; +static int nFPlans = 0; +static EPlan *PPlans = NULL; +static int nPPlans = 0; + +static EPlan *find_plan(char *ident, EPlan **eplan, int *nplans); + +/* + * check_primary_key () -- check that key in tuple being inserted/updated + * references existing tuple in "primary" table. + * Though it's called without args You have to specify referenced + * table/keys while creating trigger: key field names in triggered table, + * referenced table name, referenced key field names: + * EXECUTE PROCEDURE + * check_primary_key ('Fkey1', 'Fkey2', 'Ptable', 'Pkey1', 'Pkey2'). + */ + +PG_FUNCTION_INFO_V1(check_primary_key); + +Datum +check_primary_key(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + Trigger *trigger; /* to get trigger name */ + int nargs; /* # of args specified in CREATE TRIGGER */ + char **args; /* arguments: column names and table name */ + int nkeys; /* # of key columns (= nargs / 2) */ + Datum *kvals; /* key values */ + char *relname; /* referenced relation name */ + Relation rel; /* triggered relation */ + HeapTuple tuple = NULL; /* tuple to return */ + TupleDesc tupdesc; /* tuple description */ + EPlan *plan; /* prepared plan */ + Oid *argtypes = NULL; /* key types to prepare execution plan */ + bool isnull; /* to know is some column NULL or not */ + char ident[2 * NAMEDATALEN]; /* to identify myself */ + int ret; + int i; + +#ifdef DEBUG_QUERY + elog(DEBUG4, "check_primary_key: Enter Function"); +#endif + + /* + * Some checks first... + */ + + /* Called by trigger manager ? */ + if (!CALLED_AS_TRIGGER(fcinfo)) + /* internal error */ + elog(ERROR, "check_primary_key: not fired by trigger manager"); + + /* Should be called for ROW trigger */ + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "check_primary_key: must be fired for row"); + + /* If INSERTion then must check Tuple to being inserted */ + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + tuple = trigdata->tg_trigtuple; + + /* Not should be called for DELETE */ + else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "check_primary_key: cannot process DELETE events"); + + /* If UPDATE, then must check new Tuple, not old one */ + else + tuple = trigdata->tg_newtuple; + + trigger = trigdata->tg_trigger; + nargs = trigger->tgnargs; + args = trigger->tgargs; + + if (nargs % 2 != 1) /* odd number of arguments! */ + /* internal error */ + elog(ERROR, "check_primary_key: odd number of arguments should be specified"); + + nkeys = nargs / 2; + relname = args[nkeys]; + rel = trigdata->tg_relation; + tupdesc = rel->rd_att; + + /* Connect to SPI manager */ + if ((ret = SPI_connect()) < 0) + /* internal error */ + elog(ERROR, "check_primary_key: SPI_connect returned %d", ret); + + /* + * We use SPI plan preparation feature, so allocate space to place key + * values. + */ + kvals = (Datum *) palloc(nkeys * sizeof(Datum)); + + /* + * Construct ident string as TriggerName $ TriggeredRelationId and try to + * find prepared execution plan. + */ + snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id); + plan = find_plan(ident, &PPlans, &nPPlans); + + /* if there is no plan then allocate argtypes for preparation */ + if (plan->nplans <= 0) + argtypes = (Oid *) palloc(nkeys * sizeof(Oid)); + + /* For each column in key ... */ + for (i = 0; i < nkeys; i++) + { + /* get index of column in tuple */ + int fnumber = SPI_fnumber(tupdesc, args[i]); + + /* Bad guys may give us un-existing column in CREATE TRIGGER */ + if (fnumber <= 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("there is no attribute \"%s\" in relation \"%s\"", + args[i], SPI_getrelname(rel)))); + + /* Well, get binary (in internal format) value of column */ + kvals[i] = SPI_getbinval(tuple, tupdesc, fnumber, &isnull); + + /* + * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()! + * DON'T FORGET return tuple! Executor inserts tuple you're returning! + * If you return NULL then nothing will be inserted! + */ + if (isnull) + { + SPI_finish(); + return PointerGetDatum(tuple); + } + + if (plan->nplans <= 0) /* Get typeId of column */ + argtypes[i] = SPI_gettypeid(tupdesc, fnumber); + } + + /* + * If we have to prepare plan ... + */ + if (plan->nplans <= 0) + { + SPIPlanPtr pplan; + char sql[8192]; + + /* + * Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 = + * $1 [AND Pkey2 = $2 [...]] + */ + snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); + for (i = 0; i < nkeys; i++) + { + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", + args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : ""); + } + + /* Prepare plan for query */ + pplan = SPI_prepare(sql, nkeys, argtypes); + if (pplan == NULL) + /* internal error */ + elog(ERROR, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); + + /* + * Remember that SPI_prepare places plan in current memory context - + * so, we have to save plan in TopMemoryContext for later use. + */ + if (SPI_keepplan(pplan)) + /* internal error */ + elog(ERROR, "check_primary_key: SPI_keepplan failed"); + plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext, + sizeof(SPIPlanPtr)); + *(plan->splan) = pplan; + plan->nplans = 1; + } + + /* + * Ok, execute prepared plan. + */ + ret = SPI_execp(*(plan->splan), kvals, NULL, 1); + /* we have no NULLs - so we pass ^^^^ here */ + + if (ret < 0) + /* internal error */ + elog(ERROR, "check_primary_key: SPI_execp returned %d", ret); + + /* + * If there are no tuples returned by SELECT then ... + */ + if (SPI_processed == 0) + ereport(ERROR, + (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), + errmsg("tuple references non-existent key"), + errdetail("Trigger \"%s\" found tuple referencing non-existent key in \"%s\".", trigger->tgname, relname))); + + SPI_finish(); + + return PointerGetDatum(tuple); +} + +/* + * check_foreign_key () -- check that key in tuple being deleted/updated + * is not referenced by tuples in "foreign" table(s). + * Though it's called without args You have to specify (while creating trigger): + * number of references, action to do if key referenced + * ('restrict' | 'setnull' | 'cascade'), key field names in triggered + * ("primary") table and referencing table(s)/keys: + * EXECUTE PROCEDURE + * check_foreign_key (2, 'restrict', 'Pkey1', 'Pkey2', + * 'Ftable1', 'Fkey11', 'Fkey12', 'Ftable2', 'Fkey21', 'Fkey22'). + */ + +PG_FUNCTION_INFO_V1(check_foreign_key); + +Datum +check_foreign_key(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + Trigger *trigger; /* to get trigger name */ + int nargs; /* # of args specified in CREATE TRIGGER */ + char **args; /* arguments: as described above */ + char **args_temp; + int nrefs; /* number of references (== # of plans) */ + char action; /* 'R'estrict | 'S'etnull | 'C'ascade */ + int nkeys; /* # of key columns */ + Datum *kvals; /* key values */ + char *relname; /* referencing relation name */ + Relation rel; /* triggered relation */ + HeapTuple trigtuple = NULL; /* tuple to being changed */ + HeapTuple newtuple = NULL; /* tuple to return */ + TupleDesc tupdesc; /* tuple description */ + EPlan *plan; /* prepared plan(s) */ + Oid *argtypes = NULL; /* key types to prepare execution plan */ + bool isnull; /* to know is some column NULL or not */ + bool isequal = true; /* are keys in both tuples equal (in UPDATE) */ + char ident[2 * NAMEDATALEN]; /* to identify myself */ + int is_update = 0; + int ret; + int i, + r; + +#ifdef DEBUG_QUERY + elog(DEBUG4, "check_foreign_key: Enter Function"); +#endif + + /* + * Some checks first... + */ + + /* Called by trigger manager ? */ + if (!CALLED_AS_TRIGGER(fcinfo)) + /* internal error */ + elog(ERROR, "check_foreign_key: not fired by trigger manager"); + + /* Should be called for ROW trigger */ + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "check_foreign_key: must be fired for row"); + + /* Not should be called for INSERT */ + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + /* internal error */ + elog(ERROR, "check_foreign_key: cannot process INSERT events"); + + /* Have to check tg_trigtuple - tuple being deleted */ + trigtuple = trigdata->tg_trigtuple; + + /* + * But if this is UPDATE then we have to return tg_newtuple. Also, if key + * in tg_newtuple is the same as in tg_trigtuple then nothing to do. + */ + is_update = 0; + if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + { + newtuple = trigdata->tg_newtuple; + is_update = 1; + } + trigger = trigdata->tg_trigger; + nargs = trigger->tgnargs; + args = trigger->tgargs; + + if (nargs < 5) /* nrefs, action, key, Relation, key - at + * least */ + /* internal error */ + elog(ERROR, "check_foreign_key: too short %d (< 5) list of arguments", nargs); + + nrefs = pg_strtoint32(args[0]); + if (nrefs < 1) + /* internal error */ + elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs); + action = tolower((unsigned char) *(args[1])); + if (action != 'r' && action != 'c' && action != 's') + /* internal error */ + elog(ERROR, "check_foreign_key: invalid action %s", args[1]); + nargs -= 2; + args += 2; + nkeys = (nargs - nrefs) / (nrefs + 1); + if (nkeys <= 0 || nargs != (nrefs + nkeys * (nrefs + 1))) + /* internal error */ + elog(ERROR, "check_foreign_key: invalid number of arguments %d for %d references", + nargs + 2, nrefs); + + rel = trigdata->tg_relation; + tupdesc = rel->rd_att; + + /* Connect to SPI manager */ + if ((ret = SPI_connect()) < 0) + /* internal error */ + elog(ERROR, "check_foreign_key: SPI_connect returned %d", ret); + + /* + * We use SPI plan preparation feature, so allocate space to place key + * values. + */ + kvals = (Datum *) palloc(nkeys * sizeof(Datum)); + + /* + * Construct ident string as TriggerName $ TriggeredRelationId and try to + * find prepared execution plan(s). + */ + snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id); + plan = find_plan(ident, &FPlans, &nFPlans); + + /* if there is no plan(s) then allocate argtypes for preparation */ + if (plan->nplans <= 0) + argtypes = (Oid *) palloc(nkeys * sizeof(Oid)); + + /* + * else - check that we have exactly nrefs plan(s) ready + */ + else if (plan->nplans != nrefs) + /* internal error */ + elog(ERROR, "%s: check_foreign_key: # of plans changed in meantime", + trigger->tgname); + + /* For each column in key ... */ + for (i = 0; i < nkeys; i++) + { + /* get index of column in tuple */ + int fnumber = SPI_fnumber(tupdesc, args[i]); + + /* Bad guys may give us un-existing column in CREATE TRIGGER */ + if (fnumber <= 0) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("there is no attribute \"%s\" in relation \"%s\"", + args[i], SPI_getrelname(rel)))); + + /* Well, get binary (in internal format) value of column */ + kvals[i] = SPI_getbinval(trigtuple, tupdesc, fnumber, &isnull); + + /* + * If it's NULL then nothing to do! DON'T FORGET call SPI_finish ()! + * DON'T FORGET return tuple! Executor inserts tuple you're returning! + * If you return NULL then nothing will be inserted! + */ + if (isnull) + { + SPI_finish(); + return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple); + } + + /* + * If UPDATE then get column value from new tuple being inserted and + * compare is this the same as old one. For the moment we use string + * presentation of values... + */ + if (newtuple != NULL) + { + char *oldval = SPI_getvalue(trigtuple, tupdesc, fnumber); + char *newval; + + /* this shouldn't happen! SPI_ERROR_NOOUTFUNC ? */ + if (oldval == NULL) + /* internal error */ + elog(ERROR, "check_foreign_key: SPI_getvalue returned %s", SPI_result_code_string(SPI_result)); + newval = SPI_getvalue(newtuple, tupdesc, fnumber); + if (newval == NULL || strcmp(oldval, newval) != 0) + isequal = false; + } + + if (plan->nplans <= 0) /* Get typeId of column */ + argtypes[i] = SPI_gettypeid(tupdesc, fnumber); + } + args_temp = args; + nargs -= nkeys; + args += nkeys; + + /* + * If we have to prepare plans ... + */ + if (plan->nplans <= 0) + { + SPIPlanPtr pplan; + char sql[8192]; + char **args2 = args; + + plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext, + nrefs * sizeof(SPIPlanPtr)); + + for (r = 0; r < nrefs; r++) + { + relname = args2[0]; + + /*--------- + * For 'R'estrict action we construct SELECT query: + * + * SELECT 1 + * FROM _referencing_relation_ + * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]] + * + * to check is tuple referenced or not. + *--------- + */ + if (action == 'r') + + snprintf(sql, sizeof(sql), "select 1 from %s where ", relname); + + /*--------- + * For 'C'ascade action we construct DELETE query + * + * DELETE + * FROM _referencing_relation_ + * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]] + * + * to delete all referencing tuples. + *--------- + */ + + /* + * Max : Cascade with UPDATE query i create update query that + * updates new key values in referenced tables + */ + + + else if (action == 'c') + { + if (is_update == 1) + { + int fn; + char *nv; + int k; + + snprintf(sql, sizeof(sql), "update %s set ", relname); + for (k = 1; k <= nkeys; k++) + { + int is_char_type = 0; + char *type; + + fn = SPI_fnumber(tupdesc, args_temp[k - 1]); + Assert(fn > 0); /* already checked above */ + nv = SPI_getvalue(newtuple, tupdesc, fn); + type = SPI_gettype(tupdesc, fn); + + if (strcmp(type, "text") == 0 || + strcmp(type, "varchar") == 0 || + strcmp(type, "char") == 0 || + strcmp(type, "bpchar") == 0 || + strcmp(type, "date") == 0 || + strcmp(type, "timestamp") == 0) + is_char_type = 1; +#ifdef DEBUG_QUERY + elog(DEBUG4, "check_foreign_key Debug value %s type %s %d", + nv, type, is_char_type); +#endif + + /* + * is_char_type =1 i set ' ' for define a new value + */ + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), + " %s = %s%s%s %s ", + args2[k], (is_char_type > 0) ? "'" : "", + nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : ""); + } + strcat(sql, " where "); + } + else + /* DELETE */ + snprintf(sql, sizeof(sql), "delete from %s where ", relname); + } + + /* + * For 'S'etnull action we construct UPDATE query - UPDATE + * _referencing_relation_ SET Fkey1 null [, Fkey2 null [...]] + * WHERE Fkey1 = $1 [AND Fkey2 = $2 [...]] - to set key columns in + * all referencing tuples to NULL. + */ + else if (action == 's') + { + snprintf(sql, sizeof(sql), "update %s set ", relname); + for (i = 1; i <= nkeys; i++) + { + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), + "%s = null%s", + args2[i], (i < nkeys) ? ", " : ""); + } + strcat(sql, " where "); + } + + /* Construct WHERE qual */ + for (i = 1; i <= nkeys; i++) + { + snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s", + args2[i], i, (i < nkeys) ? "and " : ""); + } + + /* Prepare plan for query */ + pplan = SPI_prepare(sql, nkeys, argtypes); + if (pplan == NULL) + /* internal error */ + elog(ERROR, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result)); + + /* + * Remember that SPI_prepare places plan in current memory context + * - so, we have to save plan in Top memory context for later use. + */ + if (SPI_keepplan(pplan)) + /* internal error */ + elog(ERROR, "check_foreign_key: SPI_keepplan failed"); + + plan->splan[r] = pplan; + + args2 += nkeys + 1; /* to the next relation */ + } + plan->nplans = nrefs; +#ifdef DEBUG_QUERY + elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql); +#endif + } + + /* + * If UPDATE and key is not changed ... + */ + if (newtuple != NULL && isequal) + { + SPI_finish(); + return PointerGetDatum(newtuple); + } + + /* + * Ok, execute prepared plan(s). + */ + for (r = 0; r < nrefs; r++) + { + /* + * For 'R'estrict we may to execute plan for one tuple only, for other + * actions - for all tuples. + */ + int tcount = (action == 'r') ? 1 : 0; + + relname = args[0]; + + snprintf(ident, sizeof(ident), "%s$%u", trigger->tgname, rel->rd_id); + plan = find_plan(ident, &FPlans, &nFPlans); + ret = SPI_execp(plan->splan[r], kvals, NULL, tcount); + /* we have no NULLs - so we pass ^^^^ here */ + + if (ret < 0) + ereport(ERROR, + (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), + errmsg("SPI_execp returned %d", ret))); + + /* If action is 'R'estrict ... */ + if (action == 'r') + { + /* If there is tuple returned by SELECT then ... */ + if (SPI_processed > 0) + ereport(ERROR, + (errcode(ERRCODE_TRIGGERED_ACTION_EXCEPTION), + errmsg("\"%s\": tuple is referenced in \"%s\"", + trigger->tgname, relname))); + } + else + { +#ifdef REFINT_VERBOSE + elog(NOTICE, "%s: " UINT64_FORMAT " tuple(s) of %s are %s", + trigger->tgname, SPI_processed, relname, + (action == 'c') ? "deleted" : "set to null"); +#endif + } + args += nkeys + 1; /* to the next relation */ + } + + SPI_finish(); + + return PointerGetDatum((newtuple == NULL) ? trigtuple : newtuple); +} + +static EPlan * +find_plan(char *ident, EPlan **eplan, int *nplans) +{ + EPlan *newp; + int i; + MemoryContext oldcontext; + + /* + * All allocations done for the plans need to happen in a session-safe + * context. + */ + oldcontext = MemoryContextSwitchTo(TopMemoryContext); + + if (*nplans > 0) + { + for (i = 0; i < *nplans; i++) + { + if (strcmp((*eplan)[i].ident, ident) == 0) + break; + } + if (i != *nplans) + { + MemoryContextSwitchTo(oldcontext); + return (*eplan + i); + } + *eplan = (EPlan *) repalloc(*eplan, (i + 1) * sizeof(EPlan)); + newp = *eplan + i; + } + else + { + newp = *eplan = (EPlan *) palloc(sizeof(EPlan)); + (*nplans) = i = 0; + } + + newp->ident = pstrdup(ident); + newp->nplans = 0; + newp->splan = NULL; + (*nplans)++; + + MemoryContextSwitchTo(oldcontext); + return newp; +} diff --git a/contrib/spi/refint.control b/contrib/spi/refint.control new file mode 100644 index 0000000..cbede45 --- /dev/null +++ b/contrib/spi/refint.control @@ -0,0 +1,5 @@ +# refint extension +comment = 'functions for implementing referential integrity (obsolete)' +default_version = '1.0' +module_pathname = '$libdir/refint' +relocatable = true diff --git a/contrib/spi/refint.example b/contrib/spi/refint.example new file mode 100644 index 0000000..299166d --- /dev/null +++ b/contrib/spi/refint.example @@ -0,0 +1,82 @@ +--Column ID of table A is primary key: + +CREATE TABLE A ( + ID int4 not null +); +CREATE UNIQUE INDEX AI ON A (ID); + +--Columns REFB of table B and REFC of C are foreign keys referencing ID of A: + +CREATE TABLE B ( + REFB int4 +); +CREATE INDEX BI ON B (REFB); + +CREATE TABLE C ( + REFC int4 +); +CREATE INDEX CI ON C (REFC); + +--Trigger for table A: + +CREATE TRIGGER AT BEFORE DELETE OR UPDATE ON A FOR EACH ROW +EXECUTE PROCEDURE +check_foreign_key (2, 'cascade', 'ID', 'B', 'REFB', 'C', 'REFC'); +/* +2 - means that check must be performed for foreign keys of 2 tables. +cascade - defines that corresponding keys must be deleted. +ID - name of primary key column in triggered table (A). You may + use as many columns as you need. +B - name of (first) table with foreign keys. +REFB - name of foreign key column in this table. You may use as many + columns as you need, but number of key columns in referenced + table (A) must be the same. +C - name of second table with foreign keys. +REFC - name of foreign key column in this table. +*/ + +--Trigger for table B: + +CREATE TRIGGER BT BEFORE INSERT OR UPDATE ON B FOR EACH ROW +EXECUTE PROCEDURE +check_primary_key ('REFB', 'A', 'ID'); + +/* +REFB - name of foreign key column in triggered (B) table. You may use as + many columns as you need, but number of key columns in referenced + table must be the same. +A - referenced table name. +ID - name of primary key column in referenced table. +*/ + +--Trigger for table C: + +CREATE TRIGGER CT BEFORE INSERT OR UPDATE ON C FOR EACH ROW +EXECUTE PROCEDURE +check_primary_key ('REFC', 'A', 'ID'); + +-- Now try + +INSERT INTO A VALUES (10); +INSERT INTO A VALUES (20); +INSERT INTO A VALUES (30); +INSERT INTO A VALUES (40); +INSERT INTO A VALUES (50); + +INSERT INTO B VALUES (1); -- invalid reference +INSERT INTO B VALUES (10); +INSERT INTO B VALUES (30); +INSERT INTO B VALUES (30); + +INSERT INTO C VALUES (11); -- invalid reference +INSERT INTO C VALUES (20); +INSERT INTO C VALUES (20); +INSERT INTO C VALUES (30); + +DELETE FROM A WHERE ID = 10; +DELETE FROM A WHERE ID = 20; +DELETE FROM A WHERE ID = 30; + +SELECT * FROM A; +SELECT * FROM B; +SELECT * FROM C; diff --git a/contrib/sslinfo/Makefile b/contrib/sslinfo/Makefile new file mode 100644 index 0000000..dd1ff83 --- /dev/null +++ b/contrib/sslinfo/Makefile @@ -0,0 +1,23 @@ +# contrib/sslinfo/Makefile + +MODULE_big = sslinfo +OBJS = \ + $(WIN32RES) \ + sslinfo.o + +EXTENSION = sslinfo +DATA = sslinfo--1.2.sql sslinfo--1.1--1.2.sql sslinfo--1.0--1.1.sql +PGFILEDESC = "sslinfo - information about client SSL certificate" + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/sslinfo +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +SHLIB_LINK += $(filter -lssl -lcrypto -lssleay32 -leay32, $(LIBS)) diff --git a/contrib/sslinfo/meson.build b/contrib/sslinfo/meson.build new file mode 100644 index 0000000..999456d --- /dev/null +++ b/contrib/sslinfo/meson.build @@ -0,0 +1,31 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not ssl.found() + subdir_done() +endif + +sslinfo_sources = files( + 'sslinfo.c', +) + +if host_system == 'windows' + sslinfo_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'sslinfo', + '--FILEDESC', 'sslinfo - information about client SSL certificate',]) +endif + +sslinfo = shared_module('sslinfo', + sslinfo_sources, + kwargs: contrib_mod_args + { + 'dependencies': [ssl, contrib_mod_args['dependencies']], + } +) +contrib_targets += sslinfo + +install_data( + 'sslinfo--1.0--1.1.sql', + 'sslinfo--1.1--1.2.sql', + 'sslinfo--1.2.sql', + 'sslinfo.control', + kwargs: contrib_data_args, +) diff --git a/contrib/sslinfo/sslinfo--1.0--1.1.sql b/contrib/sslinfo/sslinfo--1.0--1.1.sql new file mode 100644 index 0000000..12d341f --- /dev/null +++ b/contrib/sslinfo/sslinfo--1.0--1.1.sql @@ -0,0 +1,12 @@ +/* contrib/sslinfo/sslinfo--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "ALTER EXTENSION sslinfo UPDATE TO '1.1'" to load this file. \quit + +CREATE FUNCTION +ssl_extension_info(OUT name text, + OUT value text, + OUT critical boolean +) RETURNS SETOF record +AS 'MODULE_PATHNAME', 'ssl_extension_info' +LANGUAGE C STRICT; diff --git a/contrib/sslinfo/sslinfo--1.1--1.2.sql b/contrib/sslinfo/sslinfo--1.1--1.2.sql new file mode 100644 index 0000000..f4f9014 --- /dev/null +++ b/contrib/sslinfo/sslinfo--1.1--1.2.sql @@ -0,0 +1,15 @@ +/* contrib/sslinfo/sslinfo--1.1--1.2.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION sslinfo UPDATE TO '1.2'" to load this file. \quit + +ALTER FUNCTION ssl_client_serial() PARALLEL RESTRICTED; +ALTER FUNCTION ssl_is_used() PARALLEL RESTRICTED; +ALTER FUNCTION ssl_version() PARALLEL RESTRICTED; +ALTER FUNCTION ssl_cipher() PARALLEL RESTRICTED; +ALTER FUNCTION ssl_client_cert_present() PARALLEL RESTRICTED; +ALTER FUNCTION ssl_client_dn_field(text) PARALLEL RESTRICTED; +ALTER FUNCTION ssl_issuer_field(text) PARALLEL RESTRICTED; +ALTER FUNCTION ssl_client_dn() PARALLEL RESTRICTED; +ALTER FUNCTION ssl_issuer_dn() PARALLEL RESTRICTED; +ALTER FUNCTION ssl_extension_info() PARALLEL RESTRICTED; diff --git a/contrib/sslinfo/sslinfo--1.2.sql b/contrib/sslinfo/sslinfo--1.2.sql new file mode 100644 index 0000000..a555cfb --- /dev/null +++ b/contrib/sslinfo/sslinfo--1.2.sql @@ -0,0 +1,48 @@ +/* contrib/sslinfo/sslinfo--1.2.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION sslinfo" to load this file. \quit + +CREATE FUNCTION ssl_client_serial() RETURNS numeric +AS 'MODULE_PATHNAME', 'ssl_client_serial' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION ssl_is_used() RETURNS boolean +AS 'MODULE_PATHNAME', 'ssl_is_used' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION ssl_version() RETURNS text +AS 'MODULE_PATHNAME', 'ssl_version' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION ssl_cipher() RETURNS text +AS 'MODULE_PATHNAME', 'ssl_cipher' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION ssl_client_cert_present() RETURNS boolean +AS 'MODULE_PATHNAME', 'ssl_client_cert_present' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION ssl_client_dn_field(text) RETURNS text +AS 'MODULE_PATHNAME', 'ssl_client_dn_field' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION ssl_issuer_field(text) RETURNS text +AS 'MODULE_PATHNAME', 'ssl_issuer_field' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION ssl_client_dn() RETURNS text +AS 'MODULE_PATHNAME', 'ssl_client_dn' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION ssl_issuer_dn() RETURNS text +AS 'MODULE_PATHNAME', 'ssl_issuer_dn' +LANGUAGE C STRICT PARALLEL RESTRICTED; + +CREATE FUNCTION +ssl_extension_info(OUT name text, + OUT value text, + OUT critical boolean +) RETURNS SETOF record +AS 'MODULE_PATHNAME', 'ssl_extension_info' +LANGUAGE C STRICT PARALLEL RESTRICTED; diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c new file mode 100644 index 0000000..5fd46b9 --- /dev/null +++ b/contrib/sslinfo/sslinfo.c @@ -0,0 +1,484 @@ +/* + * module for PostgreSQL to access client SSL certificate information + * + * Written by Victor B. Wagner , Cryptocom LTD + * This file is distributed under BSD-style license. + * + * contrib/sslinfo/sslinfo.c + */ + +#include "postgres.h" + +#include +#include +#include + +#include "access/htup_details.h" +#include "funcapi.h" +#include "libpq/libpq-be.h" +#include "miscadmin.h" +#include "utils/builtins.h" + +/* + * On Windows, includes a #define for X509_NAME, which breaks our + * ability to use OpenSSL's version of that symbol if is pulled + * in after ... and, at least on some builds, it is. We + * can't reliably fix that by re-ordering #includes, because libpq/libpq-be.h + * #includes . Instead, just zap the #define again here. + */ +#ifdef X509_NAME +#undef X509_NAME +#endif + +PG_MODULE_MAGIC; + +static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName); +static Datum ASN1_STRING_to_text(ASN1_STRING *str); + +/* + * Function context for data persisting over repeated calls. + */ +typedef struct +{ + TupleDesc tupdesc; +} SSLExtensionInfoContext; + +/* + * Indicates whether current session uses SSL + * + * Function has no arguments. Returns bool. True if current session + * is SSL session and false if it is local or non-ssl session. + */ +PG_FUNCTION_INFO_V1(ssl_is_used); +Datum +ssl_is_used(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(MyProcPort->ssl_in_use); +} + + +/* + * Returns SSL version currently in use. + */ +PG_FUNCTION_INFO_V1(ssl_version); +Datum +ssl_version(PG_FUNCTION_ARGS) +{ + const char *version; + + if (!MyProcPort->ssl_in_use) + PG_RETURN_NULL(); + + version = be_tls_get_version(MyProcPort); + if (version == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(cstring_to_text(version)); +} + + +/* + * Returns SSL cipher currently in use. + */ +PG_FUNCTION_INFO_V1(ssl_cipher); +Datum +ssl_cipher(PG_FUNCTION_ARGS) +{ + const char *cipher; + + if (!MyProcPort->ssl_in_use) + PG_RETURN_NULL(); + + cipher = be_tls_get_cipher(MyProcPort); + if (cipher == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(cstring_to_text(cipher)); +} + + +/* + * Indicates whether current client provided a certificate + * + * Function has no arguments. Returns bool. True if current session + * is SSL session and client certificate is verified, otherwise false. + */ +PG_FUNCTION_INFO_V1(ssl_client_cert_present); +Datum +ssl_client_cert_present(PG_FUNCTION_ARGS) +{ + PG_RETURN_BOOL(MyProcPort->peer_cert_valid); +} + + +/* + * Returns serial number of certificate used to establish current + * session + * + * Function has no arguments. It returns the certificate serial + * number as numeric or null if current session doesn't use SSL or if + * SSL connection is established without sending client certificate. + */ +PG_FUNCTION_INFO_V1(ssl_client_serial); +Datum +ssl_client_serial(PG_FUNCTION_ARGS) +{ + char decimal[NAMEDATALEN]; + Datum result; + + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN); + + if (!*decimal) + PG_RETURN_NULL(); + + result = DirectFunctionCall3(numeric_in, + CStringGetDatum(decimal), + ObjectIdGetDatum(0), + Int32GetDatum(-1)); + return result; +} + + +/* + * Converts OpenSSL ASN1_STRING structure into text + * + * Converts ASN1_STRING into text, converting all the characters into + * current database encoding if possible. Any invalid characters are + * replaced by question marks. + * + * Parameter: str - OpenSSL ASN1_STRING structure. Memory management + * of this structure is responsibility of caller. + * + * Returns Datum, which can be directly returned from a C language SQL + * function. + */ +static Datum +ASN1_STRING_to_text(ASN1_STRING *str) +{ + BIO *membuf; + size_t size; + char nullterm; + char *sp; + char *dp; + text *result; + + membuf = BIO_new(BIO_s_mem()); + if (membuf == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("could not create OpenSSL BIO structure"))); + (void) BIO_set_close(membuf, BIO_CLOSE); + ASN1_STRING_print_ex(membuf, str, + ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB) + | ASN1_STRFLGS_UTF8_CONVERT)); + /* ensure null termination of the BIO's content */ + nullterm = '\0'; + BIO_write(membuf, &nullterm, 1); + size = BIO_get_mem_data(membuf, &sp); + dp = pg_any_to_server(sp, size - 1, PG_UTF8); + result = cstring_to_text(dp); + if (dp != sp) + pfree(dp); + if (BIO_free(membuf) != 1) + elog(ERROR, "could not free OpenSSL BIO structure"); + + PG_RETURN_TEXT_P(result); +} + + +/* + * Returns specified field of specified X509_NAME structure + * + * Common part of ssl_client_dn and ssl_issuer_dn functions. + * + * Parameter: X509_NAME *name - either subject or issuer of certificate + * Parameter: text fieldName - field name string like 'CN' or commonName + * to be looked up in the OpenSSL ASN1 OID database + * + * Returns result of ASN1_STRING_to_text applied to appropriate + * part of name + */ +static Datum +X509_NAME_field_to_text(X509_NAME *name, text *fieldName) +{ + char *string_fieldname; + int nid, + index; + ASN1_STRING *data; + + string_fieldname = text_to_cstring(fieldName); + nid = OBJ_txt2nid(string_fieldname); + if (nid == NID_undef) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid X.509 field name: \"%s\"", + string_fieldname))); + pfree(string_fieldname); + index = X509_NAME_get_index_by_NID(name, nid, -1); + if (index < 0) + return (Datum) 0; + data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, index)); + return ASN1_STRING_to_text(data); +} + + +/* + * Returns specified field of client certificate distinguished name + * + * Receives field name (like 'commonName' and 'emailAddress') and + * returns appropriate part of certificate subject converted into + * database encoding. + * + * Parameter: fieldname text - will be looked up in OpenSSL object + * identifier database + * + * Returns text string with appropriate value. + * + * Throws an error if argument cannot be converted into ASN1 OID by + * OpenSSL. Returns null if no client certificate is present, or if + * there is no field with such name in the certificate. + */ +PG_FUNCTION_INFO_V1(ssl_client_dn_field); +Datum +ssl_client_dn_field(PG_FUNCTION_ARGS) +{ + text *fieldname = PG_GETARG_TEXT_PP(0); + Datum result; + + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname); + + if (!result) + PG_RETURN_NULL(); + else + return result; +} + + +/* + * Returns specified field of client certificate issuer name + * + * Receives field name (like 'commonName' and 'emailAddress') and + * returns appropriate part of certificate subject converted into + * database encoding. + * + * Parameter: fieldname text - would be looked up in OpenSSL object + * identifier database + * + * Returns text string with appropriate value. + * + * Throws an error if argument cannot be converted into ASN1 OID by + * OpenSSL. Returns null if no client certificate is present, or if + * there is no field with such name in the certificate. + */ +PG_FUNCTION_INFO_V1(ssl_issuer_field); +Datum +ssl_issuer_field(PG_FUNCTION_ARGS) +{ + text *fieldname = PG_GETARG_TEXT_PP(0); + Datum result; + + if (!(MyProcPort->peer)) + PG_RETURN_NULL(); + + result = X509_NAME_field_to_text(X509_get_issuer_name(MyProcPort->peer), fieldname); + + if (!result) + PG_RETURN_NULL(); + else + return result; +} + + +/* + * Returns current client certificate subject as one string + * + * This function returns distinguished name (subject) of the client + * certificate used in the current SSL connection, converting it into + * the current database encoding. + * + * Returns text datum. + */ +PG_FUNCTION_INFO_V1(ssl_client_dn); +Datum +ssl_client_dn(PG_FUNCTION_ARGS) +{ + char subject[NAMEDATALEN]; + + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN); + + if (!*subject) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(cstring_to_text(subject)); +} + + +/* + * Returns current client certificate issuer as one string + * + * This function returns issuer's distinguished name of the client + * certificate used in the current SSL connection, converting it into + * the current database encoding. + * + * Returns text datum. + */ +PG_FUNCTION_INFO_V1(ssl_issuer_dn); +Datum +ssl_issuer_dn(PG_FUNCTION_ARGS) +{ + char issuer[NAMEDATALEN]; + + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN); + + if (!*issuer) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(cstring_to_text(issuer)); +} + + +/* + * Returns information about available SSL extensions. + * + * Returns setof record made of the following values: + * - name of the extension. + * - value of the extension. + * - critical status of the extension. + */ +PG_FUNCTION_INFO_V1(ssl_extension_info); +Datum +ssl_extension_info(PG_FUNCTION_ARGS) +{ + X509 *cert = MyProcPort->peer; + FuncCallContext *funcctx; + int call_cntr; + int max_calls; + MemoryContext oldcontext; + SSLExtensionInfoContext *fctx; + + if (SRF_IS_FIRSTCALL()) + { + + TupleDesc tupdesc; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * Switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* Create a user function context for cross-call persistence */ + fctx = (SSLExtensionInfoContext *) palloc(sizeof(SSLExtensionInfoContext)); + + /* Construct tuple descriptor */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context that cannot accept type record"))); + fctx->tupdesc = BlessTupleDesc(tupdesc); + + /* Set max_calls as a count of extensions in certificate */ + max_calls = cert != NULL ? X509_get_ext_count(cert) : 0; + + if (max_calls > 0) + { + /* got results, keep track of them */ + funcctx->max_calls = max_calls; + funcctx->user_fctx = fctx; + } + else + { + /* fast track when no results */ + MemoryContextSwitchTo(oldcontext); + SRF_RETURN_DONE(funcctx); + } + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + /* + * Initialize per-call variables. + */ + call_cntr = funcctx->call_cntr; + max_calls = funcctx->max_calls; + fctx = funcctx->user_fctx; + + /* do while there are more left to send */ + if (call_cntr < max_calls) + { + Datum values[3]; + bool nulls[3]; + char *buf; + HeapTuple tuple; + Datum result; + BIO *membuf; + X509_EXTENSION *ext; + ASN1_OBJECT *obj; + int nid; + int len; + + /* need a BIO for this */ + membuf = BIO_new(BIO_s_mem()); + if (membuf == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("could not create OpenSSL BIO structure"))); + + /* Get the extension from the certificate */ + ext = X509_get_ext(cert, call_cntr); + obj = X509_EXTENSION_get_object(ext); + + /* Get the extension name */ + nid = OBJ_obj2nid(obj); + if (nid == NID_undef) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unknown OpenSSL extension in certificate at position %d", + call_cntr))); + values[0] = CStringGetTextDatum(OBJ_nid2sn(nid)); + nulls[0] = false; + + /* Get the extension value */ + if (X509V3_EXT_print(membuf, ext, 0, 0) <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not print extension value in certificate at position %d", + call_cntr))); + len = BIO_get_mem_data(membuf, &buf); + values[1] = PointerGetDatum(cstring_to_text_with_len(buf, len)); + nulls[1] = false; + + /* Get critical status */ + values[2] = BoolGetDatum(X509_EXTENSION_get_critical(ext)); + nulls[2] = false; + + /* Build tuple */ + tuple = heap_form_tuple(fctx->tupdesc, values, nulls); + result = HeapTupleGetDatum(tuple); + + if (BIO_free(membuf) != 1) + elog(ERROR, "could not free OpenSSL BIO structure"); + + SRF_RETURN_NEXT(funcctx, result); + } + + /* All done */ + SRF_RETURN_DONE(funcctx); +} diff --git a/contrib/sslinfo/sslinfo.control b/contrib/sslinfo/sslinfo.control new file mode 100644 index 0000000..c7754f9 --- /dev/null +++ b/contrib/sslinfo/sslinfo.control @@ -0,0 +1,5 @@ +# sslinfo extension +comment = 'information about SSL certificates' +default_version = '1.2' +module_pathname = '$libdir/sslinfo' +relocatable = true diff --git a/contrib/start-scripts/freebsd b/contrib/start-scripts/freebsd new file mode 100644 index 0000000..ed4f9ba --- /dev/null +++ b/contrib/start-scripts/freebsd @@ -0,0 +1,66 @@ +#! /bin/sh + +# PostgreSQL boot time startup script for FreeBSD. Copy this file to +# /usr/local/etc/rc.d/postgresql. + +# Created through merger of the Linux start script by Ryan Kirkpatrick +# and the script in the FreeBSD ports collection. + +# contrib/start-scripts/freebsd + +## EDIT FROM HERE + +# Installation prefix +prefix=/usr/local/pgsql + +# Data directory +PGDATA="/usr/local/pgsql/data" + +# Who to run postgres as, usually "postgres". (NOT "root") +PGUSER=postgres + +# Where to keep a log file +PGLOG="$PGDATA/serverlog" + +## STOP EDITING HERE + +# The path that is to be used for the script +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +# What to use to start up postgres. (If you want the script to wait +# until the server has started, you could use "pg_ctl start" here.) +DAEMON="$prefix/bin/postgres" + +# What to use to shut down postgres +PGCTL="$prefix/bin/pg_ctl" + +# Only start if we can find postgres. +test -x $DAEMON || +{ + echo "$DAEMON not found" + exit 0 +} + +case $1 in + start) + su -l $PGUSER -c "$DAEMON -D '$PGDATA' >>$PGLOG 2>&1 &" + echo -n ' postgresql' + ;; + stop) + su -l $PGUSER -c "$PGCTL stop -D '$PGDATA' -s" + ;; + restart) + su -l $PGUSER -c "$PGCTL stop -D '$PGDATA' -s" + su -l $PGUSER -c "$DAEMON -D '$PGDATA' >>$PGLOG 2>&1 &" + ;; + status) + su -l $PGUSER -c "$PGCTL status -D '$PGDATA'" + ;; + *) + # Print help + echo "Usage: `basename $0` {start|stop|restart|status}" 1>&2 + exit 1 + ;; +esac + +exit 0 diff --git a/contrib/start-scripts/linux b/contrib/start-scripts/linux new file mode 100644 index 0000000..ca01e96 --- /dev/null +++ b/contrib/start-scripts/linux @@ -0,0 +1,124 @@ +#! /bin/sh + +# chkconfig: 2345 98 02 +# description: PostgreSQL RDBMS + +# This is an example of a start/stop script for SysV-style init, such +# as is used on Linux systems. You should edit some of the variables +# and maybe the 'echo' commands. +# +# Place this file at /etc/init.d/postgresql (or +# /etc/rc.d/init.d/postgresql) and make symlinks to +# /etc/rc.d/rc0.d/K02postgresql +# /etc/rc.d/rc1.d/K02postgresql +# /etc/rc.d/rc2.d/K02postgresql +# /etc/rc.d/rc3.d/S98postgresql +# /etc/rc.d/rc4.d/S98postgresql +# /etc/rc.d/rc5.d/S98postgresql +# Or, if you have chkconfig, simply: +# chkconfig --add postgresql +# +# Proper init scripts on Linux systems normally require setting lock +# and pid files under /var/run as well as reacting to network +# settings, so you should treat this with care. + +# Original author: Ryan Kirkpatrick + +# contrib/start-scripts/linux + +## EDIT FROM HERE + +# Installation prefix +prefix=/usr/local/pgsql + +# Data directory +PGDATA="/usr/local/pgsql/data" + +# Who to run postgres as, usually "postgres". (NOT "root") +PGUSER=postgres + +# Where to keep a log file +PGLOG="$PGDATA/serverlog" + +# It's often a good idea to protect the postmaster from being killed by the +# OOM killer (which will tend to preferentially kill the postmaster because +# of the way it accounts for shared memory). To do that, uncomment these +# three lines: +#PG_OOM_ADJUST_FILE=/proc/self/oom_score_adj +#PG_MASTER_OOM_SCORE_ADJ=-1000 +#PG_CHILD_OOM_SCORE_ADJ=0 +# Older Linux kernels may not have /proc/self/oom_score_adj, but instead +# /proc/self/oom_adj, which works similarly except for having a different +# range of scores. For such a system, uncomment these three lines instead: +#PG_OOM_ADJUST_FILE=/proc/self/oom_adj +#PG_MASTER_OOM_SCORE_ADJ=-17 +#PG_CHILD_OOM_SCORE_ADJ=0 + +## STOP EDITING HERE + +# The path that is to be used for the script +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +# What to use to start up postgres. (If you want the script to wait +# until the server has started, you could use "pg_ctl start" here.) +DAEMON="$prefix/bin/postgres" + +# What to use to shut down postgres +PGCTL="$prefix/bin/pg_ctl" + +set -e + +# Only start if we can find postgres. +test -x $DAEMON || +{ + echo "$DAEMON not found" + if [ "$1" = "stop" ] + then exit 0 + else exit 5 + fi +} + +# If we want to tell child processes to adjust their OOM scores, set up the +# necessary environment variables. Can't just export them through the "su". +if [ -e "$PG_OOM_ADJUST_FILE" -a -n "$PG_CHILD_OOM_SCORE_ADJ" ] +then + DAEMON_ENV="PG_OOM_ADJUST_FILE=$PG_OOM_ADJUST_FILE PG_OOM_ADJUST_VALUE=$PG_CHILD_OOM_SCORE_ADJ" +fi + + +# Parse command line parameters. +case $1 in + start) + echo -n "Starting PostgreSQL: " + test -e "$PG_OOM_ADJUST_FILE" && echo "$PG_MASTER_OOM_SCORE_ADJ" > "$PG_OOM_ADJUST_FILE" + su - $PGUSER -c "$DAEMON_ENV $DAEMON -D '$PGDATA' >>$PGLOG 2>&1 &" + echo "ok" + ;; + stop) + echo -n "Stopping PostgreSQL: " + su - $PGUSER -c "$PGCTL stop -D '$PGDATA' -s" + echo "ok" + ;; + restart) + echo -n "Restarting PostgreSQL: " + su - $PGUSER -c "$PGCTL stop -D '$PGDATA' -s" + test -e "$PG_OOM_ADJUST_FILE" && echo "$PG_MASTER_OOM_SCORE_ADJ" > "$PG_OOM_ADJUST_FILE" + su - $PGUSER -c "$DAEMON_ENV $DAEMON -D '$PGDATA' >>$PGLOG 2>&1 &" + echo "ok" + ;; + reload) + echo -n "Reload PostgreSQL: " + su - $PGUSER -c "$PGCTL reload -D '$PGDATA' -s" + echo "ok" + ;; + status) + su - $PGUSER -c "$PGCTL status -D '$PGDATA'" + ;; + *) + # Print help + echo "Usage: $0 {start|stop|restart|reload|status}" 1>&2 + exit 1 + ;; +esac + +exit 0 diff --git a/contrib/start-scripts/macos/README b/contrib/start-scripts/macos/README new file mode 100644 index 0000000..c4f2d9a --- /dev/null +++ b/contrib/start-scripts/macos/README @@ -0,0 +1,24 @@ +To make macOS automatically launch your PostgreSQL server at system start, +do the following: + +1. Edit the postgres-wrapper.sh script and adjust the file path +variables at its start to reflect where you have installed Postgres, +if that's not /usr/local/pgsql. + +2. Copy the modified postgres-wrapper.sh script into some suitable +installation directory. It can be, but doesn't have to be, where +you keep the Postgres executables themselves. + +3. Edit the org.postgresql.postgres.plist file and adjust its path +for postgres-wrapper.sh to match what you did in step 2. Also, +if you plan to run the Postgres server under some user name other +than "postgres", adjust the UserName parameter value for that. + +4. Copy the modified org.postgresql.postgres.plist file into +/Library/LaunchDaemons/. You must do this as root: + sudo cp org.postgresql.postgres.plist /Library/LaunchDaemons +because the file will be ignored if it is not root-owned. + +At this point a reboot should launch the server. But if you want +to test it without rebooting, you can do + sudo launchctl load /Library/LaunchDaemons/org.postgresql.postgres.plist diff --git a/contrib/start-scripts/macos/org.postgresql.postgres.plist b/contrib/start-scripts/macos/org.postgresql.postgres.plist new file mode 100644 index 0000000..fdbd74f --- /dev/null +++ b/contrib/start-scripts/macos/org.postgresql.postgres.plist @@ -0,0 +1,17 @@ + + + + + Label + org.postgresql.postgres + ProgramArguments + + /bin/sh + /usr/local/pgsql/bin/postgres-wrapper.sh + + UserName + postgres + KeepAlive + + + diff --git a/contrib/start-scripts/macos/postgres-wrapper.sh b/contrib/start-scripts/macos/postgres-wrapper.sh new file mode 100644 index 0000000..3a4ebda --- /dev/null +++ b/contrib/start-scripts/macos/postgres-wrapper.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# PostgreSQL server start script (launched by org.postgresql.postgres.plist) + +# edit these as needed: + +# directory containing postgres executable: +PGBINDIR="/usr/local/pgsql/bin" +# data directory: +PGDATA="/usr/local/pgsql/data" +# file to receive postmaster's initial log messages: +PGLOGFILE="${PGDATA}/pgstart.log" + +# (it's recommendable to enable the Postgres logging_collector feature +# so that PGLOGFILE doesn't grow without bound) + + +# set umask to ensure PGLOGFILE is not created world-readable +umask 077 + +# wait for networking to be up (else server may not bind to desired ports) +/usr/sbin/ipconfig waitall + +# and launch the server +exec "$PGBINDIR"/postgres -D "$PGDATA" >>"$PGLOGFILE" 2>&1 diff --git a/contrib/tablefunc/.gitignore b/contrib/tablefunc/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/tablefunc/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/tablefunc/Makefile b/contrib/tablefunc/Makefile new file mode 100644 index 0000000..191a3a1 --- /dev/null +++ b/contrib/tablefunc/Makefile @@ -0,0 +1,22 @@ +# contrib/tablefunc/Makefile + +MODULES = tablefunc + +EXTENSION = tablefunc +DATA = tablefunc--1.0.sql +PGFILEDESC = "tablefunc - various functions that return tables" + +REGRESS = tablefunc + +LDFLAGS_SL += $(filter -lm, $(LIBS)) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/tablefunc +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/tablefunc/data/connectby_int.data b/contrib/tablefunc/data/connectby_int.data new file mode 100644 index 0000000..c061be3 --- /dev/null +++ b/contrib/tablefunc/data/connectby_int.data @@ -0,0 +1,9 @@ +1 \N +2 1 +3 1 +4 2 +5 2 +6 4 +7 3 +8 6 +9 5 diff --git a/contrib/tablefunc/data/connectby_text.data b/contrib/tablefunc/data/connectby_text.data new file mode 100644 index 0000000..a903820 --- /dev/null +++ b/contrib/tablefunc/data/connectby_text.data @@ -0,0 +1,9 @@ +row1 \N 0 +row2 row1 0 +row3 row1 0 +row4 row2 1 +row5 row2 0 +row6 row4 0 +row7 row3 0 +row8 row6 0 +row9 row5 0 diff --git a/contrib/tablefunc/data/ct.data b/contrib/tablefunc/data/ct.data new file mode 100644 index 0000000..7733fc2 --- /dev/null +++ b/contrib/tablefunc/data/ct.data @@ -0,0 +1,18 @@ +1 group1 test1 att1 val1 +2 group1 test1 att2 val2 +3 group1 test1 att3 val3 +4 group1 test1 att4 val4 +5 group1 test2 att1 val5 +6 group1 test2 att2 val6 +7 group1 test2 att3 val7 +8 group1 test2 att4 val8 +9 group2 test3 att1 val1 +10 group2 test3 att2 val2 +11 group2 test3 att3 val3 +12 group2 test4 att1 val4 +13 group2 test4 att2 val5 +14 group2 test4 att3 val6 +15 group1 \N att1 val9 +16 group1 \N att2 val10 +17 group1 \N att3 val11 +18 group1 \N att4 val12 diff --git a/contrib/tablefunc/expected/tablefunc.out b/contrib/tablefunc/expected/tablefunc.out new file mode 100644 index 0000000..464c210 --- /dev/null +++ b/contrib/tablefunc/expected/tablefunc.out @@ -0,0 +1,430 @@ +CREATE EXTENSION tablefunc; +-- +-- normal_rand() +-- no easy way to do this for regression testing +-- +SELECT avg(normal_rand)::int, count(*) FROM normal_rand(100, 250, 0.2); + avg | count +-----+------- + 250 | 100 +(1 row) + +-- negative number of tuples +SELECT avg(normal_rand)::int, count(*) FROM normal_rand(-1, 250, 0.2); +ERROR: number of rows cannot be negative +-- +-- crosstab() +-- +CREATE TABLE ct(id int, rowclass text, rowid text, attribute text, val text); +\copy ct from 'data/ct.data' +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); + row_name | category_1 | category_2 +----------+------------+------------ + test1 | val2 | val3 + test2 | val6 | val7 + | val10 | val11 +(3 rows) + +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 +----------+------------+------------+------------ + test1 | val2 | val3 | + test2 | val6 | val7 | + | val10 | val11 | +(3 rows) + +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 | category_4 +----------+------------+------------+------------+------------ + test1 | val2 | val3 | | + test2 | val6 | val7 | | + | val10 | val11 | | +(3 rows) + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + row_name | category_1 | category_2 +----------+------------+------------ + test1 | val1 | val2 + test2 | val5 | val6 + | val9 | val10 +(3 rows) + +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 +----------+------------+------------+------------ + test1 | val1 | val2 | val3 + test2 | val5 | val6 | val7 + | val9 | val10 | val11 +(3 rows) + +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 | category_4 +----------+------------+------------+------------+------------ + test1 | val1 | val2 | val3 | val4 + test2 | val5 | val6 | val7 | val8 + | val9 | val10 | val11 | val12 +(3 rows) + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); + row_name | category_1 | category_2 +----------+------------+------------ + test3 | val1 | val2 + test4 | val4 | val5 +(2 rows) + +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 +----------+------------+------------+------------ + test3 | val1 | val2 | + test4 | val4 | val5 | +(2 rows) + +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 | category_4 +----------+------------+------------+------------+------------ + test3 | val1 | val2 | | + test4 | val4 | val5 | | +(2 rows) + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); + row_name | category_1 | category_2 +----------+------------+------------ + test3 | val1 | val2 + test4 | val4 | val5 +(2 rows) + +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 +----------+------------+------------+------------ + test3 | val1 | val2 | val3 + test4 | val4 | val5 | val6 +(2 rows) + +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); + row_name | category_1 | category_2 | category_3 | category_4 +----------+------------+------------+------------+------------ + test3 | val1 | val2 | val3 | + test4 | val4 | val5 | val6 | +(2 rows) + +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text); + rowid | att1 | att2 +-------+------+------- + test1 | val1 | val2 + test2 | val5 | val6 + | val9 | val10 +(3 rows) + +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text); + rowid | att1 | att2 | att3 +-------+------+-------+------- + test1 | val1 | val2 | val3 + test2 | val5 | val6 | val7 + | val9 | val10 | val11 +(3 rows) + +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text, att4 text); + rowid | att1 | att2 | att3 | att4 +-------+------+-------+-------+------- + test1 | val1 | val2 | val3 | val4 + test2 | val5 | val6 | val7 | val8 + | val9 | val10 | val11 | val12 +(3 rows) + +-- check it works with OUT parameters, too +CREATE FUNCTION crosstab_out(text, + OUT rowid text, OUT att1 text, OUT att2 text, OUT att3 text) +RETURNS setof record +AS '$libdir/tablefunc','crosstab' +LANGUAGE C STABLE STRICT; +SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + rowid | att1 | att2 | att3 +-------+------+-------+------- + test1 | val1 | val2 | val3 + test2 | val5 | val6 | val7 + | val9 | val10 | val11 +(3 rows) + +-- +-- hash based crosstab +-- +create table cth(id serial, rowid text, rowdt timestamp, attribute text, val text); +insert into cth values(DEFAULT,'test1','01 March 2003','temperature','42'); +insert into cth values(DEFAULT,'test1','01 March 2003','test_result','PASS'); +-- the next line is intentionally left commented and is therefore a "missing" attribute +-- insert into cth values(DEFAULT,'test1','01 March 2003','test_startdate','28 February 2003'); +insert into cth values(DEFAULT,'test1','01 March 2003','volts','2.6987'); +insert into cth values(DEFAULT,'test2','02 March 2003','temperature','53'); +insert into cth values(DEFAULT,'test2','02 March 2003','test_result','FAIL'); +insert into cth values(DEFAULT,'test2','02 March 2003','test_startdate','01 March 2003'); +insert into cth values(DEFAULT,'test2','02 March 2003','volts','3.1234'); +-- next group tests for NULL rowids +insert into cth values(DEFAULT,NULL,'25 October 2007','temperature','57'); +insert into cth values(DEFAULT,NULL,'25 October 2007','test_result','PASS'); +insert into cth values(DEFAULT,NULL,'25 October 2007','test_startdate','24 October 2007'); +insert into cth values(DEFAULT,NULL,'25 October 2007','volts','1.41234'); +-- return attributes as plain text +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+--------------------------+-------------+-------------+-----------------+--------- + test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987 + test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | 01 March 2003 | 3.1234 + | Thu Oct 25 00:00:00 2007 | 57 | PASS | 24 October 2007 | 1.41234 +(3 rows) + +-- this time without rowdt +SELECT * FROM crosstab( + 'SELECT rowid, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, temperature text, test_result text, test_startdate text, volts text); + rowid | temperature | test_result | test_startdate | volts +-------+-------------+-------------+-----------------+--------- + test1 | 42 | PASS | | 2.6987 + test2 | 53 | FAIL | 01 March 2003 | 3.1234 + | 57 | PASS | 24 October 2007 | 1.41234 +(3 rows) + +-- convert attributes to specific datatypes +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+--------------------------+-------------+-------------+--------------------------+--------- + test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987 + test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234 + | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 | 1.41234 +(3 rows) + +-- source query and category query out of sync +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE attribute IN (''temperature'',''test_result'',''test_startdate'') ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp); + rowid | rowdt | temperature | test_result | test_startdate +-------+--------------------------+-------------+-------------+-------------------------- + test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | + test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 + | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 +(3 rows) + +-- if category query generates no rows, get expected error +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE attribute = ''a'' ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); +ERROR: provided "categories" SQL must return 1 column of at least one row +-- if category query generates more than one column, get expected error +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT rowdt, attribute FROM cth ORDER BY 2') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); +ERROR: provided "categories" SQL must return 1 column of at least one row +-- if source query returns zero rows, get zero rows returned +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+-------+-------------+-------------+----------------+------- +(0 rows) + +-- if source query returns zero rows, get zero rows returned even if category query generates no rows +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE false ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+-------+-------------+-------------+----------------+------- +(0 rows) + +-- check it works with a named result rowtype +create type my_crosstab_result as ( + rowid text, rowdt timestamp, + temperature int4, test_result text, test_startdate timestamp, volts float8); +CREATE FUNCTION crosstab_named(text, text) +RETURNS setof my_crosstab_result +AS '$libdir/tablefunc','crosstab_hash' +LANGUAGE C STABLE STRICT; +SELECT * FROM crosstab_named( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1'); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+--------------------------+-------------+-------------+--------------------------+--------- + test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987 + test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234 + | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 | 1.41234 +(3 rows) + +-- check it works with OUT parameters +CREATE FUNCTION crosstab_out(text, text, + OUT rowid text, OUT rowdt timestamp, + OUT temperature int4, OUT test_result text, + OUT test_startdate timestamp, OUT volts float8) +RETURNS setof record +AS '$libdir/tablefunc','crosstab_hash' +LANGUAGE C STABLE STRICT; +SELECT * FROM crosstab_out( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1'); + rowid | rowdt | temperature | test_result | test_startdate | volts +-------+--------------------------+-------------+-------------+--------------------------+--------- + test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987 + test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234 + | Thu Oct 25 00:00:00 2007 | 57 | PASS | Wed Oct 24 00:00:00 2007 | 1.41234 +(3 rows) + +-- +-- connectby +-- +-- test connectby with text based hierarchy +CREATE TABLE connectby_text(keyid text, parent_keyid text, pos int); +\copy connectby_text from 'data/connectby_text.data' +-- with branch, without orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text); + keyid | parent_keyid | level | branch +-------+--------------+-------+--------------------- + row2 | | 0 | row2 + row4 | row2 | 1 | row2~row4 + row6 | row4 | 2 | row2~row4~row6 + row8 | row6 | 3 | row2~row4~row6~row8 + row5 | row2 | 1 | row2~row5 + row9 | row5 | 2 | row2~row5~row9 +(6 rows) + +-- without branch, without orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0) AS t(keyid text, parent_keyid text, level int); + keyid | parent_keyid | level +-------+--------------+------- + row2 | | 0 + row4 | row2 | 1 + row6 | row4 | 2 + row8 | row6 | 3 + row5 | row2 | 1 + row9 | row5 | 2 +(6 rows) + +-- with branch, with orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos int) ORDER BY t.pos; + keyid | parent_keyid | level | branch | pos +-------+--------------+-------+---------------------+----- + row2 | | 0 | row2 | 1 + row5 | row2 | 1 | row2~row5 | 2 + row9 | row5 | 2 | row2~row5~row9 | 3 + row4 | row2 | 1 | row2~row4 | 4 + row6 | row4 | 2 | row2~row4~row6 | 5 + row8 | row6 | 3 | row2~row4~row6~row8 | 6 +(6 rows) + +-- without branch, with orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0) AS t(keyid text, parent_keyid text, level int, pos int) ORDER BY t.pos; + keyid | parent_keyid | level | pos +-------+--------------+-------+----- + row2 | | 0 | 1 + row5 | row2 | 1 | 2 + row9 | row5 | 2 | 3 + row4 | row2 | 1 | 4 + row6 | row4 | 2 | 5 + row8 | row6 | 3 | 6 +(6 rows) + +-- test connectby with int based hierarchy +CREATE TABLE connectby_int(keyid int, parent_keyid int); +\copy connectby_int from 'data/connectby_int.data' +-- with branch +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch text); + keyid | parent_keyid | level | branch +-------+--------------+-------+--------- + 2 | | 0 | 2 + 4 | 2 | 1 | 2~4 + 6 | 4 | 2 | 2~4~6 + 8 | 6 | 3 | 2~4~6~8 + 5 | 2 | 1 | 2~5 + 9 | 5 | 2 | 2~5~9 +(6 rows) + +-- without branch +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); + keyid | parent_keyid | level +-------+--------------+------- + 2 | | 0 + 4 | 2 | 1 + 6 | 4 | 2 + 8 | 6 | 3 + 5 | 2 | 1 + 9 | 5 | 2 +(6 rows) + +-- recursion detection +INSERT INTO connectby_int VALUES(10,9); +INSERT INTO connectby_int VALUES(11,10); +INSERT INTO connectby_int VALUES(9,11); +-- should fail due to infinite recursion +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch text); +ERROR: infinite recursion detected +-- infinite recursion failure avoided by depth limit +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 4, '~') AS t(keyid int, parent_keyid int, level int, branch text); + keyid | parent_keyid | level | branch +-------+--------------+-------+------------- + 2 | | 0 | 2 + 4 | 2 | 1 | 2~4 + 6 | 4 | 2 | 2~4~6 + 8 | 6 | 3 | 2~4~6~8 + 5 | 2 | 1 | 2~5 + 9 | 5 | 2 | 2~5~9 + 10 | 9 | 3 | 2~5~9~10 + 11 | 10 | 4 | 2~5~9~10~11 +(8 rows) + +-- should fail as first two columns must have the same type +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid text, parent_keyid int, level int, branch text); +ERROR: invalid return type +DETAIL: First two columns must be the same type. +-- should fail as key field datatype should match return datatype +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid float8, parent_keyid float8, level int, branch text); +ERROR: invalid return type +DETAIL: SQL key field type double precision does not match return key field type integer. +-- tests for values using custom queries +-- query with one column - failed +SELECT * FROM connectby('connectby_int', '1; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); +ERROR: invalid return type +DETAIL: Query must return at least two columns. +-- query with two columns first value as NULL +SELECT * FROM connectby('connectby_int', 'NULL::int, 1::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); + keyid | parent_keyid | level +-------+--------------+------- + 2 | | 0 + | 1 | 1 +(2 rows) + +-- query with two columns second value as NULL +SELECT * FROM connectby('connectby_int', '1::int, NULL::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); +ERROR: infinite recursion detected +-- query with two columns, both values as NULL +SELECT * FROM connectby('connectby_int', 'NULL::int, NULL::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); + keyid | parent_keyid | level +-------+--------------+------- + 2 | | 0 + | | 1 +(2 rows) + +-- test for falsely detected recursion +DROP TABLE connectby_int; +CREATE TABLE connectby_int(keyid int, parent_keyid int); +INSERT INTO connectby_int VALUES(11,NULL); +INSERT INTO connectby_int VALUES(10,11); +INSERT INTO connectby_int VALUES(111,11); +INSERT INTO connectby_int VALUES(1,111); +-- this should not fail due to recursion detection +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '11', 0, '-') AS t(keyid int, parent_keyid int, level int, branch text); + keyid | parent_keyid | level | branch +-------+--------------+-------+---------- + 11 | | 0 | 11 + 10 | 11 | 1 | 11-10 + 111 | 11 | 1 | 11-111 + 1 | 111 | 2 | 11-111-1 +(4 rows) + diff --git a/contrib/tablefunc/meson.build b/contrib/tablefunc/meson.build new file mode 100644 index 0000000..8399b31 --- /dev/null +++ b/contrib/tablefunc/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +tablefunc_sources = files( + 'tablefunc.c', +) + +if host_system == 'windows' + tablefunc_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'tablefunc', + '--FILEDESC', 'tablefunc - various functions that return tables',]) +endif + +tablefunc = shared_module('tablefunc', + tablefunc_sources, + kwargs: contrib_mod_args, +) +contrib_targets += tablefunc + +install_data( + 'tablefunc--1.0.sql', + 'tablefunc.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'tablefunc', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'tablefunc', + ], + }, +} diff --git a/contrib/tablefunc/sql/tablefunc.sql b/contrib/tablefunc/sql/tablefunc.sql new file mode 100644 index 0000000..02e8a98 --- /dev/null +++ b/contrib/tablefunc/sql/tablefunc.sql @@ -0,0 +1,208 @@ +CREATE EXTENSION tablefunc; + +-- +-- normal_rand() +-- no easy way to do this for regression testing +-- +SELECT avg(normal_rand)::int, count(*) FROM normal_rand(100, 250, 0.2); +-- negative number of tuples +SELECT avg(normal_rand)::int, count(*) FROM normal_rand(-1, 250, 0.2); + +-- +-- crosstab() +-- +CREATE TABLE ct(id int, rowclass text, rowid text, attribute text, val text); +\copy ct from 'data/ct.data' + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;'); + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' and (attribute = ''att1'' or attribute = ''att2'') ORDER BY 1,2;'); + +SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); +SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); +SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;'); + +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text); +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text); +SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text, att4 text); + +-- check it works with OUT parameters, too + +CREATE FUNCTION crosstab_out(text, + OUT rowid text, OUT att1 text, OUT att2 text, OUT att3 text) +RETURNS setof record +AS '$libdir/tablefunc','crosstab' +LANGUAGE C STABLE STRICT; + +SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;'); + +-- +-- hash based crosstab +-- +create table cth(id serial, rowid text, rowdt timestamp, attribute text, val text); +insert into cth values(DEFAULT,'test1','01 March 2003','temperature','42'); +insert into cth values(DEFAULT,'test1','01 March 2003','test_result','PASS'); +-- the next line is intentionally left commented and is therefore a "missing" attribute +-- insert into cth values(DEFAULT,'test1','01 March 2003','test_startdate','28 February 2003'); +insert into cth values(DEFAULT,'test1','01 March 2003','volts','2.6987'); +insert into cth values(DEFAULT,'test2','02 March 2003','temperature','53'); +insert into cth values(DEFAULT,'test2','02 March 2003','test_result','FAIL'); +insert into cth values(DEFAULT,'test2','02 March 2003','test_startdate','01 March 2003'); +insert into cth values(DEFAULT,'test2','02 March 2003','volts','3.1234'); +-- next group tests for NULL rowids +insert into cth values(DEFAULT,NULL,'25 October 2007','temperature','57'); +insert into cth values(DEFAULT,NULL,'25 October 2007','test_result','PASS'); +insert into cth values(DEFAULT,NULL,'25 October 2007','test_startdate','24 October 2007'); +insert into cth values(DEFAULT,NULL,'25 October 2007','volts','1.41234'); + +-- return attributes as plain text +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + +-- this time without rowdt +SELECT * FROM crosstab( + 'SELECT rowid, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, temperature text, test_result text, test_startdate text, volts text); + +-- convert attributes to specific datatypes +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); + +-- source query and category query out of sync +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE attribute IN (''temperature'',''test_result'',''test_startdate'') ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp); + +-- if category query generates no rows, get expected error +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE attribute = ''a'' ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); + +-- if category query generates more than one column, get expected error +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT rowdt, attribute FROM cth ORDER BY 2') +AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8); + +-- if source query returns zero rows, get zero rows returned +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + +-- if source query returns zero rows, get zero rows returned even if category query generates no rows +SELECT * FROM crosstab( + 'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth WHERE false ORDER BY 1') +AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text); + +-- check it works with a named result rowtype + +create type my_crosstab_result as ( + rowid text, rowdt timestamp, + temperature int4, test_result text, test_startdate timestamp, volts float8); + +CREATE FUNCTION crosstab_named(text, text) +RETURNS setof my_crosstab_result +AS '$libdir/tablefunc','crosstab_hash' +LANGUAGE C STABLE STRICT; + +SELECT * FROM crosstab_named( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1'); + +-- check it works with OUT parameters + +CREATE FUNCTION crosstab_out(text, text, + OUT rowid text, OUT rowdt timestamp, + OUT temperature int4, OUT test_result text, + OUT test_startdate timestamp, OUT volts float8) +RETURNS setof record +AS '$libdir/tablefunc','crosstab_hash' +LANGUAGE C STABLE STRICT; + +SELECT * FROM crosstab_out( + 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', + 'SELECT DISTINCT attribute FROM cth ORDER BY 1'); + +-- +-- connectby +-- + +-- test connectby with text based hierarchy +CREATE TABLE connectby_text(keyid text, parent_keyid text, pos int); +\copy connectby_text from 'data/connectby_text.data' + +-- with branch, without orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text); + +-- without branch, without orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0) AS t(keyid text, parent_keyid text, level int); + +-- with branch, with orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos int) ORDER BY t.pos; + +-- without branch, with orderby +SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0) AS t(keyid text, parent_keyid text, level int, pos int) ORDER BY t.pos; + +-- test connectby with int based hierarchy +CREATE TABLE connectby_int(keyid int, parent_keyid int); +\copy connectby_int from 'data/connectby_int.data' + +-- with branch +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch text); + +-- without branch +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); + +-- recursion detection +INSERT INTO connectby_int VALUES(10,9); +INSERT INTO connectby_int VALUES(11,10); +INSERT INTO connectby_int VALUES(9,11); + +-- should fail due to infinite recursion +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch text); + +-- infinite recursion failure avoided by depth limit +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 4, '~') AS t(keyid int, parent_keyid int, level int, branch text); + +-- should fail as first two columns must have the same type +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid text, parent_keyid int, level int, branch text); + +-- should fail as key field datatype should match return datatype +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid float8, parent_keyid float8, level int, branch text); + +-- tests for values using custom queries +-- query with one column - failed +SELECT * FROM connectby('connectby_int', '1; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); +-- query with two columns first value as NULL +SELECT * FROM connectby('connectby_int', 'NULL::int, 1::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); +-- query with two columns second value as NULL +SELECT * FROM connectby('connectby_int', '1::int, NULL::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); +-- query with two columns, both values as NULL +SELECT * FROM connectby('connectby_int', 'NULL::int, NULL::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); + +-- test for falsely detected recursion +DROP TABLE connectby_int; +CREATE TABLE connectby_int(keyid int, parent_keyid int); +INSERT INTO connectby_int VALUES(11,NULL); +INSERT INTO connectby_int VALUES(10,11); +INSERT INTO connectby_int VALUES(111,11); +INSERT INTO connectby_int VALUES(1,111); +-- this should not fail due to recursion detection +SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '11', 0, '-') AS t(keyid int, parent_keyid int, level int, branch text); diff --git a/contrib/tablefunc/tablefunc--1.0.sql b/contrib/tablefunc/tablefunc--1.0.sql new file mode 100644 index 0000000..8681ff4 --- /dev/null +++ b/contrib/tablefunc/tablefunc--1.0.sql @@ -0,0 +1,88 @@ +/* contrib/tablefunc/tablefunc--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION tablefunc" to load this file. \quit + +CREATE FUNCTION normal_rand(int4, float8, float8) +RETURNS setof float8 +AS 'MODULE_PATHNAME','normal_rand' +LANGUAGE C VOLATILE STRICT; + +-- the generic crosstab function: +CREATE FUNCTION crosstab(text) +RETURNS setof record +AS 'MODULE_PATHNAME','crosstab' +LANGUAGE C STABLE STRICT; + +-- examples of building custom type-specific crosstab functions: +CREATE TYPE tablefunc_crosstab_2 AS +( + row_name TEXT, + category_1 TEXT, + category_2 TEXT +); + +CREATE TYPE tablefunc_crosstab_3 AS +( + row_name TEXT, + category_1 TEXT, + category_2 TEXT, + category_3 TEXT +); + +CREATE TYPE tablefunc_crosstab_4 AS +( + row_name TEXT, + category_1 TEXT, + category_2 TEXT, + category_3 TEXT, + category_4 TEXT +); + +CREATE FUNCTION crosstab2(text) +RETURNS setof tablefunc_crosstab_2 +AS 'MODULE_PATHNAME','crosstab' +LANGUAGE C STABLE STRICT; + +CREATE FUNCTION crosstab3(text) +RETURNS setof tablefunc_crosstab_3 +AS 'MODULE_PATHNAME','crosstab' +LANGUAGE C STABLE STRICT; + +CREATE FUNCTION crosstab4(text) +RETURNS setof tablefunc_crosstab_4 +AS 'MODULE_PATHNAME','crosstab' +LANGUAGE C STABLE STRICT; + +-- obsolete: +CREATE FUNCTION crosstab(text,int) +RETURNS setof record +AS 'MODULE_PATHNAME','crosstab' +LANGUAGE C STABLE STRICT; + +CREATE FUNCTION crosstab(text,text) +RETURNS setof record +AS 'MODULE_PATHNAME','crosstab_hash' +LANGUAGE C STABLE STRICT; + +CREATE FUNCTION connectby(text,text,text,text,int,text) +RETURNS setof record +AS 'MODULE_PATHNAME','connectby_text' +LANGUAGE C STABLE STRICT; + +CREATE FUNCTION connectby(text,text,text,text,int) +RETURNS setof record +AS 'MODULE_PATHNAME','connectby_text' +LANGUAGE C STABLE STRICT; + +-- These 2 take the name of a field to ORDER BY as 4th arg (for sorting siblings) + +CREATE FUNCTION connectby(text,text,text,text,text,int,text) +RETURNS setof record +AS 'MODULE_PATHNAME','connectby_text_serial' +LANGUAGE C STABLE STRICT; + +CREATE FUNCTION connectby(text,text,text,text,text,int) +RETURNS setof record +AS 'MODULE_PATHNAME','connectby_text_serial' +LANGUAGE C STABLE STRICT; diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c new file mode 100644 index 0000000..7a6a367 --- /dev/null +++ b/contrib/tablefunc/tablefunc.c @@ -0,0 +1,1591 @@ +/* + * contrib/tablefunc/tablefunc.c + * + * + * tablefunc + * + * Sample to demonstrate C functions which return setof scalar + * and setof composite. + * Joe Conway + * And contributors: + * Nabil Sayegh + * + * Copyright (c) 2002-2023, PostgreSQL Global Development Group + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + */ +#include "postgres.h" + +#include + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "common/pg_prng.h" +#include "executor/spi.h" +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "tablefunc.h" +#include "utils/builtins.h" + +PG_MODULE_MAGIC; + +static HTAB *load_categories_hash(char *cats_sql, MemoryContext per_query_ctx); +static Tuplestorestate *get_crosstab_tuplestore(char *sql, + HTAB *crosstab_hash, + TupleDesc tupdesc, + bool randomAccess); +static void validateConnectbyTupleDesc(TupleDesc td, bool show_branch, bool show_serial); +static bool compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc); +static void compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc); +static void get_normal_pair(float8 *x1, float8 *x2); +static Tuplestorestate *connectby(char *relname, + char *key_fld, + char *parent_key_fld, + char *orderby_fld, + char *branch_delim, + char *start_with, + int max_depth, + bool show_branch, + bool show_serial, + MemoryContext per_query_ctx, + bool randomAccess, + AttInMetadata *attinmeta); +static void build_tuplestore_recursively(char *key_fld, + char *parent_key_fld, + char *relname, + char *orderby_fld, + char *branch_delim, + char *start_with, + char *branch, + int level, + int *serial, + int max_depth, + bool show_branch, + bool show_serial, + MemoryContext per_query_ctx, + AttInMetadata *attinmeta, + Tuplestorestate *tupstore); + +typedef struct +{ + float8 mean; /* mean of the distribution */ + float8 stddev; /* stddev of the distribution */ + float8 carry_val; /* hold second generated value */ + bool use_carry; /* use second generated value */ +} normal_rand_fctx; + +#define xpfree(var_) \ + do { \ + if (var_ != NULL) \ + { \ + pfree(var_); \ + var_ = NULL; \ + } \ + } while (0) + +#define xpstrdup(tgtvar_, srcvar_) \ + do { \ + if (srcvar_) \ + tgtvar_ = pstrdup(srcvar_); \ + else \ + tgtvar_ = NULL; \ + } while (0) + +#define xstreq(tgtvar_, srcvar_) \ + (((tgtvar_ == NULL) && (srcvar_ == NULL)) || \ + ((tgtvar_ != NULL) && (srcvar_ != NULL) && (strcmp(tgtvar_, srcvar_) == 0))) + +/* sign, 10 digits, '\0' */ +#define INT32_STRLEN 12 + +/* stored info for a crosstab category */ +typedef struct crosstab_cat_desc +{ + char *catname; /* full category name */ + uint64 attidx; /* zero based */ +} crosstab_cat_desc; + +#define MAX_CATNAME_LEN NAMEDATALEN +#define INIT_CATS 64 + +#define crosstab_HashTableLookup(HASHTAB, CATNAME, CATDESC) \ +do { \ + crosstab_HashEnt *hentry; char key[MAX_CATNAME_LEN]; \ + \ + MemSet(key, 0, MAX_CATNAME_LEN); \ + snprintf(key, MAX_CATNAME_LEN - 1, "%s", CATNAME); \ + hentry = (crosstab_HashEnt*) hash_search(HASHTAB, \ + key, HASH_FIND, NULL); \ + if (hentry) \ + CATDESC = hentry->catdesc; \ + else \ + CATDESC = NULL; \ +} while(0) + +#define crosstab_HashTableInsert(HASHTAB, CATDESC) \ +do { \ + crosstab_HashEnt *hentry; bool found; char key[MAX_CATNAME_LEN]; \ + \ + MemSet(key, 0, MAX_CATNAME_LEN); \ + snprintf(key, MAX_CATNAME_LEN - 1, "%s", CATDESC->catname); \ + hentry = (crosstab_HashEnt*) hash_search(HASHTAB, \ + key, HASH_ENTER, &found); \ + if (found) \ + ereport(ERROR, \ + (errcode(ERRCODE_DUPLICATE_OBJECT), \ + errmsg("duplicate category name"))); \ + hentry->catdesc = CATDESC; \ +} while(0) + +/* hash table */ +typedef struct crosstab_hashent +{ + char internal_catname[MAX_CATNAME_LEN]; + crosstab_cat_desc *catdesc; +} crosstab_HashEnt; + +/* + * normal_rand - return requested number of random values + * with a Gaussian (Normal) distribution. + * + * inputs are int numvals, float8 mean, and float8 stddev + * returns setof float8 + */ +PG_FUNCTION_INFO_V1(normal_rand); +Datum +normal_rand(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + uint64 call_cntr; + uint64 max_calls; + normal_rand_fctx *fctx; + float8 mean; + float8 stddev; + float8 carry_val; + bool use_carry; + MemoryContext oldcontext; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + int32 num_tuples; + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* total number of tuples to be returned */ + num_tuples = PG_GETARG_INT32(0); + if (num_tuples < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("number of rows cannot be negative"))); + funcctx->max_calls = num_tuples; + + /* allocate memory for user context */ + fctx = (normal_rand_fctx *) palloc(sizeof(normal_rand_fctx)); + + /* + * Use fctx to keep track of upper and lower bounds from call to call. + * It will also be used to carry over the spare value we get from the + * Box-Muller algorithm so that we only actually calculate a new value + * every other call. + */ + fctx->mean = PG_GETARG_FLOAT8(1); + fctx->stddev = PG_GETARG_FLOAT8(2); + fctx->carry_val = 0; + fctx->use_carry = false; + + funcctx->user_fctx = fctx; + + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + + call_cntr = funcctx->call_cntr; + max_calls = funcctx->max_calls; + fctx = funcctx->user_fctx; + mean = fctx->mean; + stddev = fctx->stddev; + carry_val = fctx->carry_val; + use_carry = fctx->use_carry; + + if (call_cntr < max_calls) /* do when there is more left to send */ + { + float8 result; + + if (use_carry) + { + /* + * reset use_carry and use second value obtained on last pass + */ + fctx->use_carry = false; + result = carry_val; + } + else + { + float8 normval_1; + float8 normval_2; + + /* Get the next two normal values */ + get_normal_pair(&normval_1, &normval_2); + + /* use the first */ + result = mean + (stddev * normval_1); + + /* and save the second */ + fctx->carry_val = mean + (stddev * normval_2); + fctx->use_carry = true; + } + + /* send the result */ + SRF_RETURN_NEXT(funcctx, Float8GetDatum(result)); + } + else + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); +} + +/* + * get_normal_pair() + * Assigns normally distributed (Gaussian) values to a pair of provided + * parameters, with mean 0, standard deviation 1. + * + * This routine implements Algorithm P (Polar method for normal deviates) + * from Knuth's _The_Art_of_Computer_Programming_, Volume 2, 3rd ed., pages + * 122-126. Knuth cites his source as "The polar method", G. E. P. Box, M. E. + * Muller, and G. Marsaglia, _Annals_Math,_Stat._ 29 (1958), 610-611. + * + */ +static void +get_normal_pair(float8 *x1, float8 *x2) +{ + float8 u1, + u2, + v1, + v2, + s; + + do + { + u1 = pg_prng_double(&pg_global_prng_state); + u2 = pg_prng_double(&pg_global_prng_state); + + v1 = (2.0 * u1) - 1.0; + v2 = (2.0 * u2) - 1.0; + + s = v1 * v1 + v2 * v2; + } while (s >= 1.0); + + if (s == 0) + { + *x1 = 0; + *x2 = 0; + } + else + { + s = sqrt((-2.0 * log(s)) / s); + *x1 = v1 * s; + *x2 = v2 * s; + } +} + +/* + * crosstab - create a crosstab of rowids and values columns from a + * SQL statement returning one rowid column, one category column, + * and one value column. + * + * e.g. given sql which produces: + * + * rowid cat value + * ------+-------+------- + * row1 cat1 val1 + * row1 cat2 val2 + * row1 cat3 val3 + * row1 cat4 val4 + * row2 cat1 val5 + * row2 cat2 val6 + * row2 cat3 val7 + * row2 cat4 val8 + * + * crosstab returns: + * <===== values columns =====> + * rowid cat1 cat2 cat3 cat4 + * ------+-------+-------+-------+------- + * row1 val1 val2 val3 val4 + * row2 val5 val6 val7 val8 + * + * NOTES: + * 1. SQL result must be ordered by 1,2. + * 2. The number of values columns depends on the tuple description + * of the function's declared return type. The return type's columns + * must match the datatypes of the SQL query's result. The datatype + * of the category column can be anything, however. + * 3. Missing values (i.e. not enough adjacent rows of same rowid to + * fill the number of result values columns) are filled in with nulls. + * 4. Extra values (i.e. too many adjacent rows of same rowid to fill + * the number of result values columns) are skipped. + * 5. Rows with all nulls in the values columns are skipped. + */ +PG_FUNCTION_INFO_V1(crosstab); +Datum +crosstab(PG_FUNCTION_ARGS) +{ + char *sql = text_to_cstring(PG_GETARG_TEXT_PP(0)); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Tuplestorestate *tupstore; + TupleDesc tupdesc; + uint64 call_cntr; + uint64 max_calls; + AttInMetadata *attinmeta; + SPITupleTable *spi_tuptable; + TupleDesc spi_tupdesc; + bool firstpass; + char *lastrowid; + int i; + int num_categories; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + int ret; + uint64 proc; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + + /* Connect to SPI manager */ + if ((ret = SPI_connect()) < 0) + /* internal error */ + elog(ERROR, "crosstab: SPI_connect returned %d", ret); + + /* Retrieve the desired rows */ + ret = SPI_execute(sql, true, 0); + proc = SPI_processed; + + /* If no qualifying tuples, fall out early */ + if (ret != SPI_OK_SELECT || proc == 0) + { + SPI_finish(); + rsinfo->isDone = ExprEndResult; + PG_RETURN_NULL(); + } + + spi_tuptable = SPI_tuptable; + spi_tupdesc = spi_tuptable->tupdesc; + + /*---------- + * The provided SQL query must always return three columns. + * + * 1. rowname + * the label or identifier for each row in the final result + * 2. category + * the label or identifier for each column in the final result + * 3. values + * the value for each column in the final result + *---------- + */ + if (spi_tupdesc->natts != 3) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid source data SQL statement"), + errdetail("The provided SQL must return 3 " + "columns: rowid, category, and values."))); + + /* get a tuple descriptor for our result type */ + switch (get_call_result_type(fcinfo, NULL, &tupdesc)) + { + case TYPEFUNC_COMPOSITE: + /* success */ + break; + case TYPEFUNC_RECORD: + /* failed to determine actual type of RECORD */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + break; + default: + /* result type isn't composite */ + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("return type must be a row type"))); + break; + } + + /* + * Check that return tupdesc is compatible with the data we got from SPI, + * at least based on number and type of attributes + */ + if (!compatCrosstabTupleDescs(tupdesc, spi_tupdesc)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("return and sql tuple descriptions are " \ + "incompatible"))); + + /* + * switch to long-lived memory context + */ + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* make sure we have a persistent copy of the result tupdesc */ + tupdesc = CreateTupleDescCopy(tupdesc); + + /* initialize our tuplestore in long-lived context */ + tupstore = + tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random, + false, work_mem); + + MemoryContextSwitchTo(oldcontext); + + /* + * Generate attribute metadata needed later to produce tuples from raw C + * strings + */ + attinmeta = TupleDescGetAttInMetadata(tupdesc); + + /* total number of tuples to be examined */ + max_calls = proc; + + /* the return tuple always must have 1 rowid + num_categories columns */ + num_categories = tupdesc->natts - 1; + + firstpass = true; + lastrowid = NULL; + + for (call_cntr = 0; call_cntr < max_calls; call_cntr++) + { + bool skip_tuple = false; + char **values; + + /* allocate and zero space */ + values = (char **) palloc0((1 + num_categories) * sizeof(char *)); + + /* + * now loop through the sql results and assign each value in sequence + * to the next category + */ + for (i = 0; i < num_categories; i++) + { + HeapTuple spi_tuple; + char *rowid; + + /* see if we've gone too far already */ + if (call_cntr >= max_calls) + break; + + /* get the next sql result tuple */ + spi_tuple = spi_tuptable->vals[call_cntr]; + + /* get the rowid from the current sql result tuple */ + rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1); + + /* + * If this is the first pass through the values for this rowid, + * set the first column to rowid + */ + if (i == 0) + { + xpstrdup(values[0], rowid); + + /* + * Check to see if the rowid is the same as that of the last + * tuple sent -- if so, skip this tuple entirely + */ + if (!firstpass && xstreq(lastrowid, rowid)) + { + xpfree(rowid); + skip_tuple = true; + break; + } + } + + /* + * If rowid hasn't changed on us, continue building the output + * tuple. + */ + if (xstreq(rowid, values[0])) + { + /* + * Get the next category item value, which is always attribute + * number three. + * + * Be careful to assign the value to the array index based on + * which category we are presently processing. + */ + values[1 + i] = SPI_getvalue(spi_tuple, spi_tupdesc, 3); + + /* + * increment the counter since we consume a row for each + * category, but not for last pass because the outer loop will + * do that for us + */ + if (i < (num_categories - 1)) + call_cntr++; + xpfree(rowid); + } + else + { + /* + * We'll fill in NULLs for the missing values, but we need to + * decrement the counter since this sql result row doesn't + * belong to the current output tuple. + */ + call_cntr--; + xpfree(rowid); + break; + } + } + + if (!skip_tuple) + { + HeapTuple tuple; + + /* build the tuple and store it */ + tuple = BuildTupleFromCStrings(attinmeta, values); + tuplestore_puttuple(tupstore, tuple); + heap_freetuple(tuple); + } + + /* Remember current rowid */ + xpfree(lastrowid); + xpstrdup(lastrowid, values[0]); + firstpass = false; + + /* Clean up */ + for (i = 0; i < num_categories + 1; i++) + if (values[i] != NULL) + pfree(values[i]); + pfree(values); + } + + /* let the caller know we're sending back a tuplestore */ + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + /* release SPI related resources (and return to caller's context) */ + SPI_finish(); + + return (Datum) 0; +} + +/* + * crosstab_hash - reimplement crosstab as materialized function and + * properly deal with missing values (i.e. don't pack remaining + * values to the left) + * + * crosstab - create a crosstab of rowids and values columns from a + * SQL statement returning one rowid column, one category column, + * and one value column. + * + * e.g. given sql which produces: + * + * rowid cat value + * ------+-------+------- + * row1 cat1 val1 + * row1 cat2 val2 + * row1 cat4 val4 + * row2 cat1 val5 + * row2 cat2 val6 + * row2 cat3 val7 + * row2 cat4 val8 + * + * crosstab returns: + * <===== values columns =====> + * rowid cat1 cat2 cat3 cat4 + * ------+-------+-------+-------+------- + * row1 val1 val2 null val4 + * row2 val5 val6 val7 val8 + * + * NOTES: + * 1. SQL result must be ordered by 1. + * 2. The number of values columns depends on the tuple description + * of the function's declared return type. + * 3. Missing values (i.e. missing category) are filled in with nulls. + * 4. Extra values (i.e. not in category results) are skipped. + */ +PG_FUNCTION_INFO_V1(crosstab_hash); +Datum +crosstab_hash(PG_FUNCTION_ARGS) +{ + char *sql = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *cats_sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + HTAB *crosstab_hash; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize) || + rsinfo->expectedDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* get the requested return tuple description */ + tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); + + /* + * Check to make sure we have a reasonable tuple descriptor + * + * Note we will attempt to coerce the values into whatever the return + * attribute type is and depend on the "in" function to complain if + * needed. + */ + if (tupdesc->natts < 2) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("query-specified return tuple and " \ + "crosstab function are not compatible"))); + + /* load up the categories hash table */ + crosstab_hash = load_categories_hash(cats_sql, per_query_ctx); + + /* let the caller know we're sending back a tuplestore */ + rsinfo->returnMode = SFRM_Materialize; + + /* now go build it */ + rsinfo->setResult = get_crosstab_tuplestore(sql, + crosstab_hash, + tupdesc, + rsinfo->allowedModes & SFRM_Materialize_Random); + + /* + * SFRM_Materialize mode expects us to return a NULL Datum. The actual + * tuples are in our tuplestore and passed back through rsinfo->setResult. + * rsinfo->setDesc is set to the tuple description that we actually used + * to build our tuples with, so the caller can verify we did what it was + * expecting. + */ + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + return (Datum) 0; +} + +/* + * load up the categories hash table + */ +static HTAB * +load_categories_hash(char *cats_sql, MemoryContext per_query_ctx) +{ + HTAB *crosstab_hash; + HASHCTL ctl; + int ret; + uint64 proc; + MemoryContext SPIcontext; + + /* initialize the category hash table */ + ctl.keysize = MAX_CATNAME_LEN; + ctl.entrysize = sizeof(crosstab_HashEnt); + ctl.hcxt = per_query_ctx; + + /* + * use INIT_CATS, defined above as a guess of how many hash table entries + * to create, initially + */ + crosstab_hash = hash_create("crosstab hash", + INIT_CATS, + &ctl, + HASH_ELEM | HASH_STRINGS | HASH_CONTEXT); + + /* Connect to SPI manager */ + if ((ret = SPI_connect()) < 0) + /* internal error */ + elog(ERROR, "load_categories_hash: SPI_connect returned %d", ret); + + /* Retrieve the category name rows */ + ret = SPI_execute(cats_sql, true, 0); + proc = SPI_processed; + + /* Check for qualifying tuples */ + if ((ret == SPI_OK_SELECT) && (proc > 0)) + { + SPITupleTable *spi_tuptable = SPI_tuptable; + TupleDesc spi_tupdesc = spi_tuptable->tupdesc; + uint64 i; + + /* + * The provided categories SQL query must always return one column: + * category - the label or identifier for each column + */ + if (spi_tupdesc->natts != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("provided \"categories\" SQL must " \ + "return 1 column of at least one row"))); + + for (i = 0; i < proc; i++) + { + crosstab_cat_desc *catdesc; + char *catname; + HeapTuple spi_tuple; + + /* get the next sql result tuple */ + spi_tuple = spi_tuptable->vals[i]; + + /* get the category from the current sql result tuple */ + catname = SPI_getvalue(spi_tuple, spi_tupdesc, 1); + if (catname == NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("provided \"categories\" SQL must " \ + "not return NULL values"))); + + SPIcontext = MemoryContextSwitchTo(per_query_ctx); + + catdesc = (crosstab_cat_desc *) palloc(sizeof(crosstab_cat_desc)); + catdesc->catname = catname; + catdesc->attidx = i; + + /* Add the proc description block to the hashtable */ + crosstab_HashTableInsert(crosstab_hash, catdesc); + + MemoryContextSwitchTo(SPIcontext); + } + } + + if (SPI_finish() != SPI_OK_FINISH) + /* internal error */ + elog(ERROR, "load_categories_hash: SPI_finish() failed"); + + return crosstab_hash; +} + +/* + * create and populate the crosstab tuplestore using the provided source query + */ +static Tuplestorestate * +get_crosstab_tuplestore(char *sql, + HTAB *crosstab_hash, + TupleDesc tupdesc, + bool randomAccess) +{ + Tuplestorestate *tupstore; + int num_categories = hash_get_num_entries(crosstab_hash); + AttInMetadata *attinmeta = TupleDescGetAttInMetadata(tupdesc); + char **values; + HeapTuple tuple; + int ret; + uint64 proc; + + /* initialize our tuplestore (while still in query context!) */ + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + + /* Connect to SPI manager */ + if ((ret = SPI_connect()) < 0) + /* internal error */ + elog(ERROR, "get_crosstab_tuplestore: SPI_connect returned %d", ret); + + /* Now retrieve the crosstab source rows */ + ret = SPI_execute(sql, true, 0); + proc = SPI_processed; + + /* Check for qualifying tuples */ + if ((ret == SPI_OK_SELECT) && (proc > 0)) + { + SPITupleTable *spi_tuptable = SPI_tuptable; + TupleDesc spi_tupdesc = spi_tuptable->tupdesc; + int ncols = spi_tupdesc->natts; + char *rowid; + char *lastrowid = NULL; + bool firstpass = true; + uint64 i; + int j; + int result_ncols; + + if (num_categories == 0) + { + /* no qualifying category tuples */ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("provided \"categories\" SQL must " \ + "return 1 column of at least one row"))); + } + + /* + * The provided SQL query must always return at least three columns: + * + * 1. rowname the label for each row - column 1 in the final result + * 2. category the label for each value-column in the final result 3. + * value the values used to populate the value-columns + * + * If there are more than three columns, the last two are taken as + * "category" and "values". The first column is taken as "rowname". + * Additional columns (2 thru N-2) are assumed the same for the same + * "rowname", and are copied into the result tuple from the first time + * we encounter a particular rowname. + */ + if (ncols < 3) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid source data SQL statement"), + errdetail("The provided SQL must return 3 " \ + " columns; rowid, category, and values."))); + + result_ncols = (ncols - 2) + num_categories; + + /* Recheck to make sure we tuple descriptor still looks reasonable */ + if (tupdesc->natts != result_ncols) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid return type"), + errdetail("Query-specified return " \ + "tuple has %d columns but crosstab " \ + "returns %d.", tupdesc->natts, result_ncols))); + + /* allocate space and make sure it's clear */ + values = (char **) palloc0(result_ncols * sizeof(char *)); + + for (i = 0; i < proc; i++) + { + HeapTuple spi_tuple; + crosstab_cat_desc *catdesc; + char *catname; + + /* get the next sql result tuple */ + spi_tuple = spi_tuptable->vals[i]; + + /* get the rowid from the current sql result tuple */ + rowid = SPI_getvalue(spi_tuple, spi_tupdesc, 1); + + /* + * if we're on a new output row, grab the column values up to + * column N-2 now + */ + if (firstpass || !xstreq(lastrowid, rowid)) + { + /* + * a new row means we need to flush the old one first, unless + * we're on the very first row + */ + if (!firstpass) + { + /* rowid changed, flush the previous output row */ + tuple = BuildTupleFromCStrings(attinmeta, values); + + tuplestore_puttuple(tupstore, tuple); + + for (j = 0; j < result_ncols; j++) + xpfree(values[j]); + } + + values[0] = rowid; + for (j = 1; j < ncols - 2; j++) + values[j] = SPI_getvalue(spi_tuple, spi_tupdesc, j + 1); + + /* we're no longer on the first pass */ + firstpass = false; + } + + /* look up the category and fill in the appropriate column */ + catname = SPI_getvalue(spi_tuple, spi_tupdesc, ncols - 1); + + if (catname != NULL) + { + crosstab_HashTableLookup(crosstab_hash, catname, catdesc); + + if (catdesc) + values[catdesc->attidx + ncols - 2] = + SPI_getvalue(spi_tuple, spi_tupdesc, ncols); + } + + xpfree(lastrowid); + xpstrdup(lastrowid, rowid); + } + + /* flush the last output row */ + tuple = BuildTupleFromCStrings(attinmeta, values); + + tuplestore_puttuple(tupstore, tuple); + } + + if (SPI_finish() != SPI_OK_FINISH) + /* internal error */ + elog(ERROR, "get_crosstab_tuplestore: SPI_finish() failed"); + + return tupstore; +} + +/* + * connectby_text - produce a result set from a hierarchical (parent/child) + * table. + * + * e.g. given table foo: + * + * keyid parent_keyid pos + * ------+------------+-- + * row1 NULL 0 + * row2 row1 0 + * row3 row1 0 + * row4 row2 1 + * row5 row2 0 + * row6 row4 0 + * row7 row3 0 + * row8 row6 0 + * row9 row5 0 + * + * + * connectby(text relname, text keyid_fld, text parent_keyid_fld + * [, text orderby_fld], text start_with, int max_depth + * [, text branch_delim]) + * connectby('foo', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') returns: + * + * keyid parent_id level branch serial + * ------+-----------+--------+----------------------- + * row2 NULL 0 row2 1 + * row5 row2 1 row2~row5 2 + * row9 row5 2 row2~row5~row9 3 + * row4 row2 1 row2~row4 4 + * row6 row4 2 row2~row4~row6 5 + * row8 row6 3 row2~row4~row6~row8 6 + * + */ +PG_FUNCTION_INFO_V1(connectby_text); + +#define CONNECTBY_NCOLS 4 +#define CONNECTBY_NCOLS_NOBRANCH 3 + +Datum +connectby_text(PG_FUNCTION_ARGS) +{ + char *relname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *key_fld = text_to_cstring(PG_GETARG_TEXT_PP(1)); + char *parent_key_fld = text_to_cstring(PG_GETARG_TEXT_PP(2)); + char *start_with = text_to_cstring(PG_GETARG_TEXT_PP(3)); + int max_depth = PG_GETARG_INT32(4); + char *branch_delim = NULL; + bool show_branch = false; + bool show_serial = false; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + AttInMetadata *attinmeta; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize) || + rsinfo->expectedDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + if (fcinfo->nargs == 6) + { + branch_delim = text_to_cstring(PG_GETARG_TEXT_PP(5)); + show_branch = true; + } + else + /* default is no show, tilde for the delimiter */ + branch_delim = pstrdup("~"); + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* get the requested return tuple description */ + tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); + + /* does it meet our needs */ + validateConnectbyTupleDesc(tupdesc, show_branch, show_serial); + + /* OK, use it then */ + attinmeta = TupleDescGetAttInMetadata(tupdesc); + + /* OK, go to work */ + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = connectby(relname, + key_fld, + parent_key_fld, + NULL, + branch_delim, + start_with, + max_depth, + show_branch, + show_serial, + per_query_ctx, + rsinfo->allowedModes & SFRM_Materialize_Random, + attinmeta); + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + /* + * SFRM_Materialize mode expects us to return a NULL Datum. The actual + * tuples are in our tuplestore and passed back through rsinfo->setResult. + * rsinfo->setDesc is set to the tuple description that we actually used + * to build our tuples with, so the caller can verify we did what it was + * expecting. + */ + return (Datum) 0; +} + +PG_FUNCTION_INFO_V1(connectby_text_serial); +Datum +connectby_text_serial(PG_FUNCTION_ARGS) +{ + char *relname = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *key_fld = text_to_cstring(PG_GETARG_TEXT_PP(1)); + char *parent_key_fld = text_to_cstring(PG_GETARG_TEXT_PP(2)); + char *orderby_fld = text_to_cstring(PG_GETARG_TEXT_PP(3)); + char *start_with = text_to_cstring(PG_GETARG_TEXT_PP(4)); + int max_depth = PG_GETARG_INT32(5); + char *branch_delim = NULL; + bool show_branch = false; + bool show_serial = true; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + AttInMetadata *attinmeta; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize) || + rsinfo->expectedDesc == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + if (fcinfo->nargs == 7) + { + branch_delim = text_to_cstring(PG_GETARG_TEXT_PP(6)); + show_branch = true; + } + else + /* default is no show, tilde for the delimiter */ + branch_delim = pstrdup("~"); + + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* get the requested return tuple description */ + tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); + + /* does it meet our needs */ + validateConnectbyTupleDesc(tupdesc, show_branch, show_serial); + + /* OK, use it then */ + attinmeta = TupleDescGetAttInMetadata(tupdesc); + + /* OK, go to work */ + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = connectby(relname, + key_fld, + parent_key_fld, + orderby_fld, + branch_delim, + start_with, + max_depth, + show_branch, + show_serial, + per_query_ctx, + rsinfo->allowedModes & SFRM_Materialize_Random, + attinmeta); + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + /* + * SFRM_Materialize mode expects us to return a NULL Datum. The actual + * tuples are in our tuplestore and passed back through rsinfo->setResult. + * rsinfo->setDesc is set to the tuple description that we actually used + * to build our tuples with, so the caller can verify we did what it was + * expecting. + */ + return (Datum) 0; +} + + +/* + * connectby - does the real work for connectby_text() + */ +static Tuplestorestate * +connectby(char *relname, + char *key_fld, + char *parent_key_fld, + char *orderby_fld, + char *branch_delim, + char *start_with, + int max_depth, + bool show_branch, + bool show_serial, + MemoryContext per_query_ctx, + bool randomAccess, + AttInMetadata *attinmeta) +{ + Tuplestorestate *tupstore = NULL; + int ret; + MemoryContext oldcontext; + + int serial = 1; + + /* Connect to SPI manager */ + if ((ret = SPI_connect()) < 0) + /* internal error */ + elog(ERROR, "connectby: SPI_connect returned %d", ret); + + /* switch to longer term context to create the tuple store */ + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* initialize our tuplestore */ + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + + MemoryContextSwitchTo(oldcontext); + + /* now go get the whole tree */ + build_tuplestore_recursively(key_fld, + parent_key_fld, + relname, + orderby_fld, + branch_delim, + start_with, + start_with, /* current_branch */ + 0, /* initial level is 0 */ + &serial, /* initial serial is 1 */ + max_depth, + show_branch, + show_serial, + per_query_ctx, + attinmeta, + tupstore); + + SPI_finish(); + + return tupstore; +} + +static void +build_tuplestore_recursively(char *key_fld, + char *parent_key_fld, + char *relname, + char *orderby_fld, + char *branch_delim, + char *start_with, + char *branch, + int level, + int *serial, + int max_depth, + bool show_branch, + bool show_serial, + MemoryContext per_query_ctx, + AttInMetadata *attinmeta, + Tuplestorestate *tupstore) +{ + TupleDesc tupdesc = attinmeta->tupdesc; + int ret; + uint64 proc; + int serial_column; + StringInfoData sql; + char **values; + char *current_key; + char *current_key_parent; + char current_level[INT32_STRLEN]; + char serial_str[INT32_STRLEN]; + char *current_branch; + HeapTuple tuple; + + if (max_depth > 0 && level > max_depth) + return; + + initStringInfo(&sql); + + /* Build initial sql statement */ + if (!show_serial) + { + appendStringInfo(&sql, "SELECT %s, %s FROM %s WHERE %s = %s AND %s IS NOT NULL AND %s <> %s", + key_fld, + parent_key_fld, + relname, + parent_key_fld, + quote_literal_cstr(start_with), + key_fld, key_fld, parent_key_fld); + serial_column = 0; + } + else + { + appendStringInfo(&sql, "SELECT %s, %s FROM %s WHERE %s = %s AND %s IS NOT NULL AND %s <> %s ORDER BY %s", + key_fld, + parent_key_fld, + relname, + parent_key_fld, + quote_literal_cstr(start_with), + key_fld, key_fld, parent_key_fld, + orderby_fld); + serial_column = 1; + } + + if (show_branch) + values = (char **) palloc((CONNECTBY_NCOLS + serial_column) * sizeof(char *)); + else + values = (char **) palloc((CONNECTBY_NCOLS_NOBRANCH + serial_column) * sizeof(char *)); + + /* First time through, do a little setup */ + if (level == 0) + { + /* root value is the one we initially start with */ + values[0] = start_with; + + /* root value has no parent */ + values[1] = NULL; + + /* root level is 0 */ + sprintf(current_level, "%d", level); + values[2] = current_level; + + /* root branch is just starting root value */ + if (show_branch) + values[3] = start_with; + + /* root starts the serial with 1 */ + if (show_serial) + { + sprintf(serial_str, "%d", (*serial)++); + if (show_branch) + values[4] = serial_str; + else + values[3] = serial_str; + } + + /* construct the tuple */ + tuple = BuildTupleFromCStrings(attinmeta, values); + + /* now store it */ + tuplestore_puttuple(tupstore, tuple); + + /* increment level */ + level++; + } + + /* Retrieve the desired rows */ + ret = SPI_execute(sql.data, true, 0); + proc = SPI_processed; + + /* Check for qualifying tuples */ + if ((ret == SPI_OK_SELECT) && (proc > 0)) + { + HeapTuple spi_tuple; + SPITupleTable *tuptable = SPI_tuptable; + TupleDesc spi_tupdesc = tuptable->tupdesc; + uint64 i; + StringInfoData branchstr; + StringInfoData chk_branchstr; + StringInfoData chk_current_key; + + /* + * Check that return tupdesc is compatible with the one we got from + * the query. + */ + compatConnectbyTupleDescs(tupdesc, spi_tupdesc); + + initStringInfo(&branchstr); + initStringInfo(&chk_branchstr); + initStringInfo(&chk_current_key); + + for (i = 0; i < proc; i++) + { + /* initialize branch for this pass */ + appendStringInfoString(&branchstr, branch); + appendStringInfo(&chk_branchstr, "%s%s%s", branch_delim, branch, branch_delim); + + /* get the next sql result tuple */ + spi_tuple = tuptable->vals[i]; + + /* get the current key (might be NULL) */ + current_key = SPI_getvalue(spi_tuple, spi_tupdesc, 1); + + /* get the parent key (might be NULL) */ + current_key_parent = SPI_getvalue(spi_tuple, spi_tupdesc, 2); + + /* get the current level */ + sprintf(current_level, "%d", level); + + /* check to see if this key is also an ancestor */ + if (current_key) + { + appendStringInfo(&chk_current_key, "%s%s%s", + branch_delim, current_key, branch_delim); + if (strstr(chk_branchstr.data, chk_current_key.data)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_RECURSION), + errmsg("infinite recursion detected"))); + } + + /* OK, extend the branch */ + if (current_key) + appendStringInfo(&branchstr, "%s%s", branch_delim, current_key); + current_branch = branchstr.data; + + /* build a tuple */ + values[0] = current_key; + values[1] = current_key_parent; + values[2] = current_level; + if (show_branch) + values[3] = current_branch; + if (show_serial) + { + sprintf(serial_str, "%d", (*serial)++); + if (show_branch) + values[4] = serial_str; + else + values[3] = serial_str; + } + + tuple = BuildTupleFromCStrings(attinmeta, values); + + /* store the tuple for later use */ + tuplestore_puttuple(tupstore, tuple); + + heap_freetuple(tuple); + + /* recurse using current_key as the new start_with */ + if (current_key) + build_tuplestore_recursively(key_fld, + parent_key_fld, + relname, + orderby_fld, + branch_delim, + current_key, + current_branch, + level + 1, + serial, + max_depth, + show_branch, + show_serial, + per_query_ctx, + attinmeta, + tupstore); + + xpfree(current_key); + xpfree(current_key_parent); + + /* reset branch for next pass */ + resetStringInfo(&branchstr); + resetStringInfo(&chk_branchstr); + resetStringInfo(&chk_current_key); + } + + xpfree(branchstr.data); + xpfree(chk_branchstr.data); + xpfree(chk_current_key.data); + } +} + +/* + * Check expected (query runtime) tupdesc suitable for Connectby + */ +static void +validateConnectbyTupleDesc(TupleDesc td, bool show_branch, bool show_serial) +{ + int serial_column = 0; + + if (show_serial) + serial_column = 1; + + /* are there the correct number of columns */ + if (show_branch) + { + if (td->natts != (CONNECTBY_NCOLS + serial_column)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("invalid return type"), + errdetail("Query-specified return tuple has " \ + "wrong number of columns."))); + } + else + { + if (td->natts != CONNECTBY_NCOLS_NOBRANCH + serial_column) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("invalid return type"), + errdetail("Query-specified return tuple has " \ + "wrong number of columns."))); + } + + /* check that the types of the first two columns match */ + if (TupleDescAttr(td, 0)->atttypid != TupleDescAttr(td, 1)->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("invalid return type"), + errdetail("First two columns must be the same type."))); + + /* check that the type of the third column is INT4 */ + if (TupleDescAttr(td, 2)->atttypid != INT4OID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("invalid return type"), + errdetail("Third column must be type %s.", + format_type_be(INT4OID)))); + + /* check that the type of the fourth column is TEXT if applicable */ + if (show_branch && TupleDescAttr(td, 3)->atttypid != TEXTOID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("invalid return type"), + errdetail("Fourth column must be type %s.", + format_type_be(TEXTOID)))); + + /* check that the type of the fifth column is INT4 */ + if (show_branch && show_serial && + TupleDescAttr(td, 4)->atttypid != INT4OID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("query-specified return tuple not valid for Connectby: " + "fifth column must be type %s", + format_type_be(INT4OID)))); + + /* check that the type of the fourth column is INT4 */ + if (!show_branch && show_serial && + TupleDescAttr(td, 3)->atttypid != INT4OID) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("query-specified return tuple not valid for Connectby: " + "fourth column must be type %s", + format_type_be(INT4OID)))); + + /* OK, the tupdesc is valid for our purposes */ +} + +/* + * Check if spi sql tupdesc and return tupdesc are compatible + */ +static void +compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc) +{ + Oid ret_atttypid; + Oid sql_atttypid; + int32 ret_atttypmod; + int32 sql_atttypmod; + + /* + * Result must have at least 2 columns. + */ + if (sql_tupdesc->natts < 2) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("invalid return type"), + errdetail("Query must return at least two columns."))); + + /* + * These columns must match the result type indicated by the calling + * query. + */ + ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid; + sql_atttypid = TupleDescAttr(sql_tupdesc, 0)->atttypid; + ret_atttypmod = TupleDescAttr(ret_tupdesc, 0)->atttypmod; + sql_atttypmod = TupleDescAttr(sql_tupdesc, 0)->atttypmod; + if (ret_atttypid != sql_atttypid || + (ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("invalid return type"), + errdetail("SQL key field type %s does " \ + "not match return key field type %s.", + format_type_with_typemod(ret_atttypid, ret_atttypmod), + format_type_with_typemod(sql_atttypid, sql_atttypmod)))); + + ret_atttypid = TupleDescAttr(ret_tupdesc, 1)->atttypid; + sql_atttypid = TupleDescAttr(sql_tupdesc, 1)->atttypid; + ret_atttypmod = TupleDescAttr(ret_tupdesc, 1)->atttypmod; + sql_atttypmod = TupleDescAttr(sql_tupdesc, 1)->atttypmod; + if (ret_atttypid != sql_atttypid || + (ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("invalid return type"), + errdetail("SQL parent key field type %s does " \ + "not match return parent key field type %s.", + format_type_with_typemod(ret_atttypid, ret_atttypmod), + format_type_with_typemod(sql_atttypid, sql_atttypmod)))); + + /* OK, the two tupdescs are compatible for our purposes */ +} + +/* + * Check if two tupdescs match in type of attributes + */ +static bool +compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc) +{ + int i; + Form_pg_attribute ret_attr; + Oid ret_atttypid; + Form_pg_attribute sql_attr; + Oid sql_atttypid; + + if (ret_tupdesc->natts < 2 || + sql_tupdesc->natts < 3) + return false; + + /* check the rowid types match */ + ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid; + sql_atttypid = TupleDescAttr(sql_tupdesc, 0)->atttypid; + if (ret_atttypid != sql_atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("invalid return type"), + errdetail("SQL rowid datatype does not match " \ + "return rowid datatype."))); + + /* + * - attribute [1] of the sql tuple is the category; no need to check it - + * attribute [2] of the sql tuple should match attributes [1] to [natts] + * of the return tuple + */ + sql_attr = TupleDescAttr(sql_tupdesc, 2); + for (i = 1; i < ret_tupdesc->natts; i++) + { + ret_attr = TupleDescAttr(ret_tupdesc, i); + + if (ret_attr->atttypid != sql_attr->atttypid) + return false; + } + + /* OK, the two tupdescs are compatible for our purposes */ + return true; +} diff --git a/contrib/tablefunc/tablefunc.control b/contrib/tablefunc/tablefunc.control new file mode 100644 index 0000000..7b25d16 --- /dev/null +++ b/contrib/tablefunc/tablefunc.control @@ -0,0 +1,6 @@ +# tablefunc extension +comment = 'functions that manipulate whole tables, including crosstab' +default_version = '1.0' +module_pathname = '$libdir/tablefunc' +relocatable = true +trusted = true diff --git a/contrib/tablefunc/tablefunc.h b/contrib/tablefunc/tablefunc.h new file mode 100644 index 0000000..0398c6d --- /dev/null +++ b/contrib/tablefunc/tablefunc.h @@ -0,0 +1,39 @@ +/* + * contrib/tablefunc/tablefunc.h + * + * + * tablefunc + * + * Sample to demonstrate C functions which return setof scalar + * and setof composite. + * Joe Conway + * And contributors: + * Nabil Sayegh + * + * Copyright (c) 2002-2023, PostgreSQL Global Development Group + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + */ + +#ifndef TABLEFUNC_H +#define TABLEFUNC_H + +#include "fmgr.h" + +#endif /* TABLEFUNC_H */ diff --git a/contrib/tcn/.gitignore b/contrib/tcn/.gitignore new file mode 100644 index 0000000..b4903eb --- /dev/null +++ b/contrib/tcn/.gitignore @@ -0,0 +1,6 @@ +# Generated subdirectories +/log/ +/results/ +/output_iso/ +/tmp_check/ +/tmp_check_iso/ diff --git a/contrib/tcn/Makefile b/contrib/tcn/Makefile new file mode 100644 index 0000000..6813289 --- /dev/null +++ b/contrib/tcn/Makefile @@ -0,0 +1,21 @@ +# contrib/tcn/Makefile + +MODULES = tcn + +EXTENSION = tcn +DATA = tcn--1.0.sql +PGFILEDESC = "tcn - trigger function notifying listeners" + +ISOLATION = tcn +ISOLATION_OPTS = --load-extension=tcn + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/tcn +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/tcn/expected/tcn.out b/contrib/tcn/expected/tcn.out new file mode 100644 index 0000000..8c67113 --- /dev/null +++ b/contrib/tcn/expected/tcn.out @@ -0,0 +1,13 @@ +Parsed test spec with 1 sessions + +starting permutation: listen insert insert2 update delete +step listen: LISTEN mychannel; +step insert: INSERT INTO mytable VALUES(1, 'one'); +s1: NOTIFY "mychannel" with payload ""mytable",I,"key"='1'" from s1 +step insert2: INSERT INTO mytable VALUES(2, 'two'); +s1: NOTIFY "mychannel" with payload ""mytable",I,"key"='2'" from s1 +step update: UPDATE mytable SET value = 'foo' WHERE key = 2; +s1: NOTIFY "mychannel" with payload ""mytable",U,"key"='2'" from s1 +step delete: DELETE FROM mytable; +s1: NOTIFY "mychannel" with payload ""mytable",D,"key"='1'" from s1 +s1: NOTIFY "mychannel" with payload ""mytable",D,"key"='2'" from s1 diff --git a/contrib/tcn/meson.build b/contrib/tcn/meson.build new file mode 100644 index 0000000..3028bc5 --- /dev/null +++ b/contrib/tcn/meson.build @@ -0,0 +1,35 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +tcn_sources = files( + 'tcn.c', +) + +if host_system == 'windows' + tcn_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'tcn', + '--FILEDESC', 'tcn - trigger function notifying listeners',]) +endif + +tcn = shared_module('tcn', + tcn_sources, + kwargs: contrib_mod_args, +) +contrib_targets += tcn + +install_data( + 'tcn--1.0.sql', + 'tcn.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'tcn', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'isolation': { + 'specs': [ + 'tcn', + ], + 'regress_args': ['--load-extension=tcn'], + }, +} diff --git a/contrib/tcn/specs/tcn.spec b/contrib/tcn/specs/tcn.spec new file mode 100644 index 0000000..fb9e730 --- /dev/null +++ b/contrib/tcn/specs/tcn.spec @@ -0,0 +1,28 @@ +# Tests for contrib/tcn + +# These tests use only self-notifies within a single session, +# which are convenient because they minimize timing concerns. +# Whether the NOTIFY mechanism works across sessions is not +# really tcn's problem. + +setup +{ + CREATE TABLE mytable (key int PRIMARY KEY, value text); + CREATE TRIGGER tcntrig AFTER INSERT OR UPDATE OR DELETE ON mytable + FOR EACH ROW EXECUTE FUNCTION triggered_change_notification(mychannel); +} + +teardown +{ + DROP TABLE mytable; +} + +session s1 +step listen { LISTEN mychannel; } +step insert { INSERT INTO mytable VALUES(1, 'one'); } +step insert2 { INSERT INTO mytable VALUES(2, 'two'); } +step update { UPDATE mytable SET value = 'foo' WHERE key = 2; } +step delete { DELETE FROM mytable; } + + +permutation listen insert insert2 update delete diff --git a/contrib/tcn/tcn--1.0.sql b/contrib/tcn/tcn--1.0.sql new file mode 100644 index 0000000..027a4ef --- /dev/null +++ b/contrib/tcn/tcn--1.0.sql @@ -0,0 +1,9 @@ +/* contrib/tcn/tcn--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION tcn" to load this file. \quit + +CREATE FUNCTION triggered_change_notification() +RETURNS pg_catalog.trigger +AS 'MODULE_PATHNAME' +LANGUAGE C; diff --git a/contrib/tcn/tcn.c b/contrib/tcn/tcn.c new file mode 100644 index 0000000..546fbf2 --- /dev/null +++ b/contrib/tcn/tcn.c @@ -0,0 +1,180 @@ +/*------------------------------------------------------------------------- + * + * tcn.c + * triggered change notification support for PostgreSQL + * + * Portions Copyright (c) 2011-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/tcn/tcn.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "commands/async.h" +#include "commands/trigger.h" +#include "executor/spi.h" +#include "lib/stringinfo.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +PG_MODULE_MAGIC; + +/* + * Copy from s (for source) to r (for result), wrapping with q (quote) + * characters and doubling any quote characters found. + */ +static void +strcpy_quoted(StringInfo r, const char *s, const char q) +{ + appendStringInfoCharMacro(r, q); + while (*s) + { + if (*s == q) + appendStringInfoCharMacro(r, q); + appendStringInfoCharMacro(r, *s); + s++; + } + appendStringInfoCharMacro(r, q); +} + +/* + * triggered_change_notification + * + * This trigger function will send a notification of data modification with + * primary key values. The channel will be "tcn" unless the trigger is + * created with a parameter, in which case that parameter will be used. + */ +PG_FUNCTION_INFO_V1(triggered_change_notification); + +Datum +triggered_change_notification(PG_FUNCTION_ARGS) +{ + TriggerData *trigdata = (TriggerData *) fcinfo->context; + Trigger *trigger; + int nargs; + HeapTuple trigtuple; + Relation rel; + TupleDesc tupdesc; + char *channel; + char operation; + StringInfo payload = makeStringInfo(); + bool foundPK; + + List *indexoidlist; + ListCell *indexoidscan; + + /* make sure it's called as a trigger */ + if (!CALLED_AS_TRIGGER(fcinfo)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("triggered_change_notification: must be called as trigger"))); + + /* and that it's called after the change */ + if (!TRIGGER_FIRED_AFTER(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("triggered_change_notification: must be called after the change"))); + + /* and that it's called for each row */ + if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("triggered_change_notification: must be called for each row"))); + + if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) + operation = 'I'; + else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) + operation = 'U'; + else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) + operation = 'D'; + else + { + elog(ERROR, "triggered_change_notification: trigger fired by unrecognized operation"); + operation = 'X'; /* silence compiler warning */ + } + + trigger = trigdata->tg_trigger; + nargs = trigger->tgnargs; + if (nargs > 1) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("triggered_change_notification: must not be called with more than one parameter"))); + + if (nargs == 0) + channel = "tcn"; + else + channel = trigger->tgargs[0]; + + /* get tuple data */ + trigtuple = trigdata->tg_trigtuple; + rel = trigdata->tg_relation; + tupdesc = rel->rd_att; + + foundPK = false; + + /* + * Get the list of index OIDs for the table from the relcache, and look up + * each one in the pg_index syscache until we find one marked primary key + * (hopefully there isn't more than one such). + */ + indexoidlist = RelationGetIndexList(rel); + + foreach(indexoidscan, indexoidlist) + { + Oid indexoid = lfirst_oid(indexoidscan); + HeapTuple indexTuple; + Form_pg_index index; + + indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid)); + if (!HeapTupleIsValid(indexTuple)) /* should not happen */ + elog(ERROR, "cache lookup failed for index %u", indexoid); + index = (Form_pg_index) GETSTRUCT(indexTuple); + /* we're only interested if it is the primary key and valid */ + if (index->indisprimary && index->indisvalid) + { + int indnkeyatts = index->indnkeyatts; + + if (indnkeyatts > 0) + { + int i; + + foundPK = true; + + strcpy_quoted(payload, RelationGetRelationName(rel), '"'); + appendStringInfoCharMacro(payload, ','); + appendStringInfoCharMacro(payload, operation); + + for (i = 0; i < indnkeyatts; i++) + { + int colno = index->indkey.values[i]; + Form_pg_attribute attr = TupleDescAttr(tupdesc, colno - 1); + + appendStringInfoCharMacro(payload, ','); + strcpy_quoted(payload, NameStr(attr->attname), '"'); + appendStringInfoCharMacro(payload, '='); + strcpy_quoted(payload, SPI_getvalue(trigtuple, tupdesc, colno), '\''); + } + + Async_Notify(channel, payload->data); + } + ReleaseSysCache(indexTuple); + break; + } + ReleaseSysCache(indexTuple); + } + + list_free(indexoidlist); + + if (!foundPK) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("triggered_change_notification: must be called on a table with a primary key"))); + + return PointerGetDatum(NULL); /* after trigger; value doesn't matter */ +} diff --git a/contrib/tcn/tcn.control b/contrib/tcn/tcn.control new file mode 100644 index 0000000..6972e11 --- /dev/null +++ b/contrib/tcn/tcn.control @@ -0,0 +1,6 @@ +# tcn extension +comment = 'Triggered change notifications' +default_version = '1.0' +module_pathname = '$libdir/tcn' +relocatable = true +trusted = true diff --git a/contrib/test_decoding/.gitignore b/contrib/test_decoding/.gitignore new file mode 100644 index 0000000..b4903eb --- /dev/null +++ b/contrib/test_decoding/.gitignore @@ -0,0 +1,6 @@ +# Generated subdirectories +/log/ +/results/ +/output_iso/ +/tmp_check/ +/tmp_check_iso/ diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile new file mode 100644 index 0000000..c7ce603 --- /dev/null +++ b/contrib/test_decoding/Makefile @@ -0,0 +1,37 @@ +# contrib/test_decoding/Makefile + +MODULES = test_decoding +PGFILEDESC = "test_decoding - example of a logical decoding output plugin" + +REGRESS = ddl xact rewrite toast permissions decoding_in_xact \ + decoding_into_rel binary prepared replorigin time messages \ + spill slot truncate stream stats twophase twophase_stream +ISOLATION = mxact delayed_startup ondisk_startup concurrent_ddl_dml \ + oldest_xmin snapshot_transfer subxact_without_top concurrent_stream \ + twophase_snapshot slot_creation_error catalog_change_snapshot + +REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/test_decoding/logical.conf +ISOLATION_OPTS = --temp-config $(top_srcdir)/contrib/test_decoding/logical.conf + +# Disabled because these tests require "wal_level=logical", 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 = contrib/test_decoding +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_regress_installcheck) $(REGRESS) + $(pg_isolation_regress_installcheck) $(ISOLATION) diff --git a/contrib/test_decoding/expected/binary.out b/contrib/test_decoding/expected/binary.out new file mode 100644 index 0000000..b3a3509 --- /dev/null +++ b/contrib/test_decoding/expected/binary.out @@ -0,0 +1,35 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +-- succeeds, textual plugin, textual consumer +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force-binary', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +-- fails, binary plugin, textual consumer +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force-binary', '1', 'skip-empty-xacts', '1'); +ERROR: logical decoding output plugin "test_decoding" produces binary output, but function "pg_logical_slot_get_changes(name,pg_lsn,integer,text[])" expects textual data +-- succeeds, textual plugin, binary consumer +SELECT data FROM pg_logical_slot_get_binary_changes('regression_slot', NULL, NULL, 'force-binary', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +-- succeeds, binary plugin, binary consumer +SELECT data FROM pg_logical_slot_get_binary_changes('regression_slot', NULL, NULL, 'force-binary', '1', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +SELECT 'init' FROM pg_drop_replication_slot('regression_slot'); + ?column? +---------- + init +(1 row) + diff --git a/contrib/test_decoding/expected/catalog_change_snapshot.out b/contrib/test_decoding/expected/catalog_change_snapshot.out new file mode 100644 index 0000000..8722787 --- /dev/null +++ b/contrib/test_decoding/expected/catalog_change_snapshot.out @@ -0,0 +1,143 @@ +Parsed test spec with 3 sessions + +starting permutation: s0_init s0_begin s0_savepoint s0_truncate s1_checkpoint s1_get_changes s0_commit s0_begin s0_insert s1_checkpoint s1_get_changes s0_commit s1_get_changes +step s0_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s0_begin: BEGIN; +step s0_savepoint: SAVEPOINT sp1; +step s0_truncate: TRUNCATE tbl1; +step s1_checkpoint: CHECKPOINT; +step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +data +---- +(0 rows) + +step s0_commit: COMMIT; +step s0_begin: BEGIN; +step s0_insert: INSERT INTO tbl1 VALUES (1); +step s1_checkpoint: CHECKPOINT; +step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +data +--------------------------------------- +BEGIN +table public.tbl1: TRUNCATE: (no-flags) +COMMIT +(3 rows) + +step s0_commit: COMMIT; +step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +data +------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:null +COMMIT +(3 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s0_init s0_begin s0_truncate s2_begin s2_truncate s1_checkpoint s1_get_changes s0_commit s0_begin s0_insert s1_checkpoint s1_get_changes s2_commit s1_checkpoint s1_get_changes s0_commit s1_get_changes +step s0_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s0_begin: BEGIN; +step s0_truncate: TRUNCATE tbl1; +step s2_begin: BEGIN; +step s2_truncate: TRUNCATE tbl2; +step s1_checkpoint: CHECKPOINT; +step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +data +---- +(0 rows) + +step s0_commit: COMMIT; +step s0_begin: BEGIN; +step s0_insert: INSERT INTO tbl1 VALUES (1); +step s1_checkpoint: CHECKPOINT; +step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +data +--------------------------------------- +BEGIN +table public.tbl1: TRUNCATE: (no-flags) +COMMIT +(3 rows) + +step s2_commit: COMMIT; +step s1_checkpoint: CHECKPOINT; +step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +data +--------------------------------------- +BEGIN +table public.tbl2: TRUNCATE: (no-flags) +COMMIT +(3 rows) + +step s0_commit: COMMIT; +step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +data +------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:null +COMMIT +(3 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s0_init s0_begin s0_savepoint s0_insert s1_checkpoint s1_get_changes s0_insert2 s0_commit s0_begin s0_insert s1_checkpoint s1_get_changes s0_commit s1_get_changes +step s0_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s0_begin: BEGIN; +step s0_savepoint: SAVEPOINT sp1; +step s0_insert: INSERT INTO tbl1 VALUES (1); +step s1_checkpoint: CHECKPOINT; +step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +data +---- +(0 rows) + +step s0_insert2: INSERT INTO user_cat VALUES (1); +step s0_commit: COMMIT; +step s0_begin: BEGIN; +step s0_insert: INSERT INTO tbl1 VALUES (1); +step s1_checkpoint: CHECKPOINT; +step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +data +------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:null +table public.user_cat: INSERT: val1[integer]:1 +COMMIT +(4 rows) + +step s0_commit: COMMIT; +step s1_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +data +------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:null +COMMIT +(3 rows) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/expected/concurrent_ddl_dml.out b/contrib/test_decoding/expected/concurrent_ddl_dml.out new file mode 100644 index 0000000..3742a2a --- /dev/null +++ b/contrib/test_decoding/expected/concurrent_ddl_dml.out @@ -0,0 +1,799 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl2_float s1_insert_tbl2 s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_float: ALTER TABLE tbl2 ALTER COLUMN val2 TYPE float; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[double precision]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl1_float s1_insert_tbl2 s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl1_float: ALTER TABLE tbl1 ALTER COLUMN val2 TYPE float; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s2_alter_tbl1_float: <... completed> +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl2_char s1_insert_tbl2 s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_char: ALTER TABLE tbl2 ALTER COLUMN val2 TYPE character varying; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[character varying]:'1' +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl1_char s1_insert_tbl2 s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl1_char: ALTER TABLE tbl1 ALTER COLUMN val2 TYPE character varying; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s2_alter_tbl1_char: <... completed> +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s1_insert_tbl2 s2_alter_tbl1_float s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s2_alter_tbl1_float: ALTER TABLE tbl1 ALTER COLUMN val2 TYPE float; +step s1_commit: COMMIT; +step s2_alter_tbl1_float: <... completed> +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s1_insert_tbl2 s2_alter_tbl1_char s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s2_alter_tbl1_char: ALTER TABLE tbl1 ALTER COLUMN val2 TYPE character varying; +step s1_commit: COMMIT; +step s2_alter_tbl1_char: <... completed> +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl2_float s1_insert_tbl2 s2_alter_tbl1_float s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_float: ALTER TABLE tbl2 ALTER COLUMN val2 TYPE float; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s2_alter_tbl1_float: ALTER TABLE tbl1 ALTER COLUMN val2 TYPE float; +step s1_commit: COMMIT; +step s2_alter_tbl1_float: <... completed> +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[double precision]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl2_char s1_insert_tbl2 s2_alter_tbl1_char s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_char: ALTER TABLE tbl2 ALTER COLUMN val2 TYPE character varying; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s2_alter_tbl1_char: ALTER TABLE tbl1 ALTER COLUMN val2 TYPE character varying; +step s1_commit: COMMIT; +step s2_alter_tbl1_char: <... completed> +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[character varying]:'1' +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s2_alter_tbl2_char s1_begin s1_insert_tbl1 s2_alter_tbl2_text s1_insert_tbl2 s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s2_alter_tbl2_char: ALTER TABLE tbl2 ALTER COLUMN val2 TYPE character varying; +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_text: ALTER TABLE tbl2 ALTER COLUMN val2 TYPE text; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[text]:'1' +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s2_alter_tbl2_char s1_begin s1_insert_tbl1 s2_alter_tbl2_text s1_insert_tbl2 s2_alter_tbl1_char s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s2_alter_tbl2_char: ALTER TABLE tbl2 ALTER COLUMN val2 TYPE character varying; +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_text: ALTER TABLE tbl2 ALTER COLUMN val2 TYPE text; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s2_alter_tbl1_char: ALTER TABLE tbl1 ALTER COLUMN val2 TYPE character varying; +step s1_commit: COMMIT; +step s2_alter_tbl1_char: <... completed> +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[text]:'1' +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl2_boolean s1_insert_tbl2 s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_boolean: ALTER TABLE tbl2 ALTER COLUMN val2 TYPE boolean; +ERROR: column "val2" cannot be cast automatically to type boolean +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl2_boolean s1_insert_tbl2 s2_alter_tbl1_boolean s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_boolean: ALTER TABLE tbl2 ALTER COLUMN val2 TYPE boolean; +ERROR: column "val2" cannot be cast automatically to type boolean +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s2_alter_tbl1_boolean: ALTER TABLE tbl1 ALTER COLUMN val2 TYPE boolean; +step s1_commit: COMMIT; +step s2_alter_tbl1_boolean: <... completed> +ERROR: column "val2" cannot be cast automatically to type boolean +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl2_add_int s1_insert_tbl2_3col s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_add_int: ALTER TABLE tbl2 ADD COLUMN val3 INTEGER; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +-------------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[integer]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s1_insert_tbl2 s1_commit s1_begin s2_alter_tbl2_add_int s1_insert_tbl2_3col s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s1_begin: BEGIN; +step s2_alter_tbl2_add_int: ALTER TABLE tbl2 ADD COLUMN val3 INTEGER; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +-------------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[integer]:1 +COMMIT +(7 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl2_add_float s1_insert_tbl2_3col s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_add_float: ALTER TABLE tbl2 ADD COLUMN val3 FLOAT; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +----------------------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[double precision]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s1_insert_tbl2 s1_commit s1_begin s2_alter_tbl2_add_float s1_insert_tbl2_3col s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s1_begin: BEGIN; +step s2_alter_tbl2_add_float: ALTER TABLE tbl2 ADD COLUMN val3 FLOAT; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +----------------------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[double precision]:1 +COMMIT +(7 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s2_alter_tbl2_add_char s1_insert_tbl2_3col s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_add_char: ALTER TABLE tbl2 ADD COLUMN val3 character varying; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +-------------------------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[character varying]:'1' +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s1_begin s1_insert_tbl1 s1_insert_tbl2 s1_commit s1_begin s2_alter_tbl2_add_char s1_insert_tbl2_3col s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s1_begin: BEGIN; +step s2_alter_tbl2_add_char: ALTER TABLE tbl2 ADD COLUMN val3 character varying; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +-------------------------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[character varying]:'1' +COMMIT +(7 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s2_alter_tbl2_add_int s1_begin s1_insert_tbl2_3col s2_alter_tbl2_drop_3rd_col s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s2_alter_tbl2_add_int: ALTER TABLE tbl2 ADD COLUMN val3 INTEGER; +step s1_begin: BEGIN; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s2_alter_tbl2_drop_3rd_col: ALTER TABLE tbl2 DROP COLUMN val3; +step s1_commit: COMMIT; +step s2_alter_tbl2_drop_3rd_col: <... completed> +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +-------------------------------------------------------------------------- +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[integer]:1 +COMMIT +(3 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s2_alter_tbl2_add_int s1_begin s1_insert_tbl2_3col s2_alter_tbl2_drop_3rd_col s1_insert_tbl2 s1_commit s1_insert_tbl2 s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s2_alter_tbl2_add_int: ALTER TABLE tbl2 ADD COLUMN val3 INTEGER; +step s1_begin: BEGIN; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s2_alter_tbl2_drop_3rd_col: ALTER TABLE tbl2 DROP COLUMN val3; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s2_alter_tbl2_drop_3rd_col: <... completed> +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +----------------------------------------------------------------------------- +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[integer]:null +COMMIT +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +(7 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s2_alter_tbl2_add_int s1_begin s1_insert_tbl2_3col s2_alter_tbl2_drop_3rd_col s1_commit s2_get_changes s2_alter_tbl2_add_text s1_begin s1_insert_tbl2_3col s2_alter_tbl2_3rd_char s1_insert_tbl2_3col s1_commit s2_get_changes s2_alter_tbl2_3rd_int s1_insert_tbl2_3col s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s2_alter_tbl2_add_int: ALTER TABLE tbl2 ADD COLUMN val3 INTEGER; +step s1_begin: BEGIN; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s2_alter_tbl2_drop_3rd_col: ALTER TABLE tbl2 DROP COLUMN val3; +step s1_commit: COMMIT; +step s2_alter_tbl2_drop_3rd_col: <... completed> +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +-------------------------------------------------------------------------- +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[integer]:1 +COMMIT +(3 rows) + +step s2_alter_tbl2_add_text: ALTER TABLE tbl2 ADD COLUMN val3 TEXT; +step s1_begin: BEGIN; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s2_alter_tbl2_3rd_char: ALTER TABLE tbl2 ALTER COLUMN val3 TYPE character varying; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_alter_tbl2_3rd_char: <... completed> +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +------------------------------------------------------------------------- +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[text]:'1' +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[text]:'1' +COMMIT +(4 rows) + +step s2_alter_tbl2_3rd_int: ALTER TABLE tbl2 ALTER COLUMN val3 TYPE int USING val3::integer; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +-------------------------------------------------------------------------- +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[integer]:1 +COMMIT +(3 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s2_alter_tbl2_add_char s1_begin s1_insert_tbl1 s1_insert_tbl2_3col s2_alter_tbl2_3rd_text s1_insert_tbl2_3col s1_commit s1_insert_tbl2_3col s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s2_alter_tbl2_add_char: ALTER TABLE tbl2 ADD COLUMN val3 character varying; +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s2_alter_tbl2_3rd_text: ALTER TABLE tbl2 ALTER COLUMN val3 TYPE text; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_alter_tbl2_3rd_text: <... completed> +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +-------------------------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[character varying]:'1' +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[character varying]:'1' +COMMIT +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[text]:'1' +COMMIT +(8 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s2_alter_tbl2_add_text s1_begin s1_insert_tbl1 s1_insert_tbl2_3col s2_alter_tbl2_3rd_char s1_insert_tbl2_3col s1_commit s1_insert_tbl2_3col s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s2_alter_tbl2_add_text: ALTER TABLE tbl2 ADD COLUMN val3 TEXT; +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s2_alter_tbl2_3rd_char: ALTER TABLE tbl2 ALTER COLUMN val3 TYPE character varying; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_alter_tbl2_3rd_char: <... completed> +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +-------------------------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[text]:'1' +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[text]:'1' +COMMIT +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[character varying]:'1' +COMMIT +(8 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s2_alter_tbl2_add_char s1_begin s1_insert_tbl1 s2_alter_tbl2_3rd_text s1_insert_tbl2_3col s1_commit s2_alter_tbl2_drop_3rd_col s1_insert_tbl2 s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s2_alter_tbl2_add_char: ALTER TABLE tbl2 ADD COLUMN val3 character varying; +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_3rd_text: ALTER TABLE tbl2 ALTER COLUMN val3 TYPE text; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_alter_tbl2_drop_3rd_col: ALTER TABLE tbl2 DROP COLUMN val3; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +------------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[text]:'1' +COMMIT +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +(7 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s2_alter_tbl2_add_text s1_begin s1_insert_tbl1 s2_alter_tbl2_3rd_char s1_insert_tbl2_3col s1_commit s2_alter_tbl2_drop_3rd_col s1_insert_tbl2 s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s2_alter_tbl2_add_text: ALTER TABLE tbl2 ADD COLUMN val3 TEXT; +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_3rd_char: ALTER TABLE tbl2 ALTER COLUMN val3 TYPE character varying; +step s1_insert_tbl2_3col: INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); +step s1_commit: COMMIT; +step s2_alter_tbl2_drop_3rd_col: ALTER TABLE tbl2 DROP COLUMN val3; +step s1_insert_tbl2: INSERT INTO tbl2 (val1, val2) VALUES (1, 1); +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +-------------------------------------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 val3[character varying]:'1' +COMMIT +BEGIN +table public.tbl2: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +(7 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s1_init s2_alter_tbl2_add_char s1_begin s1_insert_tbl1 s2_alter_tbl2_drop_3rd_col s1_insert_tbl1 s1_commit s2_get_changes +step s1_init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s2_alter_tbl2_add_char: ALTER TABLE tbl2 ADD COLUMN val3 character varying; +step s1_begin: BEGIN; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s2_alter_tbl2_drop_3rd_col: ALTER TABLE tbl2 DROP COLUMN val3; +step s1_insert_tbl1: INSERT INTO tbl1 (val1, val2) VALUES (1, 1); +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------- +BEGIN +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +table public.tbl1: INSERT: val1[integer]:1 val2[integer]:1 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/expected/concurrent_stream.out b/contrib/test_decoding/expected/concurrent_stream.out new file mode 100644 index 0000000..bf1e132 --- /dev/null +++ b/contrib/test_decoding/expected/concurrent_stream.out @@ -0,0 +1,24 @@ +Parsed test spec with 3 sessions + +starting permutation: s0_begin s0_ddl s1_ddl s1_begin s1_toast_insert s2_ddl s1_commit s1_get_stream_changes +step s0_begin: BEGIN; +step s0_ddl: CREATE TABLE stream_test1(data text); +step s1_ddl: CREATE TABLE stream_test(data text); +step s1_begin: BEGIN; +step s1_toast_insert: INSERT INTO stream_test SELECT large_val(); +step s2_ddl: CREATE TABLE stream_test2(data text); +step s1_commit: COMMIT; +step s1_get_stream_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); +data +---------------------------------------- +opening a streamed block for transaction +streaming change for transaction +closing a streamed block for transaction +committing streamed transaction +(4 rows) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out new file mode 100644 index 0000000..5713b8a --- /dev/null +++ b/contrib/test_decoding/expected/ddl.out @@ -0,0 +1,845 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +-- fail because of an already existing slot +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +ERROR: replication slot "regression_slot" already exists +-- fail because of an invalid name +SELECT 'init' FROM pg_create_logical_replication_slot('Invalid Name', 'test_decoding'); +ERROR: replication slot name "Invalid Name" contains invalid character +HINT: Replication slot names may only contain lower case letters, numbers, and the underscore character. +-- fail twice because of an invalid parameter values +SELECT 'init' FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', 'frakbar'); +ERROR: could not parse value "frakbar" for parameter "include-xids" +CONTEXT: slot "regression_slot", output plugin "test_decoding", in the startup callback +SELECT 'init' FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'nonexistent-option', 'frakbar'); +ERROR: option "nonexistent-option" = "frakbar" is unknown +CONTEXT: slot "regression_slot", output plugin "test_decoding", in the startup callback +SELECT 'init' FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', 'frakbar'); +ERROR: could not parse value "frakbar" for parameter "include-xids" +CONTEXT: slot "regression_slot", output plugin "test_decoding", in the startup callback +-- succeed once +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +-- fail +SELECT pg_drop_replication_slot('regression_slot'); +ERROR: replication slot "regression_slot" does not exist +-- check that we're detecting a streaming rep slot used for logical decoding +SELECT 'init' FROM pg_create_physical_replication_slot('repl'); + ?column? +---------- + init +(1 row) + +SELECT data FROM pg_logical_slot_get_changes('repl', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +ERROR: cannot use physical replication slot for logical decoding +SELECT pg_drop_replication_slot('repl'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +/* check whether status function reports us, only reproduceable columns */ +SELECT slot_name, plugin, slot_type, active, + NOT catalog_xmin IS NULL AS catalog_xmin_set, + xmin IS NULl AS data_xmin_not_set, + pg_wal_lsn_diff(restart_lsn, '0/01000000') > 0 AS some_wal +FROM pg_replication_slots; + slot_name | plugin | slot_type | active | catalog_xmin_set | data_xmin_not_set | some_wal +-----------------+---------------+-----------+--------+------------------+-------------------+---------- + regression_slot | test_decoding | logical | f | t | t | t +(1 row) + +/* + * Check that changes are handled correctly when interleaved with ddl + */ +CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120)); +BEGIN; +INSERT INTO replication_example(somedata, text) VALUES (1, 1); +INSERT INTO replication_example(somedata, text) VALUES (1, 2); +COMMIT; +ALTER TABLE replication_example ADD COLUMN bar int; +INSERT INTO replication_example(somedata, text, bar) VALUES (2, 1, 4); +BEGIN; +INSERT INTO replication_example(somedata, text, bar) VALUES (2, 2, 4); +INSERT INTO replication_example(somedata, text, bar) VALUES (2, 3, 4); +INSERT INTO replication_example(somedata, text, bar) VALUES (2, 4, NULL); +COMMIT; +ALTER TABLE replication_example DROP COLUMN bar; +INSERT INTO replication_example(somedata, text) VALUES (3, 1); +BEGIN; +INSERT INTO replication_example(somedata, text) VALUES (3, 2); +INSERT INTO replication_example(somedata, text) VALUES (3, 3); +COMMIT; +ALTER TABLE replication_example RENAME COLUMN text TO somenum; +INSERT INTO replication_example(somedata, somenum) VALUES (4, 1); +-- collect all changes +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +--------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.replication_example: INSERT: id[integer]:1 somedata[integer]:1 text[character varying]:'1' + table public.replication_example: INSERT: id[integer]:2 somedata[integer]:1 text[character varying]:'2' + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:3 somedata[integer]:2 text[character varying]:'1' bar[integer]:4 + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:4 somedata[integer]:2 text[character varying]:'2' bar[integer]:4 + table public.replication_example: INSERT: id[integer]:5 somedata[integer]:2 text[character varying]:'3' bar[integer]:4 + table public.replication_example: INSERT: id[integer]:6 somedata[integer]:2 text[character varying]:'4' bar[integer]:null + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:7 somedata[integer]:3 text[character varying]:'1' + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:8 somedata[integer]:3 text[character varying]:'2' + table public.replication_example: INSERT: id[integer]:9 somedata[integer]:3 text[character varying]:'3' + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:10 somedata[integer]:4 somenum[character varying]:'1' + COMMIT +(22 rows) + +ALTER TABLE replication_example ALTER COLUMN somenum TYPE int4 USING (somenum::int4); +-- check that this doesn't produce any changes from the heap rewrite +SELECT count(data) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + count +------- + 0 +(1 row) + +INSERT INTO replication_example(somedata, somenum) VALUES (5, 1); +BEGIN; +INSERT INTO replication_example(somedata, somenum) VALUES (6, 1); +ALTER TABLE replication_example ADD COLUMN zaphod1 int; +INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 2, 1); +ALTER TABLE replication_example ADD COLUMN zaphod2 int; +INSERT INTO replication_example(somedata, somenum, zaphod2) VALUES (6, 3, 1); +INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 4, 2); +COMMIT; +-- show changes +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------------------------------------------------------------------------------------------------------------ + BEGIN + table public.replication_example: INSERT: id[integer]:11 somedata[integer]:5 somenum[integer]:1 + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:12 somedata[integer]:6 somenum[integer]:1 + table public.replication_example: INSERT: id[integer]:13 somedata[integer]:6 somenum[integer]:2 zaphod1[integer]:1 + table public.replication_example: INSERT: id[integer]:14 somedata[integer]:6 somenum[integer]:3 zaphod1[integer]:null zaphod2[integer]:1 + table public.replication_example: INSERT: id[integer]:15 somedata[integer]:6 somenum[integer]:4 zaphod1[integer]:2 zaphod2[integer]:null + COMMIT +(9 rows) + +-- ON CONFLICT DO UPDATE support +BEGIN; +INSERT INTO replication_example(id, somedata, somenum) SELECT i, i, i FROM generate_series(-15, 15) i + ON CONFLICT (id) DO UPDATE SET somenum = excluded.somenum + 1; +COMMIT; +/* display results */ +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +-------------------------------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.replication_example: INSERT: id[integer]:-15 somedata[integer]:-15 somenum[integer]:-15 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-14 somedata[integer]:-14 somenum[integer]:-14 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-13 somedata[integer]:-13 somenum[integer]:-13 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-12 somedata[integer]:-12 somenum[integer]:-12 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-11 somedata[integer]:-11 somenum[integer]:-11 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-10 somedata[integer]:-10 somenum[integer]:-10 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-9 somedata[integer]:-9 somenum[integer]:-9 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-8 somedata[integer]:-8 somenum[integer]:-8 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-7 somedata[integer]:-7 somenum[integer]:-7 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-6 somedata[integer]:-6 somenum[integer]:-6 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-5 somedata[integer]:-5 somenum[integer]:-5 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-4 somedata[integer]:-4 somenum[integer]:-4 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-3 somedata[integer]:-3 somenum[integer]:-3 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-2 somedata[integer]:-2 somenum[integer]:-2 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-1 somedata[integer]:-1 somenum[integer]:-1 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:0 somedata[integer]:0 somenum[integer]:0 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:1 somedata[integer]:1 somenum[integer]:2 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:2 somedata[integer]:1 somenum[integer]:3 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:3 somedata[integer]:2 somenum[integer]:4 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:4 somedata[integer]:2 somenum[integer]:5 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:5 somedata[integer]:2 somenum[integer]:6 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:6 somedata[integer]:2 somenum[integer]:7 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:7 somedata[integer]:3 somenum[integer]:8 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:8 somedata[integer]:3 somenum[integer]:9 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:9 somedata[integer]:3 somenum[integer]:10 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:10 somedata[integer]:4 somenum[integer]:11 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:11 somedata[integer]:5 somenum[integer]:12 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:12 somedata[integer]:6 somenum[integer]:13 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:13 somedata[integer]:6 somenum[integer]:14 zaphod1[integer]:1 zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:14 somedata[integer]:6 somenum[integer]:15 zaphod1[integer]:null zaphod2[integer]:1 + table public.replication_example: UPDATE: id[integer]:15 somedata[integer]:6 somenum[integer]:16 zaphod1[integer]:2 zaphod2[integer]:null + COMMIT +(33 rows) + +-- MERGE support +BEGIN; +MERGE INTO replication_example t + USING (SELECT i as id, i as data, i as num FROM generate_series(-20, 5) i) s + ON t.id = s.id + WHEN MATCHED AND t.id < 0 THEN + UPDATE SET somenum = somenum + 1 + WHEN MATCHED AND t.id >= 0 THEN + DELETE + WHEN NOT MATCHED THEN + INSERT VALUES (s.*); +COMMIT; +/* display results */ +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +-------------------------------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.replication_example: INSERT: id[integer]:-20 somedata[integer]:-20 somenum[integer]:-20 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-19 somedata[integer]:-19 somenum[integer]:-19 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-18 somedata[integer]:-18 somenum[integer]:-18 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-17 somedata[integer]:-17 somenum[integer]:-17 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: INSERT: id[integer]:-16 somedata[integer]:-16 somenum[integer]:-16 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-15 somedata[integer]:-15 somenum[integer]:-14 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-14 somedata[integer]:-14 somenum[integer]:-13 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-13 somedata[integer]:-13 somenum[integer]:-12 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-12 somedata[integer]:-12 somenum[integer]:-11 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-11 somedata[integer]:-11 somenum[integer]:-10 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-10 somedata[integer]:-10 somenum[integer]:-9 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-9 somedata[integer]:-9 somenum[integer]:-8 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-8 somedata[integer]:-8 somenum[integer]:-7 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-7 somedata[integer]:-7 somenum[integer]:-6 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-6 somedata[integer]:-6 somenum[integer]:-5 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-5 somedata[integer]:-5 somenum[integer]:-4 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-4 somedata[integer]:-4 somenum[integer]:-3 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-3 somedata[integer]:-3 somenum[integer]:-2 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-2 somedata[integer]:-2 somenum[integer]:-1 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: UPDATE: id[integer]:-1 somedata[integer]:-1 somenum[integer]:0 zaphod1[integer]:null zaphod2[integer]:null + table public.replication_example: DELETE: id[integer]:0 + table public.replication_example: DELETE: id[integer]:1 + table public.replication_example: DELETE: id[integer]:2 + table public.replication_example: DELETE: id[integer]:3 + table public.replication_example: DELETE: id[integer]:4 + table public.replication_example: DELETE: id[integer]:5 + COMMIT +(28 rows) + +CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int); +INSERT INTO tr_unique(data) VALUES(10); +ALTER TABLE tr_unique RENAME TO tr_pkey; +ALTER TABLE tr_pkey ADD COLUMN id serial primary key; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'include-rewrites', '1'); + data +----------------------------------------------------------------------------- + BEGIN + table public.tr_unique: INSERT: id2[integer]:1 data[integer]:10 + COMMIT + BEGIN + table public.tr_pkey: INSERT: id2[integer]:1 data[integer]:10 id[integer]:1 + COMMIT +(6 rows) + +INSERT INTO tr_pkey(data) VALUES(1); +--show deletion with primary key +DELETE FROM tr_pkey; +/* display results */ +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +---------------------------------------------------------------------------- + BEGIN + table public.tr_pkey: INSERT: id2[integer]:2 data[integer]:1 id[integer]:2 + COMMIT + BEGIN + table public.tr_pkey: DELETE: id[integer]:1 + table public.tr_pkey: DELETE: id[integer]:2 + COMMIT +(7 rows) + +/* + * check that disk spooling works (also for logical messages) + */ +BEGIN; +CREATE TABLE tr_etoomuch (id serial primary key, data int); +INSERT INTO tr_etoomuch(data) SELECT g.i FROM generate_series(1, 10234) g(i); +SELECT 'tx logical msg' FROM pg_logical_emit_message(true, 'test', 'tx logical msg'); + ?column? +---------------- + tx logical msg +(1 row) + +DELETE FROM tr_etoomuch WHERE id < 5000; +UPDATE tr_etoomuch SET data = - data WHERE id > 5000; +CREATE TABLE tr_oddlength (id text primary key, data text); +INSERT INTO tr_oddlength VALUES('ab', 'foo'); +COMMIT; +/* display results, but hide most of the output */ +SELECT count(*), min(data), max(data) +FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') +GROUP BY substring(data, 1, 24) +ORDER BY 1,2; + count | min | max +-------+-----------------------------------------------------------------------+------------------------------------------------------------------------ + 1 | BEGIN | BEGIN + 1 | COMMIT | COMMIT + 1 | message: transactional: 1 prefix: test, sz: 14 content:tx logical msg | message: transactional: 1 prefix: test, sz: 14 content:tx logical msg + 1 | table public.tr_oddlength: INSERT: id[text]:'ab' data[text]:'foo' | table public.tr_oddlength: INSERT: id[text]:'ab' data[text]:'foo' + 20467 | table public.tr_etoomuch: DELETE: id[integer]:1 | table public.tr_etoomuch: UPDATE: id[integer]:9999 data[integer]:-9999 +(5 rows) + +-- check updates of primary keys work correctly +BEGIN; +CREATE TABLE spoolme AS SELECT g.i FROM generate_series(1, 5000) g(i); +UPDATE tr_etoomuch SET id = -id WHERE id = 5000; +UPDATE tr_oddlength SET id = 'x', data = 'quux'; +UPDATE tr_oddlength SET id = 'yy', data = 'a'; +DELETE FROM spoolme; +DROP TABLE spoolme; +COMMIT; +SELECT data +FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') +WHERE data ~ 'UPDATE'; + data +------------------------------------------------------------------------------------------------------------- + table public.tr_etoomuch: UPDATE: old-key: id[integer]:5000 new-tuple: id[integer]:-5000 data[integer]:5000 + table public.tr_oddlength: UPDATE: old-key: id[text]:'ab' new-tuple: id[text]:'x' data[text]:'quux' + table public.tr_oddlength: UPDATE: old-key: id[text]:'x' new-tuple: id[text]:'yy' data[text]:'a' +(3 rows) + +-- check that a large, spooled, upsert works +INSERT INTO tr_etoomuch (id, data) +SELECT g.i, -g.i FROM generate_series(8000, 12000) g(i) +ON CONFLICT(id) DO UPDATE SET data = EXCLUDED.data; +SELECT substring(data, 1, 29), count(*) +FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') WITH ORDINALITY +GROUP BY 1 +ORDER BY min(ordinality); + substring | count +-------------------------------+------- + BEGIN | 1 + table public.tr_etoomuch: UPD | 2235 + table public.tr_etoomuch: INS | 1766 + COMMIT | 1 +(4 rows) + +/* + * check whether we decode subtransactions correctly in relation with each + * other + */ +CREATE TABLE tr_sub (id serial primary key, path text); +-- toplevel, subtxn, toplevel, subtxn, subtxn +BEGIN; +INSERT INTO tr_sub(path) VALUES ('1-top-#1'); +SAVEPOINT a; +INSERT INTO tr_sub(path) VALUES ('1-top-1-#1'); +INSERT INTO tr_sub(path) VALUES ('1-top-1-#2'); +RELEASE SAVEPOINT a; +SAVEPOINT b; +SAVEPOINT c; +INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#1'); +INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#2'); +RELEASE SAVEPOINT c; +INSERT INTO tr_sub(path) VALUES ('1-top-2-#1'); +RELEASE SAVEPOINT b; +COMMIT; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +---------------------------------------------------------------------- + BEGIN + table public.tr_sub: INSERT: id[integer]:1 path[text]:'1-top-#1' + table public.tr_sub: INSERT: id[integer]:2 path[text]:'1-top-1-#1' + table public.tr_sub: INSERT: id[integer]:3 path[text]:'1-top-1-#2' + table public.tr_sub: INSERT: id[integer]:4 path[text]:'1-top-2-1-#1' + table public.tr_sub: INSERT: id[integer]:5 path[text]:'1-top-2-1-#2' + table public.tr_sub: INSERT: id[integer]:6 path[text]:'1-top-2-#1' + COMMIT +(8 rows) + +-- check that we handle xlog assignments correctly +BEGIN; +-- nest 80 subtxns +SAVEPOINT subtop;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +-- assign xid by inserting +INSERT INTO tr_sub(path) VALUES ('2-top-1...--#1'); +INSERT INTO tr_sub(path) VALUES ('2-top-1...--#2'); +INSERT INTO tr_sub(path) VALUES ('2-top-1...--#3'); +RELEASE SAVEPOINT subtop; +INSERT INTO tr_sub(path) VALUES ('2-top-#1'); +COMMIT; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------------------------------------------ + BEGIN + table public.tr_sub: INSERT: id[integer]:7 path[text]:'2-top-1...--#1' + table public.tr_sub: INSERT: id[integer]:8 path[text]:'2-top-1...--#2' + table public.tr_sub: INSERT: id[integer]:9 path[text]:'2-top-1...--#3' + table public.tr_sub: INSERT: id[integer]:10 path[text]:'2-top-#1' + COMMIT +(6 rows) + +-- make sure rollbacked subtransactions aren't decoded +BEGIN; +INSERT INTO tr_sub(path) VALUES ('3-top-2-#1'); +SAVEPOINT a; +INSERT INTO tr_sub(path) VALUES ('3-top-2-1-#1'); +SAVEPOINT b; +INSERT INTO tr_sub(path) VALUES ('3-top-2-2-#1'); +ROLLBACK TO SAVEPOINT b; +INSERT INTO tr_sub(path) VALUES ('3-top-2-#2'); +COMMIT; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +----------------------------------------------------------------------- + BEGIN + table public.tr_sub: INSERT: id[integer]:11 path[text]:'3-top-2-#1' + table public.tr_sub: INSERT: id[integer]:12 path[text]:'3-top-2-1-#1' + table public.tr_sub: INSERT: id[integer]:14 path[text]:'3-top-2-#2' + COMMIT +(5 rows) + +-- test whether a known, but not yet logged toplevel xact, followed by a +-- subxact commit is handled correctly +BEGIN; +SELECT pg_current_xact_id() != '0'; -- so no fixed xid appears in the outfile + ?column? +---------- + t +(1 row) + +SAVEPOINT a; +INSERT INTO tr_sub(path) VALUES ('4-top-1-#1'); +RELEASE SAVEPOINT a; +COMMIT; +-- test whether a change in a subtransaction, in an unknown toplevel +-- xact is handled correctly. +BEGIN; +SAVEPOINT a; +INSERT INTO tr_sub(path) VALUES ('5-top-1-#1'); +COMMIT; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +--------------------------------------------------------------------- + BEGIN + table public.tr_sub: INSERT: id[integer]:15 path[text]:'4-top-1-#1' + COMMIT + BEGIN + table public.tr_sub: INSERT: id[integer]:16 path[text]:'5-top-1-#1' + COMMIT +(6 rows) + +-- check that DDL in aborted subtransactions handled correctly +CREATE TABLE tr_sub_ddl(data int); +BEGIN; +SAVEPOINT a; +ALTER TABLE tr_sub_ddl ALTER COLUMN data TYPE text; +INSERT INTO tr_sub_ddl VALUES ('blah-blah'); +ROLLBACK TO SAVEPOINT a; +ALTER TABLE tr_sub_ddl ALTER COLUMN data TYPE bigint; +INSERT INTO tr_sub_ddl VALUES(43); +COMMIT; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +-------------------------------------------------- + BEGIN + table public.tr_sub_ddl: INSERT: data[bigint]:43 + COMMIT +(3 rows) + +/* + * Check whether treating a table as a catalog table works somewhat + */ +CREATE TABLE replication_metadata ( + id serial primary key, + relation name NOT NULL, + options text[] +) +WITH (user_catalog_table = true) +; +\d+ replication_metadata + Table "public.replication_metadata" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +----------+---------+-----------+----------+--------------------------------------------------+----------+--------------+------------- + id | integer | | not null | nextval('replication_metadata_id_seq'::regclass) | plain | | + relation | name | | not null | | plain | | + options | text[] | | | | extended | | +Indexes: + "replication_metadata_pkey" PRIMARY KEY, btree (id) +Options: user_catalog_table=true + +INSERT INTO replication_metadata(relation, options) +VALUES ('foo', ARRAY['a', 'b']); +ALTER TABLE replication_metadata RESET (user_catalog_table); +\d+ replication_metadata + Table "public.replication_metadata" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +----------+---------+-----------+----------+--------------------------------------------------+----------+--------------+------------- + id | integer | | not null | nextval('replication_metadata_id_seq'::regclass) | plain | | + relation | name | | not null | | plain | | + options | text[] | | | | extended | | +Indexes: + "replication_metadata_pkey" PRIMARY KEY, btree (id) + +INSERT INTO replication_metadata(relation, options) +VALUES ('bar', ARRAY['a', 'b']); +ALTER TABLE replication_metadata SET (user_catalog_table = true); +\d+ replication_metadata + Table "public.replication_metadata" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +----------+---------+-----------+----------+--------------------------------------------------+----------+--------------+------------- + id | integer | | not null | nextval('replication_metadata_id_seq'::regclass) | plain | | + relation | name | | not null | | plain | | + options | text[] | | | | extended | | +Indexes: + "replication_metadata_pkey" PRIMARY KEY, btree (id) +Options: user_catalog_table=true + +INSERT INTO replication_metadata(relation, options) +VALUES ('blub', NULL); +-- make sure rewrites don't work +ALTER TABLE replication_metadata ADD COLUMN rewritemeornot int; +ALTER TABLE replication_metadata ALTER COLUMN rewritemeornot TYPE text; +ERROR: cannot rewrite table "replication_metadata" used as a catalog table +ALTER TABLE replication_metadata SET (user_catalog_table = false); +\d+ replication_metadata + Table "public.replication_metadata" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +----------------+---------+-----------+----------+--------------------------------------------------+----------+--------------+------------- + id | integer | | not null | nextval('replication_metadata_id_seq'::regclass) | plain | | + relation | name | | not null | | plain | | + options | text[] | | | | extended | | + rewritemeornot | integer | | | | plain | | +Indexes: + "replication_metadata_pkey" PRIMARY KEY, btree (id) +Options: user_catalog_table=false + +INSERT INTO replication_metadata(relation, options) +VALUES ('zaphod', NULL); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------------------------------------------------------------------------------------------------------ + BEGIN + table public.replication_metadata: INSERT: id[integer]:1 relation[name]:'foo' options[text[]]:'{a,b}' + COMMIT + BEGIN + table public.replication_metadata: INSERT: id[integer]:2 relation[name]:'bar' options[text[]]:'{a,b}' + COMMIT + BEGIN + table public.replication_metadata: INSERT: id[integer]:3 relation[name]:'blub' options[text[]]:null + COMMIT + BEGIN + table public.replication_metadata: INSERT: id[integer]:4 relation[name]:'zaphod' options[text[]]:null rewritemeornot[integer]:null + COMMIT +(12 rows) + +/* + * check whether we handle updates/deletes correct with & without a pkey + */ +/* we should handle the case without a key at all more gracefully */ +CREATE TABLE table_without_key(id serial, data int); +INSERT INTO table_without_key(data) VALUES(1),(2); +DELETE FROM table_without_key WHERE data = 1; +-- won't log old keys +UPDATE table_without_key SET data = 3 WHERE data = 2; +UPDATE table_without_key SET id = -id; +UPDATE table_without_key SET id = -id; +-- should log the full old row now +ALTER TABLE table_without_key REPLICA IDENTITY FULL; +UPDATE table_without_key SET data = 3 WHERE data = 2; +UPDATE table_without_key SET id = -id; +UPDATE table_without_key SET id = -id; +-- ensure that FULL correctly deals with new columns +ALTER TABLE table_without_key ADD COLUMN new_column text; +UPDATE table_without_key SET id = -id; +UPDATE table_without_key SET id = -id, new_column = 'someval'; +DELETE FROM table_without_key WHERE data = 3; +CREATE TABLE table_with_pkey(id serial primary key, data int); +INSERT INTO table_with_pkey(data) VALUES(1), (2); +DELETE FROM table_with_pkey WHERE data = 1; +-- should log the old pkey +UPDATE table_with_pkey SET data = 3 WHERE data = 2; +UPDATE table_with_pkey SET id = -id; +UPDATE table_with_pkey SET id = -id; +-- check that we log nothing despite having a pkey +ALTER TABLE table_without_key REPLICA IDENTITY NOTHING; +UPDATE table_with_pkey SET id = -id; +-- check that we log everything despite having a pkey +ALTER TABLE table_without_key REPLICA IDENTITY FULL; +UPDATE table_with_pkey SET id = -id; +DELETE FROM table_with_pkey WHERE data = 3; +CREATE TABLE table_with_unique_not_null(id serial unique, data int); +ALTER TABLE table_with_unique_not_null ALTER COLUMN id SET NOT NULL; --already set +-- won't log anything, replica identity not setup +INSERT INTO table_with_unique_not_null(data) VALUES(1), (2); +DELETE FROM table_with_unique_not_null WHERE data = 1; +UPDATE table_with_unique_not_null SET data = 3 WHERE data = 2; +UPDATE table_with_unique_not_null SET id = -id; +UPDATE table_with_unique_not_null SET id = -id; +DELETE FROM table_with_unique_not_null WHERE data = 3; +-- should log old key +ALTER TABLE table_with_unique_not_null REPLICA IDENTITY USING INDEX table_with_unique_not_null_id_key; +INSERT INTO table_with_unique_not_null(data) VALUES(1), (2); +DELETE FROM table_with_unique_not_null WHERE data = 1; +UPDATE table_with_unique_not_null SET data = 3 WHERE data = 2; +UPDATE table_with_unique_not_null SET id = -id; +UPDATE table_with_unique_not_null SET id = -id; +DELETE FROM table_with_unique_not_null WHERE data = 3; +-- check tables with dropped indexes used in REPLICA IDENTITY +-- table with primary key +CREATE TABLE table_dropped_index_with_pk (a int PRIMARY KEY, b int, c int); +CREATE UNIQUE INDEX table_dropped_index_with_pk_idx + ON table_dropped_index_with_pk(a); +ALTER TABLE table_dropped_index_with_pk REPLICA IDENTITY + USING INDEX table_dropped_index_with_pk_idx; +DROP INDEX table_dropped_index_with_pk_idx; +INSERT INTO table_dropped_index_with_pk VALUES (1,1,1), (2,2,2), (3,3,3); +UPDATE table_dropped_index_with_pk SET a = 4 WHERE a = 1; +UPDATE table_dropped_index_with_pk SET b = 5 WHERE a = 2; +UPDATE table_dropped_index_with_pk SET b = 6, c = 7 WHERE a = 3; +DELETE FROM table_dropped_index_with_pk WHERE b = 1; +DELETE FROM table_dropped_index_with_pk WHERE a = 3; +DROP TABLE table_dropped_index_with_pk; +-- table without primary key +CREATE TABLE table_dropped_index_no_pk (a int NOT NULL, b int, c int); +CREATE UNIQUE INDEX table_dropped_index_no_pk_idx + ON table_dropped_index_no_pk(a); +ALTER TABLE table_dropped_index_no_pk REPLICA IDENTITY + USING INDEX table_dropped_index_no_pk_idx; +DROP INDEX table_dropped_index_no_pk_idx; +INSERT INTO table_dropped_index_no_pk VALUES (1,1,1), (2,2,2), (3,3,3); +UPDATE table_dropped_index_no_pk SET a = 4 WHERE a = 1; +UPDATE table_dropped_index_no_pk SET b = 5 WHERE a = 2; +UPDATE table_dropped_index_no_pk SET b = 6, c = 7 WHERE a = 3; +DELETE FROM table_dropped_index_no_pk WHERE b = 1; +DELETE FROM table_dropped_index_no_pk WHERE a = 3; +DROP TABLE table_dropped_index_no_pk; +-- check toast support +BEGIN; +CREATE SEQUENCE toasttable_rand_seq START 79 INCREMENT 1499; -- portable "random" +CREATE TABLE toasttable( + id serial primary key, + toasted_col1 text, + rand1 float8 DEFAULT nextval('toasttable_rand_seq'), + toasted_col2 text, + rand2 float8 DEFAULT nextval('toasttable_rand_seq') + ); +COMMIT; +-- uncompressed external toast data +INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i); +-- compressed external toast data +INSERT INTO toasttable(toasted_col2) SELECT repeat(string_agg(to_char(g.i, 'FM0000'), ''), 50) FROM generate_series(1, 500) g(i); +-- update of existing column +UPDATE toasttable + SET toasted_col1 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i)) +WHERE id = 1; +-- This output is extremely wide, and using aligned mode causes psql to +-- produce 200kB of useless dashes. Turn that off temporarily to avoid it. +\pset format unaligned +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +BEGIN +table public.table_without_key: INSERT: id[integer]:1 data[integer]:1 +table public.table_without_key: INSERT: id[integer]:2 data[integer]:2 +COMMIT +BEGIN +table public.table_without_key: DELETE: (no-tuple-data) +COMMIT +BEGIN +table public.table_without_key: UPDATE: id[integer]:2 data[integer]:3 +COMMIT +BEGIN +table public.table_without_key: UPDATE: id[integer]:-2 data[integer]:3 +COMMIT +BEGIN +table public.table_without_key: UPDATE: id[integer]:2 data[integer]:3 +COMMIT +BEGIN +table public.table_without_key: UPDATE: old-key: id[integer]:2 data[integer]:3 new-tuple: id[integer]:-2 data[integer]:3 +COMMIT +BEGIN +table public.table_without_key: UPDATE: old-key: id[integer]:-2 data[integer]:3 new-tuple: id[integer]:2 data[integer]:3 +COMMIT +BEGIN +table public.table_without_key: UPDATE: old-key: id[integer]:2 data[integer]:3 new-tuple: id[integer]:-2 data[integer]:3 new_column[text]:null +COMMIT +BEGIN +table public.table_without_key: UPDATE: old-key: id[integer]:-2 data[integer]:3 new-tuple: id[integer]:2 data[integer]:3 new_column[text]:'someval' +COMMIT +BEGIN +table public.table_without_key: DELETE: id[integer]:2 data[integer]:3 new_column[text]:'someval' +COMMIT +BEGIN +table public.table_with_pkey: INSERT: id[integer]:1 data[integer]:1 +table public.table_with_pkey: INSERT: id[integer]:2 data[integer]:2 +COMMIT +BEGIN +table public.table_with_pkey: DELETE: id[integer]:1 +COMMIT +BEGIN +table public.table_with_pkey: UPDATE: id[integer]:2 data[integer]:3 +COMMIT +BEGIN +table public.table_with_pkey: UPDATE: old-key: id[integer]:2 new-tuple: id[integer]:-2 data[integer]:3 +COMMIT +BEGIN +table public.table_with_pkey: UPDATE: old-key: id[integer]:-2 new-tuple: id[integer]:2 data[integer]:3 +COMMIT +BEGIN +table public.table_with_pkey: UPDATE: old-key: id[integer]:2 new-tuple: id[integer]:-2 data[integer]:3 +COMMIT +BEGIN +table public.table_with_pkey: UPDATE: old-key: id[integer]:-2 new-tuple: id[integer]:2 data[integer]:3 +COMMIT +BEGIN +table public.table_with_pkey: DELETE: id[integer]:2 +COMMIT +BEGIN +table public.table_with_unique_not_null: INSERT: id[integer]:1 data[integer]:1 +table public.table_with_unique_not_null: INSERT: id[integer]:2 data[integer]:2 +COMMIT +BEGIN +table public.table_with_unique_not_null: DELETE: (no-tuple-data) +COMMIT +BEGIN +table public.table_with_unique_not_null: UPDATE: id[integer]:2 data[integer]:3 +COMMIT +BEGIN +table public.table_with_unique_not_null: UPDATE: id[integer]:-2 data[integer]:3 +COMMIT +BEGIN +table public.table_with_unique_not_null: UPDATE: id[integer]:2 data[integer]:3 +COMMIT +BEGIN +table public.table_with_unique_not_null: DELETE: (no-tuple-data) +COMMIT +BEGIN +table public.table_with_unique_not_null: INSERT: id[integer]:3 data[integer]:1 +table public.table_with_unique_not_null: INSERT: id[integer]:4 data[integer]:2 +COMMIT +BEGIN +table public.table_with_unique_not_null: DELETE: id[integer]:3 +COMMIT +BEGIN +table public.table_with_unique_not_null: UPDATE: id[integer]:4 data[integer]:3 +COMMIT +BEGIN +table public.table_with_unique_not_null: UPDATE: old-key: id[integer]:4 new-tuple: id[integer]:-4 data[integer]:3 +COMMIT +BEGIN +table public.table_with_unique_not_null: UPDATE: old-key: id[integer]:-4 new-tuple: id[integer]:4 data[integer]:3 +COMMIT +BEGIN +table public.table_with_unique_not_null: DELETE: id[integer]:4 +COMMIT +BEGIN +table public.table_dropped_index_with_pk: INSERT: a[integer]:1 b[integer]:1 c[integer]:1 +table public.table_dropped_index_with_pk: INSERT: a[integer]:2 b[integer]:2 c[integer]:2 +table public.table_dropped_index_with_pk: INSERT: a[integer]:3 b[integer]:3 c[integer]:3 +COMMIT +BEGIN +table public.table_dropped_index_with_pk: UPDATE: a[integer]:4 b[integer]:1 c[integer]:1 +COMMIT +BEGIN +table public.table_dropped_index_with_pk: UPDATE: a[integer]:2 b[integer]:5 c[integer]:2 +COMMIT +BEGIN +table public.table_dropped_index_with_pk: UPDATE: a[integer]:3 b[integer]:6 c[integer]:7 +COMMIT +BEGIN +table public.table_dropped_index_with_pk: DELETE: (no-tuple-data) +COMMIT +BEGIN +table public.table_dropped_index_with_pk: DELETE: (no-tuple-data) +COMMIT +BEGIN +table public.table_dropped_index_no_pk: INSERT: a[integer]:1 b[integer]:1 c[integer]:1 +table public.table_dropped_index_no_pk: INSERT: a[integer]:2 b[integer]:2 c[integer]:2 +table public.table_dropped_index_no_pk: INSERT: a[integer]:3 b[integer]:3 c[integer]:3 +COMMIT +BEGIN +table public.table_dropped_index_no_pk: UPDATE: a[integer]:4 b[integer]:1 c[integer]:1 +COMMIT +BEGIN +table public.table_dropped_index_no_pk: UPDATE: a[integer]:2 b[integer]:5 c[integer]:2 +COMMIT +BEGIN +table public.table_dropped_index_no_pk: UPDATE: a[integer]:3 b[integer]:6 c[integer]:7 +COMMIT +BEGIN +table public.table_dropped_index_no_pk: DELETE: (no-tuple-data) +COMMIT +BEGIN +table public.table_dropped_index_no_pk: DELETE: (no-tuple-data) +COMMIT +BEGIN +table public.toasttable: INSERT: id[integer]:1 toasted_col1[text]:'12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000' rand1[double precision]:79 toasted_col2[text]:null rand2[double precision]:1578 +COMMIT +BEGIN +table public.toasttable: INSERT: id[integer]:2 toasted_col1[text]:null rand1[double precision]:3077 toasted_col2[text]:'0001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500000100020003000400050006000700080009001000110012001300140015001600170018001900200021002200230024002500260027002800290030003100320033003400350036003700380039004000410042004300440045004600470048004900500051005200530054005500560057005800590060006100620063006400650066006700680069007000710072007300740075007600770078007900800081008200830084008500860087008800890090009100920093009400950096009700980099010001010102010301040105010601070108010901100111011201130114011501160117011801190120012101220123012401250126012701280129013001310132013301340135013601370138013901400141014201430144014501460147014801490150015101520153015401550156015701580159016001610162016301640165016601670168016901700171017201730174017501760177017801790180018101820183018401850186018701880189019001910192019301940195019601970198019902000201020202030204020502060207020802090210021102120213021402150216021702180219022002210222022302240225022602270228022902300231023202330234023502360237023802390240024102420243024402450246024702480249025002510252025302540255025602570258025902600261026202630264026502660267026802690270027102720273027402750276027702780279028002810282028302840285028602870288028902900291029202930294029502960297029802990300030103020303030403050306030703080309031003110312031303140315031603170318031903200321032203230324032503260327032803290330033103320333033403350336033703380339034003410342034303440345034603470348034903500351035203530354035503560357035803590360036103620363036403650366036703680369037003710372037303740375037603770378037903800381038203830384038503860387038803890390039103920393039403950396039703980399040004010402040304040405040604070408040904100411041204130414041504160417041804190420042104220423042404250426042704280429043004310432043304340435043604370438043904400441044204430444044504460447044804490450045104520453045404550456045704580459046004610462046304640465046604670468046904700471047204730474047504760477047804790480048104820483048404850486048704880489049004910492049304940495049604970498049905000001000200030004000500060007000800090010001100120013001400150016001700180019002000210022002300240025002600270028002900300031003200330034003500360037003800390040004100420043004400450046004700480049005000510052005300540055005600570058005900600061006200630064006500660067006800690070007100720073007400750076007700780079008000810082008300840085008600870088008900900091009200930094009500960097009800990100010101020103010401050106010701080109011001110112011301140115011601170118011901200121012201230124012501260127012801290130013101320133013401350136013701380139014001410142014301440145014601470148014901500151015201530154015501560157015801590160016101620163016401650166016701680169017001710172017301740175017601770178017901800181018201830184018501860187018801890190019101920193019401950196019701980199020002010202020302040205020602070208020902100211021202130214021502160217021802190220022102220223022402250226022702280229023002310232023302340235023602370238023902400241024202430244024502460247024802490250025102520253025402550256025702580259026002610262026302640265026602670268026902700271027202730274027502760277027802790280028102820283028402850286028702880289029002910292029302940295029602970298029903000301030203030304030503060307030803090310031103120313031403150316031703180319032003210322032303240325032603270328032903300331033203330334033503360337033803390340034103420343034403450346034703480349035003510352035303540355035603570358035903600361036203630364036503660367036803690370037103720373037403750376037703780379038003810382038303840385038603870388038903900391039203930394039503960397039803990400040104020403040404050406040704080409041004110412041304140415041604170418041904200421042204230424042504260427042804290430043104320433043404350436043704380439044004410442044304440445044604470448044904500451045204530454045504560457045804590460046104620463046404650466046704680469047004710472047304740475047604770478047904800481048204830484048504860487048804890490049104920493049404950496049704980499050000010002000300040005000600070008000900100011001200130014001500160017001800190020002100220023002400250026002700280029003000310032003300340035003600370038003900400041004200430044004500460047004800490050005100520053005400550056005700580059006000610062006300640065006600670068006900700071007200730074007500760077007800790080008100820083008400850086008700880089009000910092009300940095009600970098009901000101010201030104010501060107010801090110011101120113011401150116011701180119012001210122012301240125012601270128012901300131013201330134013501360137013801390140014101420143014401450146014701480149015001510152015301540155015601570158015901600161016201630164016501660167016801690170017101720173017401750176017701780179018001810182018301840185018601870188018901900191019201930194019501960197019801990200020102020203020402050206020702080209021002110212021302140215021602170218021902200221022202230224022502260227022802290230023102320233023402350236023702380239024002410242024302440245024602470248024902500251025202530254025502560257025802590260026102620263026402650266026702680269027002710272027302740275027602770278027902800281028202830284028502860287028802890290029102920293029402950296029702980299030003010302030303040305030603070308030903100311031203130314031503160317031803190320032103220323032403250326032703280329033003310332033303340335033603370338033903400341034203430344034503460347034803490350035103520353035403550356035703580359036003610362036303640365036603670368036903700371037203730374037503760377037803790380038103820383038403850386038703880389039003910392039303940395039603970398039904000401040204030404040504060407040804090410041104120413041404150416041704180419042004210422042304240425042604270428042904300431043204330434043504360437043804390440044104420443044404450446044704480449045004510452045304540455045604570458045904600461046204630464046504660467046804690470047104720473047404750476047704780479048004810482048304840485048604870488048904900491049204930494049504960497049804990500' rand2[double precision]:4576 +COMMIT +BEGIN +table public.toasttable: UPDATE: id[integer]:1 toasted_col1[text]:'12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000' rand1[double precision]:79 toasted_col2[text]:null rand2[double precision]:1578 +COMMIT +(143 rows) +\pset format aligned +INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i); +-- update of second column, first column unchanged +UPDATE toasttable + SET toasted_col2 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i)) +WHERE id = 1; +-- make sure we decode correctly even if the toast table is gone +DROP TABLE toasttable; +\pset format unaligned +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +BEGIN +table public.toasttable: INSERT: id[integer]:3 toasted_col1[text]:'12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000' rand1[double precision]:6075 toasted_col2[text]:null rand2[double precision]:7574 +COMMIT +BEGIN +table public.toasttable: UPDATE: id[integer]:1 toasted_col1[text]:unchanged-toast-datum rand1[double precision]:79 toasted_col2[text]:'12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000' rand2[double precision]:1578 +COMMIT +(6 rows) +-- done, free logical replication slot +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +(0 rows) +\pset format aligned +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +/* check that the slot is gone */ +\x +SELECT * FROM pg_replication_slots; +(0 rows) + +\x diff --git a/contrib/test_decoding/expected/decoding_in_xact.out b/contrib/test_decoding/expected/decoding_in_xact.out new file mode 100644 index 0000000..b65253f --- /dev/null +++ b/contrib/test_decoding/expected/decoding_in_xact.out @@ -0,0 +1,87 @@ +-- predictability +SET synchronous_commit = on; +-- fail because we're creating a slot while in an xact with xid +BEGIN; +SELECT pg_current_xact_id() = '0'; + ?column? +---------- + f +(1 row) + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +ERROR: cannot create logical replication slot in transaction that has performed writes +ROLLBACK; +-- fail because we're creating a slot while in a subxact whose topxact has an xid +BEGIN; +SELECT pg_current_xact_id() = '0'; + ?column? +---------- + f +(1 row) + +SAVEPOINT barf; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +ERROR: cannot create logical replication slot in transaction that has performed writes +ROLLBACK TO SAVEPOINT barf; +ROLLBACK; +-- succeed, outside tx. +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +SELECT 'stop' FROM pg_drop_replication_slot('regression_slot'); + ?column? +---------- + stop +(1 row) + +-- succeed, in tx without xid. +BEGIN; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +COMMIT; +CREATE TABLE nobarf(id serial primary key, data text); +INSERT INTO nobarf(data) VALUES('1'); +-- decoding works in transaction with xid +BEGIN; +SELECT pg_current_xact_id() = '0'; + ?column? +---------- + f +(1 row) + +-- don't show yet, haven't committed +INSERT INTO nobarf(data) VALUES('2'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +----------------------------------------------------------- + BEGIN + table public.nobarf: INSERT: id[integer]:1 data[text]:'1' + COMMIT +(3 rows) + +COMMIT; +INSERT INTO nobarf(data) VALUES('3'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +----------------------------------------------------------- + BEGIN + table public.nobarf: INSERT: id[integer]:2 data[text]:'2' + COMMIT + BEGIN + table public.nobarf: INSERT: id[integer]:3 data[text]:'3' + COMMIT +(6 rows) + +SELECT 'stop' FROM pg_drop_replication_slot('regression_slot'); + ?column? +---------- + stop +(1 row) + diff --git a/contrib/test_decoding/expected/decoding_into_rel.out b/contrib/test_decoding/expected/decoding_into_rel.out new file mode 100644 index 0000000..8fd3390 --- /dev/null +++ b/contrib/test_decoding/expected/decoding_into_rel.out @@ -0,0 +1,111 @@ +-- test that we can insert the result of a get_changes call into a +-- logged relation. That's really not a good idea in practical terms, +-- but provides a nice test. +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +-- slot works +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +-- create some changes +CREATE TABLE somechange(id serial primary key); +INSERT INTO somechange DEFAULT VALUES; +CREATE TABLE changeresult AS + SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +SELECT * FROM changeresult; + data +------------------------------------------------ + BEGIN + table public.somechange: INSERT: id[integer]:1 + COMMIT +(3 rows) + +INSERT INTO changeresult + SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +INSERT INTO changeresult + SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +SELECT * FROM changeresult; + data +-------------------------------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.somechange: INSERT: id[integer]:1 + COMMIT + BEGIN + table public.changeresult: INSERT: data[text]:'BEGIN' + table public.changeresult: INSERT: data[text]:'table public.somechange: INSERT: id[integer]:1' + table public.changeresult: INSERT: data[text]:'COMMIT' + COMMIT + BEGIN + table public.changeresult: INSERT: data[text]:'BEGIN' + table public.changeresult: INSERT: data[text]:'table public.somechange: INSERT: id[integer]:1' + table public.changeresult: INSERT: data[text]:'COMMIT' + COMMIT + BEGIN + table public.changeresult: INSERT: data[text]:'BEGIN' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''BEGIN''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.somechange: INSERT: id[integer]:1''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''COMMIT''' + table public.changeresult: INSERT: data[text]:'COMMIT' + COMMIT +(20 rows) + +DROP TABLE changeresult; +DROP TABLE somechange; +-- check calling logical decoding from pl/pgsql +CREATE FUNCTION slot_changes_wrapper(slot_name name) RETURNS SETOF TEXT AS $$ +BEGIN + RETURN QUERY + SELECT data FROM pg_logical_slot_peek_changes(slot_name, NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +END$$ LANGUAGE plpgsql; +SELECT * FROM slot_changes_wrapper('regression_slot'); + slot_changes_wrapper +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.changeresult: INSERT: data[text]:'BEGIN' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''BEGIN''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.somechange: INSERT: id[integer]:1''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''COMMIT''' + table public.changeresult: INSERT: data[text]:'COMMIT' + table public.changeresult: INSERT: data[text]:'BEGIN' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''BEGIN''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.changeresult: INSERT: data[text]:''''BEGIN''''''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.changeresult: INSERT: data[text]:''''table public.somechange: INSERT: id[integer]:1''''''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.changeresult: INSERT: data[text]:''''COMMIT''''''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''COMMIT''' + table public.changeresult: INSERT: data[text]:'COMMIT' + COMMIT +(14 rows) + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.changeresult: INSERT: data[text]:'BEGIN' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''BEGIN''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.somechange: INSERT: id[integer]:1''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''COMMIT''' + table public.changeresult: INSERT: data[text]:'COMMIT' + table public.changeresult: INSERT: data[text]:'BEGIN' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''BEGIN''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.changeresult: INSERT: data[text]:''''BEGIN''''''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.changeresult: INSERT: data[text]:''''table public.somechange: INSERT: id[integer]:1''''''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''table public.changeresult: INSERT: data[text]:''''COMMIT''''''' + table public.changeresult: INSERT: data[text]:'table public.changeresult: INSERT: data[text]:''COMMIT''' + table public.changeresult: INSERT: data[text]:'COMMIT' + COMMIT +(14 rows) + +SELECT 'stop' FROM pg_drop_replication_slot('regression_slot'); + ?column? +---------- + stop +(1 row) + diff --git a/contrib/test_decoding/expected/delayed_startup.out b/contrib/test_decoding/expected/delayed_startup.out new file mode 100644 index 0000000..d10de36 --- /dev/null +++ b/contrib/test_decoding/expected/delayed_startup.out @@ -0,0 +1,50 @@ +Parsed test spec with 2 sessions + +starting permutation: s1b s1w s2init s1c s2start s1b s1w s1c s2start s1b s1w s2start s1c s2start +step s1b: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s1w: INSERT INTO do_write DEFAULT VALUES; +step s2init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +step s1c: COMMIT; +step s2init: <... completed> +?column? +-------- +init +(1 row) + +step s2start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false'); +data +---- +(0 rows) + +step s1b: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s1w: INSERT INTO do_write DEFAULT VALUES; +step s1c: COMMIT; +step s2start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false'); +data +-------------------------------------------- +BEGIN +table public.do_write: INSERT: id[integer]:2 +COMMIT +(3 rows) + +step s1b: BEGIN ISOLATION LEVEL SERIALIZABLE; +step s1w: INSERT INTO do_write DEFAULT VALUES; +step s2start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false'); +data +---- +(0 rows) + +step s1c: COMMIT; +step s2start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false'); +data +-------------------------------------------- +BEGIN +table public.do_write: INSERT: id[integer]:3 +COMMIT +(3 rows) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/expected/messages.out b/contrib/test_decoding/expected/messages.out new file mode 100644 index 0000000..c75d401 --- /dev/null +++ b/contrib/test_decoding/expected/messages.out @@ -0,0 +1,99 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +SELECT 'msg1' FROM pg_logical_emit_message(true, 'test', 'msg1'); + ?column? +---------- + msg1 +(1 row) + +SELECT 'msg2' FROM pg_logical_emit_message(false, 'test', 'msg2'); + ?column? +---------- + msg2 +(1 row) + +BEGIN; +SELECT 'msg3' FROM pg_logical_emit_message(true, 'test', 'msg3'); + ?column? +---------- + msg3 +(1 row) + +SELECT 'msg4' FROM pg_logical_emit_message(false, 'test', 'msg4'); + ?column? +---------- + msg4 +(1 row) + +ROLLBACK; +BEGIN; +SELECT 'msg5' FROM pg_logical_emit_message(true, 'test', 'msg5'); + ?column? +---------- + msg5 +(1 row) + +SELECT 'msg6' FROM pg_logical_emit_message(false, 'test', 'msg6'); + ?column? +---------- + msg6 +(1 row) + +SELECT 'msg7' FROM pg_logical_emit_message(true, 'test', 'msg7'); + ?column? +---------- + msg7 +(1 row) + +COMMIT; +SELECT 'ignorethis' FROM pg_logical_emit_message(true, 'test', 'czechtastic'); + ?column? +------------ + ignorethis +(1 row) + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force-binary', '0', 'skip-empty-xacts', '1'); + data +-------------------------------------------------------------------- + message: transactional: 1 prefix: test, sz: 4 content:msg1 + message: transactional: 0 prefix: test, sz: 4 content:msg2 + message: transactional: 0 prefix: test, sz: 4 content:msg4 + message: transactional: 0 prefix: test, sz: 4 content:msg6 + message: transactional: 1 prefix: test, sz: 4 content:msg5 + message: transactional: 1 prefix: test, sz: 4 content:msg7 + message: transactional: 1 prefix: test, sz: 11 content:czechtastic +(7 rows) + +-- test db filtering +\set prevdb :DBNAME +\c template1 +SELECT 'otherdb1' FROM pg_logical_emit_message(false, 'test', 'otherdb1'); + ?column? +---------- + otherdb1 +(1 row) + +SELECT 'otherdb2' FROM pg_logical_emit_message(true, 'test', 'otherdb2'); + ?column? +---------- + otherdb2 +(1 row) + +\c :prevdb +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force-binary', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +SELECT 'cleanup' FROM pg_drop_replication_slot('regression_slot'); + ?column? +---------- + cleanup +(1 row) + diff --git a/contrib/test_decoding/expected/mxact.out b/contrib/test_decoding/expected/mxact.out new file mode 100644 index 0000000..03ad3df --- /dev/null +++ b/contrib/test_decoding/expected/mxact.out @@ -0,0 +1,90 @@ +Parsed test spec with 3 sessions + +starting permutation: s0init s0start s1begin s1sharepgclass s2begin s2sharepgclass s0w s0start s2commit s1commit +step s0init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s0start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false'); +data +---- +(0 rows) + +step s1begin: BEGIN; +step s1sharepgclass: SELECT count(*) > 1 FROM (SELECT * FROM pg_class FOR SHARE) s; +?column? +-------- +t +(1 row) + +step s2begin: BEGIN; +step s2sharepgclass: SELECT count(*) > 1 FROM (SELECT * FROM pg_class FOR SHARE) s; +?column? +-------- +t +(1 row) + +step s0w: INSERT INTO do_write DEFAULT VALUES; +step s0start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false'); +data +-------------------------------------------- +BEGIN +table public.do_write: INSERT: id[integer]:1 +COMMIT +(3 rows) + +step s2commit: COMMIT; +step s1commit: COMMIT; +?column? +-------- +stop +(1 row) + + +starting permutation: s0init s0start s1begin s1keysharepgclass s2begin s2keysharepgclass s0alter s0w s0start s2commit s1commit +step s0init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +?column? +-------- +init +(1 row) + +step s0start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false'); +data +---- +(0 rows) + +step s1begin: BEGIN; +step s1keysharepgclass: SELECT count(*) > 1 FROM (SELECT * FROM pg_class FOR KEY SHARE) s; +?column? +-------- +t +(1 row) + +step s2begin: BEGIN; +step s2keysharepgclass: SELECT count(*) > 1 FROM (SELECT * FROM pg_class FOR KEY SHARE) s; +?column? +-------- +t +(1 row) + +step s0alter: ALTER TABLE do_write ADD column ts timestamptz; +step s0w: INSERT INTO do_write DEFAULT VALUES; +step s0start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false'); +data +------------------------------------------------------------------------------ +BEGIN +COMMIT +BEGIN +table public.do_write: INSERT: id[integer]:1 ts[timestamp with time zone]:null +COMMIT +(5 rows) + +step s2commit: COMMIT; +step s1commit: COMMIT; +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/expected/oldest_xmin.out b/contrib/test_decoding/expected/oldest_xmin.out new file mode 100644 index 0000000..dd6053f --- /dev/null +++ b/contrib/test_decoding/expected/oldest_xmin.out @@ -0,0 +1,40 @@ +Parsed test spec with 2 sessions + +starting permutation: s0_begin s0_getxid s1_begin s1_insert s0_alter s0_commit s0_checkpoint s0_get_changes s0_get_changes s1_commit s0_vacuum s0_get_changes +step s0_begin: BEGIN; +step s0_getxid: SELECT pg_current_xact_id() IS NULL; +?column? +-------- +f +(1 row) + +step s1_begin: BEGIN; +step s1_insert: INSERT INTO harvest VALUES ((1, 2, 3)); +step s0_alter: ALTER TYPE basket DROP ATTRIBUTE mangos; +step s0_commit: COMMIT; +step s0_checkpoint: CHECKPOINT; +step s0_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---- +(0 rows) + +step s0_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---- +(0 rows) + +step s1_commit: COMMIT; +step s0_vacuum: VACUUM pg_attribute; +step s0_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +------------------------------------------------------ +BEGIN +table public.harvest: INSERT: fruits[basket]:'(1,2,3)' +COMMIT +(3 rows) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/expected/ondisk_startup.out b/contrib/test_decoding/expected/ondisk_startup.out new file mode 100644 index 0000000..bc7ff07 --- /dev/null +++ b/contrib/test_decoding/expected/ondisk_startup.out @@ -0,0 +1,66 @@ +Parsed test spec with 3 sessions + +starting permutation: s2b s2txid s1init s3b s3txid s2alter s2c s2b s2txid s3c s2c s1insert s1checkpoint s1start s1insert s1alter s1insert s1start +step s2b: BEGIN; +step s2txid: SELECT pg_current_xact_id() IS NULL; +?column? +-------- +f +(1 row) + +step s1init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); +step s3b: BEGIN; +step s3txid: SELECT pg_current_xact_id() IS NULL; +?column? +-------- +f +(1 row) + +step s2alter: ALTER TABLE do_write ADD COLUMN addedbys2 int; +step s2c: COMMIT; +step s2b: BEGIN; +step s2txid: SELECT pg_current_xact_id() IS NULL; +?column? +-------- +f +(1 row) + +step s3c: COMMIT; +step s1init: <... completed> +?column? +-------- +init +(1 row) + +step s2c: COMMIT; +step s1insert: INSERT INTO do_write DEFAULT VALUES; +step s1checkpoint: CHECKPOINT; +step s1start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false'); +data +-------------------------------------------------------------------- +BEGIN +table public.do_write: INSERT: id[integer]:1 addedbys2[integer]:null +COMMIT +(3 rows) + +step s1insert: INSERT INTO do_write DEFAULT VALUES; +step s1alter: ALTER TABLE do_write ADD COLUMN addedbys1 int; +step s1insert: INSERT INTO do_write DEFAULT VALUES; +step s1start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false'); +data +-------------------------------------------------------------------------------------------- +BEGIN +table public.do_write: INSERT: id[integer]:2 addedbys2[integer]:null +COMMIT +BEGIN +COMMIT +BEGIN +table public.do_write: INSERT: id[integer]:3 addedbys2[integer]:null addedbys1[integer]:null +COMMIT +(8 rows) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/expected/permissions.out b/contrib/test_decoding/expected/permissions.out new file mode 100644 index 0000000..d6eaba8 --- /dev/null +++ b/contrib/test_decoding/expected/permissions.out @@ -0,0 +1,134 @@ +-- predictability +SET synchronous_commit = on; +-- setup +CREATE ROLE regress_lr_normal; +CREATE ROLE regress_lr_superuser SUPERUSER; +CREATE ROLE regress_lr_replication REPLICATION; +CREATE TABLE lr_test(data text); +-- superuser can control replication +SET ROLE regress_lr_superuser; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +INSERT INTO lr_test VALUES('lr_superuser_init'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +-------------------------------------------------------------- + BEGIN + table public.lr_test: INSERT: data[text]:'lr_superuser_init' + COMMIT +(3 rows) + +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +RESET ROLE; +-- replication user can control replication +SET ROLE regress_lr_replication; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +INSERT INTO lr_test VALUES('lr_superuser_init'); +ERROR: permission denied for table lr_test +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +RESET ROLE; +-- plain user *can't* can control replication +SET ROLE regress_lr_normal; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +ERROR: permission denied to use replication slots +DETAIL: Only roles with the REPLICATION attribute may use replication slots. +INSERT INTO lr_test VALUES('lr_superuser_init'); +ERROR: permission denied for table lr_test +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +ERROR: permission denied to use replication slots +DETAIL: Only roles with the REPLICATION attribute may use replication slots. +SELECT pg_drop_replication_slot('regression_slot'); +ERROR: permission denied to use replication slots +DETAIL: Only roles with the REPLICATION attribute may use replication slots. +RESET ROLE; +-- replication users can drop superuser created slots +SET ROLE regress_lr_superuser; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +RESET ROLE; +SET ROLE regress_lr_replication; +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +RESET ROLE; +-- normal users can't drop existing slots +SET ROLE regress_lr_superuser; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +RESET ROLE; +SET ROLE regress_lr_normal; +SELECT pg_drop_replication_slot('regression_slot'); +ERROR: permission denied to use replication slots +DETAIL: Only roles with the REPLICATION attribute may use replication slots. +RESET ROLE; +-- all users can see existing slots +SET ROLE regress_lr_superuser; +SELECT slot_name, plugin FROM pg_replication_slots; + slot_name | plugin +-----------------+--------------- + regression_slot | test_decoding +(1 row) + +RESET ROLE; +SET ROLE regress_lr_replication; +SELECT slot_name, plugin FROM pg_replication_slots; + slot_name | plugin +-----------------+--------------- + regression_slot | test_decoding +(1 row) + +RESET ROLE; +SET ROLE regress_lr_normal; +SELECT slot_name, plugin FROM pg_replication_slots; + slot_name | plugin +-----------------+--------------- + regression_slot | test_decoding +(1 row) + +RESET ROLE; +-- cleanup +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +DROP ROLE regress_lr_normal; +DROP ROLE regress_lr_superuser; +DROP ROLE regress_lr_replication; +DROP TABLE lr_test; diff --git a/contrib/test_decoding/expected/prepared.out b/contrib/test_decoding/expected/prepared.out new file mode 100644 index 0000000..46e915d --- /dev/null +++ b/contrib/test_decoding/expected/prepared.out @@ -0,0 +1,74 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +CREATE TABLE test_prepared1(id int); +CREATE TABLE test_prepared2(id int); +-- test simple successful use of a prepared xact +BEGIN; +INSERT INTO test_prepared1 VALUES (1); +PREPARE TRANSACTION 'test_prepared#1'; +COMMIT PREPARED 'test_prepared#1'; +INSERT INTO test_prepared1 VALUES (2); +-- test abort of a prepared xact +BEGIN; +INSERT INTO test_prepared1 VALUES (3); +PREPARE TRANSACTION 'test_prepared#2'; +ROLLBACK PREPARED 'test_prepared#2'; +INSERT INTO test_prepared1 VALUES (4); +-- test prepared xact containing ddl +BEGIN; +INSERT INTO test_prepared1 VALUES (5); +ALTER TABLE test_prepared1 ADD COLUMN data text; +INSERT INTO test_prepared1 VALUES (6, 'frakbar'); +PREPARE TRANSACTION 'test_prepared#3'; +-- test that we decode correctly while an uncommitted prepared xact +-- with ddl exists. +-- separate table because of the lock from the ALTER +-- this will come before the '5' row above, as this commits before it. +INSERT INTO test_prepared2 VALUES (7); +COMMIT PREPARED 'test_prepared#3'; +-- make sure stuff still works +INSERT INTO test_prepared1 VALUES (8); +INSERT INTO test_prepared2 VALUES (9); +-- cleanup +DROP TABLE test_prepared1; +DROP TABLE test_prepared2; +-- show results +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------------------------------------------- + BEGIN + table public.test_prepared1: INSERT: id[integer]:1 + COMMIT + BEGIN + table public.test_prepared1: INSERT: id[integer]:2 + COMMIT + BEGIN + table public.test_prepared1: INSERT: id[integer]:4 + COMMIT + BEGIN + table public.test_prepared2: INSERT: id[integer]:7 + COMMIT + BEGIN + table public.test_prepared1: INSERT: id[integer]:5 + table public.test_prepared1: INSERT: id[integer]:6 data[text]:'frakbar' + COMMIT + BEGIN + table public.test_prepared1: INSERT: id[integer]:8 data[text]:null + COMMIT + BEGIN + table public.test_prepared2: INSERT: id[integer]:9 + COMMIT +(22 rows) + +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/expected/replorigin.out b/contrib/test_decoding/expected/replorigin.out new file mode 100644 index 0000000..c85e1a0 --- /dev/null +++ b/contrib/test_decoding/expected/replorigin.out @@ -0,0 +1,325 @@ +-- predictability +SET synchronous_commit = on; +-- superuser required by default +CREATE ROLE regress_origin_replication REPLICATION; +SET ROLE regress_origin_replication; +SELECT pg_replication_origin_advance('regress_test_decoding: perm', '0/1'); +ERROR: permission denied for function pg_replication_origin_advance +SELECT pg_replication_origin_create('regress_test_decoding: perm'); +ERROR: permission denied for function pg_replication_origin_create +SELECT pg_replication_origin_drop('regress_test_decoding: perm'); +ERROR: permission denied for function pg_replication_origin_drop +SELECT pg_replication_origin_oid('regress_test_decoding: perm'); +ERROR: permission denied for function pg_replication_origin_oid +SELECT pg_replication_origin_progress('regress_test_decoding: perm', false); +ERROR: permission denied for function pg_replication_origin_progress +SELECT pg_replication_origin_session_is_setup(); +ERROR: permission denied for function pg_replication_origin_session_is_setup +SELECT pg_replication_origin_session_progress(false); +ERROR: permission denied for function pg_replication_origin_session_progress +SELECT pg_replication_origin_session_reset(); +ERROR: permission denied for function pg_replication_origin_session_reset +SELECT pg_replication_origin_session_setup('regress_test_decoding: perm'); +ERROR: permission denied for function pg_replication_origin_session_setup +SELECT pg_replication_origin_xact_reset(); +ERROR: permission denied for function pg_replication_origin_xact_reset +SELECT pg_replication_origin_xact_setup('0/1', '2013-01-01 00:00'); +ERROR: permission denied for function pg_replication_origin_xact_setup +SELECT pg_show_replication_origin_status(); +ERROR: permission denied for function pg_show_replication_origin_status +RESET ROLE; +DROP ROLE regress_origin_replication; +CREATE TABLE origin_tbl(id serial primary key, data text); +CREATE TABLE target_tbl(id serial primary key, data text); +SELECT pg_replication_origin_create('regress_test_decoding: regression_slot'); + pg_replication_origin_create +------------------------------ + 1 +(1 row) + +-- ensure duplicate creations fail +SELECT pg_replication_origin_create('regress_test_decoding: regression_slot'); +ERROR: duplicate key value violates unique constraint "pg_replication_origin_roname_index" +DETAIL: Key (roname)=(regress_test_decoding: regression_slot) already exists. +--ensure deletions work (once) +SELECT pg_replication_origin_create('regress_test_decoding: temp'); + pg_replication_origin_create +------------------------------ + 2 +(1 row) + +SELECT pg_replication_origin_drop('regress_test_decoding: temp'); + pg_replication_origin_drop +---------------------------- + +(1 row) + +SELECT pg_replication_origin_drop('regress_test_decoding: temp'); +ERROR: replication origin "regress_test_decoding: temp" does not exist +-- specifying reserved origin names is not supported +SELECT pg_replication_origin_create('any'); +ERROR: replication origin name "any" is reserved +DETAIL: Origin names "any", "none", and names starting with "pg_" are reserved. +SELECT pg_replication_origin_create('none'); +ERROR: replication origin name "none" is reserved +DETAIL: Origin names "any", "none", and names starting with "pg_" are reserved. +SELECT pg_replication_origin_create('pg_replication_origin'); +ERROR: replication origin name "pg_replication_origin" is reserved +DETAIL: Origin names "any", "none", and names starting with "pg_" are reserved. +-- various failure checks for undefined slots +select pg_replication_origin_advance('regress_test_decoding: temp', '0/1'); +ERROR: replication origin "regress_test_decoding: temp" does not exist +select pg_replication_origin_session_setup('regress_test_decoding: temp'); +ERROR: replication origin "regress_test_decoding: temp" does not exist +select pg_replication_origin_progress('regress_test_decoding: temp', true); +ERROR: replication origin "regress_test_decoding: temp" does not exist +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +-- origin tx +INSERT INTO origin_tbl(data) VALUES ('will be replicated and decoded and decoded again'); +INSERT INTO target_tbl(data) +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +-- as is normal, the insert into target_tbl shows up +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.target_tbl: INSERT: id[integer]:1 data[text]:'BEGIN' + table public.target_tbl: INSERT: id[integer]:2 data[text]:'table public.origin_tbl: INSERT: id[integer]:1 data[text]:''will be replicated and decoded and decoded again''' + table public.target_tbl: INSERT: id[integer]:3 data[text]:'COMMIT' + COMMIT +(5 rows) + +INSERT INTO origin_tbl(data) VALUES ('will be replicated, but not decoded again'); +-- mark session as replaying +SELECT pg_replication_origin_session_setup('regress_test_decoding: regression_slot'); + pg_replication_origin_session_setup +------------------------------------- + +(1 row) + +-- ensure we prevent duplicate setup +SELECT pg_replication_origin_session_setup('regress_test_decoding: regression_slot'); +ERROR: cannot setup replication origin when one is already setup +SELECT '' FROM pg_logical_emit_message(false, 'test', 'this message will not be decoded'); + ?column? +---------- + +(1 row) + +BEGIN; +-- setup transaction origin +SELECT pg_replication_origin_xact_setup('0/aabbccdd', '2013-01-01 00:00'); + pg_replication_origin_xact_setup +---------------------------------- + +(1 row) + +INSERT INTO target_tbl(data) +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'only-local', '1'); +COMMIT; +-- check replication progress for the session is correct +SELECT pg_replication_origin_session_progress(false); + pg_replication_origin_session_progress +---------------------------------------- + 0/AABBCCDD +(1 row) + +SELECT pg_replication_origin_session_progress(true); + pg_replication_origin_session_progress +---------------------------------------- + 0/AABBCCDD +(1 row) + +SELECT pg_replication_origin_session_reset(); + pg_replication_origin_session_reset +------------------------------------- + +(1 row) + +SELECT local_id, external_id, remote_lsn, local_lsn <> '0/0' FROM pg_replication_origin_status; + local_id | external_id | remote_lsn | ?column? +----------+----------------------------------------+------------+---------- + 1 | regress_test_decoding: regression_slot | 0/AABBCCDD | t +(1 row) + +-- check replication progress identified by name is correct +SELECT pg_replication_origin_progress('regress_test_decoding: regression_slot', false); + pg_replication_origin_progress +-------------------------------- + 0/AABBCCDD +(1 row) + +SELECT pg_replication_origin_progress('regress_test_decoding: regression_slot', true); + pg_replication_origin_progress +-------------------------------- + 0/AABBCCDD +(1 row) + +-- ensure reset requires previously setup state +SELECT pg_replication_origin_session_reset(); +ERROR: no replication origin is configured +-- and magically the replayed xact will be filtered! +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'only-local', '1'); + data +------ +(0 rows) + +--but new original changes still show up +INSERT INTO origin_tbl(data) VALUES ('will be replicated'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'only-local', '1'); + data +-------------------------------------------------------------------------------- + BEGIN + table public.origin_tbl: INSERT: id[integer]:3 data[text]:'will be replicated' + COMMIT +(3 rows) + +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT pg_replication_origin_drop('regress_test_decoding: regression_slot'); + pg_replication_origin_drop +---------------------------- + +(1 row) + +-- Set of transactions with no origin LSNs and commit timestamps set for +-- this session. +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_no_lsn', 'test_decoding'); + ?column? +---------- + init +(1 row) + +SELECT pg_replication_origin_create('regress_test_decoding: regression_slot_no_lsn'); + pg_replication_origin_create +------------------------------ + 1 +(1 row) + +-- mark session as replaying +SELECT pg_replication_origin_session_setup('regress_test_decoding: regression_slot_no_lsn'); + pg_replication_origin_session_setup +------------------------------------- + +(1 row) + +-- Simple transactions +BEGIN; +INSERT INTO origin_tbl(data) VALUES ('no_lsn, commit'); +COMMIT; +BEGIN; +INSERT INTO origin_tbl(data) VALUES ('no_lsn, rollback'); +ROLLBACK; +-- 2PC transactions +BEGIN; +INSERT INTO origin_tbl(data) VALUES ('no_lsn, commit prepared'); +PREPARE TRANSACTION 'replorigin_prepared'; +COMMIT PREPARED 'replorigin_prepared'; +BEGIN; +INSERT INTO origin_tbl(data) VALUES ('no_lsn, rollback prepared'); +PREPARE TRANSACTION 'replorigin_prepared'; +ROLLBACK PREPARED 'replorigin_prepared'; +SELECT local_id, external_id, + remote_lsn <> '0/0' AS valid_remote_lsn, + local_lsn <> '0/0' AS valid_local_lsn + FROM pg_replication_origin_status; + local_id | external_id | valid_remote_lsn | valid_local_lsn +----------+-----------------------------------------------+------------------+----------------- + 1 | regress_test_decoding: regression_slot_no_lsn | f | t +(1 row) + +SELECT data FROM pg_logical_slot_get_changes('regression_slot_no_lsn', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); + data +------------------------------------------------------------------------------------- + BEGIN + table public.origin_tbl: INSERT: id[integer]:4 data[text]:'no_lsn, commit' + COMMIT + BEGIN + table public.origin_tbl: INSERT: id[integer]:6 data[text]:'no_lsn, commit prepared' + COMMIT +(6 rows) + +-- Clean up +SELECT pg_replication_origin_session_reset(); + pg_replication_origin_session_reset +------------------------------------- + +(1 row) + +SELECT pg_drop_replication_slot('regression_slot_no_lsn'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT pg_replication_origin_drop('regress_test_decoding: regression_slot_no_lsn'); + pg_replication_origin_drop +---------------------------- + +(1 row) + +-- Test that the pgoutput correctly filters changes corresponding to the provided origin value. +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'pgoutput'); + ?column? +---------- + init +(1 row) + +CREATE PUBLICATION pub FOR TABLE target_tbl; +SELECT pg_replication_origin_create('regress_test_decoding: regression_slot'); + pg_replication_origin_create +------------------------------ + 1 +(1 row) + +-- mark session as replaying +SELECT pg_replication_origin_session_setup('regress_test_decoding: regression_slot'); + pg_replication_origin_session_setup +------------------------------------- + +(1 row) + +INSERT INTO target_tbl(data) VALUES ('test data'); +-- The replayed change will be filtered. +SELECT count(*) = 0 FROM pg_logical_slot_peek_binary_changes('regression_slot', NULL, NULL, 'proto_version', '4', 'publication_names', 'pub', 'origin', 'none'); + ?column? +---------- + t +(1 row) + +-- The replayed change will be output if the origin value is not specified. +SELECT count(*) != 0 FROM pg_logical_slot_peek_binary_changes('regression_slot', NULL, NULL, 'proto_version', '4', 'publication_names', 'pub'); + ?column? +---------- + t +(1 row) + +-- Clean up +SELECT pg_replication_origin_session_reset(); + pg_replication_origin_session_reset +------------------------------------- + +(1 row) + +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT pg_replication_origin_drop('regress_test_decoding: regression_slot'); + pg_replication_origin_drop +---------------------------- + +(1 row) + +DROP PUBLICATION pub; diff --git a/contrib/test_decoding/expected/rewrite.out b/contrib/test_decoding/expected/rewrite.out new file mode 100644 index 0000000..b30999c --- /dev/null +++ b/contrib/test_decoding/expected/rewrite.out @@ -0,0 +1,164 @@ +-- predictability +SET synchronous_commit = on; +DROP TABLE IF EXISTS replication_example; +-- Ensure there's tables with toast datums. To do so, we dynamically +-- create a function returning a large textblob. We want tables of +-- different kinds: mapped catalog table, unmapped catalog table, +-- shared catalog table and usertable. +CREATE FUNCTION exec(text) returns void language plpgsql volatile + AS $f$ + BEGIN + EXECUTE $1; + END; +$f$; +CREATE ROLE regress_justforcomments NOLOGIN; +SELECT exec( + format($outer$CREATE FUNCTION iamalongfunction() RETURNS TEXT IMMUTABLE LANGUAGE SQL AS $f$SELECT text %L$f$$outer$, + (SELECT repeat(string_agg(to_char(g.i, 'FM0000'), ''), 50) FROM generate_series(1, 500) g(i)))); + exec +------ + +(1 row) + +SELECT exec( + format($outer$COMMENT ON FUNCTION iamalongfunction() IS %L$outer$, + iamalongfunction())); + exec +------ + +(1 row) + +SELECT exec( + format($outer$COMMENT ON ROLE REGRESS_JUSTFORCOMMENTS IS %L$outer$, + iamalongfunction())); + exec +------ + +(1 row) + +CREATE TABLE iamalargetable AS SELECT iamalongfunction() longfunctionoutput; +-- verify toast usage +SELECT pg_relation_size((SELECT reltoastrelid FROM pg_class WHERE oid = 'pg_proc'::regclass)) > 0; + ?column? +---------- + t +(1 row) + +SELECT pg_relation_size((SELECT reltoastrelid FROM pg_class WHERE oid = 'pg_description'::regclass)) > 0; + ?column? +---------- + t +(1 row) + +SELECT pg_relation_size((SELECT reltoastrelid FROM pg_class WHERE oid = 'pg_shdescription'::regclass)) > 0; + ?column? +---------- + t +(1 row) + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120)); +INSERT INTO replication_example(somedata) VALUES (1); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +---------------------------------------------------------------------------------------------------------- + BEGIN + table public.replication_example: INSERT: id[integer]:1 somedata[integer]:1 text[character varying]:null + COMMIT +(3 rows) + +BEGIN; +INSERT INTO replication_example(somedata) VALUES (2); +ALTER TABLE replication_example ADD COLUMN testcolumn1 int; +INSERT INTO replication_example(somedata, testcolumn1) VALUES (3, 1); +COMMIT; +BEGIN; +INSERT INTO replication_example(somedata) VALUES (3); +ALTER TABLE replication_example ADD COLUMN testcolumn2 int; +INSERT INTO replication_example(somedata, testcolumn1, testcolumn2) VALUES (4, 2, 1); +COMMIT; +VACUUM FULL pg_am; +VACUUM FULL pg_amop; +VACUUM FULL pg_proc; +VACUUM FULL pg_opclass; +VACUUM FULL pg_type; +VACUUM FULL pg_index; +VACUUM FULL pg_database; +-- repeated rewrites that fail +BEGIN; +CLUSTER pg_class USING pg_class_oid_index; +CLUSTER pg_class USING pg_class_oid_index; +ROLLBACK; +-- repeated rewrites that succeed +BEGIN; +CLUSTER pg_class USING pg_class_oid_index; +CLUSTER pg_class USING pg_class_oid_index; +CLUSTER pg_class USING pg_class_oid_index; +COMMIT; + -- repeated rewrites in different transactions +VACUUM FULL pg_class; +VACUUM FULL pg_class; +-- reindexing of important relations / indexes +REINDEX TABLE pg_class; +REINDEX INDEX pg_class_oid_index; +REINDEX INDEX pg_class_tblspc_relfilenode_index; +INSERT INTO replication_example(somedata, testcolumn1) VALUES (5, 3); +BEGIN; +INSERT INTO replication_example(somedata, testcolumn1) VALUES (6, 4); +ALTER TABLE replication_example ADD COLUMN testcolumn3 int; +INSERT INTO replication_example(somedata, testcolumn1, testcolumn3) VALUES (7, 5, 1); +COMMIT; +-- make old files go away +CHECKPOINT; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.replication_example: INSERT: id[integer]:2 somedata[integer]:2 text[character varying]:null + table public.replication_example: INSERT: id[integer]:3 somedata[integer]:3 text[character varying]:null testcolumn1[integer]:1 + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:4 somedata[integer]:3 text[character varying]:null testcolumn1[integer]:null + table public.replication_example: INSERT: id[integer]:5 somedata[integer]:4 text[character varying]:null testcolumn1[integer]:2 testcolumn2[integer]:1 + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:6 somedata[integer]:5 text[character varying]:null testcolumn1[integer]:3 testcolumn2[integer]:null + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:7 somedata[integer]:6 text[character varying]:null testcolumn1[integer]:4 testcolumn2[integer]:null + table public.replication_example: INSERT: id[integer]:8 somedata[integer]:7 text[character varying]:null testcolumn1[integer]:5 testcolumn2[integer]:null testcolumn3[integer]:1 + COMMIT +(15 rows) + +-- trigger repeated rewrites of a system catalog with a toast table, +-- that previously was buggy: 20180914021046.oi7dm4ra3ot2g2kt@alap3.anarazel.de +VACUUM FULL pg_proc; VACUUM FULL pg_description; VACUUM FULL pg_shdescription; VACUUM FULL iamalargetable; +INSERT INTO replication_example(somedata, testcolumn1, testcolumn3) VALUES (8, 6, 1); +VACUUM FULL pg_proc; VACUUM FULL pg_description; VACUUM FULL pg_shdescription; VACUUM FULL iamalargetable; +INSERT INTO replication_example(somedata, testcolumn1, testcolumn3) VALUES (9, 7, 1); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.replication_example: INSERT: id[integer]:9 somedata[integer]:8 text[character varying]:null testcolumn1[integer]:6 testcolumn2[integer]:null testcolumn3[integer]:1 + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:10 somedata[integer]:9 text[character varying]:null testcolumn1[integer]:7 testcolumn2[integer]:null testcolumn3[integer]:1 + COMMIT +(6 rows) + +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +DROP TABLE IF EXISTS replication_example; +DROP FUNCTION iamalongfunction(); +DROP FUNCTION exec(text); +DROP ROLE regress_justforcomments; diff --git a/contrib/test_decoding/expected/slot.out b/contrib/test_decoding/expected/slot.out new file mode 100644 index 0000000..63a9940 --- /dev/null +++ b/contrib/test_decoding/expected/slot.out @@ -0,0 +1,408 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding'); + ?column? +---------- + init +(1 row) + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t', 'test_decoding', true); + ?column? +---------- + init +(1 row) + +SELECT pg_drop_replication_slot('regression_slot_p'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding', false); + ?column? +---------- + init +(1 row) + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t2', 'test_decoding', true); + ?column? +---------- + init +(1 row) + +SELECT pg_create_logical_replication_slot('foo', 'nonexistent'); +ERROR: could not access file "nonexistent": No such file or directory +-- here we want to start a new session and wait till old one is gone +select pg_backend_pid() as oldpid \gset +\c - +SET synchronous_commit = on; +do 'declare c int = 0; +begin + while (select count(*) from pg_replication_slots where active_pid = ' + :'oldpid' + ') > 0 loop c := c + 1; perform pg_sleep(0.01); end loop; + raise log ''slot test looped % times'', c; +end'; +-- should fail because the temporary slots were dropped automatically +SELECT pg_drop_replication_slot('regression_slot_t'); +ERROR: replication slot "regression_slot_t" does not exist +SELECT pg_drop_replication_slot('regression_slot_t2'); +ERROR: replication slot "regression_slot_t2" does not exist +-- monitoring functions for slot directories +SELECT count(*) >= 0 AS ok FROM pg_ls_logicalmapdir(); + ok +---- + t +(1 row) + +SELECT count(*) >= 0 AS ok FROM pg_ls_logicalsnapdir(); + ok +---- + t +(1 row) + +SELECT count(*) >= 0 AS ok FROM pg_ls_replslotdir('regression_slot_p'); + ok +---- + t +(1 row) + +SELECT count(*) >= 0 AS ok FROM pg_ls_replslotdir('not_existing_slot'); -- fails +ERROR: replication slot "not_existing_slot" does not exist +-- permanent slot has survived +SELECT pg_drop_replication_slot('regression_slot_p'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +-- test switching between slots in a session +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot1', 'test_decoding', true); + ?column? +---------- + init +(1 row) + +CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120)); +BEGIN; +INSERT INTO replication_example(somedata, text) VALUES (1, 1); +INSERT INTO replication_example(somedata, text) VALUES (1, 2); +COMMIT; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot2', 'test_decoding', true); + ?column? +---------- + init +(1 row) + +INSERT INTO replication_example(somedata, text) VALUES (1, 3); +SELECT data FROM pg_logical_slot_get_changes('regression_slot1', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +--------------------------------------------------------------------------------------------------------- + BEGIN + table public.replication_example: INSERT: id[integer]:1 somedata[integer]:1 text[character varying]:'1' + table public.replication_example: INSERT: id[integer]:2 somedata[integer]:1 text[character varying]:'2' + COMMIT + BEGIN + table public.replication_example: INSERT: id[integer]:3 somedata[integer]:1 text[character varying]:'3' + COMMIT +(7 rows) + +SELECT data FROM pg_logical_slot_get_changes('regression_slot2', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +--------------------------------------------------------------------------------------------------------- + BEGIN + table public.replication_example: INSERT: id[integer]:3 somedata[integer]:1 text[character varying]:'3' + COMMIT +(3 rows) + +INSERT INTO replication_example(somedata, text) VALUES (1, 4); +INSERT INTO replication_example(somedata, text) VALUES (1, 5); +SELECT pg_current_wal_lsn() AS wal_lsn \gset +INSERT INTO replication_example(somedata, text) VALUES (1, 6); +SELECT end_lsn FROM pg_replication_slot_advance('regression_slot1', :'wal_lsn') \gset +SELECT slot_name FROM pg_replication_slot_advance('regression_slot2', pg_current_wal_lsn()); + slot_name +------------------ + regression_slot2 +(1 row) + +SELECT :'wal_lsn' = :'end_lsn'; + ?column? +---------- + t +(1 row) + +SELECT data FROM pg_logical_slot_get_changes('regression_slot1', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +--------------------------------------------------------------------------------------------------------- + BEGIN + table public.replication_example: INSERT: id[integer]:6 somedata[integer]:1 text[character varying]:'6' + COMMIT +(3 rows) + +SELECT data FROM pg_logical_slot_get_changes('regression_slot2', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +DROP TABLE replication_example; +-- error +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot1', 'test_decoding', true); +ERROR: replication slot "regression_slot1" already exists +-- both should error as they should be dropped on error +SELECT pg_drop_replication_slot('regression_slot1'); +ERROR: replication slot "regression_slot1" does not exist +SELECT pg_drop_replication_slot('regression_slot2'); +ERROR: replication slot "regression_slot2" does not exist +-- slot advance with physical slot, error with non-reserved slot +SELECT slot_name FROM pg_create_physical_replication_slot('regression_slot3'); + slot_name +------------------ + regression_slot3 +(1 row) + +SELECT pg_replication_slot_advance('regression_slot3', '0/0'); -- invalid LSN +ERROR: invalid target WAL LSN +SELECT pg_replication_slot_advance('regression_slot3', '0/1'); -- error +ERROR: replication slot "regression_slot3" cannot be advanced +DETAIL: This slot has never previously reserved WAL, or it has been invalidated. +SELECT pg_drop_replication_slot('regression_slot3'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +-- +-- Test copy functions for logical replication slots +-- +-- Create and copy logical slots +SELECT 'init' FROM pg_create_logical_replication_slot('orig_slot1', 'test_decoding', false); + ?column? +---------- + init +(1 row) + +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot1', 'copied_slot1_no_change'); + ?column? +---------- + copy +(1 row) + +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot1', 'copied_slot1_change_plugin', false, 'pgoutput'); + ?column? +---------- + copy +(1 row) + +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot1', 'copied_slot1_change_plugin_temp', true, 'pgoutput'); + ?column? +---------- + copy +(1 row) + +-- Check all copied slots status +SELECT + o.slot_name, o.plugin, o.temporary, c.slot_name, c.plugin, c.temporary +FROM + (SELECT * FROM pg_replication_slots WHERE slot_name LIKE 'orig%') as o + LEFT JOIN pg_replication_slots as c ON o.restart_lsn = c.restart_lsn AND o.confirmed_flush_lsn = c.confirmed_flush_lsn +WHERE + o.slot_name != c.slot_name +ORDER BY o.slot_name, c.slot_name; + slot_name | plugin | temporary | slot_name | plugin | temporary +------------+---------------+-----------+---------------------------------+---------------+----------- + orig_slot1 | test_decoding | f | copied_slot1_change_plugin | pgoutput | f + orig_slot1 | test_decoding | f | copied_slot1_change_plugin_temp | pgoutput | t + orig_slot1 | test_decoding | f | copied_slot1_no_change | test_decoding | f +(3 rows) + +-- Now we have maximum 4 replication slots. Check slots are properly +-- released even when raise error during creating the target slot. +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot1', 'failed'); -- error +ERROR: all replication slots are in use +HINT: Free one or increase max_replication_slots. +-- temporary slots were dropped automatically +SELECT pg_drop_replication_slot('orig_slot1'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT pg_drop_replication_slot('copied_slot1_no_change'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT pg_drop_replication_slot('copied_slot1_change_plugin'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +-- Test based on the temporary logical slot +SELECT 'init' FROM pg_create_logical_replication_slot('orig_slot2', 'test_decoding', true); + ?column? +---------- + init +(1 row) + +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot2', 'copied_slot2_no_change'); + ?column? +---------- + copy +(1 row) + +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot2', 'copied_slot2_change_plugin', true, 'pgoutput'); + ?column? +---------- + copy +(1 row) + +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot2', 'copied_slot2_change_plugin_temp', false, 'pgoutput'); + ?column? +---------- + copy +(1 row) + +-- Check all copied slots status +SELECT + o.slot_name, o.plugin, o.temporary, c.slot_name, c.plugin, c.temporary +FROM + (SELECT * FROM pg_replication_slots WHERE slot_name LIKE 'orig%') as o + LEFT JOIN pg_replication_slots as c ON o.restart_lsn = c.restart_lsn AND o.confirmed_flush_lsn = c.confirmed_flush_lsn +WHERE + o.slot_name != c.slot_name +ORDER BY o.slot_name, c.slot_name; + slot_name | plugin | temporary | slot_name | plugin | temporary +------------+---------------+-----------+---------------------------------+---------------+----------- + orig_slot2 | test_decoding | t | copied_slot2_change_plugin | pgoutput | t + orig_slot2 | test_decoding | t | copied_slot2_change_plugin_temp | pgoutput | f + orig_slot2 | test_decoding | t | copied_slot2_no_change | test_decoding | t +(3 rows) + +-- Cannot copy a logical slot to a physical slot +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot2', 'failed'); -- error +ERROR: cannot copy physical replication slot "orig_slot2" as a logical replication slot +-- temporary slots were dropped automatically +SELECT pg_drop_replication_slot('copied_slot2_change_plugin_temp'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +-- +-- Test copy functions for physical replication slots +-- +-- Create and copy physical slots +SELECT 'init' FROM pg_create_physical_replication_slot('orig_slot1', true); + ?column? +---------- + init +(1 row) + +SELECT 'init' FROM pg_create_physical_replication_slot('orig_slot2', false); + ?column? +---------- + init +(1 row) + +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot1', 'copied_slot1_no_change'); + ?column? +---------- + copy +(1 row) + +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot1', 'copied_slot1_temp', true); + ?column? +---------- + copy +(1 row) + +-- Check all copied slots status. Since all slots don't reserve WAL we check only other fields. +SELECT slot_name, slot_type, temporary FROM pg_replication_slots; + slot_name | slot_type | temporary +------------------------+-----------+----------- + orig_slot1 | physical | f + orig_slot2 | physical | f + copied_slot1_no_change | physical | f + copied_slot1_temp | physical | t +(4 rows) + +-- Cannot copy a physical slot to a logical slot +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot1', 'failed'); -- error +ERROR: cannot copy logical replication slot "orig_slot1" as a physical replication slot +-- Cannot copy a physical slot that doesn't reserve WAL +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot2', 'failed'); -- error +ERROR: cannot copy a replication slot that doesn't reserve WAL +-- temporary slots were dropped automatically +SELECT pg_drop_replication_slot('orig_slot1'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT pg_drop_replication_slot('orig_slot2'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT pg_drop_replication_slot('copied_slot1_no_change'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +-- Test based on the temporary physical slot +SELECT 'init' FROM pg_create_physical_replication_slot('orig_slot2', true, true); + ?column? +---------- + init +(1 row) + +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot2', 'copied_slot2_no_change'); + ?column? +---------- + copy +(1 row) + +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot2', 'copied_slot2_notemp', false); + ?column? +---------- + copy +(1 row) + +-- Check all copied slots status +SELECT + o.slot_name, o.temporary, c.slot_name, c.temporary +FROM + (SELECT * FROM pg_replication_slots WHERE slot_name LIKE 'orig%') as o + LEFT JOIN pg_replication_slots as c ON o.restart_lsn = c.restart_lsn +WHERE + o.slot_name != c.slot_name +ORDER BY o.slot_name, c.slot_name; + slot_name | temporary | slot_name | temporary +------------+-----------+------------------------+----------- + orig_slot2 | t | copied_slot2_no_change | t + orig_slot2 | t | copied_slot2_notemp | f +(2 rows) + +SELECT pg_drop_replication_slot('orig_slot2'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT pg_drop_replication_slot('copied_slot2_no_change'); + pg_drop_replication_slot +-------------------------- + +(1 row) + +SELECT pg_drop_replication_slot('copied_slot2_notemp'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/expected/slot_creation_error.out b/contrib/test_decoding/expected/slot_creation_error.out new file mode 100644 index 0000000..25883b5 --- /dev/null +++ b/contrib/test_decoding/expected/slot_creation_error.out @@ -0,0 +1,114 @@ +Parsed test spec with 2 sessions + +starting permutation: s1_b s1_xid s2_init s1_view_slot s1_cancel_s2 s1_view_slot s1_c +step s1_b: BEGIN; +step s1_xid: SELECT 'xid' FROM txid_current(); +?column? +-------- +xid +(1 row) + +step s2_init: + SELECT 'init' FROM pg_create_logical_replication_slot('slot_creation_error', 'test_decoding'); + +step s1_view_slot: + SELECT slot_name, slot_type, active FROM pg_replication_slots WHERE slot_name = 'slot_creation_error' + +slot_name |slot_type|active +-------------------+---------+------ +slot_creation_error|logical |t +(1 row) + +step s1_cancel_s2: + SELECT pg_cancel_backend(pid) + FROM pg_stat_activity + WHERE application_name = 'isolation/slot_creation_error/s2'; + +step s2_init: <... completed> +ERROR: canceling statement due to user request +step s1_cancel_s2: <... completed> +pg_cancel_backend +----------------- +t +(1 row) + +step s1_view_slot: + SELECT slot_name, slot_type, active FROM pg_replication_slots WHERE slot_name = 'slot_creation_error' + +slot_name|slot_type|active +---------+---------+------ +(0 rows) + +step s1_c: COMMIT; + +starting permutation: s1_b s1_xid s2_init s1_c s1_view_slot s1_drop_slot +step s1_b: BEGIN; +step s1_xid: SELECT 'xid' FROM txid_current(); +?column? +-------- +xid +(1 row) + +step s2_init: + SELECT 'init' FROM pg_create_logical_replication_slot('slot_creation_error', 'test_decoding'); + +step s1_c: COMMIT; +step s2_init: <... completed> +?column? +-------- +init +(1 row) + +step s1_view_slot: + SELECT slot_name, slot_type, active FROM pg_replication_slots WHERE slot_name = 'slot_creation_error' + +slot_name |slot_type|active +-------------------+---------+------ +slot_creation_error|logical |f +(1 row) + +step s1_drop_slot: + SELECT pg_drop_replication_slot('slot_creation_error'); + +pg_drop_replication_slot +------------------------ + +(1 row) + + +starting permutation: s1_b s1_xid s2_init s1_terminate_s2 s1_c s1_view_slot +step s1_b: BEGIN; +step s1_xid: SELECT 'xid' FROM txid_current(); +?column? +-------- +xid +(1 row) + +step s2_init: + SELECT 'init' FROM pg_create_logical_replication_slot('slot_creation_error', 'test_decoding'); + +step s1_terminate_s2: + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE application_name = 'isolation/slot_creation_error/s2'; + +step s2_init: <... completed> +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 s1_terminate_s2: <... completed> +pg_terminate_backend +-------------------- +t +(1 row) + +step s1_c: COMMIT; +step s1_view_slot: + SELECT slot_name, slot_type, active FROM pg_replication_slots WHERE slot_name = 'slot_creation_error' + +slot_name|slot_type|active +---------+---------+------ +(0 rows) + diff --git a/contrib/test_decoding/expected/snapshot_transfer.out b/contrib/test_decoding/expected/snapshot_transfer.out new file mode 100644 index 0000000..833f478 --- /dev/null +++ b/contrib/test_decoding/expected/snapshot_transfer.out @@ -0,0 +1,61 @@ +Parsed test spec with 2 sessions + +starting permutation: s0_begin s0_begin_sub0 s0_log_assignment s0_sub_get_base_snap s1_produce_new_snap s0_insert s0_end_sub0 s0_commit s0_get_changes +step s0_begin: BEGIN; +step s0_begin_sub0: SAVEPOINT s0; +step s0_log_assignment: SELECT pg_current_xact_id() IS NULL; +?column? +-------- +f +(1 row) + +step s0_sub_get_base_snap: INSERT INTO dummy VALUES (0); +step s1_produce_new_snap: ALTER TABLE harvest ADD COLUMN mangos int; +step s0_insert: INSERT INTO harvest VALUES (1, 2, 3); +step s0_end_sub0: RELEASE SAVEPOINT s0; +step s0_commit: COMMIT; +step s0_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------------------------------- +BEGIN +table public.dummy: INSERT: i[integer]:0 +table public.harvest: INSERT: apples[integer]:1 pears[integer]:2 mangos[integer]:3 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + + +starting permutation: s0_begin s0_begin_sub0 s0_log_assignment s0_begin_sub1 s0_sub_get_base_snap s1_produce_new_snap s0_insert s0_end_sub1 s0_end_sub0 s0_commit s0_get_changes +step s0_begin: BEGIN; +step s0_begin_sub0: SAVEPOINT s0; +step s0_log_assignment: SELECT pg_current_xact_id() IS NULL; +?column? +-------- +f +(1 row) + +step s0_begin_sub1: SAVEPOINT s1; +step s0_sub_get_base_snap: INSERT INTO dummy VALUES (0); +step s1_produce_new_snap: ALTER TABLE harvest ADD COLUMN mangos int; +step s0_insert: INSERT INTO harvest VALUES (1, 2, 3); +step s0_end_sub1: RELEASE SAVEPOINT s1; +step s0_end_sub0: RELEASE SAVEPOINT s0; +step s0_commit: COMMIT; +step s0_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +---------------------------------------------------------------------------------- +BEGIN +table public.dummy: INSERT: i[integer]:0 +table public.harvest: INSERT: apples[integer]:1 pears[integer]:2 mangos[integer]:3 +COMMIT +(4 rows) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/expected/spill.out b/contrib/test_decoding/expected/spill.out new file mode 100644 index 0000000..10734bd --- /dev/null +++ b/contrib/test_decoding/expected/spill.out @@ -0,0 +1,256 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +CREATE TABLE spill_test(data text); +-- consume DDL +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +-- spilling main xact +BEGIN; +INSERT INTO spill_test SELECT 'serialize-topbig--1:'||g.i FROM generate_series(1, 5000) g(i); +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-----------------------+-------+---------------------------------------------------------------------+------------------------------------------------------------------------ + 'serialize-topbig--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-topbig--1:1' | table public.spill_test: INSERT: data[text]:'serialize-topbig--1:5000' +(1 row) + +-- spilling subxact, nothing in main +BEGIN; +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-----------------------+-------+---------------------------------------------------------------------+------------------------------------------------------------------------ + 'serialize-subbig--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-subbig--1:1' | table public.spill_test: INSERT: data[text]:'serialize-subbig--1:5000' +(1 row) + +-- spilling subxact, spilling main xact +BEGIN; +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topbig--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topbig--2:'||g.i FROM generate_series(5001, 10000) g(i); +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-----------------------------+-------+-------------------------------------------------------------------------------+-------------------------------------------------------------------------------- + 'serialize-subbig-topbig--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-subbig-topbig--1:1' | table public.spill_test: INSERT: data[text]:'serialize-subbig-topbig--1:5000' + 'serialize-subbig-topbig--2 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-subbig-topbig--2:5001' | table public.spill_test: INSERT: data[text]:'serialize-subbig-topbig--2:10000' +(2 rows) + +-- spilling subxact, non-spilling main xact +BEGIN; +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topsmall--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topsmall--2:'||g.i FROM generate_series(5001, 5001) g(i); +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-------------------------------+-------+---------------------------------------------------------------------------------+--------------------------------------------------------------------------------- + 'serialize-subbig-topsmall--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-subbig-topsmall--1:1' | table public.spill_test: INSERT: data[text]:'serialize-subbig-topsmall--1:5000' + 'serialize-subbig-topsmall--2 | 1 | table public.spill_test: INSERT: data[text]:'serialize-subbig-topsmall--2:5001' | table public.spill_test: INSERT: data[text]:'serialize-subbig-topsmall--2:5001' +(2 rows) + +-- not-spilling subxact, spilling main xact +BEGIN; +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topbig--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topbig--2:'||g.i FROM generate_series(5001, 10000) g(i); +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-----------------------------+-------+-------------------------------------------------------------------------------+-------------------------------------------------------------------------------- + 'serialize-subbig-topbig--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-subbig-topbig--1:1' | table public.spill_test: INSERT: data[text]:'serialize-subbig-topbig--1:5000' + 'serialize-subbig-topbig--2 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-subbig-topbig--2:5001' | table public.spill_test: INSERT: data[text]:'serialize-subbig-topbig--2:10000' +(2 rows) + +-- spilling main xact, spilling subxact +BEGIN; +INSERT INTO spill_test SELECT 'serialize-topbig-subbig--1:'||g.i FROM generate_series(1, 5000) g(i); +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-topbig-subbig--2:'||g.i FROM generate_series(5001, 10000) g(i); +RELEASE SAVEPOINT s; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-----------------------------+-------+-------------------------------------------------------------------------------+-------------------------------------------------------------------------------- + 'serialize-topbig-subbig--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-topbig-subbig--1:1' | table public.spill_test: INSERT: data[text]:'serialize-topbig-subbig--1:5000' + 'serialize-topbig-subbig--2 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-topbig-subbig--2:5001' | table public.spill_test: INSERT: data[text]:'serialize-topbig-subbig--2:10000' +(2 rows) + +-- spilling main xact, not spilling subxact +BEGIN; +INSERT INTO spill_test SELECT 'serialize-topbig-subsmall--1:'||g.i FROM generate_series(1, 5000) g(i); +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-topbig-subsmall--2:'||g.i FROM generate_series(5001, 5001) g(i); +RELEASE SAVEPOINT s; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-------------------------------+-------+---------------------------------------------------------------------------------+--------------------------------------------------------------------------------- + 'serialize-topbig-subsmall--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-topbig-subsmall--1:1' | table public.spill_test: INSERT: data[text]:'serialize-topbig-subsmall--1:5000' + 'serialize-topbig-subsmall--2 | 1 | table public.spill_test: INSERT: data[text]:'serialize-topbig-subsmall--2:5001' | table public.spill_test: INSERT: data[text]:'serialize-topbig-subsmall--2:5001' +(2 rows) + +-- spilling subxact, followed by another spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-subbig-subbig--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s1; +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-subbig-subbig--2:'||g.i FROM generate_series(5001, 10000) g(i); +RELEASE SAVEPOINT s2; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-----------------------------+-------+-------------------------------------------------------------------------------+-------------------------------------------------------------------------------- + 'serialize-subbig-subbig--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-subbig-subbig--1:1' | table public.spill_test: INSERT: data[text]:'serialize-subbig-subbig--1:5000' + 'serialize-subbig-subbig--2 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-subbig-subbig--2:5001' | table public.spill_test: INSERT: data[text]:'serialize-subbig-subbig--2:10000' +(2 rows) + +-- spilling subxact, followed by not spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-subbig-subsmall--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s1; +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-subbig-subsmall--2:'||g.i FROM generate_series(5001, 5001) g(i); +RELEASE SAVEPOINT s2; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-------------------------------+-------+---------------------------------------------------------------------------------+--------------------------------------------------------------------------------- + 'serialize-subbig-subsmall--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-subbig-subsmall--1:1' | table public.spill_test: INSERT: data[text]:'serialize-subbig-subsmall--1:5000' + 'serialize-subbig-subsmall--2 | 1 | table public.spill_test: INSERT: data[text]:'serialize-subbig-subsmall--2:5001' | table public.spill_test: INSERT: data[text]:'serialize-subbig-subsmall--2:5001' +(2 rows) + +-- not spilling subxact, followed by spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-subsmall-subbig--1:'||g.i FROM generate_series(1, 1) g(i); +RELEASE SAVEPOINT s1; +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-subsmall-subbig--2:'||g.i FROM generate_series(2, 5001) g(i); +RELEASE SAVEPOINT s2; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4] COLLATE "C", COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-------------------------------+-------+------------------------------------------------------------------------------+--------------------------------------------------------------------------------- + 'serialize-subsmall-subbig--1 | 1 | table public.spill_test: INSERT: data[text]:'serialize-subsmall-subbig--1:1' | table public.spill_test: INSERT: data[text]:'serialize-subsmall-subbig--1:1' + 'serialize-subsmall-subbig--2 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-subsmall-subbig--2:2' | table public.spill_test: INSERT: data[text]:'serialize-subsmall-subbig--2:5001' +(2 rows) + +-- spilling subxact, containing another spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subbig--1:'||g.i FROM generate_series(1, 5000) g(i); +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subbig--2:'||g.i FROM generate_series(5001, 10000) g(i); +RELEASE SAVEPOINT s2; +RELEASE SAVEPOINT s1; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4] COLLATE "C", COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +------------------------------------+-------+--------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------- + 'serialize-nested-subbig-subbig--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subbig--1:1' | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subbig--1:5000' + 'serialize-nested-subbig-subbig--2 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subbig--2:5001' | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subbig--2:10000' +(2 rows) + +-- spilling subxact, containing a not spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subsmall--1:'||g.i FROM generate_series(1, 5000) g(i); +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subsmall--2:'||g.i FROM generate_series(5001, 5001) g(i); +RELEASE SAVEPOINT s2; +RELEASE SAVEPOINT s1; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4] COLLATE "C", COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +--------------------------------------+-------+----------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------- + 'serialize-nested-subbig-subsmall--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subsmall--1:1' | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subsmall--1:5000' + 'serialize-nested-subbig-subsmall--2 | 1 | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subsmall--2:5001' | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subsmall--2:5001' +(2 rows) + +-- not spilling subxact, containing a spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-nested-subsmall-subbig--1:'||g.i FROM generate_series(1, 1) g(i); +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-nested-subsmall-subbig--2:'||g.i FROM generate_series(2, 5001) g(i); +RELEASE SAVEPOINT s2; +RELEASE SAVEPOINT s1; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4] COLLATE "C", COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +--------------------------------------+-------+-------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------- + 'serialize-nested-subsmall-subbig--1 | 1 | table public.spill_test: INSERT: data[text]:'serialize-nested-subsmall-subbig--1:1' | table public.spill_test: INSERT: data[text]:'serialize-nested-subsmall-subbig--1:1' + 'serialize-nested-subsmall-subbig--2 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-nested-subsmall-subbig--2:2' | table public.spill_test: INSERT: data[text]:'serialize-nested-subsmall-subbig--2:5001' +(2 rows) + +-- not spilling subxact, containing a spilling subxact that aborts and one that commits +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subbigabort--1:'||g.i FROM generate_series(1, 5000) g(i); +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subbigabort--2:'||g.i FROM generate_series(5001, 10000) g(i); +ROLLBACK TO SAVEPOINT s2; +SAVEPOINT s3; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subbigabort-subbig-3:'||g.i FROM generate_series(5001, 10000) g(i); +RELEASE SAVEPOINT s1; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4] COLLATE "C", COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + regexp_split_to_array | count | array_agg | array_agg +-----------------------------------------------+-------+-------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------- + 'serialize-nested-subbig-subbigabort--1 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subbigabort--1:1' | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subbigabort--1:5000' + 'serialize-nested-subbig-subbigabort-subbig-3 | 5000 | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subbigabort-subbig-3:5001' | table public.spill_test: INSERT: data[text]:'serialize-nested-subbig-subbigabort-subbig-3:10000' +(2 rows) + +DROP TABLE spill_test; +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/expected/stats.out b/contrib/test_decoding/expected/stats.out new file mode 100644 index 0000000..78d3642 --- /dev/null +++ b/contrib/test_decoding/expected/stats.out @@ -0,0 +1,149 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM + pg_create_logical_replication_slot('regression_slot_stats1', 'test_decoding') s1, + pg_create_logical_replication_slot('regression_slot_stats2', 'test_decoding') s2, + pg_create_logical_replication_slot('regression_slot_stats3', 'test_decoding') s3; + ?column? +---------- + init +(1 row) + +CREATE TABLE stats_test(data text); +-- non-spilled xact +SET logical_decoding_work_mem to '64MB'; +INSERT INTO stats_test values(1); +SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats1', NULL, NULL, 'skip-empty-xacts', '1'); + count +------- + 3 +(1 row) + +SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats2', NULL, NULL, 'skip-empty-xacts', '1'); + count +------- + 3 +(1 row) + +SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats3', NULL, NULL, 'skip-empty-xacts', '1'); + count +------- + 3 +(1 row) + +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count, total_txns > 0 AS total_txns, total_bytes > 0 AS total_bytes FROM pg_stat_replication_slots ORDER BY slot_name; + slot_name | spill_txns | spill_count | total_txns | total_bytes +------------------------+------------+-------------+------------+------------- + regression_slot_stats1 | t | t | t | t + regression_slot_stats2 | t | t | t | t + regression_slot_stats3 | t | t | t | t +(3 rows) + +RESET logical_decoding_work_mem; +-- reset stats for one slot, others should be unaffected +SELECT pg_stat_reset_replication_slot('regression_slot_stats1'); + pg_stat_reset_replication_slot +-------------------------------- + +(1 row) + +SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count, total_txns > 0 AS total_txns, total_bytes > 0 AS total_bytes FROM pg_stat_replication_slots ORDER BY slot_name; + slot_name | spill_txns | spill_count | total_txns | total_bytes +------------------------+------------+-------------+------------+------------- + regression_slot_stats1 | t | t | f | f + regression_slot_stats2 | t | t | t | t + regression_slot_stats3 | t | t | t | t +(3 rows) + +-- reset stats for all slots +SELECT pg_stat_reset_replication_slot(NULL); + pg_stat_reset_replication_slot +-------------------------------- + +(1 row) + +SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count, total_txns > 0 AS total_txns, total_bytes > 0 AS total_bytes FROM pg_stat_replication_slots ORDER BY slot_name; + slot_name | spill_txns | spill_count | total_txns | total_bytes +------------------------+------------+-------------+------------+------------- + regression_slot_stats1 | t | t | f | f + regression_slot_stats2 | t | t | f | f + regression_slot_stats3 | t | t | f | f +(3 rows) + +-- verify accessing/resetting stats for non-existent slot does something reasonable +SELECT * FROM pg_stat_get_replication_slot('do-not-exist'); + slot_name | spill_txns | spill_count | spill_bytes | stream_txns | stream_count | stream_bytes | total_txns | total_bytes | stats_reset +--------------+------------+-------------+-------------+-------------+--------------+--------------+------------+-------------+------------- + do-not-exist | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +(1 row) + +SELECT pg_stat_reset_replication_slot('do-not-exist'); +ERROR: replication slot "do-not-exist" does not exist +SELECT * FROM pg_stat_get_replication_slot('do-not-exist'); + slot_name | spill_txns | spill_count | spill_bytes | stream_txns | stream_count | stream_bytes | total_txns | total_bytes | stats_reset +--------------+------------+-------------+-------------+-------------+--------------+--------------+------------+-------------+------------- + do-not-exist | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +(1 row) + +-- spilling the xact +BEGIN; +INSERT INTO stats_test SELECT 'serialize-topbig--1:'||g.i FROM generate_series(1, 5000) g(i); +COMMIT; +SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot_stats1', NULL, NULL, 'skip-empty-xacts', '1'); + count +------- + 5002 +(1 row) + +-- Check stats. We can't test the exact stats count as that can vary if any +-- background transaction (say by autovacuum) happens in parallel to the main +-- transaction. +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +SELECT slot_name, spill_txns > 0 AS spill_txns, spill_count > 0 AS spill_count FROM pg_stat_replication_slots; + slot_name | spill_txns | spill_count +------------------------+------------+------------- + regression_slot_stats1 | t | t + regression_slot_stats2 | f | f + regression_slot_stats3 | f | f +(3 rows) + +-- Ensure stats can be repeatedly accessed using the same stats snapshot. See +-- https://postgr.es/m/20210317230447.c7uc4g3vbs4wi32i%40alap3.anarazel.de +BEGIN; +SELECT slot_name FROM pg_stat_replication_slots; + slot_name +------------------------ + regression_slot_stats1 + regression_slot_stats2 + regression_slot_stats3 +(3 rows) + +SELECT slot_name FROM pg_stat_replication_slots; + slot_name +------------------------ + regression_slot_stats1 + regression_slot_stats2 + regression_slot_stats3 +(3 rows) + +COMMIT; +DROP TABLE stats_test; +SELECT pg_drop_replication_slot('regression_slot_stats1'), + pg_drop_replication_slot('regression_slot_stats2'), + pg_drop_replication_slot('regression_slot_stats3'); + pg_drop_replication_slot | pg_drop_replication_slot | pg_drop_replication_slot +--------------------------+--------------------------+-------------------------- + | | +(1 row) + diff --git a/contrib/test_decoding/expected/stream.out b/contrib/test_decoding/expected/stream.out new file mode 100644 index 0000000..0f21dcb --- /dev/null +++ b/contrib/test_decoding/expected/stream.out @@ -0,0 +1,115 @@ +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +CREATE TABLE stream_test(data text); +-- consume DDL +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +-- streaming test with sub-transaction +BEGIN; +savepoint s1; +SELECT 'msg5' FROM pg_logical_emit_message(true, 'test', repeat('a', 50)); + ?column? +---------- + msg5 +(1 row) + +INSERT INTO stream_test SELECT repeat('a', 2000) || g.i FROM generate_series(1, 35) g(i); +TRUNCATE table stream_test; +rollback to s1; +INSERT INTO stream_test SELECT repeat('a', 10) || g.i FROM generate_series(1, 20) g(i); +COMMIT; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + data +---------------------------------------------------------- + streaming message: transactional: 1 prefix: test, sz: 50 + opening a streamed block for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + closing a streamed block for transaction + committing streamed transaction +(24 rows) + +-- streaming test for toast changes +ALTER TABLE stream_test ALTER COLUMN data set storage external; +-- consume DDL +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +INSERT INTO stream_test SELECT repeat('a', 6000) || g.i FROM generate_series(1, 10) g(i); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + data +------------------------------------------ + opening a streamed block for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + closing a streamed block for transaction + committing streamed transaction +(13 rows) + +-- streaming test for toast with multi-insert +\COPY stream_test FROM STDIN +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + data +------------------------------------------ + opening a streamed block for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + closing a streamed block for transaction + opening a streamed block for transaction + streaming change for transaction + closing a streamed block for transaction + committing streamed transaction +(17 rows) + +DROP TABLE stream_test; +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/expected/subxact_without_top.out b/contrib/test_decoding/expected/subxact_without_top.out new file mode 100644 index 0000000..4241b00 --- /dev/null +++ b/contrib/test_decoding/expected/subxact_without_top.out @@ -0,0 +1,49 @@ +Parsed test spec with 3 sessions + +starting permutation: s0_begin s0_first_subxact s2_checkpoint s1_begin s1_dml s0_many_subxacts s0_commit s2_checkpoint s2_get_changes_suppress_output s2_get_changes_suppress_output s1_commit s2_get_changes +step s0_begin: BEGIN; +step s0_first_subxact: + DO LANGUAGE plpgsql $$ + BEGIN + BEGIN + INSERT INTO harvest VALUES (41); + EXCEPTION WHEN OTHERS THEN RAISE; + END; + END $$; + +step s2_checkpoint: CHECKPOINT; +step s1_begin: BEGIN; +step s1_dml: INSERT INTO harvest VALUES (43); +step s0_many_subxacts: select subxacts(); +subxacts +-------- + +(1 row) + +step s0_commit: COMMIT; +step s2_checkpoint: CHECKPOINT; +step s2_get_changes_suppress_output: SELECT null n FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') GROUP BY n; +n +- + +(1 row) + +step s2_get_changes_suppress_output: SELECT null n FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') GROUP BY n; +n +- +(0 rows) + +step s1_commit: COMMIT; +step s2_get_changes: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +data +------------------------------------------------ +BEGIN +table public.harvest: INSERT: apples[integer]:43 +COMMIT +(3 rows) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/expected/time.out b/contrib/test_decoding/expected/time.out new file mode 100644 index 0000000..3b06849 --- /dev/null +++ b/contrib/test_decoding/expected/time.out @@ -0,0 +1,40 @@ +SET synchronous_commit = on; +CREATE TABLE test_time(data text); +-- remember the current time +SELECT set_config('test.time_before', NOW()::text, false) IS NOT NULL; + ?column? +---------- + t +(1 row) + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +-- a single transaction, to get the commit time +INSERT INTO test_time(data) VALUES (''); +-- parse the commit time from the changeset +SELECT set_config('test.time_after', regexp_replace(data, '^COMMIT \(at (.*)\)$', '\1'), false) IS NOT NULL +FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'include-timestamp', '1') +WHERE data ~ 'COMMIT' LIMIT 1; + ?column? +---------- + t +(1 row) + +-- ensure commit time is sane in relation to the previous time +SELECT (time_after - time_before) <= '10 minutes'::interval, time_after >= time_before +FROM (SELECT current_setting('test.time_after')::timestamptz AS time_after, (SELECT current_setting('test.time_before')::timestamptz) AS time_before) AS d; + ?column? | ?column? +----------+---------- + t | t +(1 row) + +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/expected/toast.out b/contrib/test_decoding/expected/toast.out new file mode 100644 index 0000000..a757e7d --- /dev/null +++ b/contrib/test_decoding/expected/toast.out @@ -0,0 +1,390 @@ +-- predictability +SET synchronous_commit = on; +DROP TABLE IF EXISTS xpto; +NOTICE: table "xpto" does not exist, skipping +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +CREATE SEQUENCE xpto_rand_seq START 79 INCREMENT 1499; -- portable "random" +CREATE TABLE xpto ( + id serial primary key, + toasted_col1 text, + rand1 float8 DEFAULT nextval('xpto_rand_seq'), + toasted_col2 text, + rand2 float8 DEFAULT nextval('xpto_rand_seq') +); +-- uncompressed external toast data +INSERT INTO xpto (toasted_col1, toasted_col2) SELECT string_agg(g.i::text, ''), string_agg((g.i*2)::text, '') FROM generate_series(1, 2000) g(i); +-- compressed external toast data +INSERT INTO xpto (toasted_col2) SELECT repeat(string_agg(to_char(g.i, 'FM0000'), ''), 50) FROM generate_series(1, 500) g(i); +-- update of existing column +UPDATE xpto SET toasted_col1 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i)) WHERE id = 1; +UPDATE xpto SET rand1 = 123.456 WHERE id = 1; +-- updating external via INSERT ... ON CONFLICT DO UPDATE +INSERT INTO xpto(id, toasted_col2) VALUES (2, 'toasted2-upsert') +ON CONFLICT (id) +DO UPDATE SET toasted_col2 = EXCLUDED.toasted_col2 || xpto.toasted_col2; +DELETE FROM xpto WHERE id = 1; +DROP TABLE IF EXISTS toasted_key; +NOTICE: table "toasted_key" does not exist, skipping +CREATE TABLE toasted_key ( + id serial, + toasted_key text PRIMARY KEY, + toasted_col1 text, + toasted_col2 text +); +ALTER TABLE toasted_key ALTER COLUMN toasted_key SET STORAGE EXTERNAL; +ALTER TABLE toasted_key ALTER COLUMN toasted_col1 SET STORAGE EXTERNAL; +INSERT INTO toasted_key(toasted_key, toasted_col1) VALUES(repeat('1234567890', 200), repeat('9876543210', 200)); +-- test update of a toasted key without changing it +UPDATE toasted_key SET toasted_col2 = toasted_col1; +-- test update of a toasted key, changing it +UPDATE toasted_key SET toasted_key = toasted_key || '1'; +DELETE FROM toasted_key; +-- Test that HEAP2_MULTI_INSERT insertions with and without toasted +-- columns are handled correctly +CREATE TABLE toasted_copy ( + id int primary key, -- no default, copy didn't use to handle that with multi inserts + data text +); +ALTER TABLE toasted_copy ALTER COLUMN data SET STORAGE EXTERNAL; +\copy toasted_copy FROM STDIN +SELECT substr(data, 1, 200) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + substr +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.xpto: INSERT: id[integer]:1 toasted_col1[text]:'1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 + COMMIT + BEGIN + table public.xpto: INSERT: id[integer]:2 toasted_col1[text]:null rand1[double precision]:3077 toasted_col2[text]:'00010002000300040005000600070008000900100011001200130014001500160017001800190020002100 + COMMIT + BEGIN + table public.xpto: UPDATE: id[integer]:1 toasted_col1[text]:'1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 + COMMIT + BEGIN + table public.xpto: UPDATE: id[integer]:1 toasted_col1[text]:unchanged-toast-datum rand1[double precision]:123.456 toasted_col2[text]:unchanged-toast-datum rand2[double precision]:1578 + COMMIT + BEGIN + table public.xpto: UPDATE: id[integer]:2 toasted_col1[text]:null rand1[double precision]:3077 toasted_col2[text]:'toasted2-upsert00010002000300040005000600070008000900100011001200130014001500160017001 + COMMIT + BEGIN + table public.xpto: DELETE: id[integer]:1 + COMMIT + BEGIN + table public.toasted_key: INSERT: id[integer]:1 toasted_key[text]:'1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123 + COMMIT + BEGIN + table public.toasted_key: UPDATE: old-key: toasted_key[text]:'123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 + COMMIT + BEGIN + table public.toasted_key: UPDATE: old-key: toasted_key[text]:'123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 + COMMIT + BEGIN + table public.toasted_key: DELETE: toasted_key[text]:'123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 + COMMIT + BEGIN + table public.toasted_copy: INSERT: id[integer]:1 data[text]:'untoasted1' + table public.toasted_copy: INSERT: id[integer]:2 data[text]:'toasted1-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + table public.toasted_copy: INSERT: id[integer]:3 data[text]:'untoasted2' + table public.toasted_copy: INSERT: id[integer]:4 data[text]:'toasted2-1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + table public.toasted_copy: INSERT: id[integer]:5 data[text]:'untoasted3' + table public.toasted_copy: INSERT: id[integer]:6 data[text]:'untoasted4' + table public.toasted_copy: INSERT: id[integer]:7 data[text]:'untoasted5' + table public.toasted_copy: INSERT: id[integer]:8 data[text]:'untoasted6' + table public.toasted_copy: INSERT: id[integer]:9 data[text]:'untoasted7' + table public.toasted_copy: INSERT: id[integer]:10 data[text]:'untoasted8' + table public.toasted_copy: INSERT: id[integer]:11 data[text]:'untoasted9' + table public.toasted_copy: INSERT: id[integer]:12 data[text]:'untoasted10' + table public.toasted_copy: INSERT: id[integer]:13 data[text]:'untoasted11' + table public.toasted_copy: INSERT: id[integer]:14 data[text]:'untoasted12' + table public.toasted_copy: INSERT: id[integer]:15 data[text]:'untoasted13' + table public.toasted_copy: INSERT: id[integer]:16 data[text]:'untoasted14' + table public.toasted_copy: INSERT: id[integer]:17 data[text]:'untoasted15' + table public.toasted_copy: INSERT: id[integer]:18 data[text]:'untoasted16' + table public.toasted_copy: INSERT: id[integer]:19 data[text]:'untoasted17' + table public.toasted_copy: INSERT: id[integer]:20 data[text]:'untoasted18' + table public.toasted_copy: INSERT: id[integer]:21 data[text]:'untoasted19' + table public.toasted_copy: INSERT: id[integer]:22 data[text]:'untoasted20' + table public.toasted_copy: INSERT: id[integer]:23 data[text]:'untoasted21' + table public.toasted_copy: INSERT: id[integer]:24 data[text]:'untoasted22' + table public.toasted_copy: INSERT: id[integer]:25 data[text]:'untoasted23' + table public.toasted_copy: INSERT: id[integer]:26 data[text]:'untoasted24' + table public.toasted_copy: INSERT: id[integer]:27 data[text]:'untoasted25' + table public.toasted_copy: INSERT: id[integer]:28 data[text]:'untoasted26' + table public.toasted_copy: INSERT: id[integer]:29 data[text]:'untoasted27' + table public.toasted_copy: INSERT: id[integer]:30 data[text]:'untoasted28' + table public.toasted_copy: INSERT: id[integer]:31 data[text]:'untoasted29' + table public.toasted_copy: INSERT: id[integer]:32 data[text]:'untoasted30' + table public.toasted_copy: INSERT: id[integer]:33 data[text]:'untoasted31' + table public.toasted_copy: INSERT: id[integer]:34 data[text]:'untoasted32' + table public.toasted_copy: INSERT: id[integer]:35 data[text]:'untoasted33' + table public.toasted_copy: INSERT: id[integer]:36 data[text]:'untoasted34' + table public.toasted_copy: INSERT: id[integer]:37 data[text]:'untoasted35' + table public.toasted_copy: INSERT: id[integer]:38 data[text]:'untoasted36' + table public.toasted_copy: INSERT: id[integer]:39 data[text]:'untoasted37' + table public.toasted_copy: INSERT: id[integer]:40 data[text]:'untoasted38' + table public.toasted_copy: INSERT: id[integer]:41 data[text]:'untoasted39' + table public.toasted_copy: INSERT: id[integer]:42 data[text]:'untoasted40' + table public.toasted_copy: INSERT: id[integer]:43 data[text]:'untoasted41' + table public.toasted_copy: INSERT: id[integer]:44 data[text]:'untoasted42' + table public.toasted_copy: INSERT: id[integer]:45 data[text]:'untoasted43' + table public.toasted_copy: INSERT: id[integer]:46 data[text]:'untoasted44' + table public.toasted_copy: INSERT: id[integer]:47 data[text]:'untoasted45' + table public.toasted_copy: INSERT: id[integer]:48 data[text]:'untoasted46' + table public.toasted_copy: INSERT: id[integer]:49 data[text]:'untoasted47' + table public.toasted_copy: INSERT: id[integer]:50 data[text]:'untoasted48' + table public.toasted_copy: INSERT: id[integer]:51 data[text]:'untoasted49' + table public.toasted_copy: INSERT: id[integer]:52 data[text]:'untoasted50' + table public.toasted_copy: INSERT: id[integer]:53 data[text]:'untoasted51' + table public.toasted_copy: INSERT: id[integer]:54 data[text]:'untoasted52' + table public.toasted_copy: INSERT: id[integer]:55 data[text]:'untoasted53' + table public.toasted_copy: INSERT: id[integer]:56 data[text]:'untoasted54' + table public.toasted_copy: INSERT: id[integer]:57 data[text]:'untoasted55' + table public.toasted_copy: INSERT: id[integer]:58 data[text]:'untoasted56' + table public.toasted_copy: INSERT: id[integer]:59 data[text]:'untoasted57' + table public.toasted_copy: INSERT: id[integer]:60 data[text]:'untoasted58' + table public.toasted_copy: INSERT: id[integer]:61 data[text]:'untoasted59' + table public.toasted_copy: INSERT: id[integer]:62 data[text]:'untoasted60' + table public.toasted_copy: INSERT: id[integer]:63 data[text]:'untoasted61' + table public.toasted_copy: INSERT: id[integer]:64 data[text]:'untoasted62' + table public.toasted_copy: INSERT: id[integer]:65 data[text]:'untoasted63' + table public.toasted_copy: INSERT: id[integer]:66 data[text]:'untoasted64' + table public.toasted_copy: INSERT: id[integer]:67 data[text]:'untoasted65' + table public.toasted_copy: INSERT: id[integer]:68 data[text]:'untoasted66' + table public.toasted_copy: INSERT: id[integer]:69 data[text]:'untoasted67' + table public.toasted_copy: INSERT: id[integer]:70 data[text]:'untoasted68' + table public.toasted_copy: INSERT: id[integer]:71 data[text]:'untoasted69' + table public.toasted_copy: INSERT: id[integer]:72 data[text]:'untoasted70' + table public.toasted_copy: INSERT: id[integer]:73 data[text]:'untoasted71' + table public.toasted_copy: INSERT: id[integer]:74 data[text]:'untoasted72' + table public.toasted_copy: INSERT: id[integer]:75 data[text]:'untoasted73' + table public.toasted_copy: INSERT: id[integer]:76 data[text]:'untoasted74' + table public.toasted_copy: INSERT: id[integer]:77 data[text]:'untoasted75' + table public.toasted_copy: INSERT: id[integer]:78 data[text]:'untoasted76' + table public.toasted_copy: INSERT: id[integer]:79 data[text]:'untoasted77' + table public.toasted_copy: INSERT: id[integer]:80 data[text]:'untoasted78' + table public.toasted_copy: INSERT: id[integer]:81 data[text]:'untoasted79' + table public.toasted_copy: INSERT: id[integer]:82 data[text]:'untoasted80' + table public.toasted_copy: INSERT: id[integer]:83 data[text]:'untoasted81' + table public.toasted_copy: INSERT: id[integer]:84 data[text]:'untoasted82' + table public.toasted_copy: INSERT: id[integer]:85 data[text]:'untoasted83' + table public.toasted_copy: INSERT: id[integer]:86 data[text]:'untoasted84' + table public.toasted_copy: INSERT: id[integer]:87 data[text]:'untoasted85' + table public.toasted_copy: INSERT: id[integer]:88 data[text]:'untoasted86' + table public.toasted_copy: INSERT: id[integer]:89 data[text]:'untoasted87' + table public.toasted_copy: INSERT: id[integer]:90 data[text]:'untoasted88' + table public.toasted_copy: INSERT: id[integer]:91 data[text]:'untoasted89' + table public.toasted_copy: INSERT: id[integer]:92 data[text]:'untoasted90' + table public.toasted_copy: INSERT: id[integer]:93 data[text]:'untoasted91' + table public.toasted_copy: INSERT: id[integer]:94 data[text]:'untoasted92' + table public.toasted_copy: INSERT: id[integer]:95 data[text]:'untoasted93' + table public.toasted_copy: INSERT: id[integer]:96 data[text]:'untoasted94' + table public.toasted_copy: INSERT: id[integer]:97 data[text]:'untoasted95' + table public.toasted_copy: INSERT: id[integer]:98 data[text]:'untoasted96' + table public.toasted_copy: INSERT: id[integer]:99 data[text]:'untoasted97' + table public.toasted_copy: INSERT: id[integer]:100 data[text]:'untoasted98' + table public.toasted_copy: INSERT: id[integer]:101 data[text]:'untoasted99' + table public.toasted_copy: INSERT: id[integer]:102 data[text]:'untoasted100' + table public.toasted_copy: INSERT: id[integer]:103 data[text]:'untoasted101' + table public.toasted_copy: INSERT: id[integer]:104 data[text]:'untoasted102' + table public.toasted_copy: INSERT: id[integer]:105 data[text]:'untoasted103' + table public.toasted_copy: INSERT: id[integer]:106 data[text]:'untoasted104' + table public.toasted_copy: INSERT: id[integer]:107 data[text]:'untoasted105' + table public.toasted_copy: INSERT: id[integer]:108 data[text]:'untoasted106' + table public.toasted_copy: INSERT: id[integer]:109 data[text]:'untoasted107' + table public.toasted_copy: INSERT: id[integer]:110 data[text]:'untoasted108' + table public.toasted_copy: INSERT: id[integer]:111 data[text]:'untoasted109' + table public.toasted_copy: INSERT: id[integer]:112 data[text]:'untoasted110' + table public.toasted_copy: INSERT: id[integer]:113 data[text]:'untoasted111' + table public.toasted_copy: INSERT: id[integer]:114 data[text]:'untoasted112' + table public.toasted_copy: INSERT: id[integer]:115 data[text]:'untoasted113' + table public.toasted_copy: INSERT: id[integer]:116 data[text]:'untoasted114' + table public.toasted_copy: INSERT: id[integer]:117 data[text]:'untoasted115' + table public.toasted_copy: INSERT: id[integer]:118 data[text]:'untoasted116' + table public.toasted_copy: INSERT: id[integer]:119 data[text]:'untoasted117' + table public.toasted_copy: INSERT: id[integer]:120 data[text]:'untoasted118' + table public.toasted_copy: INSERT: id[integer]:121 data[text]:'untoasted119' + table public.toasted_copy: INSERT: id[integer]:122 data[text]:'untoasted120' + table public.toasted_copy: INSERT: id[integer]:123 data[text]:'untoasted121' + table public.toasted_copy: INSERT: id[integer]:124 data[text]:'untoasted122' + table public.toasted_copy: INSERT: id[integer]:125 data[text]:'untoasted123' + table public.toasted_copy: INSERT: id[integer]:126 data[text]:'untoasted124' + table public.toasted_copy: INSERT: id[integer]:127 data[text]:'untoasted125' + table public.toasted_copy: INSERT: id[integer]:128 data[text]:'untoasted126' + table public.toasted_copy: INSERT: id[integer]:129 data[text]:'untoasted127' + table public.toasted_copy: INSERT: id[integer]:130 data[text]:'untoasted128' + table public.toasted_copy: INSERT: id[integer]:131 data[text]:'untoasted129' + table public.toasted_copy: INSERT: id[integer]:132 data[text]:'untoasted130' + table public.toasted_copy: INSERT: id[integer]:133 data[text]:'untoasted131' + table public.toasted_copy: INSERT: id[integer]:134 data[text]:'untoasted132' + table public.toasted_copy: INSERT: id[integer]:135 data[text]:'untoasted133' + table public.toasted_copy: INSERT: id[integer]:136 data[text]:'untoasted134' + table public.toasted_copy: INSERT: id[integer]:137 data[text]:'untoasted135' + table public.toasted_copy: INSERT: id[integer]:138 data[text]:'untoasted136' + table public.toasted_copy: INSERT: id[integer]:139 data[text]:'untoasted137' + table public.toasted_copy: INSERT: id[integer]:140 data[text]:'untoasted138' + table public.toasted_copy: INSERT: id[integer]:141 data[text]:'untoasted139' + table public.toasted_copy: INSERT: id[integer]:142 data[text]:'untoasted140' + table public.toasted_copy: INSERT: id[integer]:143 data[text]:'untoasted141' + table public.toasted_copy: INSERT: id[integer]:144 data[text]:'untoasted142' + table public.toasted_copy: INSERT: id[integer]:145 data[text]:'untoasted143' + table public.toasted_copy: INSERT: id[integer]:146 data[text]:'untoasted144' + table public.toasted_copy: INSERT: id[integer]:147 data[text]:'untoasted145' + table public.toasted_copy: INSERT: id[integer]:148 data[text]:'untoasted146' + table public.toasted_copy: INSERT: id[integer]:149 data[text]:'untoasted147' + table public.toasted_copy: INSERT: id[integer]:150 data[text]:'untoasted148' + table public.toasted_copy: INSERT: id[integer]:151 data[text]:'untoasted149' + table public.toasted_copy: INSERT: id[integer]:152 data[text]:'untoasted150' + table public.toasted_copy: INSERT: id[integer]:153 data[text]:'untoasted151' + table public.toasted_copy: INSERT: id[integer]:154 data[text]:'untoasted152' + table public.toasted_copy: INSERT: id[integer]:155 data[text]:'untoasted153' + table public.toasted_copy: INSERT: id[integer]:156 data[text]:'untoasted154' + table public.toasted_copy: INSERT: id[integer]:157 data[text]:'untoasted155' + table public.toasted_copy: INSERT: id[integer]:158 data[text]:'untoasted156' + table public.toasted_copy: INSERT: id[integer]:159 data[text]:'untoasted157' + table public.toasted_copy: INSERT: id[integer]:160 data[text]:'untoasted158' + table public.toasted_copy: INSERT: id[integer]:161 data[text]:'untoasted159' + table public.toasted_copy: INSERT: id[integer]:162 data[text]:'untoasted160' + table public.toasted_copy: INSERT: id[integer]:163 data[text]:'untoasted161' + table public.toasted_copy: INSERT: id[integer]:164 data[text]:'untoasted162' + table public.toasted_copy: INSERT: id[integer]:165 data[text]:'untoasted163' + table public.toasted_copy: INSERT: id[integer]:166 data[text]:'untoasted164' + table public.toasted_copy: INSERT: id[integer]:167 data[text]:'untoasted165' + table public.toasted_copy: INSERT: id[integer]:168 data[text]:'untoasted166' + table public.toasted_copy: INSERT: id[integer]:169 data[text]:'untoasted167' + table public.toasted_copy: INSERT: id[integer]:170 data[text]:'untoasted168' + table public.toasted_copy: INSERT: id[integer]:171 data[text]:'untoasted169' + table public.toasted_copy: INSERT: id[integer]:172 data[text]:'untoasted170' + table public.toasted_copy: INSERT: id[integer]:173 data[text]:'untoasted171' + table public.toasted_copy: INSERT: id[integer]:174 data[text]:'untoasted172' + table public.toasted_copy: INSERT: id[integer]:175 data[text]:'untoasted173' + table public.toasted_copy: INSERT: id[integer]:176 data[text]:'untoasted174' + table public.toasted_copy: INSERT: id[integer]:177 data[text]:'untoasted175' + table public.toasted_copy: INSERT: id[integer]:178 data[text]:'untoasted176' + table public.toasted_copy: INSERT: id[integer]:179 data[text]:'untoasted177' + table public.toasted_copy: INSERT: id[integer]:180 data[text]:'untoasted178' + table public.toasted_copy: INSERT: id[integer]:181 data[text]:'untoasted179' + table public.toasted_copy: INSERT: id[integer]:182 data[text]:'untoasted180' + table public.toasted_copy: INSERT: id[integer]:183 data[text]:'untoasted181' + table public.toasted_copy: INSERT: id[integer]:184 data[text]:'untoasted182' + table public.toasted_copy: INSERT: id[integer]:185 data[text]:'untoasted183' + table public.toasted_copy: INSERT: id[integer]:186 data[text]:'untoasted184' + table public.toasted_copy: INSERT: id[integer]:187 data[text]:'untoasted185' + table public.toasted_copy: INSERT: id[integer]:188 data[text]:'untoasted186' + table public.toasted_copy: INSERT: id[integer]:189 data[text]:'untoasted187' + table public.toasted_copy: INSERT: id[integer]:190 data[text]:'untoasted188' + table public.toasted_copy: INSERT: id[integer]:191 data[text]:'untoasted189' + table public.toasted_copy: INSERT: id[integer]:192 data[text]:'untoasted190' + table public.toasted_copy: INSERT: id[integer]:193 data[text]:'untoasted191' + table public.toasted_copy: INSERT: id[integer]:194 data[text]:'untoasted192' + table public.toasted_copy: INSERT: id[integer]:195 data[text]:'untoasted193' + table public.toasted_copy: INSERT: id[integer]:196 data[text]:'untoasted194' + table public.toasted_copy: INSERT: id[integer]:197 data[text]:'untoasted195' + table public.toasted_copy: INSERT: id[integer]:198 data[text]:'untoasted196' + table public.toasted_copy: INSERT: id[integer]:199 data[text]:'untoasted197' + table public.toasted_copy: INSERT: id[integer]:200 data[text]:'untoasted198' + table public.toasted_copy: INSERT: id[integer]:201 data[text]:'toasted3-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 + table public.toasted_copy: INSERT: id[integer]:202 data[text]:'untoasted199' + table public.toasted_copy: INSERT: id[integer]:203 data[text]:'untoasted200' + COMMIT +(235 rows) + +-- test we can decode "old" tuples bigger than the max heap tuple size correctly +DROP TABLE IF EXISTS toasted_several; +NOTICE: table "toasted_several" does not exist, skipping +CREATE TABLE toasted_several ( + id serial unique not null, + toasted_key text primary key, + toasted_col1 text, + toasted_col2 text +); +ALTER TABLE toasted_several REPLICA IDENTITY FULL; +ALTER TABLE toasted_several ALTER COLUMN toasted_key SET STORAGE EXTERNAL; +ALTER TABLE toasted_several ALTER COLUMN toasted_col1 SET STORAGE EXTERNAL; +ALTER TABLE toasted_several ALTER COLUMN toasted_col2 SET STORAGE EXTERNAL; +-- Change the storage of the index back to EXTENDED, separately from +-- the table. This is currently not doable via DDL, but it is +-- supported internally. +UPDATE pg_attribute SET attstorage = 'x' WHERE attrelid = 'toasted_several_pkey'::regclass AND attname = 'toasted_key'; +INSERT INTO toasted_several(toasted_key) VALUES(repeat('9876543210', 10000)); +SELECT pg_column_size(toasted_key) > 2^16 FROM toasted_several; + ?column? +---------- + t +(1 row) + +SELECT regexp_replace(data, '^(.{100}).*(.{100})$', '\1..\2') FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + regexp_replace +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + BEGIN + table public.toasted_several: INSERT: id[integer]:1 toasted_key[text]:'98765432109876543210987654321..098765432109876543210987654321098765432109876543210' toasted_col1[text]:null toasted_col2[text]:null + COMMIT +(3 rows) + +-- test update of a toasted key without changing it +UPDATE toasted_several SET toasted_col1 = toasted_key; +UPDATE toasted_several SET toasted_col2 = toasted_col1; +SELECT regexp_replace(data, '^(.{100}).*(.{100})$', '\1..\2') FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + regexp_replace +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + BEGIN + table public.toasted_several: INSERT: id[integer]:1 toasted_key[text]:'98765432109876543210987654321..098765432109876543210987654321098765432109876543210' toasted_col1[text]:null toasted_col2[text]:null + COMMIT + BEGIN + table public.toasted_several: UPDATE: old-key: id[integer]:1 toasted_key[text]:'98765432109876543210..432109876543210987654321098765432109876543210987654321098765432109876543210' toasted_col2[text]:null + COMMIT + BEGIN + table public.toasted_several: UPDATE: old-key: id[integer]:1 toasted_key[text]:'98765432109876543210..876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210' + COMMIT +(9 rows) + +/* + * update with large tuplebuf, in a transaction large enough to force to spool to disk + */ +BEGIN; +INSERT INTO toasted_several(toasted_key) SELECT * FROM generate_series(1, 10234); +UPDATE toasted_several SET toasted_col1 = toasted_col2 WHERE id = 1; +DELETE FROM toasted_several WHERE id = 1; +COMMIT; +DROP TABLE toasted_several; +SELECT regexp_replace(data, '^(.{100}).*(.{100})$', '\1..\2') FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') +WHERE data NOT LIKE '%INSERT: %'; + regexp_replace +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + BEGIN + table public.toasted_several: UPDATE: old-key: id[integer]:1 toasted_key[text]:'98765432109876543210..7654321098765432109876543210987654321098765432109876543210' toasted_col2[text]:unchanged-toast-datum + table public.toasted_several: DELETE: id[integer]:1 toasted_key[text]:'98765432109876543210987654321..876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210' + COMMIT +(4 rows) + +/* + * Test decoding relation rewrite with toast. The insert into tbl2 within the + * same transaction is there to check that there is no remaining toast_hash not + * being reset. + */ +CREATE TABLE tbl1 (a INT, b TEXT); +CREATE TABLE tbl2 (a INT); +ALTER TABLE tbl1 ALTER COLUMN b SET STORAGE EXTERNAL; +BEGIN; +INSERT INTO tbl1 VALUES(1, repeat('a', 4000)) ; +ALTER TABLE tbl1 ADD COLUMN id serial primary key; +INSERT INTO tbl2 VALUES(1); +commit; +SELECT substr(data, 1, 200) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + substr +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + BEGIN + table public.tbl1: INSERT: a[integer]:1 b[text]:'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + table public.tbl2: INSERT: a[integer]:1 + COMMIT +(4 rows) + +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/expected/truncate.out b/contrib/test_decoding/expected/truncate.out new file mode 100644 index 0000000..e64d377 --- /dev/null +++ b/contrib/test_decoding/expected/truncate.out @@ -0,0 +1,33 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +CREATE TABLE tab1 (id serial unique, data int); +CREATE TABLE tab2 (a int primary key, b int); +TRUNCATE tab1; +TRUNCATE tab1, tab1 RESTART IDENTITY CASCADE; +TRUNCATE tab1, tab2; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------------------------ + BEGIN + table public.tab1: TRUNCATE: (no-flags) + COMMIT + BEGIN + table public.tab1: TRUNCATE: restart_seqs cascade + COMMIT + BEGIN + table public.tab1, public.tab2: TRUNCATE: (no-flags) + COMMIT +(9 rows) + +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/expected/twophase.out b/contrib/test_decoding/expected/twophase.out new file mode 100644 index 0000000..e89dc74 --- /dev/null +++ b/contrib/test_decoding/expected/twophase.out @@ -0,0 +1,223 @@ +-- Test prepared transactions. When two-phase-commit is enabled, transactions are +-- decoded at PREPARE time rather than at COMMIT PREPARED time. +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding', false, true); + ?column? +---------- + init +(1 row) + +CREATE TABLE test_prepared1(id integer primary key); +CREATE TABLE test_prepared2(id integer primary key); +-- Test that decoding happens at PREPARE time when two-phase-commit is enabled. +-- Decoding after COMMIT PREPARED must have all the commands in the transaction. +BEGIN; +INSERT INTO test_prepared1 VALUES (1); +INSERT INTO test_prepared1 VALUES (2); +-- should show nothing because the xact has not been prepared yet. +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +PREPARE TRANSACTION 'test_prepared#1'; +-- should show both the above inserts and the PREPARE TRANSACTION. +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +---------------------------------------------------- + BEGIN + table public.test_prepared1: INSERT: id[integer]:1 + table public.test_prepared1: INSERT: id[integer]:2 + PREPARE TRANSACTION 'test_prepared#1' +(4 rows) + +COMMIT PREPARED 'test_prepared#1'; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +----------------------------------- + COMMIT PREPARED 'test_prepared#1' +(1 row) + +-- Test that rollback of a prepared xact is decoded. +BEGIN; +INSERT INTO test_prepared1 VALUES (3); +PREPARE TRANSACTION 'test_prepared#2'; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +---------------------------------------------------- + BEGIN + table public.test_prepared1: INSERT: id[integer]:3 + PREPARE TRANSACTION 'test_prepared#2' +(3 rows) + +ROLLBACK PREPARED 'test_prepared#2'; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------- + ROLLBACK PREPARED 'test_prepared#2' +(1 row) + +-- Test prepare of a xact containing ddl. Leaving xact uncommitted for next test. +BEGIN; +ALTER TABLE test_prepared1 ADD COLUMN data text; +INSERT INTO test_prepared1 VALUES (4, 'frakbar'); +PREPARE TRANSACTION 'test_prepared#3'; +-- confirm that exclusive lock from the ALTER command is held on test_prepared1 table +SELECT 'test_prepared_1' AS relation, locktype, mode +FROM pg_locks +WHERE locktype = 'relation' + AND relation = 'test_prepared1'::regclass; + relation | locktype | mode +-----------------+----------+--------------------- + test_prepared_1 | relation | RowExclusiveLock + test_prepared_1 | relation | AccessExclusiveLock +(2 rows) + +-- The insert should show the newly altered column but not the DDL. +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------------------------------------------- + BEGIN + table public.test_prepared1: INSERT: id[integer]:4 data[text]:'frakbar' + PREPARE TRANSACTION 'test_prepared#3' +(3 rows) + +-- Test that we decode correctly while an uncommitted prepared xact +-- with ddl exists. +-- +-- Use a separate table for the concurrent transaction because the lock from +-- the ALTER will stop us inserting into the other one. +-- +INSERT INTO test_prepared2 VALUES (5); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +---------------------------------------------------- + BEGIN + table public.test_prepared2: INSERT: id[integer]:5 + COMMIT +(3 rows) + +COMMIT PREPARED 'test_prepared#3'; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +----------------------------------- + COMMIT PREPARED 'test_prepared#3' +(1 row) + +-- make sure stuff still works +INSERT INTO test_prepared1 VALUES (6); +INSERT INTO test_prepared2 VALUES (7); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +-------------------------------------------------------------------- + BEGIN + table public.test_prepared1: INSERT: id[integer]:6 data[text]:null + COMMIT + BEGIN + table public.test_prepared2: INSERT: id[integer]:7 + COMMIT +(6 rows) + +-- Check 'CLUSTER' (as operation that hold exclusive lock) doesn't block +-- logical decoding. +BEGIN; +INSERT INTO test_prepared1 VALUES (8, 'othercol'); +CLUSTER test_prepared1 USING test_prepared1_pkey; +INSERT INTO test_prepared1 VALUES (9, 'othercol2'); +PREPARE TRANSACTION 'test_prepared_lock'; +SELECT 'test_prepared1' AS relation, locktype, mode +FROM pg_locks +WHERE locktype = 'relation' + AND relation = 'test_prepared1'::regclass; + relation | locktype | mode +----------------+----------+--------------------- + test_prepared1 | relation | RowExclusiveLock + test_prepared1 | relation | ShareLock + test_prepared1 | relation | AccessExclusiveLock +(3 rows) + +-- The above CLUSTER command shouldn't cause a timeout on 2pc decoding. +\set env_timeout '' +\getenv env_timeout PG_TEST_TIMEOUT_DEFAULT +SELECT COALESCE(NULLIF(:'env_timeout', ''), '180') || 's' AS timeout \gset +SET statement_timeout = :'timeout'; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +--------------------------------------------------------------------------- + BEGIN + table public.test_prepared1: INSERT: id[integer]:8 data[text]:'othercol' + table public.test_prepared1: INSERT: id[integer]:9 data[text]:'othercol2' + PREPARE TRANSACTION 'test_prepared_lock' +(4 rows) + +RESET statement_timeout; +COMMIT PREPARED 'test_prepared_lock'; +-- consume the commit +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +-------------------------------------- + COMMIT PREPARED 'test_prepared_lock' +(1 row) + +-- Test savepoints and sub-xacts. Creating savepoints will create +-- sub-xacts implicitly. +BEGIN; +CREATE TABLE test_prepared_savepoint (a int); +INSERT INTO test_prepared_savepoint VALUES (1); +SAVEPOINT test_savepoint; +INSERT INTO test_prepared_savepoint VALUES (2); +ROLLBACK TO SAVEPOINT test_savepoint; +PREPARE TRANSACTION 'test_prepared_savepoint'; +-- should show only 1, not 2 +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------------------------------ + BEGIN + table public.test_prepared_savepoint: INSERT: a[integer]:1 + PREPARE TRANSACTION 'test_prepared_savepoint' +(3 rows) + +COMMIT PREPARED 'test_prepared_savepoint'; +-- consume the commit +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------------- + COMMIT PREPARED 'test_prepared_savepoint' +(1 row) + +-- Test that a GID containing "_nodecode" gets decoded at commit prepared time. +BEGIN; +INSERT INTO test_prepared1 VALUES (20); +PREPARE TRANSACTION 'test_prepared_nodecode'; +-- should show nothing +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +COMMIT PREPARED 'test_prepared_nodecode'; +-- should be decoded now +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +--------------------------------------------------------------------- + BEGIN + table public.test_prepared1: INSERT: id[integer]:20 data[text]:null + COMMIT +(3 rows) + +-- Test 8: +-- cleanup and make sure results are also empty +DROP TABLE test_prepared1; +DROP TABLE test_prepared2; +-- show results. There should be nothing to show +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/expected/twophase_snapshot.out b/contrib/test_decoding/expected/twophase_snapshot.out new file mode 100644 index 0000000..f555ffd --- /dev/null +++ b/contrib/test_decoding/expected/twophase_snapshot.out @@ -0,0 +1,53 @@ +Parsed test spec with 3 sessions + +starting permutation: s2b s2txid s1init s3b s3txid s2c s2b s2insert s2p s3c s1insert s1start s2cp s1start +step s2b: BEGIN; +step s2txid: SELECT pg_current_xact_id() IS NULL; +?column? +-------- +f +(1 row) + +step s1init: SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding', false, true); +step s3b: BEGIN; +step s3txid: SELECT pg_current_xact_id() IS NULL; +?column? +-------- +f +(1 row) + +step s2c: COMMIT; +step s2b: BEGIN; +step s2insert: INSERT INTO do_write DEFAULT VALUES; +step s2p: PREPARE TRANSACTION 'test1'; +step s3c: COMMIT; +step s1init: <... completed> +?column? +-------- +init +(1 row) + +step s1insert: INSERT INTO do_write DEFAULT VALUES; +step s1start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false', 'skip-empty-xacts', '1'); +data +-------------------------------------------- +BEGIN +table public.do_write: INSERT: id[integer]:2 +COMMIT +(3 rows) + +step s2cp: COMMIT PREPARED 'test1'; +step s1start: SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false', 'skip-empty-xacts', '1'); +data +-------------------------------------------- +BEGIN +table public.do_write: INSERT: id[integer]:1 +PREPARE TRANSACTION 'test1' +COMMIT PREPARED 'test1' +(4 rows) + +?column? +-------- +stop +(1 row) + diff --git a/contrib/test_decoding/expected/twophase_stream.out b/contrib/test_decoding/expected/twophase_stream.out new file mode 100644 index 0000000..b08bb0e --- /dev/null +++ b/contrib/test_decoding/expected/twophase_stream.out @@ -0,0 +1,125 @@ +-- Test streaming of two-phase commits +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding', false, true); + ?column? +---------- + init +(1 row) + +CREATE TABLE stream_test(data text); +-- consume DDL +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------ +(0 rows) + +-- streaming test with sub-transaction and PREPARE/COMMIT PREPARED +BEGIN; +SAVEPOINT s1; +SELECT 'msg5' FROM pg_logical_emit_message(true, 'test', repeat('a', 50)); + ?column? +---------- + msg5 +(1 row) + +INSERT INTO stream_test SELECT repeat('a', 2000) || g.i FROM generate_series(1, 35) g(i); +TRUNCATE table stream_test; +ROLLBACK TO s1; +INSERT INTO stream_test SELECT repeat('a', 10) || g.i FROM generate_series(1, 20) g(i); +PREPARE TRANSACTION 'test1'; +-- should show the inserts after a ROLLBACK +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + data +---------------------------------------------------------- + streaming message: transactional: 1 prefix: test, sz: 50 + opening a streamed block for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + streaming change for transaction + closing a streamed block for transaction + preparing streamed transaction 'test1' +(24 rows) + +COMMIT PREPARED 'test1'; +--should show the COMMIT PREPARED and the other changes in the transaction +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + data +------------------------- + COMMIT PREPARED 'test1' +(1 row) + +-- streaming test with sub-transaction and PREPARE/COMMIT PREPARED but with +-- filtered gid. gids with '_nodecode' will not be decoded at prepare time. +BEGIN; +SAVEPOINT s1; +SELECT 'msg5' FROM pg_logical_emit_message(true, 'test', repeat('a', 50)); + ?column? +---------- + msg5 +(1 row) + +INSERT INTO stream_test SELECT repeat('a', 2000) || g.i FROM generate_series(1, 35) g(i); +TRUNCATE table stream_test; +ROLLBACK to s1; +INSERT INTO stream_test SELECT repeat('a', 10) || g.i FROM generate_series(1, 20) g(i); +PREPARE TRANSACTION 'test1_nodecode'; +-- should NOT show inserts after a ROLLBACK +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + data +---------------------------------------------------------- + streaming message: transactional: 1 prefix: test, sz: 50 +(1 row) + +COMMIT PREPARED 'test1_nodecode'; +-- should show the inserts but not show a COMMIT PREPARED but a COMMIT +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + data +------------------------------------------------------------- + BEGIN + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa1' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa2' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa3' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa4' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa5' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa6' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa7' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa8' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa9' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa10' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa11' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa12' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa13' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa14' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa15' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa16' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa17' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa18' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa19' + table public.stream_test: INSERT: data[text]:'aaaaaaaaaa20' + COMMIT +(22 rows) + +DROP TABLE stream_test; +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/expected/xact.out b/contrib/test_decoding/expected/xact.out new file mode 100644 index 0000000..ec47450 --- /dev/null +++ b/contrib/test_decoding/expected/xact.out @@ -0,0 +1,64 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + ?column? +---------- + init +(1 row) + +CREATE TABLE xact_test(data text); +INSERT INTO xact_test VALUES ('before-test'); +-- bug #13844, xids in non-decoded records need to be inspected +BEGIN; +-- perform operation in xact that creates and logs xid, but isn't decoded +SELECT * FROM xact_test FOR UPDATE; + data +------------- + before-test +(1 row) + +SAVEPOINT foo; +-- and now actually insert in subxact, xid is expected to be known +INSERT INTO xact_test VALUES ('after-assignment'); +COMMIT; +-- and now show those changes +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +--------------------------------------------------------------- + BEGIN + table public.xact_test: INSERT: data[text]:'before-test' + COMMIT + BEGIN + table public.xact_test: INSERT: data[text]:'after-assignment' + COMMIT +(6 rows) + +-- bug #14279, do not propagate null snapshot from subtransaction +BEGIN; +-- first insert +INSERT INTO xact_test VALUES ('main-txn'); +SAVEPOINT foo; +-- now perform operation in subxact that creates and logs xid, but isn't decoded +SELECT 1 FROM xact_test FOR UPDATE LIMIT 1; + ?column? +---------- + 1 +(1 row) + +COMMIT; +-- and now show those changes +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + data +------------------------------------------------------- + BEGIN + table public.xact_test: INSERT: data[text]:'main-txn' + COMMIT +(3 rows) + +DROP TABLE xact_test; +SELECT pg_drop_replication_slot('regression_slot'); + pg_drop_replication_slot +-------------------------- + +(1 row) + diff --git a/contrib/test_decoding/logical.conf b/contrib/test_decoding/logical.conf new file mode 100644 index 0000000..cc12f25 --- /dev/null +++ b/contrib/test_decoding/logical.conf @@ -0,0 +1,4 @@ +wal_level = logical +max_replication_slots = 4 +logical_decoding_work_mem = 64kB +autovacuum_naptime = 1d diff --git a/contrib/test_decoding/meson.build b/contrib/test_decoding/meson.build new file mode 100644 index 0000000..7b05cc2 --- /dev/null +++ b/contrib/test_decoding/meson.build @@ -0,0 +1,77 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +test_decoding_sources = files( + 'test_decoding.c', +) + +if host_system == 'windows' + test_decoding_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_decoding', + '--FILEDESC', 'test_decoding - example of a logical decoding output plugin',]) +endif + +test_decoding = shared_module('test_decoding', + test_decoding_sources, + kwargs: contrib_mod_args, +) +contrib_targets += test_decoding + +tests += { + 'name': 'test_decoding', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'ddl', + 'xact', + 'rewrite', + 'toast', + 'permissions', + 'decoding_in_xact', + 'decoding_into_rel', + 'binary', + 'prepared', + 'replorigin', + 'time', + 'messages', + 'spill', + 'slot', + 'truncate', + 'stream', + 'stats', + 'twophase', + 'twophase_stream', + ], + 'regress_args': [ + '--temp-config', files('logical.conf'), + ], + # Disabled because these tests require "wal_level=logical", which + # typical runningcheck users do not have (e.g. buildfarm clients). + 'runningcheck': false, + }, + 'isolation': { + 'specs': [ + 'mxact', + 'delayed_startup', + 'ondisk_startup', + 'catalog_change_snapshot', + 'concurrent_ddl_dml', + 'oldest_xmin', + 'snapshot_transfer', + 'subxact_without_top', + 'concurrent_stream', + 'twophase_snapshot', + 'slot_creation_error', + ], + 'regress_args': [ + '--temp-config', files('logical.conf'), + ], + # see above + 'runningcheck': false, + }, + 'tap': { + 'tests': [ + 't/001_repl_stats.pl', + ], + }, +} diff --git a/contrib/test_decoding/specs/catalog_change_snapshot.spec b/contrib/test_decoding/specs/catalog_change_snapshot.spec new file mode 100644 index 0000000..673bccf --- /dev/null +++ b/contrib/test_decoding/specs/catalog_change_snapshot.spec @@ -0,0 +1,75 @@ +# Test decoding only the commit record of the transaction that have +# modified catalogs. +setup +{ + DROP TABLE IF EXISTS tbl1; + DROP TABLE IF EXISTS tbl2; + CREATE TABLE tbl1 (val1 integer, val2 integer); + CREATE TABLE tbl2 (val1 integer, val2 integer); + CREATE TABLE user_cat (val1 integer) WITH (user_catalog_table = true); +} + +teardown +{ + DROP TABLE tbl1; + DROP TABLE tbl2; + DROP TABLE user_cat; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + +session "s0" +setup { SET synchronous_commit=on; } +step "s0_init" { SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); } +step "s0_begin" { BEGIN; } +step "s0_savepoint" { SAVEPOINT sp1; } +step "s0_truncate" { TRUNCATE tbl1; } +step "s0_insert" { INSERT INTO tbl1 VALUES (1); } +step "s0_insert2" { INSERT INTO user_cat VALUES (1); } +step "s0_commit" { COMMIT; } + +session "s1" +setup { SET synchronous_commit=on; } +step "s1_checkpoint" { CHECKPOINT; } +step "s1_get_changes" { SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); } + +session "s2" +setup { SET synchronous_commit=on; } +step "s2_begin" { BEGIN; } +step "s2_truncate" { TRUNCATE tbl2; } +step "s2_commit" { COMMIT; } + +# For the transaction that TRUNCATEd the table tbl1, the last decoding decodes +# only its COMMIT record, because it starts from the RUNNING_XACTS record emitted +# during the first checkpoint execution. This transaction must be marked as +# containing catalog changes while decoding the COMMIT record and the decoding +# of the INSERT record must read the pg_class with the correct historic snapshot. +# +# Note that in a case where bgwriter wrote the RUNNING_XACTS record between "s0_commit" +# and "s0_begin", this doesn't happen as the decoding starts from the RUNNING_XACTS +# record written by bgwriter. One might think we can either stop the bgwriter or +# increase LOG_SNAPSHOT_INTERVAL_MS but it's not practical via tests. +permutation "s0_init" "s0_begin" "s0_savepoint" "s0_truncate" "s1_checkpoint" "s1_get_changes" "s0_commit" "s0_begin" "s0_insert" "s1_checkpoint" "s1_get_changes" "s0_commit" "s1_get_changes" + +# Test that we can purge the old catalog modifying transactions after restoring +# them from the serialized snapshot. The first checkpoint will serialize the list +# of two catalog modifying xacts. The purpose of the second checkpoint is to allow +# partial pruning of the list of catalog modifying xact. The third checkpoint +# followed by get_changes establishes a restart_point at the first checkpoint LSN. +# The last get_changes will start decoding from the first checkpoint which +# restores the list of catalog modifying xacts and then while decoding the second +# checkpoint record it prunes one of the xacts in that list and when decoding the +# next checkpoint, it will completely prune that list. +permutation "s0_init" "s0_begin" "s0_truncate" "s2_begin" "s2_truncate" "s1_checkpoint" "s1_get_changes" "s0_commit" "s0_begin" "s0_insert" "s1_checkpoint" "s1_get_changes" "s2_commit" "s1_checkpoint" "s1_get_changes" "s0_commit" "s1_get_changes" + +# Test that we can handle the case where there is no association between top-level +# transaction and its subtransactions. The last decoding restarts from the first +# checkpoint, decodes NEW_CID generated by "s0_insert2", and marks the subtransaction +# as containing catalog changes while adding tuple cids to its top-level transaction. +# During that, both transaction entries are created in ReorderBuffer as top-level +# transactions and have the same LSN. We check if the assertion check for the order +# of transaction LSNs in AssertTXNLsnOrder() is skipped since we are still before the +# LSN at which we start replaying the contents of transactions. Besides, when decoding +# the commit record of the top-level transaction, we must force the top-level +# transaction to do timetravel since one of its subtransactions has been marked as +# containing catalog changes. +permutation "s0_init" "s0_begin" "s0_savepoint" "s0_insert" "s1_checkpoint" "s1_get_changes" "s0_insert2" "s0_commit" "s0_begin" "s0_insert" "s1_checkpoint" "s1_get_changes" "s0_commit" "s1_get_changes" diff --git a/contrib/test_decoding/specs/concurrent_ddl_dml.spec b/contrib/test_decoding/specs/concurrent_ddl_dml.spec new file mode 100644 index 0000000..d16515e --- /dev/null +++ b/contrib/test_decoding/specs/concurrent_ddl_dml.spec @@ -0,0 +1,88 @@ +setup +{ + DROP TABLE IF EXISTS tbl1; + DROP TABLE IF EXISTS tbl2; + CREATE TABLE tbl1(val1 integer, val2 integer); + CREATE TABLE tbl2(val1 integer, val2 integer); +} + +teardown +{ + DROP TABLE tbl1; + DROP TABLE tbl2; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + +session "s1" +setup { SET synchronous_commit=on; } + +step "s1_init" { SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); } +step "s1_begin" { BEGIN; } +step "s1_insert_tbl1" { INSERT INTO tbl1 (val1, val2) VALUES (1, 1); } +step "s1_insert_tbl2" { INSERT INTO tbl2 (val1, val2) VALUES (1, 1); } +step "s1_insert_tbl2_3col" { INSERT INTO tbl2 (val1, val2, val3) VALUES (1, 1, 1); } +step "s1_commit" { COMMIT; } + +session "s2" +setup { SET synchronous_commit=on; } + +step "s2_alter_tbl1_float" { ALTER TABLE tbl1 ALTER COLUMN val2 TYPE float; } +step "s2_alter_tbl1_char" { ALTER TABLE tbl1 ALTER COLUMN val2 TYPE character varying; } +step "s2_alter_tbl1_boolean" { ALTER TABLE tbl1 ALTER COLUMN val2 TYPE boolean; } + +step "s2_alter_tbl2_float" { ALTER TABLE tbl2 ALTER COLUMN val2 TYPE float; } +step "s2_alter_tbl2_char" { ALTER TABLE tbl2 ALTER COLUMN val2 TYPE character varying; } +step "s2_alter_tbl2_text" { ALTER TABLE tbl2 ALTER COLUMN val2 TYPE text; } +step "s2_alter_tbl2_boolean" { ALTER TABLE tbl2 ALTER COLUMN val2 TYPE boolean; } + +step "s2_alter_tbl2_add_int" { ALTER TABLE tbl2 ADD COLUMN val3 INTEGER; } +step "s2_alter_tbl2_add_float" { ALTER TABLE tbl2 ADD COLUMN val3 FLOAT; } +step "s2_alter_tbl2_add_char" { ALTER TABLE tbl2 ADD COLUMN val3 character varying; } +step "s2_alter_tbl2_add_text" { ALTER TABLE tbl2 ADD COLUMN val3 TEXT; } +step "s2_alter_tbl2_drop_3rd_col" { ALTER TABLE tbl2 DROP COLUMN val3; } +step "s2_alter_tbl2_3rd_char" { ALTER TABLE tbl2 ALTER COLUMN val3 TYPE character varying; } +step "s2_alter_tbl2_3rd_text" { ALTER TABLE tbl2 ALTER COLUMN val3 TYPE text; } +step "s2_alter_tbl2_3rd_int" { ALTER TABLE tbl2 ALTER COLUMN val3 TYPE int USING val3::integer; } + +step "s2_get_changes" { SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); } + + + +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_float" "s1_insert_tbl2" "s1_commit" "s2_get_changes" +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl1_float" "s1_insert_tbl2" "s1_commit" "s2_get_changes" +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_char" "s1_insert_tbl2" "s1_commit" "s2_get_changes" +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl1_char" "s1_insert_tbl2" "s1_commit" "s2_get_changes" + +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s1_insert_tbl2" "s2_alter_tbl1_float" "s1_commit" "s2_get_changes" +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s1_insert_tbl2" "s2_alter_tbl1_char" "s1_commit" "s2_get_changes" + +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_float" "s1_insert_tbl2" "s2_alter_tbl1_float" "s1_commit" "s2_get_changes" +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_char" "s1_insert_tbl2" "s2_alter_tbl1_char" "s1_commit" "s2_get_changes" + +permutation "s1_init" "s2_alter_tbl2_char" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_text" "s1_insert_tbl2" "s1_commit" "s2_get_changes" +permutation "s1_init" "s2_alter_tbl2_char" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_text" "s1_insert_tbl2" "s2_alter_tbl1_char" "s1_commit" "s2_get_changes" + +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_boolean" "s1_insert_tbl2" "s1_commit" "s2_get_changes" +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_boolean" "s1_insert_tbl2" "s2_alter_tbl1_boolean" "s1_commit" "s2_get_changes" + +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_add_int" "s1_insert_tbl2_3col" "s1_commit" "s2_get_changes" +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s1_insert_tbl2" "s1_commit" "s1_begin" "s2_alter_tbl2_add_int" "s1_insert_tbl2_3col" "s1_commit" "s2_get_changes" + +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_add_float" "s1_insert_tbl2_3col" "s1_commit" "s2_get_changes" +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s1_insert_tbl2" "s1_commit" "s1_begin" "s2_alter_tbl2_add_float" "s1_insert_tbl2_3col" "s1_commit" "s2_get_changes" + +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_add_char" "s1_insert_tbl2_3col" "s1_commit" "s2_get_changes" +permutation "s1_init" "s1_begin" "s1_insert_tbl1" "s1_insert_tbl2" "s1_commit" "s1_begin" "s2_alter_tbl2_add_char" "s1_insert_tbl2_3col" "s1_commit" "s2_get_changes" + +permutation "s1_init" "s2_alter_tbl2_add_int" "s1_begin" "s1_insert_tbl2_3col" "s2_alter_tbl2_drop_3rd_col" "s1_commit" "s2_get_changes" +permutation "s1_init" "s2_alter_tbl2_add_int" "s1_begin" "s1_insert_tbl2_3col" "s2_alter_tbl2_drop_3rd_col" "s1_insert_tbl2" "s1_commit" "s1_insert_tbl2" "s2_get_changes" + +permutation "s1_init" "s2_alter_tbl2_add_int" "s1_begin" "s1_insert_tbl2_3col" "s2_alter_tbl2_drop_3rd_col" "s1_commit" "s2_get_changes" "s2_alter_tbl2_add_text" "s1_begin" "s1_insert_tbl2_3col" "s2_alter_tbl2_3rd_char" "s1_insert_tbl2_3col" "s1_commit" "s2_get_changes" "s2_alter_tbl2_3rd_int" "s1_insert_tbl2_3col" "s2_get_changes" + +permutation "s1_init" "s2_alter_tbl2_add_char" "s1_begin" "s1_insert_tbl1" "s1_insert_tbl2_3col" "s2_alter_tbl2_3rd_text" "s1_insert_tbl2_3col" "s1_commit" "s1_insert_tbl2_3col" "s2_get_changes" +permutation "s1_init" "s2_alter_tbl2_add_text" "s1_begin" "s1_insert_tbl1" "s1_insert_tbl2_3col" "s2_alter_tbl2_3rd_char" "s1_insert_tbl2_3col" "s1_commit" "s1_insert_tbl2_3col" "s2_get_changes" + +permutation "s1_init" "s2_alter_tbl2_add_char" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_3rd_text" "s1_insert_tbl2_3col" "s1_commit" "s2_alter_tbl2_drop_3rd_col" "s1_insert_tbl2" "s2_get_changes" +permutation "s1_init" "s2_alter_tbl2_add_text" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_3rd_char" "s1_insert_tbl2_3col" "s1_commit" "s2_alter_tbl2_drop_3rd_col" "s1_insert_tbl2" "s2_get_changes" + +permutation "s1_init" "s2_alter_tbl2_add_char" "s1_begin" "s1_insert_tbl1" "s2_alter_tbl2_drop_3rd_col" "s1_insert_tbl1" "s1_commit" "s2_get_changes" diff --git a/contrib/test_decoding/specs/concurrent_stream.spec b/contrib/test_decoding/specs/concurrent_stream.spec new file mode 100644 index 0000000..54218a4 --- /dev/null +++ b/contrib/test_decoding/specs/concurrent_stream.spec @@ -0,0 +1,43 @@ +# Test decoding of in-progress transaction containing dml and a concurrent +# transaction with ddl operation. The transaction containing ddl operation +# should not get streamed as it doesn't have any changes. + +setup +{ + SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); + + -- consume DDL + SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + CREATE OR REPLACE FUNCTION large_val() RETURNS TEXT LANGUAGE SQL AS 'select array_agg(md5(g::text))::text from generate_series(1, 80000) g'; +} + +teardown +{ + DROP TABLE IF EXISTS stream_test; + DROP TABLE IF EXISTS stream_test1; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + +session "s0" +setup { SET synchronous_commit=on; } +step "s0_begin" { BEGIN; } +step "s0_ddl" {CREATE TABLE stream_test1(data text);} + +session "s2" +setup { SET synchronous_commit=on; } +step "s2_ddl" {CREATE TABLE stream_test2(data text);} + +# The transaction commit for s1_ddl will add the INTERNAL_SNAPSHOT change to +# the currently running s0_ddl and we want to test that s0_ddl should not get +# streamed when user asked to skip-empty-xacts. Similarly, the +# INTERNAL_SNAPSHOT change added by s2_ddl should not change the results for +# what gets streamed. +session "s1" +setup { SET synchronous_commit=on; } +step "s1_ddl" { CREATE TABLE stream_test(data text); } +step "s1_begin" { BEGIN; } +step "s1_toast_insert" {INSERT INTO stream_test SELECT large_val();} +step "s1_commit" { COMMIT; } +step "s1_get_stream_changes" { SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1');} + +permutation "s0_begin" "s0_ddl" "s1_ddl" "s1_begin" "s1_toast_insert" "s2_ddl" "s1_commit" "s1_get_stream_changes" diff --git a/contrib/test_decoding/specs/delayed_startup.spec b/contrib/test_decoding/specs/delayed_startup.spec new file mode 100644 index 0000000..b7fe814 --- /dev/null +++ b/contrib/test_decoding/specs/delayed_startup.spec @@ -0,0 +1,24 @@ +setup +{ + DROP TABLE IF EXISTS do_write; + CREATE TABLE do_write(id serial primary key); +} + +teardown +{ + DROP TABLE do_write; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + +session "s1" +setup { SET synchronous_commit=on; } +step "s1b" { BEGIN ISOLATION LEVEL SERIALIZABLE; } +step "s1w" { INSERT INTO do_write DEFAULT VALUES; } +step "s1c" { COMMIT; } +session "s2" +setup { SET synchronous_commit=on; } +step "s2init" {SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding');} +step "s2start" {SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false');} + + +permutation "s1b" "s1w" "s2init" "s1c" "s2start" "s1b" "s1w" "s1c" "s2start" "s1b" "s1w" "s2start" "s1c" "s2start" diff --git a/contrib/test_decoding/specs/mxact.spec b/contrib/test_decoding/specs/mxact.spec new file mode 100644 index 0000000..ea5b1aa --- /dev/null +++ b/contrib/test_decoding/specs/mxact.spec @@ -0,0 +1,38 @@ +setup +{ + DROP TABLE IF EXISTS do_write; + CREATE TABLE do_write(id serial primary key); +} + +teardown +{ + DROP TABLE IF EXISTS do_write; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + +session "s0" +setup { SET synchronous_commit=on; } +step "s0init" {SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding');} +step "s0start" {SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false');} +step "s0alter" {ALTER TABLE do_write ADD column ts timestamptz; } +step "s0w" { INSERT INTO do_write DEFAULT VALUES; } + +session "s1" +setup { SET synchronous_commit=on; } +step "s1begin" {BEGIN;} +step "s1sharepgclass" { SELECT count(*) > 1 FROM (SELECT * FROM pg_class FOR SHARE) s; } +step "s1keysharepgclass" { SELECT count(*) > 1 FROM (SELECT * FROM pg_class FOR KEY SHARE) s; } +step "s1commit" {COMMIT;} + +session "s2" +setup { SET synchronous_commit=on; } +step "s2begin" {BEGIN;} +step "s2sharepgclass" { SELECT count(*) > 1 FROM (SELECT * FROM pg_class FOR SHARE) s; } +step "s2keysharepgclass" { SELECT count(*) > 1 FROM (SELECT * FROM pg_class FOR KEY SHARE) s; } +step "s2commit" {COMMIT;} + +# test that we're handling an update-only mxact xmax correctly +permutation "s0init" "s0start" "s1begin" "s1sharepgclass" "s2begin" "s2sharepgclass" "s0w" "s0start" "s2commit" "s1commit" + +# test that we're handling an update-only mxact xmax correctly +permutation "s0init" "s0start" "s1begin" "s1keysharepgclass" "s2begin" "s2keysharepgclass" "s0alter" "s0w" "s0start" "s2commit" "s1commit" diff --git a/contrib/test_decoding/specs/oldest_xmin.spec b/contrib/test_decoding/specs/oldest_xmin.spec new file mode 100644 index 0000000..88bd30f --- /dev/null +++ b/contrib/test_decoding/specs/oldest_xmin.spec @@ -0,0 +1,42 @@ +# Test advancement of the slot's oldest xmin + +setup +{ + SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); -- must be first write in xact + DROP TYPE IF EXISTS basket; + CREATE TYPE basket AS (apples integer, pears integer, mangos integer); + DROP TABLE IF EXISTS harvest; + CREATE TABLE harvest(fruits basket); +} + +teardown +{ + DROP TABLE IF EXISTS harvest; + DROP TYPE IF EXISTS basket; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + +session "s0" +setup { SET synchronous_commit=on; } +step "s0_begin" { BEGIN; } +step "s0_getxid" { SELECT pg_current_xact_id() IS NULL; } +step "s0_alter" { ALTER TYPE basket DROP ATTRIBUTE mangos; } +step "s0_commit" { COMMIT; } +step "s0_checkpoint" { CHECKPOINT; } +step "s0_vacuum" { VACUUM pg_attribute; } +step "s0_get_changes" { SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); } + +session "s1" +setup { SET synchronous_commit=on; } +step "s1_begin" { BEGIN; } +step "s1_insert" { INSERT INTO harvest VALUES ((1, 2, 3)); } +step "s1_commit" { COMMIT; } + +# Checkpoint with following get_changes forces xmin advancement. We do +# get_changes twice because if one more xl_running_xacts record had slipped +# before our CHECKPOINT, xmin will be advanced only on this record, thus not +# reaching value needed for vacuuming corresponding pg_attribute entry. ALTER of +# composite type is a rare form of DDL which allows T1 to see the tuple which +# will be removed (xmax set) before T1 commits. That is, interlocking doesn't +# forbid modifying catalog after someone read it (and didn't commit yet). +permutation "s0_begin" "s0_getxid" "s1_begin" "s1_insert" "s0_alter" "s0_commit" "s0_checkpoint" "s0_get_changes" "s0_get_changes" "s1_commit" "s0_vacuum" "s0_get_changes" diff --git a/contrib/test_decoding/specs/ondisk_startup.spec b/contrib/test_decoding/specs/ondisk_startup.spec new file mode 100644 index 0000000..96ce87f --- /dev/null +++ b/contrib/test_decoding/specs/ondisk_startup.spec @@ -0,0 +1,45 @@ +# Force usage of ondisk decoding snapshots to test that code path. +setup +{ + DROP TABLE IF EXISTS do_write; + CREATE TABLE do_write(id serial primary key); +} + +teardown +{ + DROP TABLE do_write; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + + +session "s1" +setup { SET synchronous_commit=on; } + +step "s1init" {SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding');} +step "s1start" {SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false');} +step "s1insert" { INSERT INTO do_write DEFAULT VALUES; } +step "s1checkpoint" { CHECKPOINT; } +step "s1alter" { ALTER TABLE do_write ADD COLUMN addedbys1 int; } + +session "s2" +setup { SET synchronous_commit=on; } + +step "s2b" { BEGIN; } +step "s2txid" { SELECT pg_current_xact_id() IS NULL; } +step "s2alter" { ALTER TABLE do_write ADD COLUMN addedbys2 int; } +step "s2c" { COMMIT; } + + +session "s3" +setup { SET synchronous_commit=on; } + +step "s3b" { BEGIN; } +step "s3txid" { SELECT pg_current_xact_id() IS NULL; } +step "s3c" { COMMIT; } + +# Force usage of ondisk snapshot by starting and not finishing a +# transaction with an assigned xid after consistency has been +# reached. In combination with a checkpoint forcing a snapshot to be +# written and a new restart point computed that'll lead to the usage +# of the snapshot. +permutation "s2b" "s2txid" "s1init" "s3b" "s3txid" "s2alter" "s2c" "s2b" "s2txid" "s3c" "s2c" "s1insert" "s1checkpoint" "s1start" "s1insert" "s1alter" "s1insert" "s1start" diff --git a/contrib/test_decoding/specs/slot_creation_error.spec b/contrib/test_decoding/specs/slot_creation_error.spec new file mode 100644 index 0000000..d1e35bf --- /dev/null +++ b/contrib/test_decoding/specs/slot_creation_error.spec @@ -0,0 +1,41 @@ +# Test that erroring out during logical slot creation is handled properly + +session "s1" +setup { SET synchronous_commit=on; } + +step s1_b { BEGIN; } +step s1_xid { SELECT 'xid' FROM txid_current(); } +step s1_c { COMMIT; } +step s1_cancel_s2 { + SELECT pg_cancel_backend(pid) + FROM pg_stat_activity + WHERE application_name = 'isolation/slot_creation_error/s2'; +} + +step s1_terminate_s2 { + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE application_name = 'isolation/slot_creation_error/s2'; +} + +step s1_view_slot { + SELECT slot_name, slot_type, active FROM pg_replication_slots WHERE slot_name = 'slot_creation_error' +} + +step s1_drop_slot { + SELECT pg_drop_replication_slot('slot_creation_error'); +} + +session s2 +setup { SET synchronous_commit=on; } +step s2_init { + SELECT 'init' FROM pg_create_logical_replication_slot('slot_creation_error', 'test_decoding'); +} + +# The tests first start a transaction with an xid assigned in s1, then create +# a slot in s2. The slot creation waits for s1's transaction to end. Instead +# we cancel / terminate s2. +permutation s1_b s1_xid s2_init s1_view_slot s1_cancel_s2(s2_init) s1_view_slot s1_c +permutation s1_b s1_xid s2_init s1_c s1_view_slot s1_drop_slot # check slot creation still works +permutation s1_b s1_xid s2_init s1_terminate_s2(s2_init) s1_c s1_view_slot +# can't run tests after this, due to s2's connection failure diff --git a/contrib/test_decoding/specs/snapshot_transfer.spec b/contrib/test_decoding/specs/snapshot_transfer.spec new file mode 100644 index 0000000..152f2fd --- /dev/null +++ b/contrib/test_decoding/specs/snapshot_transfer.spec @@ -0,0 +1,43 @@ +# Test snapshot transfer from subxact to top-level and receival of later snaps. + +setup +{ + SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); -- must be first write in xact + DROP TABLE IF EXISTS dummy; + CREATE TABLE dummy(i int); + DROP TABLE IF EXISTS harvest; + CREATE TABLE harvest(apples int, pears int); +} + +teardown +{ + DROP TABLE IF EXISTS harvest; + DROP TABLE IF EXISTS dummy; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + +session "s0" +setup { SET synchronous_commit=on; } +step "s0_begin" { BEGIN; } +step "s0_begin_sub0" { SAVEPOINT s0; } +step "s0_log_assignment" { SELECT pg_current_xact_id() IS NULL; } +step "s0_begin_sub1" { SAVEPOINT s1; } +step "s0_sub_get_base_snap" { INSERT INTO dummy VALUES (0); } +step "s0_insert" { INSERT INTO harvest VALUES (1, 2, 3); } +step "s0_end_sub0" { RELEASE SAVEPOINT s0; } +step "s0_end_sub1" { RELEASE SAVEPOINT s1; } +step "s0_commit" { COMMIT; } +step "s0_get_changes" { SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); } + +session "s1" +setup { SET synchronous_commit=on; } +step "s1_produce_new_snap" { ALTER TABLE harvest ADD COLUMN mangos int; } + +# start top-level without base snap, get base snap in subxact, then create new +# snap and make sure it is queued. +permutation "s0_begin" "s0_begin_sub0" "s0_log_assignment" "s0_sub_get_base_snap" "s1_produce_new_snap" "s0_insert" "s0_end_sub0" "s0_commit" "s0_get_changes" + +# In previous test, we firstly associated subxact with xact and only then got +# base snap; now nest one more subxact to get snap first and only then (at +# commit) associate it with toplevel. +permutation "s0_begin" "s0_begin_sub0" "s0_log_assignment" "s0_begin_sub1" "s0_sub_get_base_snap" "s1_produce_new_snap" "s0_insert" "s0_end_sub1" "s0_end_sub0" "s0_commit" "s0_get_changes" diff --git a/contrib/test_decoding/specs/subxact_without_top.spec b/contrib/test_decoding/specs/subxact_without_top.spec new file mode 100644 index 0000000..76688c7 --- /dev/null +++ b/contrib/test_decoding/specs/subxact_without_top.spec @@ -0,0 +1,63 @@ +# Test decoding of subtransactions whose top-transaction is before restart +# point. Such transactions won't be streamed as we stream only complete +# transactions, but it is good to test that they don't cause any problem. + +setup +{ + SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding'); -- must be first write in xact + CREATE TABLE harvest(apples integer); + CREATE OR REPLACE FUNCTION subxacts() returns void as $$ + BEGIN + FOR i in 1 .. 128 LOOP + BEGIN + INSERT INTO harvest VALUES (42); + EXCEPTION + WHEN OTHERS THEN + RAISE; + END; + END LOOP; + END; $$LANGUAGE 'plpgsql'; +} + +teardown +{ + DROP TABLE IF EXISTS harvest; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + +session "s0" +setup { SET synchronous_commit=on; } +step "s0_begin" { BEGIN; } +step "s0_first_subxact" { + DO LANGUAGE plpgsql $$ + BEGIN + BEGIN + INSERT INTO harvest VALUES (41); + EXCEPTION WHEN OTHERS THEN RAISE; + END; + END $$; +} +step "s0_many_subxacts" { select subxacts(); } +step "s0_commit" { COMMIT; } + +session "s1" +setup { SET synchronous_commit=on; } +step "s1_begin" { BEGIN; } +step "s1_dml" { INSERT INTO harvest VALUES (43); } +step "s1_commit" { COMMIT; } + +session "s2" +setup { SET synchronous_commit=on; } +step "s2_checkpoint" { CHECKPOINT; } +step "s2_get_changes" { SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); } +step "s2_get_changes_suppress_output" { SELECT null n FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') GROUP BY n; } + +# The first checkpoint establishes the potential restart point (aka +# restart_lsn) for the slot after the initial subxact. The second checkpoint +# followed by get_changes will ensure that the potential restart point will +# become the actual restart point. We do get_changes twice because if one +# more xl_running_xacts record had slipped before our s0_commit, then the +# potential restart point won't become actual restart point. The s1's open +# transaction till get_changes holds the potential restart point to our first +# checkpoint location. +permutation "s0_begin" "s0_first_subxact" "s2_checkpoint" "s1_begin" "s1_dml" "s0_many_subxacts" "s0_commit" "s2_checkpoint" "s2_get_changes_suppress_output" "s2_get_changes_suppress_output" "s1_commit" "s2_get_changes" diff --git a/contrib/test_decoding/specs/twophase_snapshot.spec b/contrib/test_decoding/specs/twophase_snapshot.spec new file mode 100644 index 0000000..e8d9567 --- /dev/null +++ b/contrib/test_decoding/specs/twophase_snapshot.spec @@ -0,0 +1,53 @@ +# Test decoding of two-phase transactions during the build of a consistent snapshot. +setup +{ + DROP TABLE IF EXISTS do_write; + CREATE TABLE do_write(id serial primary key); +} + +teardown +{ + DROP TABLE do_write; + SELECT 'stop' FROM pg_drop_replication_slot('isolation_slot'); +} + + +session "s1" +setup { SET synchronous_commit=on; } + +step "s1init" {SELECT 'init' FROM pg_create_logical_replication_slot('isolation_slot', 'test_decoding', false, true);} +step "s1start" {SELECT data FROM pg_logical_slot_get_changes('isolation_slot', NULL, NULL, 'include-xids', 'false', 'skip-empty-xacts', '1');} +step "s1insert" { INSERT INTO do_write DEFAULT VALUES; } + +session "s2" +setup { SET synchronous_commit=on; } + +step "s2b" { BEGIN; } +step "s2txid" { SELECT pg_current_xact_id() IS NULL; } +step "s2c" { COMMIT; } +step "s2insert" { INSERT INTO do_write DEFAULT VALUES; } +step "s2p" { PREPARE TRANSACTION 'test1'; } +step "s2cp" { COMMIT PREPARED 'test1'; } + + +session "s3" +setup { SET synchronous_commit=on; } + +step "s3b" { BEGIN; } +step "s3txid" { SELECT pg_current_xact_id() IS NULL; } +step "s3c" { COMMIT; } + +# Force building of a consistent snapshot between a PREPARE and COMMIT PREPARED +# and ensure that the whole transaction is decoded at the time of COMMIT +# PREPARED. +# +# 's1init' step will initialize the replication slot and cause logical decoding +# to wait in initial starting point till the in-progress transaction in s2 is +# committed. 's2c' step will cause logical decoding to go to initial consistent +# point and wait for in-progress transaction s3 to commit. 's3c' step will cause +# logical decoding to find a consistent point while the transaction s2 is +# prepared and not yet committed. This will cause the first s1start to skip +# prepared transaction s2 as that will be before consistent point. The second +# s1start will allow decoding of skipped prepare along with commit prepared done +# as part of s2cp. +permutation "s2b" "s2txid" "s1init" "s3b" "s3txid" "s2c" "s2b" "s2insert" "s2p" "s3c" "s1insert" "s1start" "s2cp" "s1start" diff --git a/contrib/test_decoding/sql/binary.sql b/contrib/test_decoding/sql/binary.sql new file mode 100644 index 0000000..df1c5fb --- /dev/null +++ b/contrib/test_decoding/sql/binary.sql @@ -0,0 +1,14 @@ +-- predictability +SET synchronous_commit = on; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +-- succeeds, textual plugin, textual consumer +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force-binary', '0', 'skip-empty-xacts', '1'); +-- fails, binary plugin, textual consumer +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force-binary', '1', 'skip-empty-xacts', '1'); +-- succeeds, textual plugin, binary consumer +SELECT data FROM pg_logical_slot_get_binary_changes('regression_slot', NULL, NULL, 'force-binary', '0', 'skip-empty-xacts', '1'); +-- succeeds, binary plugin, binary consumer +SELECT data FROM pg_logical_slot_get_binary_changes('regression_slot', NULL, NULL, 'force-binary', '1', 'skip-empty-xacts', '1'); + +SELECT 'init' FROM pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/ddl.sql b/contrib/test_decoding/sql/ddl.sql new file mode 100644 index 0000000..2f8e4e7 --- /dev/null +++ b/contrib/test_decoding/sql/ddl.sql @@ -0,0 +1,445 @@ +-- predictability +SET synchronous_commit = on; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +-- fail because of an already existing slot +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +-- fail because of an invalid name +SELECT 'init' FROM pg_create_logical_replication_slot('Invalid Name', 'test_decoding'); + +-- fail twice because of an invalid parameter values +SELECT 'init' FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', 'frakbar'); +SELECT 'init' FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'nonexistent-option', 'frakbar'); +SELECT 'init' FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', 'frakbar'); + +-- succeed once +SELECT pg_drop_replication_slot('regression_slot'); +-- fail +SELECT pg_drop_replication_slot('regression_slot'); + +-- check that we're detecting a streaming rep slot used for logical decoding +SELECT 'init' FROM pg_create_physical_replication_slot('repl'); +SELECT data FROM pg_logical_slot_get_changes('repl', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +SELECT pg_drop_replication_slot('repl'); + + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +/* check whether status function reports us, only reproduceable columns */ +SELECT slot_name, plugin, slot_type, active, + NOT catalog_xmin IS NULL AS catalog_xmin_set, + xmin IS NULl AS data_xmin_not_set, + pg_wal_lsn_diff(restart_lsn, '0/01000000') > 0 AS some_wal +FROM pg_replication_slots; + +/* + * Check that changes are handled correctly when interleaved with ddl + */ +CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120)); +BEGIN; +INSERT INTO replication_example(somedata, text) VALUES (1, 1); +INSERT INTO replication_example(somedata, text) VALUES (1, 2); +COMMIT; + +ALTER TABLE replication_example ADD COLUMN bar int; + +INSERT INTO replication_example(somedata, text, bar) VALUES (2, 1, 4); + +BEGIN; +INSERT INTO replication_example(somedata, text, bar) VALUES (2, 2, 4); +INSERT INTO replication_example(somedata, text, bar) VALUES (2, 3, 4); +INSERT INTO replication_example(somedata, text, bar) VALUES (2, 4, NULL); +COMMIT; + +ALTER TABLE replication_example DROP COLUMN bar; +INSERT INTO replication_example(somedata, text) VALUES (3, 1); + +BEGIN; +INSERT INTO replication_example(somedata, text) VALUES (3, 2); +INSERT INTO replication_example(somedata, text) VALUES (3, 3); +COMMIT; + +ALTER TABLE replication_example RENAME COLUMN text TO somenum; + +INSERT INTO replication_example(somedata, somenum) VALUES (4, 1); + +-- collect all changes +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +ALTER TABLE replication_example ALTER COLUMN somenum TYPE int4 USING (somenum::int4); +-- check that this doesn't produce any changes from the heap rewrite +SELECT count(data) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +INSERT INTO replication_example(somedata, somenum) VALUES (5, 1); + +BEGIN; +INSERT INTO replication_example(somedata, somenum) VALUES (6, 1); +ALTER TABLE replication_example ADD COLUMN zaphod1 int; +INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 2, 1); +ALTER TABLE replication_example ADD COLUMN zaphod2 int; +INSERT INTO replication_example(somedata, somenum, zaphod2) VALUES (6, 3, 1); +INSERT INTO replication_example(somedata, somenum, zaphod1) VALUES (6, 4, 2); +COMMIT; + +-- show changes +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- ON CONFLICT DO UPDATE support +BEGIN; +INSERT INTO replication_example(id, somedata, somenum) SELECT i, i, i FROM generate_series(-15, 15) i + ON CONFLICT (id) DO UPDATE SET somenum = excluded.somenum + 1; +COMMIT; + +/* display results */ +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- MERGE support +BEGIN; +MERGE INTO replication_example t + USING (SELECT i as id, i as data, i as num FROM generate_series(-20, 5) i) s + ON t.id = s.id + WHEN MATCHED AND t.id < 0 THEN + UPDATE SET somenum = somenum + 1 + WHEN MATCHED AND t.id >= 0 THEN + DELETE + WHEN NOT MATCHED THEN + INSERT VALUES (s.*); +COMMIT; + +/* display results */ +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +CREATE TABLE tr_unique(id2 serial unique NOT NULL, data int); +INSERT INTO tr_unique(data) VALUES(10); +ALTER TABLE tr_unique RENAME TO tr_pkey; +ALTER TABLE tr_pkey ADD COLUMN id serial primary key; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'include-rewrites', '1'); + +INSERT INTO tr_pkey(data) VALUES(1); +--show deletion with primary key +DELETE FROM tr_pkey; + +/* display results */ +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +/* + * check that disk spooling works (also for logical messages) + */ +BEGIN; +CREATE TABLE tr_etoomuch (id serial primary key, data int); +INSERT INTO tr_etoomuch(data) SELECT g.i FROM generate_series(1, 10234) g(i); +SELECT 'tx logical msg' FROM pg_logical_emit_message(true, 'test', 'tx logical msg'); +DELETE FROM tr_etoomuch WHERE id < 5000; +UPDATE tr_etoomuch SET data = - data WHERE id > 5000; +CREATE TABLE tr_oddlength (id text primary key, data text); +INSERT INTO tr_oddlength VALUES('ab', 'foo'); +COMMIT; + +/* display results, but hide most of the output */ +SELECT count(*), min(data), max(data) +FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') +GROUP BY substring(data, 1, 24) +ORDER BY 1,2; + +-- check updates of primary keys work correctly +BEGIN; +CREATE TABLE spoolme AS SELECT g.i FROM generate_series(1, 5000) g(i); +UPDATE tr_etoomuch SET id = -id WHERE id = 5000; +UPDATE tr_oddlength SET id = 'x', data = 'quux'; +UPDATE tr_oddlength SET id = 'yy', data = 'a'; +DELETE FROM spoolme; +DROP TABLE spoolme; +COMMIT; + +SELECT data +FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') +WHERE data ~ 'UPDATE'; + +-- check that a large, spooled, upsert works +INSERT INTO tr_etoomuch (id, data) +SELECT g.i, -g.i FROM generate_series(8000, 12000) g(i) +ON CONFLICT(id) DO UPDATE SET data = EXCLUDED.data; + +SELECT substring(data, 1, 29), count(*) +FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') WITH ORDINALITY +GROUP BY 1 +ORDER BY min(ordinality); + +/* + * check whether we decode subtransactions correctly in relation with each + * other + */ +CREATE TABLE tr_sub (id serial primary key, path text); + +-- toplevel, subtxn, toplevel, subtxn, subtxn +BEGIN; +INSERT INTO tr_sub(path) VALUES ('1-top-#1'); + +SAVEPOINT a; +INSERT INTO tr_sub(path) VALUES ('1-top-1-#1'); +INSERT INTO tr_sub(path) VALUES ('1-top-1-#2'); +RELEASE SAVEPOINT a; + +SAVEPOINT b; +SAVEPOINT c; +INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#1'); +INSERT INTO tr_sub(path) VALUES ('1-top-2-1-#2'); +RELEASE SAVEPOINT c; +INSERT INTO tr_sub(path) VALUES ('1-top-2-#1'); +RELEASE SAVEPOINT b; +COMMIT; + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- check that we handle xlog assignments correctly +BEGIN; +-- nest 80 subtxns +SAVEPOINT subtop;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a;SAVEPOINT a; +-- assign xid by inserting +INSERT INTO tr_sub(path) VALUES ('2-top-1...--#1'); +INSERT INTO tr_sub(path) VALUES ('2-top-1...--#2'); +INSERT INTO tr_sub(path) VALUES ('2-top-1...--#3'); +RELEASE SAVEPOINT subtop; +INSERT INTO tr_sub(path) VALUES ('2-top-#1'); +COMMIT; + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- make sure rollbacked subtransactions aren't decoded +BEGIN; +INSERT INTO tr_sub(path) VALUES ('3-top-2-#1'); +SAVEPOINT a; +INSERT INTO tr_sub(path) VALUES ('3-top-2-1-#1'); +SAVEPOINT b; +INSERT INTO tr_sub(path) VALUES ('3-top-2-2-#1'); +ROLLBACK TO SAVEPOINT b; +INSERT INTO tr_sub(path) VALUES ('3-top-2-#2'); +COMMIT; + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- test whether a known, but not yet logged toplevel xact, followed by a +-- subxact commit is handled correctly +BEGIN; +SELECT pg_current_xact_id() != '0'; -- so no fixed xid appears in the outfile +SAVEPOINT a; +INSERT INTO tr_sub(path) VALUES ('4-top-1-#1'); +RELEASE SAVEPOINT a; +COMMIT; + +-- test whether a change in a subtransaction, in an unknown toplevel +-- xact is handled correctly. +BEGIN; +SAVEPOINT a; +INSERT INTO tr_sub(path) VALUES ('5-top-1-#1'); +COMMIT; + + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- check that DDL in aborted subtransactions handled correctly +CREATE TABLE tr_sub_ddl(data int); +BEGIN; +SAVEPOINT a; +ALTER TABLE tr_sub_ddl ALTER COLUMN data TYPE text; +INSERT INTO tr_sub_ddl VALUES ('blah-blah'); +ROLLBACK TO SAVEPOINT a; +ALTER TABLE tr_sub_ddl ALTER COLUMN data TYPE bigint; +INSERT INTO tr_sub_ddl VALUES(43); +COMMIT; + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + + +/* + * Check whether treating a table as a catalog table works somewhat + */ +CREATE TABLE replication_metadata ( + id serial primary key, + relation name NOT NULL, + options text[] +) +WITH (user_catalog_table = true) +; +\d+ replication_metadata + +INSERT INTO replication_metadata(relation, options) +VALUES ('foo', ARRAY['a', 'b']); + +ALTER TABLE replication_metadata RESET (user_catalog_table); +\d+ replication_metadata + +INSERT INTO replication_metadata(relation, options) +VALUES ('bar', ARRAY['a', 'b']); + +ALTER TABLE replication_metadata SET (user_catalog_table = true); +\d+ replication_metadata + +INSERT INTO replication_metadata(relation, options) +VALUES ('blub', NULL); + +-- make sure rewrites don't work +ALTER TABLE replication_metadata ADD COLUMN rewritemeornot int; +ALTER TABLE replication_metadata ALTER COLUMN rewritemeornot TYPE text; + +ALTER TABLE replication_metadata SET (user_catalog_table = false); +\d+ replication_metadata + +INSERT INTO replication_metadata(relation, options) +VALUES ('zaphod', NULL); + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +/* + * check whether we handle updates/deletes correct with & without a pkey + */ + +/* we should handle the case without a key at all more gracefully */ +CREATE TABLE table_without_key(id serial, data int); +INSERT INTO table_without_key(data) VALUES(1),(2); +DELETE FROM table_without_key WHERE data = 1; +-- won't log old keys +UPDATE table_without_key SET data = 3 WHERE data = 2; +UPDATE table_without_key SET id = -id; +UPDATE table_without_key SET id = -id; +-- should log the full old row now +ALTER TABLE table_without_key REPLICA IDENTITY FULL; +UPDATE table_without_key SET data = 3 WHERE data = 2; +UPDATE table_without_key SET id = -id; +UPDATE table_without_key SET id = -id; +-- ensure that FULL correctly deals with new columns +ALTER TABLE table_without_key ADD COLUMN new_column text; +UPDATE table_without_key SET id = -id; +UPDATE table_without_key SET id = -id, new_column = 'someval'; +DELETE FROM table_without_key WHERE data = 3; + +CREATE TABLE table_with_pkey(id serial primary key, data int); +INSERT INTO table_with_pkey(data) VALUES(1), (2); +DELETE FROM table_with_pkey WHERE data = 1; +-- should log the old pkey +UPDATE table_with_pkey SET data = 3 WHERE data = 2; +UPDATE table_with_pkey SET id = -id; +UPDATE table_with_pkey SET id = -id; +-- check that we log nothing despite having a pkey +ALTER TABLE table_without_key REPLICA IDENTITY NOTHING; +UPDATE table_with_pkey SET id = -id; +-- check that we log everything despite having a pkey +ALTER TABLE table_without_key REPLICA IDENTITY FULL; +UPDATE table_with_pkey SET id = -id; +DELETE FROM table_with_pkey WHERE data = 3; + +CREATE TABLE table_with_unique_not_null(id serial unique, data int); +ALTER TABLE table_with_unique_not_null ALTER COLUMN id SET NOT NULL; --already set +-- won't log anything, replica identity not setup +INSERT INTO table_with_unique_not_null(data) VALUES(1), (2); +DELETE FROM table_with_unique_not_null WHERE data = 1; +UPDATE table_with_unique_not_null SET data = 3 WHERE data = 2; +UPDATE table_with_unique_not_null SET id = -id; +UPDATE table_with_unique_not_null SET id = -id; +DELETE FROM table_with_unique_not_null WHERE data = 3; +-- should log old key +ALTER TABLE table_with_unique_not_null REPLICA IDENTITY USING INDEX table_with_unique_not_null_id_key; +INSERT INTO table_with_unique_not_null(data) VALUES(1), (2); +DELETE FROM table_with_unique_not_null WHERE data = 1; +UPDATE table_with_unique_not_null SET data = 3 WHERE data = 2; +UPDATE table_with_unique_not_null SET id = -id; +UPDATE table_with_unique_not_null SET id = -id; +DELETE FROM table_with_unique_not_null WHERE data = 3; + +-- check tables with dropped indexes used in REPLICA IDENTITY +-- table with primary key +CREATE TABLE table_dropped_index_with_pk (a int PRIMARY KEY, b int, c int); +CREATE UNIQUE INDEX table_dropped_index_with_pk_idx + ON table_dropped_index_with_pk(a); +ALTER TABLE table_dropped_index_with_pk REPLICA IDENTITY + USING INDEX table_dropped_index_with_pk_idx; +DROP INDEX table_dropped_index_with_pk_idx; +INSERT INTO table_dropped_index_with_pk VALUES (1,1,1), (2,2,2), (3,3,3); +UPDATE table_dropped_index_with_pk SET a = 4 WHERE a = 1; +UPDATE table_dropped_index_with_pk SET b = 5 WHERE a = 2; +UPDATE table_dropped_index_with_pk SET b = 6, c = 7 WHERE a = 3; +DELETE FROM table_dropped_index_with_pk WHERE b = 1; +DELETE FROM table_dropped_index_with_pk WHERE a = 3; +DROP TABLE table_dropped_index_with_pk; + +-- table without primary key +CREATE TABLE table_dropped_index_no_pk (a int NOT NULL, b int, c int); +CREATE UNIQUE INDEX table_dropped_index_no_pk_idx + ON table_dropped_index_no_pk(a); +ALTER TABLE table_dropped_index_no_pk REPLICA IDENTITY + USING INDEX table_dropped_index_no_pk_idx; +DROP INDEX table_dropped_index_no_pk_idx; +INSERT INTO table_dropped_index_no_pk VALUES (1,1,1), (2,2,2), (3,3,3); +UPDATE table_dropped_index_no_pk SET a = 4 WHERE a = 1; +UPDATE table_dropped_index_no_pk SET b = 5 WHERE a = 2; +UPDATE table_dropped_index_no_pk SET b = 6, c = 7 WHERE a = 3; +DELETE FROM table_dropped_index_no_pk WHERE b = 1; +DELETE FROM table_dropped_index_no_pk WHERE a = 3; +DROP TABLE table_dropped_index_no_pk; + +-- check toast support +BEGIN; +CREATE SEQUENCE toasttable_rand_seq START 79 INCREMENT 1499; -- portable "random" +CREATE TABLE toasttable( + id serial primary key, + toasted_col1 text, + rand1 float8 DEFAULT nextval('toasttable_rand_seq'), + toasted_col2 text, + rand2 float8 DEFAULT nextval('toasttable_rand_seq') + ); +COMMIT; +-- uncompressed external toast data +INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i); + +-- compressed external toast data +INSERT INTO toasttable(toasted_col2) SELECT repeat(string_agg(to_char(g.i, 'FM0000'), ''), 50) FROM generate_series(1, 500) g(i); + +-- update of existing column +UPDATE toasttable + SET toasted_col1 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i)) +WHERE id = 1; + +-- This output is extremely wide, and using aligned mode causes psql to +-- produce 200kB of useless dashes. Turn that off temporarily to avoid it. +\pset format unaligned +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +\pset format aligned + +INSERT INTO toasttable(toasted_col1) SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i); + +-- update of second column, first column unchanged +UPDATE toasttable + SET toasted_col2 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i)) +WHERE id = 1; + +-- make sure we decode correctly even if the toast table is gone +DROP TABLE toasttable; + +\pset format unaligned +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- done, free logical replication slot +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +\pset format aligned + +SELECT pg_drop_replication_slot('regression_slot'); + +/* check that the slot is gone */ +\x +SELECT * FROM pg_replication_slots; +\x diff --git a/contrib/test_decoding/sql/decoding_in_xact.sql b/contrib/test_decoding/sql/decoding_in_xact.sql new file mode 100644 index 0000000..108782d --- /dev/null +++ b/contrib/test_decoding/sql/decoding_in_xact.sql @@ -0,0 +1,41 @@ +-- predictability +SET synchronous_commit = on; + +-- fail because we're creating a slot while in an xact with xid +BEGIN; +SELECT pg_current_xact_id() = '0'; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +ROLLBACK; + +-- fail because we're creating a slot while in a subxact whose topxact has an xid +BEGIN; +SELECT pg_current_xact_id() = '0'; +SAVEPOINT barf; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +ROLLBACK TO SAVEPOINT barf; +ROLLBACK; + +-- succeed, outside tx. +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +SELECT 'stop' FROM pg_drop_replication_slot('regression_slot'); + +-- succeed, in tx without xid. +BEGIN; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +COMMIT; + +CREATE TABLE nobarf(id serial primary key, data text); +INSERT INTO nobarf(data) VALUES('1'); + +-- decoding works in transaction with xid +BEGIN; +SELECT pg_current_xact_id() = '0'; +-- don't show yet, haven't committed +INSERT INTO nobarf(data) VALUES('2'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +COMMIT; + +INSERT INTO nobarf(data) VALUES('3'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +SELECT 'stop' FROM pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/decoding_into_rel.sql b/contrib/test_decoding/sql/decoding_into_rel.sql new file mode 100644 index 0000000..1068cec --- /dev/null +++ b/contrib/test_decoding/sql/decoding_into_rel.sql @@ -0,0 +1,42 @@ +-- test that we can insert the result of a get_changes call into a +-- logged relation. That's really not a good idea in practical terms, +-- but provides a nice test. + +-- predictability +SET synchronous_commit = on; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +-- slot works +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- create some changes +CREATE TABLE somechange(id serial primary key); +INSERT INTO somechange DEFAULT VALUES; + +CREATE TABLE changeresult AS + SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +SELECT * FROM changeresult; + +INSERT INTO changeresult + SELECT data FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +INSERT INTO changeresult + SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +SELECT * FROM changeresult; +DROP TABLE changeresult; +DROP TABLE somechange; + +-- check calling logical decoding from pl/pgsql +CREATE FUNCTION slot_changes_wrapper(slot_name name) RETURNS SETOF TEXT AS $$ +BEGIN + RETURN QUERY + SELECT data FROM pg_logical_slot_peek_changes(slot_name, NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +END$$ LANGUAGE plpgsql; + +SELECT * FROM slot_changes_wrapper('regression_slot'); + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +SELECT 'stop' FROM pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/messages.sql b/contrib/test_decoding/sql/messages.sql new file mode 100644 index 0000000..cf3f773 --- /dev/null +++ b/contrib/test_decoding/sql/messages.sql @@ -0,0 +1,34 @@ +-- predictability +SET synchronous_commit = on; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +SELECT 'msg1' FROM pg_logical_emit_message(true, 'test', 'msg1'); +SELECT 'msg2' FROM pg_logical_emit_message(false, 'test', 'msg2'); + +BEGIN; +SELECT 'msg3' FROM pg_logical_emit_message(true, 'test', 'msg3'); +SELECT 'msg4' FROM pg_logical_emit_message(false, 'test', 'msg4'); +ROLLBACK; + +BEGIN; +SELECT 'msg5' FROM pg_logical_emit_message(true, 'test', 'msg5'); +SELECT 'msg6' FROM pg_logical_emit_message(false, 'test', 'msg6'); +SELECT 'msg7' FROM pg_logical_emit_message(true, 'test', 'msg7'); +COMMIT; + +SELECT 'ignorethis' FROM pg_logical_emit_message(true, 'test', 'czechtastic'); + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force-binary', '0', 'skip-empty-xacts', '1'); + +-- test db filtering +\set prevdb :DBNAME +\c template1 + +SELECT 'otherdb1' FROM pg_logical_emit_message(false, 'test', 'otherdb1'); +SELECT 'otherdb2' FROM pg_logical_emit_message(true, 'test', 'otherdb2'); + +\c :prevdb +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'force-binary', '0', 'skip-empty-xacts', '1'); + +SELECT 'cleanup' FROM pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/permissions.sql b/contrib/test_decoding/sql/permissions.sql new file mode 100644 index 0000000..312b514 --- /dev/null +++ b/contrib/test_decoding/sql/permissions.sql @@ -0,0 +1,69 @@ +-- predictability +SET synchronous_commit = on; + +-- setup +CREATE ROLE regress_lr_normal; +CREATE ROLE regress_lr_superuser SUPERUSER; +CREATE ROLE regress_lr_replication REPLICATION; +CREATE TABLE lr_test(data text); + +-- superuser can control replication +SET ROLE regress_lr_superuser; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +INSERT INTO lr_test VALUES('lr_superuser_init'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +SELECT pg_drop_replication_slot('regression_slot'); +RESET ROLE; + +-- replication user can control replication +SET ROLE regress_lr_replication; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +INSERT INTO lr_test VALUES('lr_superuser_init'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +SELECT pg_drop_replication_slot('regression_slot'); +RESET ROLE; + +-- plain user *can't* can control replication +SET ROLE regress_lr_normal; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +INSERT INTO lr_test VALUES('lr_superuser_init'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +SELECT pg_drop_replication_slot('regression_slot'); +RESET ROLE; + +-- replication users can drop superuser created slots +SET ROLE regress_lr_superuser; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +RESET ROLE; +SET ROLE regress_lr_replication; +SELECT pg_drop_replication_slot('regression_slot'); +RESET ROLE; + +-- normal users can't drop existing slots +SET ROLE regress_lr_superuser; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +RESET ROLE; +SET ROLE regress_lr_normal; +SELECT pg_drop_replication_slot('regression_slot'); +RESET ROLE; + +-- all users can see existing slots +SET ROLE regress_lr_superuser; +SELECT slot_name, plugin FROM pg_replication_slots; +RESET ROLE; + +SET ROLE regress_lr_replication; +SELECT slot_name, plugin FROM pg_replication_slots; +RESET ROLE; + +SET ROLE regress_lr_normal; +SELECT slot_name, plugin FROM pg_replication_slots; +RESET ROLE; + +-- cleanup +SELECT pg_drop_replication_slot('regression_slot'); + +DROP ROLE regress_lr_normal; +DROP ROLE regress_lr_superuser; +DROP ROLE regress_lr_replication; +DROP TABLE lr_test; diff --git a/contrib/test_decoding/sql/prepared.sql b/contrib/test_decoding/sql/prepared.sql new file mode 100644 index 0000000..e726397 --- /dev/null +++ b/contrib/test_decoding/sql/prepared.sql @@ -0,0 +1,50 @@ +-- predictability +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +CREATE TABLE test_prepared1(id int); +CREATE TABLE test_prepared2(id int); + +-- test simple successful use of a prepared xact +BEGIN; +INSERT INTO test_prepared1 VALUES (1); +PREPARE TRANSACTION 'test_prepared#1'; +COMMIT PREPARED 'test_prepared#1'; +INSERT INTO test_prepared1 VALUES (2); + +-- test abort of a prepared xact +BEGIN; +INSERT INTO test_prepared1 VALUES (3); +PREPARE TRANSACTION 'test_prepared#2'; +ROLLBACK PREPARED 'test_prepared#2'; + +INSERT INTO test_prepared1 VALUES (4); + +-- test prepared xact containing ddl +BEGIN; +INSERT INTO test_prepared1 VALUES (5); +ALTER TABLE test_prepared1 ADD COLUMN data text; +INSERT INTO test_prepared1 VALUES (6, 'frakbar'); +PREPARE TRANSACTION 'test_prepared#3'; + +-- test that we decode correctly while an uncommitted prepared xact +-- with ddl exists. + +-- separate table because of the lock from the ALTER +-- this will come before the '5' row above, as this commits before it. +INSERT INTO test_prepared2 VALUES (7); + +COMMIT PREPARED 'test_prepared#3'; + +-- make sure stuff still works +INSERT INTO test_prepared1 VALUES (8); +INSERT INTO test_prepared2 VALUES (9); + +-- cleanup +DROP TABLE test_prepared1; +DROP TABLE test_prepared2; + +-- show results +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +SELECT pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/replorigin.sql b/contrib/test_decoding/sql/replorigin.sql new file mode 100644 index 0000000..e71ee02 --- /dev/null +++ b/contrib/test_decoding/sql/replorigin.sql @@ -0,0 +1,148 @@ +-- predictability +SET synchronous_commit = on; + +-- superuser required by default +CREATE ROLE regress_origin_replication REPLICATION; +SET ROLE regress_origin_replication; +SELECT pg_replication_origin_advance('regress_test_decoding: perm', '0/1'); +SELECT pg_replication_origin_create('regress_test_decoding: perm'); +SELECT pg_replication_origin_drop('regress_test_decoding: perm'); +SELECT pg_replication_origin_oid('regress_test_decoding: perm'); +SELECT pg_replication_origin_progress('regress_test_decoding: perm', false); +SELECT pg_replication_origin_session_is_setup(); +SELECT pg_replication_origin_session_progress(false); +SELECT pg_replication_origin_session_reset(); +SELECT pg_replication_origin_session_setup('regress_test_decoding: perm'); +SELECT pg_replication_origin_xact_reset(); +SELECT pg_replication_origin_xact_setup('0/1', '2013-01-01 00:00'); +SELECT pg_show_replication_origin_status(); +RESET ROLE; +DROP ROLE regress_origin_replication; + +CREATE TABLE origin_tbl(id serial primary key, data text); +CREATE TABLE target_tbl(id serial primary key, data text); + +SELECT pg_replication_origin_create('regress_test_decoding: regression_slot'); +-- ensure duplicate creations fail +SELECT pg_replication_origin_create('regress_test_decoding: regression_slot'); + +--ensure deletions work (once) +SELECT pg_replication_origin_create('regress_test_decoding: temp'); +SELECT pg_replication_origin_drop('regress_test_decoding: temp'); +SELECT pg_replication_origin_drop('regress_test_decoding: temp'); + +-- specifying reserved origin names is not supported +SELECT pg_replication_origin_create('any'); +SELECT pg_replication_origin_create('none'); +SELECT pg_replication_origin_create('pg_replication_origin'); + +-- various failure checks for undefined slots +select pg_replication_origin_advance('regress_test_decoding: temp', '0/1'); +select pg_replication_origin_session_setup('regress_test_decoding: temp'); +select pg_replication_origin_progress('regress_test_decoding: temp', true); + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +-- origin tx +INSERT INTO origin_tbl(data) VALUES ('will be replicated and decoded and decoded again'); +INSERT INTO target_tbl(data) +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- as is normal, the insert into target_tbl shows up +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +INSERT INTO origin_tbl(data) VALUES ('will be replicated, but not decoded again'); + +-- mark session as replaying +SELECT pg_replication_origin_session_setup('regress_test_decoding: regression_slot'); + +-- ensure we prevent duplicate setup +SELECT pg_replication_origin_session_setup('regress_test_decoding: regression_slot'); + +SELECT '' FROM pg_logical_emit_message(false, 'test', 'this message will not be decoded'); + +BEGIN; +-- setup transaction origin +SELECT pg_replication_origin_xact_setup('0/aabbccdd', '2013-01-01 00:00'); +INSERT INTO target_tbl(data) +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'only-local', '1'); +COMMIT; + +-- check replication progress for the session is correct +SELECT pg_replication_origin_session_progress(false); +SELECT pg_replication_origin_session_progress(true); + +SELECT pg_replication_origin_session_reset(); + +SELECT local_id, external_id, remote_lsn, local_lsn <> '0/0' FROM pg_replication_origin_status; + +-- check replication progress identified by name is correct +SELECT pg_replication_origin_progress('regress_test_decoding: regression_slot', false); +SELECT pg_replication_origin_progress('regress_test_decoding: regression_slot', true); + +-- ensure reset requires previously setup state +SELECT pg_replication_origin_session_reset(); + +-- and magically the replayed xact will be filtered! +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'only-local', '1'); + +--but new original changes still show up +INSERT INTO origin_tbl(data) VALUES ('will be replicated'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'only-local', '1'); + +SELECT pg_drop_replication_slot('regression_slot'); +SELECT pg_replication_origin_drop('regress_test_decoding: regression_slot'); + +-- Set of transactions with no origin LSNs and commit timestamps set for +-- this session. +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_no_lsn', 'test_decoding'); +SELECT pg_replication_origin_create('regress_test_decoding: regression_slot_no_lsn'); +-- mark session as replaying +SELECT pg_replication_origin_session_setup('regress_test_decoding: regression_slot_no_lsn'); +-- Simple transactions +BEGIN; +INSERT INTO origin_tbl(data) VALUES ('no_lsn, commit'); +COMMIT; +BEGIN; +INSERT INTO origin_tbl(data) VALUES ('no_lsn, rollback'); +ROLLBACK; +-- 2PC transactions +BEGIN; +INSERT INTO origin_tbl(data) VALUES ('no_lsn, commit prepared'); +PREPARE TRANSACTION 'replorigin_prepared'; +COMMIT PREPARED 'replorigin_prepared'; +BEGIN; +INSERT INTO origin_tbl(data) VALUES ('no_lsn, rollback prepared'); +PREPARE TRANSACTION 'replorigin_prepared'; +ROLLBACK PREPARED 'replorigin_prepared'; +SELECT local_id, external_id, + remote_lsn <> '0/0' AS valid_remote_lsn, + local_lsn <> '0/0' AS valid_local_lsn + FROM pg_replication_origin_status; +SELECT data FROM pg_logical_slot_get_changes('regression_slot_no_lsn', NULL, NULL, 'skip-empty-xacts', '1', 'include-xids', '0'); +-- Clean up +SELECT pg_replication_origin_session_reset(); +SELECT pg_drop_replication_slot('regression_slot_no_lsn'); +SELECT pg_replication_origin_drop('regress_test_decoding: regression_slot_no_lsn'); + +-- Test that the pgoutput correctly filters changes corresponding to the provided origin value. +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'pgoutput'); +CREATE PUBLICATION pub FOR TABLE target_tbl; +SELECT pg_replication_origin_create('regress_test_decoding: regression_slot'); + +-- mark session as replaying +SELECT pg_replication_origin_session_setup('regress_test_decoding: regression_slot'); + +INSERT INTO target_tbl(data) VALUES ('test data'); + +-- The replayed change will be filtered. +SELECT count(*) = 0 FROM pg_logical_slot_peek_binary_changes('regression_slot', NULL, NULL, 'proto_version', '4', 'publication_names', 'pub', 'origin', 'none'); + +-- The replayed change will be output if the origin value is not specified. +SELECT count(*) != 0 FROM pg_logical_slot_peek_binary_changes('regression_slot', NULL, NULL, 'proto_version', '4', 'publication_names', 'pub'); + +-- Clean up +SELECT pg_replication_origin_session_reset(); +SELECT pg_drop_replication_slot('regression_slot'); +SELECT pg_replication_origin_drop('regress_test_decoding: regression_slot'); +DROP PUBLICATION pub; diff --git a/contrib/test_decoding/sql/rewrite.sql b/contrib/test_decoding/sql/rewrite.sql new file mode 100644 index 0000000..62dead3 --- /dev/null +++ b/contrib/test_decoding/sql/rewrite.sql @@ -0,0 +1,107 @@ +-- predictability +SET synchronous_commit = on; + +DROP TABLE IF EXISTS replication_example; + +-- Ensure there's tables with toast datums. To do so, we dynamically +-- create a function returning a large textblob. We want tables of +-- different kinds: mapped catalog table, unmapped catalog table, +-- shared catalog table and usertable. +CREATE FUNCTION exec(text) returns void language plpgsql volatile + AS $f$ + BEGIN + EXECUTE $1; + END; +$f$; +CREATE ROLE regress_justforcomments NOLOGIN; + +SELECT exec( + format($outer$CREATE FUNCTION iamalongfunction() RETURNS TEXT IMMUTABLE LANGUAGE SQL AS $f$SELECT text %L$f$$outer$, + (SELECT repeat(string_agg(to_char(g.i, 'FM0000'), ''), 50) FROM generate_series(1, 500) g(i)))); +SELECT exec( + format($outer$COMMENT ON FUNCTION iamalongfunction() IS %L$outer$, + iamalongfunction())); +SELECT exec( + format($outer$COMMENT ON ROLE REGRESS_JUSTFORCOMMENTS IS %L$outer$, + iamalongfunction())); +CREATE TABLE iamalargetable AS SELECT iamalongfunction() longfunctionoutput; + +-- verify toast usage +SELECT pg_relation_size((SELECT reltoastrelid FROM pg_class WHERE oid = 'pg_proc'::regclass)) > 0; +SELECT pg_relation_size((SELECT reltoastrelid FROM pg_class WHERE oid = 'pg_description'::regclass)) > 0; +SELECT pg_relation_size((SELECT reltoastrelid FROM pg_class WHERE oid = 'pg_shdescription'::regclass)) > 0; + + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); +CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120)); +INSERT INTO replication_example(somedata) VALUES (1); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +BEGIN; +INSERT INTO replication_example(somedata) VALUES (2); +ALTER TABLE replication_example ADD COLUMN testcolumn1 int; +INSERT INTO replication_example(somedata, testcolumn1) VALUES (3, 1); +COMMIT; + +BEGIN; +INSERT INTO replication_example(somedata) VALUES (3); +ALTER TABLE replication_example ADD COLUMN testcolumn2 int; +INSERT INTO replication_example(somedata, testcolumn1, testcolumn2) VALUES (4, 2, 1); +COMMIT; + +VACUUM FULL pg_am; +VACUUM FULL pg_amop; +VACUUM FULL pg_proc; +VACUUM FULL pg_opclass; +VACUUM FULL pg_type; +VACUUM FULL pg_index; +VACUUM FULL pg_database; + +-- repeated rewrites that fail +BEGIN; +CLUSTER pg_class USING pg_class_oid_index; +CLUSTER pg_class USING pg_class_oid_index; +ROLLBACK; + +-- repeated rewrites that succeed +BEGIN; +CLUSTER pg_class USING pg_class_oid_index; +CLUSTER pg_class USING pg_class_oid_index; +CLUSTER pg_class USING pg_class_oid_index; +COMMIT; + + -- repeated rewrites in different transactions +VACUUM FULL pg_class; +VACUUM FULL pg_class; + +-- reindexing of important relations / indexes +REINDEX TABLE pg_class; +REINDEX INDEX pg_class_oid_index; +REINDEX INDEX pg_class_tblspc_relfilenode_index; + +INSERT INTO replication_example(somedata, testcolumn1) VALUES (5, 3); + +BEGIN; +INSERT INTO replication_example(somedata, testcolumn1) VALUES (6, 4); +ALTER TABLE replication_example ADD COLUMN testcolumn3 int; +INSERT INTO replication_example(somedata, testcolumn1, testcolumn3) VALUES (7, 5, 1); +COMMIT; + +-- make old files go away +CHECKPOINT; + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- trigger repeated rewrites of a system catalog with a toast table, +-- that previously was buggy: 20180914021046.oi7dm4ra3ot2g2kt@alap3.anarazel.de +VACUUM FULL pg_proc; VACUUM FULL pg_description; VACUUM FULL pg_shdescription; VACUUM FULL iamalargetable; +INSERT INTO replication_example(somedata, testcolumn1, testcolumn3) VALUES (8, 6, 1); +VACUUM FULL pg_proc; VACUUM FULL pg_description; VACUUM FULL pg_shdescription; VACUUM FULL iamalargetable; +INSERT INTO replication_example(somedata, testcolumn1, testcolumn3) VALUES (9, 7, 1); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +SELECT pg_drop_replication_slot('regression_slot'); +DROP TABLE IF EXISTS replication_example; +DROP FUNCTION iamalongfunction(); +DROP FUNCTION exec(text); +DROP ROLE regress_justforcomments; diff --git a/contrib/test_decoding/sql/slot.sql b/contrib/test_decoding/sql/slot.sql new file mode 100644 index 0000000..1aa27c5 --- /dev/null +++ b/contrib/test_decoding/sql/slot.sql @@ -0,0 +1,178 @@ +-- predictability +SET synchronous_commit = on; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding'); +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t', 'test_decoding', true); + +SELECT pg_drop_replication_slot('regression_slot_p'); +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_p', 'test_decoding', false); + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot_t2', 'test_decoding', true); + +SELECT pg_create_logical_replication_slot('foo', 'nonexistent'); + +-- here we want to start a new session and wait till old one is gone +select pg_backend_pid() as oldpid \gset +\c - +SET synchronous_commit = on; + +do 'declare c int = 0; +begin + while (select count(*) from pg_replication_slots where active_pid = ' + :'oldpid' + ') > 0 loop c := c + 1; perform pg_sleep(0.01); end loop; + raise log ''slot test looped % times'', c; +end'; + +-- should fail because the temporary slots were dropped automatically +SELECT pg_drop_replication_slot('regression_slot_t'); +SELECT pg_drop_replication_slot('regression_slot_t2'); + +-- monitoring functions for slot directories +SELECT count(*) >= 0 AS ok FROM pg_ls_logicalmapdir(); +SELECT count(*) >= 0 AS ok FROM pg_ls_logicalsnapdir(); +SELECT count(*) >= 0 AS ok FROM pg_ls_replslotdir('regression_slot_p'); +SELECT count(*) >= 0 AS ok FROM pg_ls_replslotdir('not_existing_slot'); -- fails + +-- permanent slot has survived +SELECT pg_drop_replication_slot('regression_slot_p'); + +-- test switching between slots in a session +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot1', 'test_decoding', true); + +CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120)); +BEGIN; +INSERT INTO replication_example(somedata, text) VALUES (1, 1); +INSERT INTO replication_example(somedata, text) VALUES (1, 2); +COMMIT; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot2', 'test_decoding', true); + +INSERT INTO replication_example(somedata, text) VALUES (1, 3); + +SELECT data FROM pg_logical_slot_get_changes('regression_slot1', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot2', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +INSERT INTO replication_example(somedata, text) VALUES (1, 4); +INSERT INTO replication_example(somedata, text) VALUES (1, 5); + +SELECT pg_current_wal_lsn() AS wal_lsn \gset + +INSERT INTO replication_example(somedata, text) VALUES (1, 6); + +SELECT end_lsn FROM pg_replication_slot_advance('regression_slot1', :'wal_lsn') \gset +SELECT slot_name FROM pg_replication_slot_advance('regression_slot2', pg_current_wal_lsn()); + +SELECT :'wal_lsn' = :'end_lsn'; + +SELECT data FROM pg_logical_slot_get_changes('regression_slot1', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +SELECT data FROM pg_logical_slot_get_changes('regression_slot2', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +DROP TABLE replication_example; + +-- error +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot1', 'test_decoding', true); + +-- both should error as they should be dropped on error +SELECT pg_drop_replication_slot('regression_slot1'); +SELECT pg_drop_replication_slot('regression_slot2'); + +-- slot advance with physical slot, error with non-reserved slot +SELECT slot_name FROM pg_create_physical_replication_slot('regression_slot3'); +SELECT pg_replication_slot_advance('regression_slot3', '0/0'); -- invalid LSN +SELECT pg_replication_slot_advance('regression_slot3', '0/1'); -- error +SELECT pg_drop_replication_slot('regression_slot3'); + +-- +-- Test copy functions for logical replication slots +-- + +-- Create and copy logical slots +SELECT 'init' FROM pg_create_logical_replication_slot('orig_slot1', 'test_decoding', false); +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot1', 'copied_slot1_no_change'); +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot1', 'copied_slot1_change_plugin', false, 'pgoutput'); +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot1', 'copied_slot1_change_plugin_temp', true, 'pgoutput'); + +-- Check all copied slots status +SELECT + o.slot_name, o.plugin, o.temporary, c.slot_name, c.plugin, c.temporary +FROM + (SELECT * FROM pg_replication_slots WHERE slot_name LIKE 'orig%') as o + LEFT JOIN pg_replication_slots as c ON o.restart_lsn = c.restart_lsn AND o.confirmed_flush_lsn = c.confirmed_flush_lsn +WHERE + o.slot_name != c.slot_name +ORDER BY o.slot_name, c.slot_name; + +-- Now we have maximum 4 replication slots. Check slots are properly +-- released even when raise error during creating the target slot. +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot1', 'failed'); -- error + +-- temporary slots were dropped automatically +SELECT pg_drop_replication_slot('orig_slot1'); +SELECT pg_drop_replication_slot('copied_slot1_no_change'); +SELECT pg_drop_replication_slot('copied_slot1_change_plugin'); + +-- Test based on the temporary logical slot +SELECT 'init' FROM pg_create_logical_replication_slot('orig_slot2', 'test_decoding', true); +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot2', 'copied_slot2_no_change'); +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot2', 'copied_slot2_change_plugin', true, 'pgoutput'); +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot2', 'copied_slot2_change_plugin_temp', false, 'pgoutput'); + +-- Check all copied slots status +SELECT + o.slot_name, o.plugin, o.temporary, c.slot_name, c.plugin, c.temporary +FROM + (SELECT * FROM pg_replication_slots WHERE slot_name LIKE 'orig%') as o + LEFT JOIN pg_replication_slots as c ON o.restart_lsn = c.restart_lsn AND o.confirmed_flush_lsn = c.confirmed_flush_lsn +WHERE + o.slot_name != c.slot_name +ORDER BY o.slot_name, c.slot_name; + +-- Cannot copy a logical slot to a physical slot +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot2', 'failed'); -- error + +-- temporary slots were dropped automatically +SELECT pg_drop_replication_slot('copied_slot2_change_plugin_temp'); + +-- +-- Test copy functions for physical replication slots +-- + +-- Create and copy physical slots +SELECT 'init' FROM pg_create_physical_replication_slot('orig_slot1', true); +SELECT 'init' FROM pg_create_physical_replication_slot('orig_slot2', false); +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot1', 'copied_slot1_no_change'); +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot1', 'copied_slot1_temp', true); + +-- Check all copied slots status. Since all slots don't reserve WAL we check only other fields. +SELECT slot_name, slot_type, temporary FROM pg_replication_slots; + +-- Cannot copy a physical slot to a logical slot +SELECT 'copy' FROM pg_copy_logical_replication_slot('orig_slot1', 'failed'); -- error + +-- Cannot copy a physical slot that doesn't reserve WAL +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot2', 'failed'); -- error + +-- temporary slots were dropped automatically +SELECT pg_drop_replication_slot('orig_slot1'); +SELECT pg_drop_replication_slot('orig_slot2'); +SELECT pg_drop_replication_slot('copied_slot1_no_change'); + +-- Test based on the temporary physical slot +SELECT 'init' FROM pg_create_physical_replication_slot('orig_slot2', true, true); +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot2', 'copied_slot2_no_change'); +SELECT 'copy' FROM pg_copy_physical_replication_slot('orig_slot2', 'copied_slot2_notemp', false); + +-- Check all copied slots status +SELECT + o.slot_name, o.temporary, c.slot_name, c.temporary +FROM + (SELECT * FROM pg_replication_slots WHERE slot_name LIKE 'orig%') as o + LEFT JOIN pg_replication_slots as c ON o.restart_lsn = c.restart_lsn +WHERE + o.slot_name != c.slot_name +ORDER BY o.slot_name, c.slot_name; + +SELECT pg_drop_replication_slot('orig_slot2'); +SELECT pg_drop_replication_slot('copied_slot2_no_change'); +SELECT pg_drop_replication_slot('copied_slot2_notemp'); diff --git a/contrib/test_decoding/sql/spill.sql b/contrib/test_decoding/sql/spill.sql new file mode 100644 index 0000000..e638cac --- /dev/null +++ b/contrib/test_decoding/sql/spill.sql @@ -0,0 +1,179 @@ +-- predictability +SET synchronous_commit = on; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +CREATE TABLE spill_test(data text); + +-- consume DDL +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- spilling main xact +BEGIN; +INSERT INTO spill_test SELECT 'serialize-topbig--1:'||g.i FROM generate_series(1, 5000) g(i); +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- spilling subxact, nothing in main +BEGIN; +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- spilling subxact, spilling main xact +BEGIN; +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topbig--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topbig--2:'||g.i FROM generate_series(5001, 10000) g(i); +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- spilling subxact, non-spilling main xact +BEGIN; +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topsmall--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topsmall--2:'||g.i FROM generate_series(5001, 5001) g(i); +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- not-spilling subxact, spilling main xact +BEGIN; +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topbig--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-subbig-topbig--2:'||g.i FROM generate_series(5001, 10000) g(i); +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- spilling main xact, spilling subxact +BEGIN; +INSERT INTO spill_test SELECT 'serialize-topbig-subbig--1:'||g.i FROM generate_series(1, 5000) g(i); +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-topbig-subbig--2:'||g.i FROM generate_series(5001, 10000) g(i); +RELEASE SAVEPOINT s; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- spilling main xact, not spilling subxact +BEGIN; +INSERT INTO spill_test SELECT 'serialize-topbig-subsmall--1:'||g.i FROM generate_series(1, 5000) g(i); +SAVEPOINT s; +INSERT INTO spill_test SELECT 'serialize-topbig-subsmall--2:'||g.i FROM generate_series(5001, 5001) g(i); +RELEASE SAVEPOINT s; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- spilling subxact, followed by another spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-subbig-subbig--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s1; +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-subbig-subbig--2:'||g.i FROM generate_series(5001, 10000) g(i); +RELEASE SAVEPOINT s2; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- spilling subxact, followed by not spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-subbig-subsmall--1:'||g.i FROM generate_series(1, 5000) g(i); +RELEASE SAVEPOINT s1; +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-subbig-subsmall--2:'||g.i FROM generate_series(5001, 5001) g(i); +RELEASE SAVEPOINT s2; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4], COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- not spilling subxact, followed by spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-subsmall-subbig--1:'||g.i FROM generate_series(1, 1) g(i); +RELEASE SAVEPOINT s1; +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-subsmall-subbig--2:'||g.i FROM generate_series(2, 5001) g(i); +RELEASE SAVEPOINT s2; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4] COLLATE "C", COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- spilling subxact, containing another spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subbig--1:'||g.i FROM generate_series(1, 5000) g(i); +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subbig--2:'||g.i FROM generate_series(5001, 10000) g(i); +RELEASE SAVEPOINT s2; +RELEASE SAVEPOINT s1; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4] COLLATE "C", COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- spilling subxact, containing a not spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subsmall--1:'||g.i FROM generate_series(1, 5000) g(i); +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subsmall--2:'||g.i FROM generate_series(5001, 5001) g(i); +RELEASE SAVEPOINT s2; +RELEASE SAVEPOINT s1; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4] COLLATE "C", COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- not spilling subxact, containing a spilling subxact +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-nested-subsmall-subbig--1:'||g.i FROM generate_series(1, 1) g(i); +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-nested-subsmall-subbig--2:'||g.i FROM generate_series(2, 5001) g(i); +RELEASE SAVEPOINT s2; +RELEASE SAVEPOINT s1; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4] COLLATE "C", COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +-- not spilling subxact, containing a spilling subxact that aborts and one that commits +BEGIN; +SAVEPOINT s1; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subbigabort--1:'||g.i FROM generate_series(1, 5000) g(i); +SAVEPOINT s2; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subbigabort--2:'||g.i FROM generate_series(5001, 10000) g(i); +ROLLBACK TO SAVEPOINT s2; +SAVEPOINT s3; +INSERT INTO spill_test SELECT 'serialize-nested-subbig-subbigabort-subbig-3:'||g.i FROM generate_series(5001, 10000) g(i); +RELEASE SAVEPOINT s1; +COMMIT; +SELECT (regexp_split_to_array(data, ':'))[4] COLLATE "C", COUNT(*), (array_agg(data))[1], (array_agg(data))[count(*)] +FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL) WHERE data ~ 'INSERT' +GROUP BY 1 ORDER BY 1; + +DROP TABLE spill_test; + +SELECT pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/stats.sql b/contrib/test_decoding/sql/stats.sql new file mode 100644 index 0000000..630371f --- /dev/null +++ b/contrib/test_decoding/sql/stats.sql @@ -0,0 +1,56 @@ +-- predictability +SET synchronous_commit = on; + +SELECT 'init' FROM + pg_create_logical_replication_slot('regression_slot_stats1', 'test_decoding') s1, + pg_create_logical_replication_slot('regression_slot_stats2', 'test_decoding') s2, + pg_create_logical_replication_slot('regression_slot_stats3', 'test_decoding') s3; + +CREATE TABLE stats_test(data text); + +-- non-spilled xact +SET logical_decoding_work_mem to '64MB'; +INSERT INTO stats_test values(1); +SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats1', NULL, NULL, 'skip-empty-xacts', '1'); +SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats2', NULL, NULL, 'skip-empty-xacts', '1'); +SELECT count(*) FROM pg_logical_slot_get_changes('regression_slot_stats3', NULL, NULL, 'skip-empty-xacts', '1'); +SELECT pg_stat_force_next_flush(); +SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count, total_txns > 0 AS total_txns, total_bytes > 0 AS total_bytes FROM pg_stat_replication_slots ORDER BY slot_name; +RESET logical_decoding_work_mem; + +-- reset stats for one slot, others should be unaffected +SELECT pg_stat_reset_replication_slot('regression_slot_stats1'); +SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count, total_txns > 0 AS total_txns, total_bytes > 0 AS total_bytes FROM pg_stat_replication_slots ORDER BY slot_name; + +-- reset stats for all slots +SELECT pg_stat_reset_replication_slot(NULL); +SELECT slot_name, spill_txns = 0 AS spill_txns, spill_count = 0 AS spill_count, total_txns > 0 AS total_txns, total_bytes > 0 AS total_bytes FROM pg_stat_replication_slots ORDER BY slot_name; + +-- verify accessing/resetting stats for non-existent slot does something reasonable +SELECT * FROM pg_stat_get_replication_slot('do-not-exist'); +SELECT pg_stat_reset_replication_slot('do-not-exist'); +SELECT * FROM pg_stat_get_replication_slot('do-not-exist'); + +-- spilling the xact +BEGIN; +INSERT INTO stats_test SELECT 'serialize-topbig--1:'||g.i FROM generate_series(1, 5000) g(i); +COMMIT; +SELECT count(*) FROM pg_logical_slot_peek_changes('regression_slot_stats1', NULL, NULL, 'skip-empty-xacts', '1'); + +-- Check stats. We can't test the exact stats count as that can vary if any +-- background transaction (say by autovacuum) happens in parallel to the main +-- transaction. +SELECT pg_stat_force_next_flush(); +SELECT slot_name, spill_txns > 0 AS spill_txns, spill_count > 0 AS spill_count FROM pg_stat_replication_slots; + +-- Ensure stats can be repeatedly accessed using the same stats snapshot. See +-- https://postgr.es/m/20210317230447.c7uc4g3vbs4wi32i%40alap3.anarazel.de +BEGIN; +SELECT slot_name FROM pg_stat_replication_slots; +SELECT slot_name FROM pg_stat_replication_slots; +COMMIT; + +DROP TABLE stats_test; +SELECT pg_drop_replication_slot('regression_slot_stats1'), + pg_drop_replication_slot('regression_slot_stats2'), + pg_drop_replication_slot('regression_slot_stats3'); diff --git a/contrib/test_decoding/sql/stream.sql b/contrib/test_decoding/sql/stream.sql new file mode 100644 index 0000000..4feec62 --- /dev/null +++ b/contrib/test_decoding/sql/stream.sql @@ -0,0 +1,48 @@ +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +CREATE TABLE stream_test(data text); + +-- consume DDL +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- streaming test with sub-transaction +BEGIN; +savepoint s1; +SELECT 'msg5' FROM pg_logical_emit_message(true, 'test', repeat('a', 50)); +INSERT INTO stream_test SELECT repeat('a', 2000) || g.i FROM generate_series(1, 35) g(i); +TRUNCATE table stream_test; +rollback to s1; +INSERT INTO stream_test SELECT repeat('a', 10) || g.i FROM generate_series(1, 20) g(i); +COMMIT; + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + +-- streaming test for toast changes +ALTER TABLE stream_test ALTER COLUMN data set storage external; +-- consume DDL +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +INSERT INTO stream_test SELECT repeat('a', 6000) || g.i FROM generate_series(1, 10) g(i); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + +-- streaming test for toast with multi-insert +\COPY stream_test FROM STDIN +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +toasted-123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +\. + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + +DROP TABLE stream_test; +SELECT pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/time.sql b/contrib/test_decoding/sql/time.sql new file mode 100644 index 0000000..a47c973 --- /dev/null +++ b/contrib/test_decoding/sql/time.sql @@ -0,0 +1,22 @@ +SET synchronous_commit = on; + +CREATE TABLE test_time(data text); + +-- remember the current time +SELECT set_config('test.time_before', NOW()::text, false) IS NOT NULL; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +-- a single transaction, to get the commit time +INSERT INTO test_time(data) VALUES (''); + +-- parse the commit time from the changeset +SELECT set_config('test.time_after', regexp_replace(data, '^COMMIT \(at (.*)\)$', '\1'), false) IS NOT NULL +FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'include-timestamp', '1') +WHERE data ~ 'COMMIT' LIMIT 1; + +-- ensure commit time is sane in relation to the previous time +SELECT (time_after - time_before) <= '10 minutes'::interval, time_after >= time_before +FROM (SELECT current_setting('test.time_after')::timestamptz AS time_after, (SELECT current_setting('test.time_before')::timestamptz) AS time_before) AS d; + +SELECT pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/toast.sql b/contrib/test_decoding/sql/toast.sql new file mode 100644 index 0000000..d1c560a --- /dev/null +++ b/contrib/test_decoding/sql/toast.sql @@ -0,0 +1,327 @@ +-- predictability +SET synchronous_commit = on; + +DROP TABLE IF EXISTS xpto; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +CREATE SEQUENCE xpto_rand_seq START 79 INCREMENT 1499; -- portable "random" +CREATE TABLE xpto ( + id serial primary key, + toasted_col1 text, + rand1 float8 DEFAULT nextval('xpto_rand_seq'), + toasted_col2 text, + rand2 float8 DEFAULT nextval('xpto_rand_seq') +); + +-- uncompressed external toast data +INSERT INTO xpto (toasted_col1, toasted_col2) SELECT string_agg(g.i::text, ''), string_agg((g.i*2)::text, '') FROM generate_series(1, 2000) g(i); + +-- compressed external toast data +INSERT INTO xpto (toasted_col2) SELECT repeat(string_agg(to_char(g.i, 'FM0000'), ''), 50) FROM generate_series(1, 500) g(i); + +-- update of existing column +UPDATE xpto SET toasted_col1 = (SELECT string_agg(g.i::text, '') FROM generate_series(1, 2000) g(i)) WHERE id = 1; + +UPDATE xpto SET rand1 = 123.456 WHERE id = 1; + +-- updating external via INSERT ... ON CONFLICT DO UPDATE +INSERT INTO xpto(id, toasted_col2) VALUES (2, 'toasted2-upsert') +ON CONFLICT (id) +DO UPDATE SET toasted_col2 = EXCLUDED.toasted_col2 || xpto.toasted_col2; + +DELETE FROM xpto WHERE id = 1; + +DROP TABLE IF EXISTS toasted_key; +CREATE TABLE toasted_key ( + id serial, + toasted_key text PRIMARY KEY, + toasted_col1 text, + toasted_col2 text +); + +ALTER TABLE toasted_key ALTER COLUMN toasted_key SET STORAGE EXTERNAL; +ALTER TABLE toasted_key ALTER COLUMN toasted_col1 SET STORAGE EXTERNAL; + +INSERT INTO toasted_key(toasted_key, toasted_col1) VALUES(repeat('1234567890', 200), repeat('9876543210', 200)); + +-- test update of a toasted key without changing it +UPDATE toasted_key SET toasted_col2 = toasted_col1; +-- test update of a toasted key, changing it +UPDATE toasted_key SET toasted_key = toasted_key || '1'; + +DELETE FROM toasted_key; + +-- Test that HEAP2_MULTI_INSERT insertions with and without toasted +-- columns are handled correctly +CREATE TABLE toasted_copy ( + id int primary key, -- no default, copy didn't use to handle that with multi inserts + data text +); +ALTER TABLE toasted_copy ALTER COLUMN data SET STORAGE EXTERNAL; +\copy toasted_copy FROM STDIN +1 untoasted1 +2 toasted1-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +3 untoasted2 +4 toasted2-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +5 untoasted3 +6 untoasted4 +7 untoasted5 +8 untoasted6 +9 untoasted7 +10 untoasted8 +11 untoasted9 +12 untoasted10 +13 untoasted11 +14 untoasted12 +15 untoasted13 +16 untoasted14 +17 untoasted15 +18 untoasted16 +19 untoasted17 +20 untoasted18 +21 untoasted19 +22 untoasted20 +23 untoasted21 +24 untoasted22 +25 untoasted23 +26 untoasted24 +27 untoasted25 +28 untoasted26 +29 untoasted27 +30 untoasted28 +31 untoasted29 +32 untoasted30 +33 untoasted31 +34 untoasted32 +35 untoasted33 +36 untoasted34 +37 untoasted35 +38 untoasted36 +39 untoasted37 +40 untoasted38 +41 untoasted39 +42 untoasted40 +43 untoasted41 +44 untoasted42 +45 untoasted43 +46 untoasted44 +47 untoasted45 +48 untoasted46 +49 untoasted47 +50 untoasted48 +51 untoasted49 +52 untoasted50 +53 untoasted51 +54 untoasted52 +55 untoasted53 +56 untoasted54 +57 untoasted55 +58 untoasted56 +59 untoasted57 +60 untoasted58 +61 untoasted59 +62 untoasted60 +63 untoasted61 +64 untoasted62 +65 untoasted63 +66 untoasted64 +67 untoasted65 +68 untoasted66 +69 untoasted67 +70 untoasted68 +71 untoasted69 +72 untoasted70 +73 untoasted71 +74 untoasted72 +75 untoasted73 +76 untoasted74 +77 untoasted75 +78 untoasted76 +79 untoasted77 +80 untoasted78 +81 untoasted79 +82 untoasted80 +83 untoasted81 +84 untoasted82 +85 untoasted83 +86 untoasted84 +87 untoasted85 +88 untoasted86 +89 untoasted87 +90 untoasted88 +91 untoasted89 +92 untoasted90 +93 untoasted91 +94 untoasted92 +95 untoasted93 +96 untoasted94 +97 untoasted95 +98 untoasted96 +99 untoasted97 +100 untoasted98 +101 untoasted99 +102 untoasted100 +103 untoasted101 +104 untoasted102 +105 untoasted103 +106 untoasted104 +107 untoasted105 +108 untoasted106 +109 untoasted107 +110 untoasted108 +111 untoasted109 +112 untoasted110 +113 untoasted111 +114 untoasted112 +115 untoasted113 +116 untoasted114 +117 untoasted115 +118 untoasted116 +119 untoasted117 +120 untoasted118 +121 untoasted119 +122 untoasted120 +123 untoasted121 +124 untoasted122 +125 untoasted123 +126 untoasted124 +127 untoasted125 +128 untoasted126 +129 untoasted127 +130 untoasted128 +131 untoasted129 +132 untoasted130 +133 untoasted131 +134 untoasted132 +135 untoasted133 +136 untoasted134 +137 untoasted135 +138 untoasted136 +139 untoasted137 +140 untoasted138 +141 untoasted139 +142 untoasted140 +143 untoasted141 +144 untoasted142 +145 untoasted143 +146 untoasted144 +147 untoasted145 +148 untoasted146 +149 untoasted147 +150 untoasted148 +151 untoasted149 +152 untoasted150 +153 untoasted151 +154 untoasted152 +155 untoasted153 +156 untoasted154 +157 untoasted155 +158 untoasted156 +159 untoasted157 +160 untoasted158 +161 untoasted159 +162 untoasted160 +163 untoasted161 +164 untoasted162 +165 untoasted163 +166 untoasted164 +167 untoasted165 +168 untoasted166 +169 untoasted167 +170 untoasted168 +171 untoasted169 +172 untoasted170 +173 untoasted171 +174 untoasted172 +175 untoasted173 +176 untoasted174 +177 untoasted175 +178 untoasted176 +179 untoasted177 +180 untoasted178 +181 untoasted179 +182 untoasted180 +183 untoasted181 +184 untoasted182 +185 untoasted183 +186 untoasted184 +187 untoasted185 +188 untoasted186 +189 untoasted187 +190 untoasted188 +191 untoasted189 +192 untoasted190 +193 untoasted191 +194 untoasted192 +195 untoasted193 +196 untoasted194 +197 untoasted195 +198 untoasted196 +199 untoasted197 +200 untoasted198 +201 toasted3-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +202 untoasted199 +203 untoasted200 +\. +SELECT substr(data, 1, 200) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- test we can decode "old" tuples bigger than the max heap tuple size correctly +DROP TABLE IF EXISTS toasted_several; +CREATE TABLE toasted_several ( + id serial unique not null, + toasted_key text primary key, + toasted_col1 text, + toasted_col2 text +); +ALTER TABLE toasted_several REPLICA IDENTITY FULL; +ALTER TABLE toasted_several ALTER COLUMN toasted_key SET STORAGE EXTERNAL; +ALTER TABLE toasted_several ALTER COLUMN toasted_col1 SET STORAGE EXTERNAL; +ALTER TABLE toasted_several ALTER COLUMN toasted_col2 SET STORAGE EXTERNAL; + +-- Change the storage of the index back to EXTENDED, separately from +-- the table. This is currently not doable via DDL, but it is +-- supported internally. +UPDATE pg_attribute SET attstorage = 'x' WHERE attrelid = 'toasted_several_pkey'::regclass AND attname = 'toasted_key'; + +INSERT INTO toasted_several(toasted_key) VALUES(repeat('9876543210', 10000)); +SELECT pg_column_size(toasted_key) > 2^16 FROM toasted_several; + +SELECT regexp_replace(data, '^(.{100}).*(.{100})$', '\1..\2') FROM pg_logical_slot_peek_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- test update of a toasted key without changing it +UPDATE toasted_several SET toasted_col1 = toasted_key; +UPDATE toasted_several SET toasted_col2 = toasted_col1; + +SELECT regexp_replace(data, '^(.{100}).*(.{100})$', '\1..\2') FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +/* + * update with large tuplebuf, in a transaction large enough to force to spool to disk + */ +BEGIN; +INSERT INTO toasted_several(toasted_key) SELECT * FROM generate_series(1, 10234); +UPDATE toasted_several SET toasted_col1 = toasted_col2 WHERE id = 1; +DELETE FROM toasted_several WHERE id = 1; +COMMIT; + +DROP TABLE toasted_several; + +SELECT regexp_replace(data, '^(.{100}).*(.{100})$', '\1..\2') FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1') +WHERE data NOT LIKE '%INSERT: %'; + +/* + * Test decoding relation rewrite with toast. The insert into tbl2 within the + * same transaction is there to check that there is no remaining toast_hash not + * being reset. + */ +CREATE TABLE tbl1 (a INT, b TEXT); +CREATE TABLE tbl2 (a INT); +ALTER TABLE tbl1 ALTER COLUMN b SET STORAGE EXTERNAL; +BEGIN; +INSERT INTO tbl1 VALUES(1, repeat('a', 4000)) ; +ALTER TABLE tbl1 ADD COLUMN id serial primary key; +INSERT INTO tbl2 VALUES(1); +commit; +SELECT substr(data, 1, 200) FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +SELECT pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/truncate.sql b/contrib/test_decoding/sql/truncate.sql new file mode 100644 index 0000000..5633854 --- /dev/null +++ b/contrib/test_decoding/sql/truncate.sql @@ -0,0 +1,14 @@ +-- predictability +SET synchronous_commit = on; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +CREATE TABLE tab1 (id serial unique, data int); +CREATE TABLE tab2 (a int primary key, b int); + +TRUNCATE tab1; +TRUNCATE tab1, tab1 RESTART IDENTITY CASCADE; +TRUNCATE tab1, tab2; + +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +SELECT pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/twophase.sql b/contrib/test_decoding/sql/twophase.sql new file mode 100644 index 0000000..aff5114 --- /dev/null +++ b/contrib/test_decoding/sql/twophase.sql @@ -0,0 +1,114 @@ +-- Test prepared transactions. When two-phase-commit is enabled, transactions are +-- decoded at PREPARE time rather than at COMMIT PREPARED time. +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding', false, true); + +CREATE TABLE test_prepared1(id integer primary key); +CREATE TABLE test_prepared2(id integer primary key); + +-- Test that decoding happens at PREPARE time when two-phase-commit is enabled. +-- Decoding after COMMIT PREPARED must have all the commands in the transaction. +BEGIN; +INSERT INTO test_prepared1 VALUES (1); +INSERT INTO test_prepared1 VALUES (2); +-- should show nothing because the xact has not been prepared yet. +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +PREPARE TRANSACTION 'test_prepared#1'; +-- should show both the above inserts and the PREPARE TRANSACTION. +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +COMMIT PREPARED 'test_prepared#1'; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- Test that rollback of a prepared xact is decoded. +BEGIN; +INSERT INTO test_prepared1 VALUES (3); +PREPARE TRANSACTION 'test_prepared#2'; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +ROLLBACK PREPARED 'test_prepared#2'; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- Test prepare of a xact containing ddl. Leaving xact uncommitted for next test. +BEGIN; +ALTER TABLE test_prepared1 ADD COLUMN data text; +INSERT INTO test_prepared1 VALUES (4, 'frakbar'); +PREPARE TRANSACTION 'test_prepared#3'; +-- confirm that exclusive lock from the ALTER command is held on test_prepared1 table +SELECT 'test_prepared_1' AS relation, locktype, mode +FROM pg_locks +WHERE locktype = 'relation' + AND relation = 'test_prepared1'::regclass; +-- The insert should show the newly altered column but not the DDL. +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- Test that we decode correctly while an uncommitted prepared xact +-- with ddl exists. +-- +-- Use a separate table for the concurrent transaction because the lock from +-- the ALTER will stop us inserting into the other one. +-- +INSERT INTO test_prepared2 VALUES (5); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +COMMIT PREPARED 'test_prepared#3'; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +-- make sure stuff still works +INSERT INTO test_prepared1 VALUES (6); +INSERT INTO test_prepared2 VALUES (7); +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- Check 'CLUSTER' (as operation that hold exclusive lock) doesn't block +-- logical decoding. +BEGIN; +INSERT INTO test_prepared1 VALUES (8, 'othercol'); +CLUSTER test_prepared1 USING test_prepared1_pkey; +INSERT INTO test_prepared1 VALUES (9, 'othercol2'); +PREPARE TRANSACTION 'test_prepared_lock'; + +SELECT 'test_prepared1' AS relation, locktype, mode +FROM pg_locks +WHERE locktype = 'relation' + AND relation = 'test_prepared1'::regclass; +-- The above CLUSTER command shouldn't cause a timeout on 2pc decoding. +\set env_timeout '' +\getenv env_timeout PG_TEST_TIMEOUT_DEFAULT +SELECT COALESCE(NULLIF(:'env_timeout', ''), '180') || 's' AS timeout \gset +SET statement_timeout = :'timeout'; +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +RESET statement_timeout; +COMMIT PREPARED 'test_prepared_lock'; +-- consume the commit +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- Test savepoints and sub-xacts. Creating savepoints will create +-- sub-xacts implicitly. +BEGIN; +CREATE TABLE test_prepared_savepoint (a int); +INSERT INTO test_prepared_savepoint VALUES (1); +SAVEPOINT test_savepoint; +INSERT INTO test_prepared_savepoint VALUES (2); +ROLLBACK TO SAVEPOINT test_savepoint; +PREPARE TRANSACTION 'test_prepared_savepoint'; +-- should show only 1, not 2 +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +COMMIT PREPARED 'test_prepared_savepoint'; +-- consume the commit +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- Test that a GID containing "_nodecode" gets decoded at commit prepared time. +BEGIN; +INSERT INTO test_prepared1 VALUES (20); +PREPARE TRANSACTION 'test_prepared_nodecode'; +-- should show nothing +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +COMMIT PREPARED 'test_prepared_nodecode'; +-- should be decoded now +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- Test 8: +-- cleanup and make sure results are also empty +DROP TABLE test_prepared1; +DROP TABLE test_prepared2; +-- show results. There should be nothing to show +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +SELECT pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/twophase_stream.sql b/contrib/test_decoding/sql/twophase_stream.sql new file mode 100644 index 0000000..646076d --- /dev/null +++ b/contrib/test_decoding/sql/twophase_stream.sql @@ -0,0 +1,45 @@ +-- Test streaming of two-phase commits + +SET synchronous_commit = on; +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding', false, true); + +CREATE TABLE stream_test(data text); + +-- consume DDL +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- streaming test with sub-transaction and PREPARE/COMMIT PREPARED +BEGIN; +SAVEPOINT s1; +SELECT 'msg5' FROM pg_logical_emit_message(true, 'test', repeat('a', 50)); +INSERT INTO stream_test SELECT repeat('a', 2000) || g.i FROM generate_series(1, 35) g(i); +TRUNCATE table stream_test; +ROLLBACK TO s1; +INSERT INTO stream_test SELECT repeat('a', 10) || g.i FROM generate_series(1, 20) g(i); +PREPARE TRANSACTION 'test1'; +-- should show the inserts after a ROLLBACK +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + +COMMIT PREPARED 'test1'; +--should show the COMMIT PREPARED and the other changes in the transaction +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + +-- streaming test with sub-transaction and PREPARE/COMMIT PREPARED but with +-- filtered gid. gids with '_nodecode' will not be decoded at prepare time. +BEGIN; +SAVEPOINT s1; +SELECT 'msg5' FROM pg_logical_emit_message(true, 'test', repeat('a', 50)); +INSERT INTO stream_test SELECT repeat('a', 2000) || g.i FROM generate_series(1, 35) g(i); +TRUNCATE table stream_test; +ROLLBACK to s1; +INSERT INTO stream_test SELECT repeat('a', 10) || g.i FROM generate_series(1, 20) g(i); +PREPARE TRANSACTION 'test1_nodecode'; +-- should NOT show inserts after a ROLLBACK +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + +COMMIT PREPARED 'test1_nodecode'; +-- should show the inserts but not show a COMMIT PREPARED but a COMMIT +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL,NULL, 'include-xids', '0', 'skip-empty-xacts', '1', 'stream-changes', '1'); + +DROP TABLE stream_test; +SELECT pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/sql/xact.sql b/contrib/test_decoding/sql/xact.sql new file mode 100644 index 0000000..aa55591 --- /dev/null +++ b/contrib/test_decoding/sql/xact.sql @@ -0,0 +1,33 @@ +-- predictability +SET synchronous_commit = on; + +SELECT 'init' FROM pg_create_logical_replication_slot('regression_slot', 'test_decoding'); + +CREATE TABLE xact_test(data text); +INSERT INTO xact_test VALUES ('before-test'); + +-- bug #13844, xids in non-decoded records need to be inspected +BEGIN; +-- perform operation in xact that creates and logs xid, but isn't decoded +SELECT * FROM xact_test FOR UPDATE; +SAVEPOINT foo; +-- and now actually insert in subxact, xid is expected to be known +INSERT INTO xact_test VALUES ('after-assignment'); +COMMIT; +-- and now show those changes +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +-- bug #14279, do not propagate null snapshot from subtransaction +BEGIN; +-- first insert +INSERT INTO xact_test VALUES ('main-txn'); +SAVEPOINT foo; +-- now perform operation in subxact that creates and logs xid, but isn't decoded +SELECT 1 FROM xact_test FOR UPDATE LIMIT 1; +COMMIT; +-- and now show those changes +SELECT data FROM pg_logical_slot_get_changes('regression_slot', NULL, NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + +DROP TABLE xact_test; + +SELECT pg_drop_replication_slot('regression_slot'); diff --git a/contrib/test_decoding/t/001_repl_stats.pl b/contrib/test_decoding/t/001_repl_stats.pl new file mode 100644 index 0000000..7c2d875 --- /dev/null +++ b/contrib/test_decoding/t/001_repl_stats.pl @@ -0,0 +1,121 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +# Test replication statistics data in pg_stat_replication_slots is sane after +# drop replication slot and restart. +use strict; +use warnings; +use File::Path qw(rmtree); +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# Test set-up +my $node = PostgreSQL::Test::Cluster->new('test'); +$node->init(allows_streaming => 'logical'); +$node->append_conf('postgresql.conf', 'synchronous_commit = on'); +$node->start; + +# Check that replication slot stats are expected. +sub test_slot_stats +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($node, $expected, $msg) = @_; + + my $result = $node->safe_psql( + 'postgres', qq[ + SELECT slot_name, total_txns > 0 AS total_txn, + total_bytes > 0 AS total_bytes + FROM pg_stat_replication_slots + ORDER BY slot_name]); + is($result, $expected, $msg); +} + +# Create table. +$node->safe_psql('postgres', "CREATE TABLE test_repl_stat(col1 int)"); + +# Create replication slots. +$node->safe_psql( + 'postgres', qq[ + SELECT pg_create_logical_replication_slot('regression_slot1', 'test_decoding'); + SELECT pg_create_logical_replication_slot('regression_slot2', 'test_decoding'); + SELECT pg_create_logical_replication_slot('regression_slot3', 'test_decoding'); + SELECT pg_create_logical_replication_slot('regression_slot4', 'test_decoding'); +]); + +# Insert some data. +$node->safe_psql('postgres', + "INSERT INTO test_repl_stat values(generate_series(1, 5));"); + +$node->safe_psql( + 'postgres', qq[ + SELECT data FROM pg_logical_slot_get_changes('regression_slot1', NULL, + NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + SELECT data FROM pg_logical_slot_get_changes('regression_slot2', NULL, + NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + SELECT data FROM pg_logical_slot_get_changes('regression_slot3', NULL, + NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); + SELECT data FROM pg_logical_slot_get_changes('regression_slot4', NULL, + NULL, 'include-xids', '0', 'skip-empty-xacts', '1'); +]); + +# Wait for the statistics to be updated. +$node->poll_query_until( + 'postgres', qq[ + SELECT count(slot_name) >= 4 FROM pg_stat_replication_slots + WHERE slot_name ~ 'regression_slot' + AND total_txns > 0 AND total_bytes > 0; +]) or die "Timed out while waiting for statistics to be updated"; + +# Test to drop one of the replication slot and verify replication statistics data is +# fine after restart. +$node->safe_psql('postgres', + "SELECT pg_drop_replication_slot('regression_slot4')"); + +$node->stop; +$node->start; + +# Verify statistics data present in pg_stat_replication_slots are sane after +# restart. +test_slot_stats( + $node, + qq(regression_slot1|t|t +regression_slot2|t|t +regression_slot3|t|t), + 'check replication statistics are updated'); + +# Test to remove one of the replication slots and adjust +# max_replication_slots accordingly to the number of slots. This leads +# to a mismatch between the number of slots present in the stats file and the +# number of stats present in shared memory. We verify +# replication statistics data is fine after restart. + +$node->stop; +my $datadir = $node->data_dir; +my $slot3_replslotdir = "$datadir/pg_replslot/regression_slot3"; + +rmtree($slot3_replslotdir); + +$node->append_conf('postgresql.conf', 'max_replication_slots = 2'); +$node->start; + +# Verify statistics data present in pg_stat_replication_slots are sane after +# restart. +test_slot_stats( + $node, + qq(regression_slot1|t|t +regression_slot2|t|t), + 'check replication statistics after removing the slot file'); + +# cleanup +$node->safe_psql('postgres', "DROP TABLE test_repl_stat"); +$node->safe_psql('postgres', + "SELECT pg_drop_replication_slot('regression_slot1')"); +$node->safe_psql('postgres', + "SELECT pg_drop_replication_slot('regression_slot2')"); + +# shutdown +$node->stop; + +done_testing(); diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c new file mode 100644 index 0000000..12d1d05 --- /dev/null +++ b/contrib/test_decoding/test_decoding.c @@ -0,0 +1,976 @@ +/*------------------------------------------------------------------------- + * + * test_decoding.c + * example logical decoding output plugin + * + * Copyright (c) 2012-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/test_decoding/test_decoding.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_type.h" + +#include "replication/logical.h" +#include "replication/origin.h" + +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" + +PG_MODULE_MAGIC; + +typedef struct +{ + MemoryContext context; + bool include_xids; + bool include_timestamp; + bool skip_empty_xacts; + bool only_local; +} TestDecodingData; + +/* + * Maintain the per-transaction level variables to track whether the + * transaction and or streams have written any changes. In streaming mode the + * transaction can be decoded in streams so along with maintaining whether the + * transaction has written any changes, we also need to track whether the + * current stream has written any changes. This is required so that if user + * has requested to skip the empty transactions we can skip the empty streams + * even though the transaction has written some changes. + */ +typedef struct +{ + bool xact_wrote_changes; + bool stream_wrote_changes; +} TestDecodingTxnData; + +static void pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, + bool is_init); +static void pg_decode_shutdown(LogicalDecodingContext *ctx); +static void pg_decode_begin_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn); +static void pg_output_begin(LogicalDecodingContext *ctx, + TestDecodingData *data, + ReorderBufferTXN *txn, + bool last_write); +static void pg_decode_commit_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, XLogRecPtr commit_lsn); +static void pg_decode_change(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, Relation relation, + ReorderBufferChange *change); +static void pg_decode_truncate(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + int nrelations, Relation relations[], + ReorderBufferChange *change); +static bool pg_decode_filter(LogicalDecodingContext *ctx, + RepOriginId origin_id); +static void pg_decode_message(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, XLogRecPtr lsn, + bool transactional, const char *prefix, + Size sz, const char *message); +static bool pg_decode_filter_prepare(LogicalDecodingContext *ctx, + TransactionId xid, + const char *gid); +static void pg_decode_begin_prepare_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn); +static void pg_decode_prepare_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr prepare_lsn); +static void pg_decode_commit_prepared_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr commit_lsn); +static void pg_decode_rollback_prepared_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr prepare_end_lsn, + TimestampTz prepare_time); +static void pg_decode_stream_start(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn); +static void pg_output_stream_start(LogicalDecodingContext *ctx, + TestDecodingData *data, + ReorderBufferTXN *txn, + bool last_write); +static void pg_decode_stream_stop(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn); +static void pg_decode_stream_abort(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr abort_lsn); +static void pg_decode_stream_prepare(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr prepare_lsn); +static void pg_decode_stream_commit(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr commit_lsn); +static void pg_decode_stream_change(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + Relation relation, + ReorderBufferChange *change); +static void pg_decode_stream_message(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, XLogRecPtr lsn, + bool transactional, const char *prefix, + Size sz, const char *message); +static void pg_decode_stream_truncate(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + int nrelations, Relation relations[], + ReorderBufferChange *change); + +void +_PG_init(void) +{ + /* other plugins can perform things here */ +} + +/* specify output plugin callbacks */ +void +_PG_output_plugin_init(OutputPluginCallbacks *cb) +{ + cb->startup_cb = pg_decode_startup; + cb->begin_cb = pg_decode_begin_txn; + cb->change_cb = pg_decode_change; + cb->truncate_cb = pg_decode_truncate; + cb->commit_cb = pg_decode_commit_txn; + cb->filter_by_origin_cb = pg_decode_filter; + cb->shutdown_cb = pg_decode_shutdown; + cb->message_cb = pg_decode_message; + cb->filter_prepare_cb = pg_decode_filter_prepare; + cb->begin_prepare_cb = pg_decode_begin_prepare_txn; + cb->prepare_cb = pg_decode_prepare_txn; + cb->commit_prepared_cb = pg_decode_commit_prepared_txn; + cb->rollback_prepared_cb = pg_decode_rollback_prepared_txn; + cb->stream_start_cb = pg_decode_stream_start; + cb->stream_stop_cb = pg_decode_stream_stop; + cb->stream_abort_cb = pg_decode_stream_abort; + cb->stream_prepare_cb = pg_decode_stream_prepare; + cb->stream_commit_cb = pg_decode_stream_commit; + cb->stream_change_cb = pg_decode_stream_change; + cb->stream_message_cb = pg_decode_stream_message; + cb->stream_truncate_cb = pg_decode_stream_truncate; +} + + +/* initialize this plugin */ +static void +pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, + bool is_init) +{ + ListCell *option; + TestDecodingData *data; + bool enable_streaming = false; + + data = palloc0(sizeof(TestDecodingData)); + data->context = AllocSetContextCreate(ctx->context, + "text conversion context", + ALLOCSET_DEFAULT_SIZES); + data->include_xids = true; + data->include_timestamp = false; + data->skip_empty_xacts = false; + data->only_local = false; + + ctx->output_plugin_private = data; + + opt->output_type = OUTPUT_PLUGIN_TEXTUAL_OUTPUT; + opt->receive_rewrites = false; + + foreach(option, ctx->output_plugin_options) + { + DefElem *elem = lfirst(option); + + Assert(elem->arg == NULL || IsA(elem->arg, String)); + + if (strcmp(elem->defname, "include-xids") == 0) + { + /* if option does not provide a value, it means its value is true */ + if (elem->arg == NULL) + data->include_xids = true; + else if (!parse_bool(strVal(elem->arg), &data->include_xids)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse value \"%s\" for parameter \"%s\"", + strVal(elem->arg), elem->defname))); + } + else if (strcmp(elem->defname, "include-timestamp") == 0) + { + if (elem->arg == NULL) + data->include_timestamp = true; + else if (!parse_bool(strVal(elem->arg), &data->include_timestamp)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse value \"%s\" for parameter \"%s\"", + strVal(elem->arg), elem->defname))); + } + else if (strcmp(elem->defname, "force-binary") == 0) + { + bool force_binary; + + if (elem->arg == NULL) + continue; + else if (!parse_bool(strVal(elem->arg), &force_binary)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse value \"%s\" for parameter \"%s\"", + strVal(elem->arg), elem->defname))); + + if (force_binary) + opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT; + } + else if (strcmp(elem->defname, "skip-empty-xacts") == 0) + { + + if (elem->arg == NULL) + data->skip_empty_xacts = true; + else if (!parse_bool(strVal(elem->arg), &data->skip_empty_xacts)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse value \"%s\" for parameter \"%s\"", + strVal(elem->arg), elem->defname))); + } + else if (strcmp(elem->defname, "only-local") == 0) + { + + if (elem->arg == NULL) + data->only_local = true; + else if (!parse_bool(strVal(elem->arg), &data->only_local)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse value \"%s\" for parameter \"%s\"", + strVal(elem->arg), elem->defname))); + } + else if (strcmp(elem->defname, "include-rewrites") == 0) + { + + if (elem->arg == NULL) + continue; + else if (!parse_bool(strVal(elem->arg), &opt->receive_rewrites)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse value \"%s\" for parameter \"%s\"", + strVal(elem->arg), elem->defname))); + } + else if (strcmp(elem->defname, "stream-changes") == 0) + { + if (elem->arg == NULL) + continue; + else if (!parse_bool(strVal(elem->arg), &enable_streaming)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not parse value \"%s\" for parameter \"%s\"", + strVal(elem->arg), elem->defname))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("option \"%s\" = \"%s\" is unknown", + elem->defname, + elem->arg ? strVal(elem->arg) : "(null)"))); + } + } + + ctx->streaming &= enable_streaming; +} + +/* cleanup this plugin's resources */ +static void +pg_decode_shutdown(LogicalDecodingContext *ctx) +{ + TestDecodingData *data = ctx->output_plugin_private; + + /* cleanup our own resources via memory context reset */ + MemoryContextDelete(data->context); +} + +/* BEGIN callback */ +static void +pg_decode_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn) +{ + TestDecodingData *data = ctx->output_plugin_private; + TestDecodingTxnData *txndata = + MemoryContextAllocZero(ctx->context, sizeof(TestDecodingTxnData)); + + txndata->xact_wrote_changes = false; + txn->output_plugin_private = txndata; + + /* + * If asked to skip empty transactions, we'll emit BEGIN at the point + * where the first operation is received for this transaction. + */ + if (data->skip_empty_xacts) + return; + + pg_output_begin(ctx, data, txn, true); +} + +static void +pg_output_begin(LogicalDecodingContext *ctx, TestDecodingData *data, ReorderBufferTXN *txn, bool last_write) +{ + OutputPluginPrepareWrite(ctx, last_write); + if (data->include_xids) + appendStringInfo(ctx->out, "BEGIN %u", txn->xid); + else + appendStringInfoString(ctx->out, "BEGIN"); + OutputPluginWrite(ctx, last_write); +} + +/* COMMIT callback */ +static void +pg_decode_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + XLogRecPtr commit_lsn) +{ + TestDecodingData *data = ctx->output_plugin_private; + TestDecodingTxnData *txndata = txn->output_plugin_private; + bool xact_wrote_changes = txndata->xact_wrote_changes; + + pfree(txndata); + txn->output_plugin_private = NULL; + + if (data->skip_empty_xacts && !xact_wrote_changes) + return; + + OutputPluginPrepareWrite(ctx, true); + if (data->include_xids) + appendStringInfo(ctx->out, "COMMIT %u", txn->xid); + else + appendStringInfoString(ctx->out, "COMMIT"); + + if (data->include_timestamp) + appendStringInfo(ctx->out, " (at %s)", + timestamptz_to_str(txn->xact_time.commit_time)); + + OutputPluginWrite(ctx, true); +} + +/* BEGIN PREPARE callback */ +static void +pg_decode_begin_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn) +{ + TestDecodingData *data = ctx->output_plugin_private; + TestDecodingTxnData *txndata = + MemoryContextAllocZero(ctx->context, sizeof(TestDecodingTxnData)); + + txndata->xact_wrote_changes = false; + txn->output_plugin_private = txndata; + + /* + * If asked to skip empty transactions, we'll emit BEGIN at the point + * where the first operation is received for this transaction. + */ + if (data->skip_empty_xacts) + return; + + pg_output_begin(ctx, data, txn, true); +} + +/* PREPARE callback */ +static void +pg_decode_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + XLogRecPtr prepare_lsn) +{ + TestDecodingData *data = ctx->output_plugin_private; + TestDecodingTxnData *txndata = txn->output_plugin_private; + + /* + * If asked to skip empty transactions, we'll emit PREPARE at the point + * where the first operation is received for this transaction. + */ + if (data->skip_empty_xacts && !txndata->xact_wrote_changes) + return; + + OutputPluginPrepareWrite(ctx, true); + + appendStringInfo(ctx->out, "PREPARE TRANSACTION %s", + quote_literal_cstr(txn->gid)); + + if (data->include_xids) + appendStringInfo(ctx->out, ", txid %u", txn->xid); + + if (data->include_timestamp) + appendStringInfo(ctx->out, " (at %s)", + timestamptz_to_str(txn->xact_time.prepare_time)); + + OutputPluginWrite(ctx, true); +} + +/* COMMIT PREPARED callback */ +static void +pg_decode_commit_prepared_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + XLogRecPtr commit_lsn) +{ + TestDecodingData *data = ctx->output_plugin_private; + + OutputPluginPrepareWrite(ctx, true); + + appendStringInfo(ctx->out, "COMMIT PREPARED %s", + quote_literal_cstr(txn->gid)); + + if (data->include_xids) + appendStringInfo(ctx->out, ", txid %u", txn->xid); + + if (data->include_timestamp) + appendStringInfo(ctx->out, " (at %s)", + timestamptz_to_str(txn->xact_time.commit_time)); + + OutputPluginWrite(ctx, true); +} + +/* ROLLBACK PREPARED callback */ +static void +pg_decode_rollback_prepared_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr prepare_end_lsn, + TimestampTz prepare_time) +{ + TestDecodingData *data = ctx->output_plugin_private; + + OutputPluginPrepareWrite(ctx, true); + + appendStringInfo(ctx->out, "ROLLBACK PREPARED %s", + quote_literal_cstr(txn->gid)); + + if (data->include_xids) + appendStringInfo(ctx->out, ", txid %u", txn->xid); + + if (data->include_timestamp) + appendStringInfo(ctx->out, " (at %s)", + timestamptz_to_str(txn->xact_time.commit_time)); + + OutputPluginWrite(ctx, true); +} + +/* + * Filter out two-phase transactions. + * + * Each plugin can implement its own filtering logic. Here we demonstrate a + * simple logic by checking the GID. If the GID contains the "_nodecode" + * substring, then we filter it out. + */ +static bool +pg_decode_filter_prepare(LogicalDecodingContext *ctx, TransactionId xid, + const char *gid) +{ + if (strstr(gid, "_nodecode") != NULL) + return true; + + return false; +} + +static bool +pg_decode_filter(LogicalDecodingContext *ctx, + RepOriginId origin_id) +{ + TestDecodingData *data = ctx->output_plugin_private; + + if (data->only_local && origin_id != InvalidRepOriginId) + return true; + return false; +} + +/* + * Print literal `outputstr' already represented as string of type `typid' + * into stringbuf `s'. + * + * Some builtin types aren't quoted, the rest is quoted. Escaping is done as + * if standard_conforming_strings were enabled. + */ +static void +print_literal(StringInfo s, Oid typid, char *outputstr) +{ + const char *valptr; + + switch (typid) + { + case INT2OID: + case INT4OID: + case INT8OID: + case OIDOID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + /* NB: We don't care about Inf, NaN et al. */ + appendStringInfoString(s, outputstr); + break; + + case BITOID: + case VARBITOID: + appendStringInfo(s, "B'%s'", outputstr); + break; + + case BOOLOID: + if (strcmp(outputstr, "t") == 0) + appendStringInfoString(s, "true"); + else + appendStringInfoString(s, "false"); + break; + + default: + appendStringInfoChar(s, '\''); + for (valptr = outputstr; *valptr; valptr++) + { + char ch = *valptr; + + if (SQL_STR_DOUBLE(ch, false)) + appendStringInfoChar(s, ch); + appendStringInfoChar(s, ch); + } + appendStringInfoChar(s, '\''); + break; + } +} + +/* print the tuple 'tuple' into the StringInfo s */ +static void +tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool skip_nulls) +{ + int natt; + + /* print all columns individually */ + for (natt = 0; natt < tupdesc->natts; natt++) + { + Form_pg_attribute attr; /* the attribute itself */ + Oid typid; /* type of current attribute */ + Oid typoutput; /* output function */ + bool typisvarlena; + Datum origval; /* possibly toasted Datum */ + bool isnull; /* column is null? */ + + attr = TupleDescAttr(tupdesc, natt); + + /* + * don't print dropped columns, we can't be sure everything is + * available for them + */ + if (attr->attisdropped) + continue; + + /* + * Don't print system columns, oid will already have been printed if + * present. + */ + if (attr->attnum < 0) + continue; + + typid = attr->atttypid; + + /* get Datum from tuple */ + origval = heap_getattr(tuple, natt + 1, tupdesc, &isnull); + + if (isnull && skip_nulls) + continue; + + /* print attribute name */ + appendStringInfoChar(s, ' '); + appendStringInfoString(s, quote_identifier(NameStr(attr->attname))); + + /* print attribute type */ + appendStringInfoChar(s, '['); + appendStringInfoString(s, format_type_be(typid)); + appendStringInfoChar(s, ']'); + + /* query output function */ + getTypeOutputInfo(typid, + &typoutput, &typisvarlena); + + /* print separator */ + appendStringInfoChar(s, ':'); + + /* print data */ + if (isnull) + appendStringInfoString(s, "null"); + else if (typisvarlena && VARATT_IS_EXTERNAL_ONDISK(origval)) + appendStringInfoString(s, "unchanged-toast-datum"); + else if (!typisvarlena) + print_literal(s, typid, + OidOutputFunctionCall(typoutput, origval)); + else + { + Datum val; /* definitely detoasted Datum */ + + val = PointerGetDatum(PG_DETOAST_DATUM(origval)); + print_literal(s, typid, OidOutputFunctionCall(typoutput, val)); + } + } +} + +/* + * callback for individual changed tuples + */ +static void +pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + Relation relation, ReorderBufferChange *change) +{ + TestDecodingData *data; + TestDecodingTxnData *txndata; + Form_pg_class class_form; + TupleDesc tupdesc; + MemoryContext old; + + data = ctx->output_plugin_private; + txndata = txn->output_plugin_private; + + /* output BEGIN if we haven't yet */ + if (data->skip_empty_xacts && !txndata->xact_wrote_changes) + { + pg_output_begin(ctx, data, txn, false); + } + txndata->xact_wrote_changes = true; + + class_form = RelationGetForm(relation); + tupdesc = RelationGetDescr(relation); + + /* Avoid leaking memory by using and resetting our own context */ + old = MemoryContextSwitchTo(data->context); + + OutputPluginPrepareWrite(ctx, true); + + appendStringInfoString(ctx->out, "table "); + appendStringInfoString(ctx->out, + quote_qualified_identifier(get_namespace_name(get_rel_namespace(RelationGetRelid(relation))), + class_form->relrewrite ? + get_rel_name(class_form->relrewrite) : + NameStr(class_form->relname))); + appendStringInfoChar(ctx->out, ':'); + + switch (change->action) + { + case REORDER_BUFFER_CHANGE_INSERT: + appendStringInfoString(ctx->out, " INSERT:"); + if (change->data.tp.newtuple == NULL) + appendStringInfoString(ctx->out, " (no-tuple-data)"); + else + tuple_to_stringinfo(ctx->out, tupdesc, + &change->data.tp.newtuple->tuple, + false); + break; + case REORDER_BUFFER_CHANGE_UPDATE: + appendStringInfoString(ctx->out, " UPDATE:"); + if (change->data.tp.oldtuple != NULL) + { + appendStringInfoString(ctx->out, " old-key:"); + tuple_to_stringinfo(ctx->out, tupdesc, + &change->data.tp.oldtuple->tuple, + true); + appendStringInfoString(ctx->out, " new-tuple:"); + } + + if (change->data.tp.newtuple == NULL) + appendStringInfoString(ctx->out, " (no-tuple-data)"); + else + tuple_to_stringinfo(ctx->out, tupdesc, + &change->data.tp.newtuple->tuple, + false); + break; + case REORDER_BUFFER_CHANGE_DELETE: + appendStringInfoString(ctx->out, " DELETE:"); + + /* if there was no PK, we only know that a delete happened */ + if (change->data.tp.oldtuple == NULL) + appendStringInfoString(ctx->out, " (no-tuple-data)"); + /* In DELETE, only the replica identity is present; display that */ + else + tuple_to_stringinfo(ctx->out, tupdesc, + &change->data.tp.oldtuple->tuple, + true); + break; + default: + Assert(false); + } + + MemoryContextSwitchTo(old); + MemoryContextReset(data->context); + + OutputPluginWrite(ctx, true); +} + +static void +pg_decode_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + int nrelations, Relation relations[], ReorderBufferChange *change) +{ + TestDecodingData *data; + TestDecodingTxnData *txndata; + MemoryContext old; + int i; + + data = ctx->output_plugin_private; + txndata = txn->output_plugin_private; + + /* output BEGIN if we haven't yet */ + if (data->skip_empty_xacts && !txndata->xact_wrote_changes) + { + pg_output_begin(ctx, data, txn, false); + } + txndata->xact_wrote_changes = true; + + /* Avoid leaking memory by using and resetting our own context */ + old = MemoryContextSwitchTo(data->context); + + OutputPluginPrepareWrite(ctx, true); + + appendStringInfoString(ctx->out, "table "); + + for (i = 0; i < nrelations; i++) + { + if (i > 0) + appendStringInfoString(ctx->out, ", "); + + appendStringInfoString(ctx->out, + quote_qualified_identifier(get_namespace_name(relations[i]->rd_rel->relnamespace), + NameStr(relations[i]->rd_rel->relname))); + } + + appendStringInfoString(ctx->out, ": TRUNCATE:"); + + if (change->data.truncate.restart_seqs + || change->data.truncate.cascade) + { + if (change->data.truncate.restart_seqs) + appendStringInfoString(ctx->out, " restart_seqs"); + if (change->data.truncate.cascade) + appendStringInfoString(ctx->out, " cascade"); + } + else + appendStringInfoString(ctx->out, " (no-flags)"); + + MemoryContextSwitchTo(old); + MemoryContextReset(data->context); + + OutputPluginWrite(ctx, true); +} + +static void +pg_decode_message(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, XLogRecPtr lsn, bool transactional, + const char *prefix, Size sz, const char *message) +{ + OutputPluginPrepareWrite(ctx, true); + appendStringInfo(ctx->out, "message: transactional: %d prefix: %s, sz: %zu content:", + transactional, prefix, sz); + appendBinaryStringInfo(ctx->out, message, sz); + OutputPluginWrite(ctx, true); +} + +static void +pg_decode_stream_start(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn) +{ + TestDecodingData *data = ctx->output_plugin_private; + TestDecodingTxnData *txndata = txn->output_plugin_private; + + /* + * Allocate the txn plugin data for the first stream in the transaction. + */ + if (txndata == NULL) + { + txndata = + MemoryContextAllocZero(ctx->context, sizeof(TestDecodingTxnData)); + txndata->xact_wrote_changes = false; + txn->output_plugin_private = txndata; + } + + txndata->stream_wrote_changes = false; + if (data->skip_empty_xacts) + return; + pg_output_stream_start(ctx, data, txn, true); +} + +static void +pg_output_stream_start(LogicalDecodingContext *ctx, TestDecodingData *data, ReorderBufferTXN *txn, bool last_write) +{ + OutputPluginPrepareWrite(ctx, last_write); + if (data->include_xids) + appendStringInfo(ctx->out, "opening a streamed block for transaction TXN %u", txn->xid); + else + appendStringInfoString(ctx->out, "opening a streamed block for transaction"); + OutputPluginWrite(ctx, last_write); +} + +static void +pg_decode_stream_stop(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn) +{ + TestDecodingData *data = ctx->output_plugin_private; + TestDecodingTxnData *txndata = txn->output_plugin_private; + + if (data->skip_empty_xacts && !txndata->stream_wrote_changes) + return; + + OutputPluginPrepareWrite(ctx, true); + if (data->include_xids) + appendStringInfo(ctx->out, "closing a streamed block for transaction TXN %u", txn->xid); + else + appendStringInfoString(ctx->out, "closing a streamed block for transaction"); + OutputPluginWrite(ctx, true); +} + +static void +pg_decode_stream_abort(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr abort_lsn) +{ + TestDecodingData *data = ctx->output_plugin_private; + + /* + * stream abort can be sent for an individual subtransaction but we + * maintain the output_plugin_private only under the toptxn so if this is + * not the toptxn then fetch the toptxn. + */ + ReorderBufferTXN *toptxn = rbtxn_get_toptxn(txn); + TestDecodingTxnData *txndata = toptxn->output_plugin_private; + bool xact_wrote_changes = txndata->xact_wrote_changes; + + if (rbtxn_is_toptxn(txn)) + { + Assert(txn->output_plugin_private != NULL); + pfree(txndata); + txn->output_plugin_private = NULL; + } + + if (data->skip_empty_xacts && !xact_wrote_changes) + return; + + OutputPluginPrepareWrite(ctx, true); + if (data->include_xids) + appendStringInfo(ctx->out, "aborting streamed (sub)transaction TXN %u", txn->xid); + else + appendStringInfoString(ctx->out, "aborting streamed (sub)transaction"); + OutputPluginWrite(ctx, true); +} + +static void +pg_decode_stream_prepare(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr prepare_lsn) +{ + TestDecodingData *data = ctx->output_plugin_private; + TestDecodingTxnData *txndata = txn->output_plugin_private; + + if (data->skip_empty_xacts && !txndata->xact_wrote_changes) + return; + + OutputPluginPrepareWrite(ctx, true); + + if (data->include_xids) + appendStringInfo(ctx->out, "preparing streamed transaction TXN %s, txid %u", + quote_literal_cstr(txn->gid), txn->xid); + else + appendStringInfo(ctx->out, "preparing streamed transaction %s", + quote_literal_cstr(txn->gid)); + + if (data->include_timestamp) + appendStringInfo(ctx->out, " (at %s)", + timestamptz_to_str(txn->xact_time.prepare_time)); + + OutputPluginWrite(ctx, true); +} + +static void +pg_decode_stream_commit(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + XLogRecPtr commit_lsn) +{ + TestDecodingData *data = ctx->output_plugin_private; + TestDecodingTxnData *txndata = txn->output_plugin_private; + bool xact_wrote_changes = txndata->xact_wrote_changes; + + pfree(txndata); + txn->output_plugin_private = NULL; + + if (data->skip_empty_xacts && !xact_wrote_changes) + return; + + OutputPluginPrepareWrite(ctx, true); + + if (data->include_xids) + appendStringInfo(ctx->out, "committing streamed transaction TXN %u", txn->xid); + else + appendStringInfoString(ctx->out, "committing streamed transaction"); + + if (data->include_timestamp) + appendStringInfo(ctx->out, " (at %s)", + timestamptz_to_str(txn->xact_time.commit_time)); + + OutputPluginWrite(ctx, true); +} + +/* + * In streaming mode, we don't display the changes as the transaction can abort + * at a later point in time. We don't want users to see the changes until the + * transaction is committed. + */ +static void +pg_decode_stream_change(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, + Relation relation, + ReorderBufferChange *change) +{ + TestDecodingData *data = ctx->output_plugin_private; + TestDecodingTxnData *txndata = txn->output_plugin_private; + + /* output stream start if we haven't yet */ + if (data->skip_empty_xacts && !txndata->stream_wrote_changes) + { + pg_output_stream_start(ctx, data, txn, false); + } + txndata->xact_wrote_changes = txndata->stream_wrote_changes = true; + + OutputPluginPrepareWrite(ctx, true); + if (data->include_xids) + appendStringInfo(ctx->out, "streaming change for TXN %u", txn->xid); + else + appendStringInfoString(ctx->out, "streaming change for transaction"); + OutputPluginWrite(ctx, true); +} + +/* + * In streaming mode, we don't display the contents for transactional messages + * as the transaction can abort at a later point in time. We don't want users to + * see the message contents until the transaction is committed. + */ +static void +pg_decode_stream_message(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, XLogRecPtr lsn, bool transactional, + const char *prefix, Size sz, const char *message) +{ + OutputPluginPrepareWrite(ctx, true); + + if (transactional) + { + appendStringInfo(ctx->out, "streaming message: transactional: %d prefix: %s, sz: %zu", + transactional, prefix, sz); + } + else + { + appendStringInfo(ctx->out, "streaming message: transactional: %d prefix: %s, sz: %zu content:", + transactional, prefix, sz); + appendBinaryStringInfo(ctx->out, message, sz); + } + + OutputPluginWrite(ctx, true); +} + +/* + * In streaming mode, we don't display the detailed information of Truncate. + * See pg_decode_stream_change. + */ +static void +pg_decode_stream_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + int nrelations, Relation relations[], + ReorderBufferChange *change) +{ + TestDecodingData *data = ctx->output_plugin_private; + TestDecodingTxnData *txndata = txn->output_plugin_private; + + if (data->skip_empty_xacts && !txndata->stream_wrote_changes) + { + pg_output_stream_start(ctx, data, txn, false); + } + txndata->xact_wrote_changes = txndata->stream_wrote_changes = true; + + OutputPluginPrepareWrite(ctx, true); + if (data->include_xids) + appendStringInfo(ctx->out, "streaming truncate for TXN %u", txn->xid); + else + appendStringInfoString(ctx->out, "streaming truncate for transaction"); + OutputPluginWrite(ctx, true); +} diff --git a/contrib/tsm_system_rows/.gitignore b/contrib/tsm_system_rows/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/tsm_system_rows/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/tsm_system_rows/Makefile b/contrib/tsm_system_rows/Makefile new file mode 100644 index 0000000..3192ed5 --- /dev/null +++ b/contrib/tsm_system_rows/Makefile @@ -0,0 +1,23 @@ +# contrib/tsm_system_rows/Makefile + +MODULE_big = tsm_system_rows +OBJS = \ + $(WIN32RES) \ + tsm_system_rows.o +PGFILEDESC = "tsm_system_rows - TABLESAMPLE method which accepts number of rows as a limit" + +EXTENSION = tsm_system_rows +DATA = tsm_system_rows--1.0.sql + +REGRESS = tsm_system_rows + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/tsm_system_rows +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/tsm_system_rows/expected/tsm_system_rows.out b/contrib/tsm_system_rows/expected/tsm_system_rows.out new file mode 100644 index 0000000..87b4a8f --- /dev/null +++ b/contrib/tsm_system_rows/expected/tsm_system_rows.out @@ -0,0 +1,83 @@ +CREATE EXTENSION tsm_system_rows; +CREATE TABLE test_tablesample (id int, name text); +INSERT INTO test_tablesample SELECT i, repeat(i::text, 1000) + FROM generate_series(0, 30) s(i); +ANALYZE test_tablesample; +SELECT count(*) FROM test_tablesample TABLESAMPLE system_rows (0); + count +------- + 0 +(1 row) + +SELECT count(*) FROM test_tablesample TABLESAMPLE system_rows (1); + count +------- + 1 +(1 row) + +SELECT count(*) FROM test_tablesample TABLESAMPLE system_rows (10); + count +------- + 10 +(1 row) + +SELECT count(*) FROM test_tablesample TABLESAMPLE system_rows (100); + count +------- + 31 +(1 row) + +-- bad parameters should get through planning, but not execution: +EXPLAIN (COSTS OFF) +SELECT id FROM test_tablesample TABLESAMPLE system_rows (-1); + QUERY PLAN +---------------------------------------- + Sample Scan on test_tablesample + Sampling: system_rows ('-1'::bigint) +(2 rows) + +SELECT id FROM test_tablesample TABLESAMPLE system_rows (-1); +ERROR: sample size must not be negative +-- fail, this method is not repeatable: +SELECT * FROM test_tablesample TABLESAMPLE system_rows (10) REPEATABLE (0); +ERROR: tablesample method system_rows does not support REPEATABLE +LINE 1: SELECT * FROM test_tablesample TABLESAMPLE system_rows (10) ... + ^ +-- but a join should be allowed: +EXPLAIN (COSTS OFF) +SELECT * FROM + (VALUES (0),(10),(100)) v(nrows), + LATERAL (SELECT count(*) FROM test_tablesample + TABLESAMPLE system_rows (nrows)) ss; + QUERY PLAN +---------------------------------------------------------- + Nested Loop + -> Values Scan on "*VALUES*" + -> Aggregate + -> Sample Scan on test_tablesample + Sampling: system_rows ("*VALUES*".column1) +(5 rows) + +SELECT * FROM + (VALUES (0),(10),(100)) v(nrows), + LATERAL (SELECT count(*) FROM test_tablesample + TABLESAMPLE system_rows (nrows)) ss; + nrows | count +-------+------- + 0 | 0 + 10 | 10 + 100 | 31 +(3 rows) + +CREATE VIEW vv AS + SELECT count(*) FROM test_tablesample TABLESAMPLE system_rows (20); +SELECT * FROM vv; + count +------- + 20 +(1 row) + +DROP EXTENSION tsm_system_rows; -- fail, view depends on extension +ERROR: cannot drop extension tsm_system_rows because other objects depend on it +DETAIL: view vv depends on function system_rows(internal) +HINT: Use DROP ... CASCADE to drop the dependent objects too. diff --git a/contrib/tsm_system_rows/meson.build b/contrib/tsm_system_rows/meson.build new file mode 100644 index 0000000..f45940f --- /dev/null +++ b/contrib/tsm_system_rows/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +tsm_system_rows_sources = files( + 'tsm_system_rows.c', +) + +if host_system == 'windows' + tsm_system_rows_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'tsm_system_rows', + '--FILEDESC', 'tsm_system_rows - TABLESAMPLE method which accepts number of rows as a limit',]) +endif + +tsm_system_rows = shared_module('tsm_system_rows', + tsm_system_rows_sources, + kwargs: contrib_mod_args, +) +contrib_targets += tsm_system_rows + +install_data( + 'tsm_system_rows--1.0.sql', + 'tsm_system_rows.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'tsm_system_rows', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'tsm_system_rows', + ], + }, +} diff --git a/contrib/tsm_system_rows/sql/tsm_system_rows.sql b/contrib/tsm_system_rows/sql/tsm_system_rows.sql new file mode 100644 index 0000000..e3ab420 --- /dev/null +++ b/contrib/tsm_system_rows/sql/tsm_system_rows.sql @@ -0,0 +1,39 @@ +CREATE EXTENSION tsm_system_rows; + +CREATE TABLE test_tablesample (id int, name text); +INSERT INTO test_tablesample SELECT i, repeat(i::text, 1000) + FROM generate_series(0, 30) s(i); +ANALYZE test_tablesample; + +SELECT count(*) FROM test_tablesample TABLESAMPLE system_rows (0); +SELECT count(*) FROM test_tablesample TABLESAMPLE system_rows (1); +SELECT count(*) FROM test_tablesample TABLESAMPLE system_rows (10); +SELECT count(*) FROM test_tablesample TABLESAMPLE system_rows (100); + +-- bad parameters should get through planning, but not execution: +EXPLAIN (COSTS OFF) +SELECT id FROM test_tablesample TABLESAMPLE system_rows (-1); + +SELECT id FROM test_tablesample TABLESAMPLE system_rows (-1); + +-- fail, this method is not repeatable: +SELECT * FROM test_tablesample TABLESAMPLE system_rows (10) REPEATABLE (0); + +-- but a join should be allowed: +EXPLAIN (COSTS OFF) +SELECT * FROM + (VALUES (0),(10),(100)) v(nrows), + LATERAL (SELECT count(*) FROM test_tablesample + TABLESAMPLE system_rows (nrows)) ss; + +SELECT * FROM + (VALUES (0),(10),(100)) v(nrows), + LATERAL (SELECT count(*) FROM test_tablesample + TABLESAMPLE system_rows (nrows)) ss; + +CREATE VIEW vv AS + SELECT count(*) FROM test_tablesample TABLESAMPLE system_rows (20); + +SELECT * FROM vv; + +DROP EXTENSION tsm_system_rows; -- fail, view depends on extension diff --git a/contrib/tsm_system_rows/tsm_system_rows--1.0.sql b/contrib/tsm_system_rows/tsm_system_rows--1.0.sql new file mode 100644 index 0000000..de508ed --- /dev/null +++ b/contrib/tsm_system_rows/tsm_system_rows--1.0.sql @@ -0,0 +1,9 @@ +/* contrib/tsm_system_rows/tsm_system_rows--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION tsm_system_rows" to load this file. \quit + +CREATE FUNCTION system_rows(internal) +RETURNS tsm_handler +AS 'MODULE_PATHNAME', 'tsm_system_rows_handler' +LANGUAGE C STRICT; diff --git a/contrib/tsm_system_rows/tsm_system_rows.c b/contrib/tsm_system_rows/tsm_system_rows.c new file mode 100644 index 0000000..90058bb --- /dev/null +++ b/contrib/tsm_system_rows/tsm_system_rows.c @@ -0,0 +1,340 @@ +/*------------------------------------------------------------------------- + * + * tsm_system_rows.c + * support routines for SYSTEM_ROWS tablesample method + * + * The desire here is to produce a random sample with a given number of rows + * (or the whole relation, if that is fewer rows). We use a block-sampling + * approach. To ensure that the whole relation will be visited if necessary, + * we start at a randomly chosen block and then advance with a stride that + * is randomly chosen but is relatively prime to the relation's nblocks. + * + * Because of the dependence on nblocks, this method cannot be repeatable + * across queries. (Even if the user hasn't explicitly changed the relation, + * maintenance activities such as autovacuum might change nblocks.) However, + * we can at least make it repeatable across scans, by determining the + * sampling pattern only once on the first scan. This means that rescans + * won't visit blocks added after the first scan, but that is fine since + * such blocks shouldn't contain any visible tuples anyway. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * contrib/tsm_system_rows/tsm_system_rows.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/relscan.h" +#include "access/tsmapi.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "optimizer/optimizer.h" +#include "utils/sampling.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(tsm_system_rows_handler); + + +/* Private state */ +typedef struct +{ + uint32 seed; /* random seed */ + int64 ntuples; /* number of tuples to return */ + OffsetNumber lt; /* last tuple returned from current block */ + BlockNumber doneblocks; /* number of already-scanned blocks */ + BlockNumber lb; /* last block visited */ + /* these three values are not changed during a rescan: */ + BlockNumber nblocks; /* number of blocks in relation */ + BlockNumber firstblock; /* first block to sample from */ + BlockNumber step; /* step size, or 0 if not set yet */ +} SystemRowsSamplerData; + +static void system_rows_samplescangetsamplesize(PlannerInfo *root, + RelOptInfo *baserel, + List *paramexprs, + BlockNumber *pages, + double *tuples); +static void system_rows_initsamplescan(SampleScanState *node, + int eflags); +static void system_rows_beginsamplescan(SampleScanState *node, + Datum *params, + int nparams, + uint32 seed); +static BlockNumber system_rows_nextsampleblock(SampleScanState *node, BlockNumber nblocks); +static OffsetNumber system_rows_nextsampletuple(SampleScanState *node, + BlockNumber blockno, + OffsetNumber maxoffset); +static uint32 random_relative_prime(uint32 n, pg_prng_state *randstate); + + +/* + * Create a TsmRoutine descriptor for the SYSTEM_ROWS method. + */ +Datum +tsm_system_rows_handler(PG_FUNCTION_ARGS) +{ + TsmRoutine *tsm = makeNode(TsmRoutine); + + tsm->parameterTypes = list_make1_oid(INT8OID); + + /* See notes at head of file */ + tsm->repeatable_across_queries = false; + tsm->repeatable_across_scans = true; + + tsm->SampleScanGetSampleSize = system_rows_samplescangetsamplesize; + tsm->InitSampleScan = system_rows_initsamplescan; + tsm->BeginSampleScan = system_rows_beginsamplescan; + tsm->NextSampleBlock = system_rows_nextsampleblock; + tsm->NextSampleTuple = system_rows_nextsampletuple; + tsm->EndSampleScan = NULL; + + PG_RETURN_POINTER(tsm); +} + +/* + * Sample size estimation. + */ +static void +system_rows_samplescangetsamplesize(PlannerInfo *root, + RelOptInfo *baserel, + List *paramexprs, + BlockNumber *pages, + double *tuples) +{ + Node *limitnode; + int64 ntuples; + double npages; + + /* Try to extract an estimate for the limit rowcount */ + limitnode = (Node *) linitial(paramexprs); + limitnode = estimate_expression_value(root, limitnode); + + if (IsA(limitnode, Const) && + !((Const *) limitnode)->constisnull) + { + ntuples = DatumGetInt64(((Const *) limitnode)->constvalue); + if (ntuples < 0) + { + /* Default ntuples if the value is bogus */ + ntuples = 1000; + } + } + else + { + /* Default ntuples if we didn't obtain a non-null Const */ + ntuples = 1000; + } + + /* Clamp to the estimated relation size */ + if (ntuples > baserel->tuples) + ntuples = (int64) baserel->tuples; + ntuples = clamp_row_est(ntuples); + + if (baserel->tuples > 0 && baserel->pages > 0) + { + /* Estimate number of pages visited based on tuple density */ + double density = baserel->tuples / (double) baserel->pages; + + npages = ntuples / density; + } + else + { + /* For lack of data, assume one tuple per page */ + npages = ntuples; + } + + /* Clamp to sane value */ + npages = clamp_row_est(Min((double) baserel->pages, npages)); + + *pages = npages; + *tuples = ntuples; +} + +/* + * Initialize during executor setup. + */ +static void +system_rows_initsamplescan(SampleScanState *node, int eflags) +{ + node->tsm_state = palloc0(sizeof(SystemRowsSamplerData)); + /* Note the above leaves tsm_state->step equal to zero */ +} + +/* + * Examine parameters and prepare for a sample scan. + */ +static void +system_rows_beginsamplescan(SampleScanState *node, + Datum *params, + int nparams, + uint32 seed) +{ + SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state; + int64 ntuples = DatumGetInt64(params[0]); + + if (ntuples < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLESAMPLE_ARGUMENT), + errmsg("sample size must not be negative"))); + + sampler->seed = seed; + sampler->ntuples = ntuples; + sampler->lt = InvalidOffsetNumber; + sampler->doneblocks = 0; + /* lb will be initialized during first NextSampleBlock call */ + /* we intentionally do not change nblocks/firstblock/step here */ + + /* + * We *must* use pagemode visibility checking in this module, so force + * that even though it's currently default. + */ + node->use_pagemode = true; +} + +/* + * Select next block to sample. + * + * Uses linear probing algorithm for picking next block. + */ +static BlockNumber +system_rows_nextsampleblock(SampleScanState *node, BlockNumber nblocks) +{ + SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state; + + /* First call within scan? */ + if (sampler->doneblocks == 0) + { + /* First scan within query? */ + if (sampler->step == 0) + { + /* Initialize now that we have scan descriptor */ + pg_prng_state randstate; + + /* If relation is empty, there's nothing to scan */ + if (nblocks == 0) + return InvalidBlockNumber; + + /* We only need an RNG during this setup step */ + sampler_random_init_state(sampler->seed, &randstate); + + /* Compute nblocks/firstblock/step only once per query */ + sampler->nblocks = nblocks; + + /* Choose random starting block within the relation */ + /* (Actually this is the predecessor of the first block visited) */ + sampler->firstblock = sampler_random_fract(&randstate) * + sampler->nblocks; + + /* Find relative prime as step size for linear probing */ + sampler->step = random_relative_prime(sampler->nblocks, &randstate); + } + + /* Reinitialize lb */ + sampler->lb = sampler->firstblock; + } + + /* If we've read all blocks or returned all needed tuples, we're done */ + if (++sampler->doneblocks > sampler->nblocks || + node->donetuples >= sampler->ntuples) + return InvalidBlockNumber; + + /* + * It's probably impossible for scan->rs_nblocks to decrease between scans + * within a query; but just in case, loop until we select a block number + * less than scan->rs_nblocks. We don't care if scan->rs_nblocks has + * increased since the first scan. + */ + do + { + /* Advance lb, using uint64 arithmetic to forestall overflow */ + sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks; + } while (sampler->lb >= nblocks); + + return sampler->lb; +} + +/* + * Select next sampled tuple in current block. + * + * In block sampling, we just want to sample all the tuples in each selected + * block. + * + * When we reach end of the block, return InvalidOffsetNumber which tells + * SampleScan to go to next block. + */ +static OffsetNumber +system_rows_nextsampletuple(SampleScanState *node, + BlockNumber blockno, + OffsetNumber maxoffset) +{ + SystemRowsSamplerData *sampler = (SystemRowsSamplerData *) node->tsm_state; + OffsetNumber tupoffset = sampler->lt; + + /* Quit if we've returned all needed tuples */ + if (node->donetuples >= sampler->ntuples) + return InvalidOffsetNumber; + + /* Advance to next possible offset on page */ + if (tupoffset == InvalidOffsetNumber) + tupoffset = FirstOffsetNumber; + else + tupoffset++; + + /* Done? */ + if (tupoffset > maxoffset) + tupoffset = InvalidOffsetNumber; + + sampler->lt = tupoffset; + + return tupoffset; +} + +/* + * Compute greatest common divisor of two uint32's. + */ +static uint32 +gcd(uint32 a, uint32 b) +{ + uint32 c; + + while (a != 0) + { + c = a; + a = b % a; + b = c; + } + + return b; +} + +/* + * Pick a random value less than and relatively prime to n, if possible + * (else return 1). + */ +static uint32 +random_relative_prime(uint32 n, pg_prng_state *randstate) +{ + uint32 r; + + /* Safety check to avoid infinite loop or zero result for small n. */ + if (n <= 1) + return 1; + + /* + * This should only take 2 or 3 iterations as the probability of 2 numbers + * being relatively prime is ~61%; but just in case, we'll include a + * CHECK_FOR_INTERRUPTS in the loop. + */ + do + { + CHECK_FOR_INTERRUPTS(); + r = (uint32) (sampler_random_fract(randstate) * n); + } while (r == 0 || gcd(r, n) > 1); + + return r; +} diff --git a/contrib/tsm_system_rows/tsm_system_rows.control b/contrib/tsm_system_rows/tsm_system_rows.control new file mode 100644 index 0000000..b495fb1 --- /dev/null +++ b/contrib/tsm_system_rows/tsm_system_rows.control @@ -0,0 +1,6 @@ +# tsm_system_rows extension +comment = 'TABLESAMPLE method which accepts number of rows as a limit' +default_version = '1.0' +module_pathname = '$libdir/tsm_system_rows' +relocatable = true +trusted = true diff --git a/contrib/tsm_system_time/.gitignore b/contrib/tsm_system_time/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/tsm_system_time/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/tsm_system_time/Makefile b/contrib/tsm_system_time/Makefile new file mode 100644 index 0000000..6959995 --- /dev/null +++ b/contrib/tsm_system_time/Makefile @@ -0,0 +1,25 @@ +# contrib/tsm_system_time/Makefile + +MODULE_big = tsm_system_time +OBJS = \ + $(WIN32RES) \ + tsm_system_time.o +PGFILEDESC = "tsm_system_time - TABLESAMPLE method which accepts time in milliseconds as a limit" + +EXTENSION = tsm_system_time +DATA = tsm_system_time--1.0.sql + +REGRESS = tsm_system_time + +SHLIB_LINK += $(filter -lm, $(LIBS)) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/tsm_system_time +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/tsm_system_time/expected/tsm_system_time.out b/contrib/tsm_system_time/expected/tsm_system_time.out new file mode 100644 index 0000000..ac44f30 --- /dev/null +++ b/contrib/tsm_system_time/expected/tsm_system_time.out @@ -0,0 +1,100 @@ +CREATE EXTENSION tsm_system_time; +CREATE TABLE test_tablesample (id int, name text); +INSERT INTO test_tablesample SELECT i, repeat(i::text, 1000) + FROM generate_series(0, 30) s(i); +ANALYZE test_tablesample; +-- It's a bit tricky to test SYSTEM_TIME in a platform-independent way. +-- We can test the zero-time corner case ... +SELECT count(*) FROM test_tablesample TABLESAMPLE system_time (0); + count +------- + 0 +(1 row) + +-- ... and we assume that this will finish before running out of time: +SELECT count(*) FROM test_tablesample TABLESAMPLE system_time (100000); + count +------- + 31 +(1 row) + +-- bad parameters should get through planning, but not execution: +EXPLAIN (COSTS OFF) +SELECT id FROM test_tablesample TABLESAMPLE system_time (-1); + QUERY PLAN +-------------------------------------------------- + Sample Scan on test_tablesample + Sampling: system_time ('-1'::double precision) +(2 rows) + +SELECT id FROM test_tablesample TABLESAMPLE system_time (-1); +ERROR: sample collection time must not be negative +-- fail, this method is not repeatable: +SELECT * FROM test_tablesample TABLESAMPLE system_time (10) REPEATABLE (0); +ERROR: tablesample method system_time does not support REPEATABLE +LINE 1: SELECT * FROM test_tablesample TABLESAMPLE system_time (10) ... + ^ +-- since it's not repeatable, we expect a Materialize node in these plans: +EXPLAIN (COSTS OFF) +SELECT * FROM + (VALUES (0),(100000)) v(time), + LATERAL (SELECT COUNT(*) FROM test_tablesample + TABLESAMPLE system_time (100000)) ss; + QUERY PLAN +------------------------------------------------------------------------ + Nested Loop + -> Aggregate + -> Materialize + -> Sample Scan on test_tablesample + Sampling: system_time ('100000'::double precision) + -> Values Scan on "*VALUES*" +(6 rows) + +SELECT * FROM + (VALUES (0),(100000)) v(time), + LATERAL (SELECT COUNT(*) FROM test_tablesample + TABLESAMPLE system_time (100000)) ss; + time | count +--------+------- + 0 | 31 + 100000 | 31 +(2 rows) + +EXPLAIN (COSTS OFF) +SELECT * FROM + (VALUES (0),(100000)) v(time), + LATERAL (SELECT COUNT(*) FROM test_tablesample + TABLESAMPLE system_time (time)) ss; + QUERY PLAN +---------------------------------------------------------------- + Nested Loop + -> Values Scan on "*VALUES*" + -> Aggregate + -> Materialize + -> Sample Scan on test_tablesample + Sampling: system_time ("*VALUES*".column1) +(6 rows) + +SELECT * FROM + (VALUES (0),(100000)) v(time), + LATERAL (SELECT COUNT(*) FROM test_tablesample + TABLESAMPLE system_time (time)) ss; + time | count +--------+------- + 0 | 0 + 100000 | 31 +(2 rows) + +CREATE VIEW vv AS + SELECT * FROM test_tablesample TABLESAMPLE system_time (20); +EXPLAIN (COSTS OFF) SELECT * FROM vv; + QUERY PLAN +-------------------------------------------------- + Sample Scan on test_tablesample + Sampling: system_time ('20'::double precision) +(2 rows) + +DROP EXTENSION tsm_system_time; -- fail, view depends on extension +ERROR: cannot drop extension tsm_system_time because other objects depend on it +DETAIL: view vv depends on function system_time(internal) +HINT: Use DROP ... CASCADE to drop the dependent objects too. diff --git a/contrib/tsm_system_time/meson.build b/contrib/tsm_system_time/meson.build new file mode 100644 index 0000000..e43147d --- /dev/null +++ b/contrib/tsm_system_time/meson.build @@ -0,0 +1,34 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +tsm_system_time_sources = files( + 'tsm_system_time.c', +) + +if host_system == 'windows' + tsm_system_time_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'tsm_system_time', + '--FILEDESC', 'tsm_system_time - TABLESAMPLE method which accepts time in milliseconds as a limit',]) +endif + +tsm_system_time = shared_module('tsm_system_time', + tsm_system_time_sources, + kwargs: contrib_mod_args, +) +contrib_targets += tsm_system_time + +install_data( + 'tsm_system_time--1.0.sql', + 'tsm_system_time.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'tsm_system_time', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'tsm_system_time', + ], + }, +} diff --git a/contrib/tsm_system_time/sql/tsm_system_time.sql b/contrib/tsm_system_time/sql/tsm_system_time.sql new file mode 100644 index 0000000..117de16 --- /dev/null +++ b/contrib/tsm_system_time/sql/tsm_system_time.sql @@ -0,0 +1,51 @@ +CREATE EXTENSION tsm_system_time; + +CREATE TABLE test_tablesample (id int, name text); +INSERT INTO test_tablesample SELECT i, repeat(i::text, 1000) + FROM generate_series(0, 30) s(i); +ANALYZE test_tablesample; + +-- It's a bit tricky to test SYSTEM_TIME in a platform-independent way. +-- We can test the zero-time corner case ... +SELECT count(*) FROM test_tablesample TABLESAMPLE system_time (0); +-- ... and we assume that this will finish before running out of time: +SELECT count(*) FROM test_tablesample TABLESAMPLE system_time (100000); + +-- bad parameters should get through planning, but not execution: +EXPLAIN (COSTS OFF) +SELECT id FROM test_tablesample TABLESAMPLE system_time (-1); + +SELECT id FROM test_tablesample TABLESAMPLE system_time (-1); + +-- fail, this method is not repeatable: +SELECT * FROM test_tablesample TABLESAMPLE system_time (10) REPEATABLE (0); + +-- since it's not repeatable, we expect a Materialize node in these plans: +EXPLAIN (COSTS OFF) +SELECT * FROM + (VALUES (0),(100000)) v(time), + LATERAL (SELECT COUNT(*) FROM test_tablesample + TABLESAMPLE system_time (100000)) ss; + +SELECT * FROM + (VALUES (0),(100000)) v(time), + LATERAL (SELECT COUNT(*) FROM test_tablesample + TABLESAMPLE system_time (100000)) ss; + +EXPLAIN (COSTS OFF) +SELECT * FROM + (VALUES (0),(100000)) v(time), + LATERAL (SELECT COUNT(*) FROM test_tablesample + TABLESAMPLE system_time (time)) ss; + +SELECT * FROM + (VALUES (0),(100000)) v(time), + LATERAL (SELECT COUNT(*) FROM test_tablesample + TABLESAMPLE system_time (time)) ss; + +CREATE VIEW vv AS + SELECT * FROM test_tablesample TABLESAMPLE system_time (20); + +EXPLAIN (COSTS OFF) SELECT * FROM vv; + +DROP EXTENSION tsm_system_time; -- fail, view depends on extension diff --git a/contrib/tsm_system_time/tsm_system_time--1.0.sql b/contrib/tsm_system_time/tsm_system_time--1.0.sql new file mode 100644 index 0000000..c59d2e8 --- /dev/null +++ b/contrib/tsm_system_time/tsm_system_time--1.0.sql @@ -0,0 +1,9 @@ +/* contrib/tsm_system_time/tsm_system_time--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION tsm_system_time" to load this file. \quit + +CREATE FUNCTION system_time(internal) +RETURNS tsm_handler +AS 'MODULE_PATHNAME', 'tsm_system_time_handler' +LANGUAGE C STRICT; diff --git a/contrib/tsm_system_time/tsm_system_time.c b/contrib/tsm_system_time/tsm_system_time.c new file mode 100644 index 0000000..e0a8eec --- /dev/null +++ b/contrib/tsm_system_time/tsm_system_time.c @@ -0,0 +1,353 @@ +/*------------------------------------------------------------------------- + * + * tsm_system_time.c + * support routines for SYSTEM_TIME tablesample method + * + * The desire here is to produce a random sample with as many rows as possible + * in no more than the specified amount of time. We use a block-sampling + * approach. To ensure that the whole relation will be visited if necessary, + * we start at a randomly chosen block and then advance with a stride that + * is randomly chosen but is relatively prime to the relation's nblocks. + * + * Because of the time dependence, this method is necessarily unrepeatable. + * However, we do what we can to reduce surprising behavior by selecting + * the sampling pattern just once per query, much as in tsm_system_rows. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * contrib/tsm_system_time/tsm_system_time.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include + +#include "access/relscan.h" +#include "access/tsmapi.h" +#include "catalog/pg_type.h" +#include "miscadmin.h" +#include "optimizer/optimizer.h" +#include "utils/sampling.h" +#include "utils/spccache.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(tsm_system_time_handler); + + +/* Private state */ +typedef struct +{ + uint32 seed; /* random seed */ + double millis; /* time limit for sampling */ + instr_time start_time; /* scan start time */ + OffsetNumber lt; /* last tuple returned from current block */ + BlockNumber doneblocks; /* number of already-scanned blocks */ + BlockNumber lb; /* last block visited */ + /* these three values are not changed during a rescan: */ + BlockNumber nblocks; /* number of blocks in relation */ + BlockNumber firstblock; /* first block to sample from */ + BlockNumber step; /* step size, or 0 if not set yet */ +} SystemTimeSamplerData; + +static void system_time_samplescangetsamplesize(PlannerInfo *root, + RelOptInfo *baserel, + List *paramexprs, + BlockNumber *pages, + double *tuples); +static void system_time_initsamplescan(SampleScanState *node, + int eflags); +static void system_time_beginsamplescan(SampleScanState *node, + Datum *params, + int nparams, + uint32 seed); +static BlockNumber system_time_nextsampleblock(SampleScanState *node, BlockNumber nblocks); +static OffsetNumber system_time_nextsampletuple(SampleScanState *node, + BlockNumber blockno, + OffsetNumber maxoffset); +static uint32 random_relative_prime(uint32 n, pg_prng_state *randstate); + + +/* + * Create a TsmRoutine descriptor for the SYSTEM_TIME method. + */ +Datum +tsm_system_time_handler(PG_FUNCTION_ARGS) +{ + TsmRoutine *tsm = makeNode(TsmRoutine); + + tsm->parameterTypes = list_make1_oid(FLOAT8OID); + + /* See notes at head of file */ + tsm->repeatable_across_queries = false; + tsm->repeatable_across_scans = false; + + tsm->SampleScanGetSampleSize = system_time_samplescangetsamplesize; + tsm->InitSampleScan = system_time_initsamplescan; + tsm->BeginSampleScan = system_time_beginsamplescan; + tsm->NextSampleBlock = system_time_nextsampleblock; + tsm->NextSampleTuple = system_time_nextsampletuple; + tsm->EndSampleScan = NULL; + + PG_RETURN_POINTER(tsm); +} + +/* + * Sample size estimation. + */ +static void +system_time_samplescangetsamplesize(PlannerInfo *root, + RelOptInfo *baserel, + List *paramexprs, + BlockNumber *pages, + double *tuples) +{ + Node *limitnode; + double millis; + double spc_random_page_cost; + double npages; + double ntuples; + + /* Try to extract an estimate for the limit time spec */ + limitnode = (Node *) linitial(paramexprs); + limitnode = estimate_expression_value(root, limitnode); + + if (IsA(limitnode, Const) && + !((Const *) limitnode)->constisnull) + { + millis = DatumGetFloat8(((Const *) limitnode)->constvalue); + if (millis < 0 || isnan(millis)) + { + /* Default millis if the value is bogus */ + millis = 1000; + } + } + else + { + /* Default millis if we didn't obtain a non-null Const */ + millis = 1000; + } + + /* Get the planner's idea of cost per page read */ + get_tablespace_page_costs(baserel->reltablespace, + &spc_random_page_cost, + NULL); + + /* + * Estimate the number of pages we can read by assuming that the cost + * figure is expressed in milliseconds. This is completely, unmistakably + * bogus, but we have to do something to produce an estimate and there's + * no better answer. + */ + if (spc_random_page_cost > 0) + npages = millis / spc_random_page_cost; + else + npages = millis; /* even more bogus, but whatcha gonna do? */ + + /* Clamp to sane value */ + npages = clamp_row_est(Min((double) baserel->pages, npages)); + + if (baserel->tuples > 0 && baserel->pages > 0) + { + /* Estimate number of tuples returned based on tuple density */ + double density = baserel->tuples / (double) baserel->pages; + + ntuples = npages * density; + } + else + { + /* For lack of data, assume one tuple per page */ + ntuples = npages; + } + + /* Clamp to the estimated relation size */ + ntuples = clamp_row_est(Min(baserel->tuples, ntuples)); + + *pages = npages; + *tuples = ntuples; +} + +/* + * Initialize during executor setup. + */ +static void +system_time_initsamplescan(SampleScanState *node, int eflags) +{ + node->tsm_state = palloc0(sizeof(SystemTimeSamplerData)); + /* Note the above leaves tsm_state->step equal to zero */ +} + +/* + * Examine parameters and prepare for a sample scan. + */ +static void +system_time_beginsamplescan(SampleScanState *node, + Datum *params, + int nparams, + uint32 seed) +{ + SystemTimeSamplerData *sampler = (SystemTimeSamplerData *) node->tsm_state; + double millis = DatumGetFloat8(params[0]); + + if (millis < 0 || isnan(millis)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLESAMPLE_ARGUMENT), + errmsg("sample collection time must not be negative"))); + + sampler->seed = seed; + sampler->millis = millis; + sampler->lt = InvalidOffsetNumber; + sampler->doneblocks = 0; + /* start_time, lb will be initialized during first NextSampleBlock call */ + /* we intentionally do not change nblocks/firstblock/step here */ +} + +/* + * Select next block to sample. + * + * Uses linear probing algorithm for picking next block. + */ +static BlockNumber +system_time_nextsampleblock(SampleScanState *node, BlockNumber nblocks) +{ + SystemTimeSamplerData *sampler = (SystemTimeSamplerData *) node->tsm_state; + instr_time cur_time; + + /* First call within scan? */ + if (sampler->doneblocks == 0) + { + /* First scan within query? */ + if (sampler->step == 0) + { + /* Initialize now that we have scan descriptor */ + pg_prng_state randstate; + + /* If relation is empty, there's nothing to scan */ + if (nblocks == 0) + return InvalidBlockNumber; + + /* We only need an RNG during this setup step */ + sampler_random_init_state(sampler->seed, &randstate); + + /* Compute nblocks/firstblock/step only once per query */ + sampler->nblocks = nblocks; + + /* Choose random starting block within the relation */ + /* (Actually this is the predecessor of the first block visited) */ + sampler->firstblock = sampler_random_fract(&randstate) * + sampler->nblocks; + + /* Find relative prime as step size for linear probing */ + sampler->step = random_relative_prime(sampler->nblocks, &randstate); + } + + /* Reinitialize lb and start_time */ + sampler->lb = sampler->firstblock; + INSTR_TIME_SET_CURRENT(sampler->start_time); + } + + /* If we've read all blocks in relation, we're done */ + if (++sampler->doneblocks > sampler->nblocks) + return InvalidBlockNumber; + + /* If we've used up all the allotted time, we're done */ + INSTR_TIME_SET_CURRENT(cur_time); + INSTR_TIME_SUBTRACT(cur_time, sampler->start_time); + if (INSTR_TIME_GET_MILLISEC(cur_time) >= sampler->millis) + return InvalidBlockNumber; + + /* + * It's probably impossible for scan->rs_nblocks to decrease between scans + * within a query; but just in case, loop until we select a block number + * less than scan->rs_nblocks. We don't care if scan->rs_nblocks has + * increased since the first scan. + */ + do + { + /* Advance lb, using uint64 arithmetic to forestall overflow */ + sampler->lb = ((uint64) sampler->lb + sampler->step) % sampler->nblocks; + } while (sampler->lb >= nblocks); + + return sampler->lb; +} + +/* + * Select next sampled tuple in current block. + * + * In block sampling, we just want to sample all the tuples in each selected + * block. + * + * When we reach end of the block, return InvalidOffsetNumber which tells + * SampleScan to go to next block. + */ +static OffsetNumber +system_time_nextsampletuple(SampleScanState *node, + BlockNumber blockno, + OffsetNumber maxoffset) +{ + SystemTimeSamplerData *sampler = (SystemTimeSamplerData *) node->tsm_state; + OffsetNumber tupoffset = sampler->lt; + + /* Advance to next possible offset on page */ + if (tupoffset == InvalidOffsetNumber) + tupoffset = FirstOffsetNumber; + else + tupoffset++; + + /* Done? */ + if (tupoffset > maxoffset) + tupoffset = InvalidOffsetNumber; + + sampler->lt = tupoffset; + + return tupoffset; +} + +/* + * Compute greatest common divisor of two uint32's. + */ +static uint32 +gcd(uint32 a, uint32 b) +{ + uint32 c; + + while (a != 0) + { + c = a; + a = b % a; + b = c; + } + + return b; +} + +/* + * Pick a random value less than and relatively prime to n, if possible + * (else return 1). + */ +static uint32 +random_relative_prime(uint32 n, pg_prng_state *randstate) +{ + uint32 r; + + /* Safety check to avoid infinite loop or zero result for small n. */ + if (n <= 1) + return 1; + + /* + * This should only take 2 or 3 iterations as the probability of 2 numbers + * being relatively prime is ~61%; but just in case, we'll include a + * CHECK_FOR_INTERRUPTS in the loop. + */ + do + { + CHECK_FOR_INTERRUPTS(); + r = (uint32) (sampler_random_fract(randstate) * n); + } while (r == 0 || gcd(r, n) > 1); + + return r; +} diff --git a/contrib/tsm_system_time/tsm_system_time.control b/contrib/tsm_system_time/tsm_system_time.control new file mode 100644 index 0000000..b1b9789 --- /dev/null +++ b/contrib/tsm_system_time/tsm_system_time.control @@ -0,0 +1,6 @@ +# tsm_system_time extension +comment = 'TABLESAMPLE method which accepts time in milliseconds as a limit' +default_version = '1.0' +module_pathname = '$libdir/tsm_system_time' +relocatable = true +trusted = true diff --git a/contrib/unaccent/.gitignore b/contrib/unaccent/.gitignore new file mode 100644 index 0000000..bccda73 --- /dev/null +++ b/contrib/unaccent/.gitignore @@ -0,0 +1,7 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ + +# Downloaded files +/Latin-ASCII.xml diff --git a/contrib/unaccent/Makefile b/contrib/unaccent/Makefile new file mode 100644 index 0000000..b837e86 --- /dev/null +++ b/contrib/unaccent/Makefile @@ -0,0 +1,49 @@ +# contrib/unaccent/Makefile + +MODULE_big = unaccent +OBJS = \ + $(WIN32RES) \ + unaccent.o + +EXTENSION = unaccent +DATA = unaccent--1.1.sql unaccent--1.0--1.1.sql +DATA_TSEARCH = unaccent.rules +PGFILEDESC = "unaccent - text search dictionary that removes accents" + +REGRESS = unaccent + +# We need a UTF8 database +ENCODING = UTF8 +NO_LOCALE = 1 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/unaccent +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +update-unicode: $(srcdir)/unaccent.rules + +# Allow running this even without --with-python +ifeq ($(PYTHON),) +PYTHON = python +endif + +$(srcdir)/unaccent.rules: generate_unaccent_rules.py ../../src/common/unicode/UnicodeData.txt Latin-ASCII.xml + $(PYTHON) $< --unicode-data-file $(word 2,$^) --latin-ascii-file $(word 3,$^) >$@ + +# Only download it once; dependencies must match src/common/unicode/ +../../src/common/unicode/UnicodeData.txt: $(top_builddir)/src/Makefile.global + $(MAKE) -C $(@D) $(@F) + +# Dependency on Makefile.global is for CLDR_VERSION +Latin-ASCII.xml: $(top_builddir)/src/Makefile.global + $(DOWNLOAD) https://raw.githubusercontent.com/unicode-org/cldr/release-$(subst .,-,$(CLDR_VERSION))/common/transforms/Latin-ASCII.xml + +distclean: + rm -f Latin-ASCII.xml diff --git a/contrib/unaccent/expected/unaccent.out b/contrib/unaccent/expected/unaccent.out new file mode 100644 index 0000000..ee0ac71 --- /dev/null +++ b/contrib/unaccent/expected/unaccent.out @@ -0,0 +1,143 @@ +CREATE EXTENSION unaccent; +-- must have a UTF8 database +SELECT getdatabaseencoding(); + getdatabaseencoding +--------------------- + UTF8 +(1 row) + +SET client_encoding TO 'UTF8'; +SELECT unaccent('foobar'); + unaccent +---------- + foobar +(1 row) + +SELECT unaccent('ёлка'); + unaccent +---------- + елка +(1 row) + +SELECT unaccent('ЁЖИК'); + unaccent +---------- + ЕЖИК +(1 row) + +SELECT unaccent('˃˖˗˜'); + unaccent +---------- + >+-~ +(1 row) + +SELECT unaccent('À'); -- Remove combining diacritical 0x0300 + unaccent +---------- + A +(1 row) + +SELECT unaccent('℃℉'); -- degree signs + unaccent +---------- + °C°F +(1 row) + +SELECT unaccent('℗'); -- sound recording copyright + unaccent +---------- + (P) +(1 row) + +SELECT unaccent('unaccent', 'foobar'); + unaccent +---------- + foobar +(1 row) + +SELECT unaccent('unaccent', 'ёлка'); + unaccent +---------- + елка +(1 row) + +SELECT unaccent('unaccent', 'ЁЖИК'); + unaccent +---------- + ЕЖИК +(1 row) + +SELECT unaccent('unaccent', '˃˖˗˜'); + unaccent +---------- + >+-~ +(1 row) + +SELECT unaccent('unaccent', 'À'); + unaccent +---------- + A +(1 row) + +SELECT unaccent('unaccent', '℃℉'); + unaccent +---------- + °C°F +(1 row) + +SELECT unaccent('unaccent', '℗'); + unaccent +---------- + (P) +(1 row) + +SELECT ts_lexize('unaccent', 'foobar'); + ts_lexize +----------- + +(1 row) + +SELECT ts_lexize('unaccent', 'ёлка'); + ts_lexize +----------- + {елка} +(1 row) + +SELECT ts_lexize('unaccent', 'ЁЖИК'); + ts_lexize +----------- + {ЕЖИК} +(1 row) + +SELECT ts_lexize('unaccent', '˃˖˗˜'); + ts_lexize +----------- + {>+-~} +(1 row) + +SELECT ts_lexize('unaccent', 'À'); + ts_lexize +----------- + {A} +(1 row) + +SELECT ts_lexize('unaccent', '℃℉'); + ts_lexize +----------- + {°C°F} +(1 row) + +SELECT ts_lexize('unaccent', '℗'); + ts_lexize +----------- + {(P)} +(1 row) + +-- Controversial case. Black-Letter Capital H (U+210C) is translated by +-- Latin-ASCII.xml as 'x', but it should be 'H'. +SELECT unaccent('ℌ'); + unaccent +---------- + x +(1 row) + diff --git a/contrib/unaccent/generate_unaccent_rules.py b/contrib/unaccent/generate_unaccent_rules.py new file mode 100644 index 0000000..b4b4c38 --- /dev/null +++ b/contrib/unaccent/generate_unaccent_rules.py @@ -0,0 +1,285 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# This script builds unaccent.rules on standard output when given the +# contents of UnicodeData.txt [1] and Latin-ASCII.xml [2] given as +# arguments. Optionally includes ligature expansion and Unicode CLDR +# Latin-ASCII transliterator, enabled by default, this can be disabled +# with "--no-ligatures-expansion" command line option. +# +# The approach is to use the Unicode decomposition data to identify +# precomposed codepoints that are equivalent to a ligature of several +# letters, or a base letter with any number of diacritical marks. +# +# This approach handles most letters with diacritical marks and some +# ligatures. However, several characters (notably a majority of +# ligatures) don't have decomposition. To handle all these cases, one can +# use a standard Unicode transliterator available in Common Locale Data +# Repository (CLDR): Latin-ASCII. This transliterator associates Unicode +# characters to ASCII-range equivalent. Unless "--no-ligatures-expansion" +# option is enabled, the XML file of this transliterator [2] -- given as a +# command line argument -- will be parsed and used. +# +# Ideally you should use the latest release for each data set. This +# script is compatible with at least CLDR release 29. +# +# [1] https://www.unicode.org/Public/${UNICODE_VERSION}/ucd/UnicodeData.txt +# [2] https://raw.githubusercontent.com/unicode-org/cldr/${TAG}/common/transforms/Latin-ASCII.xml + +import argparse +import codecs +import re +import sys +import xml.etree.ElementTree as ET + +sys.stdout = codecs.getwriter('utf8')(sys.stdout.buffer) + +# The ranges of Unicode characters that we consider to be "plain letters". +# For now we are being conservative by including only Latin and Greek. This +# could be extended in future based on feedback from people with relevant +# language knowledge. +PLAIN_LETTER_RANGES = ((ord('a'), ord('z')), # Latin lower case + (ord('A'), ord('Z')), # Latin upper case + (0x03b1, 0x03c9), # GREEK SMALL LETTER ALPHA, GREEK SMALL LETTER OMEGA + (0x0391, 0x03a9)) # GREEK CAPITAL LETTER ALPHA, GREEK CAPITAL LETTER OMEGA + +# Combining marks follow a "base" character, and result in a composite +# character. Example: "U&'A\0300'"produces "À".There are three types of +# combining marks: enclosing (Me), non-spacing combining (Mn), spacing +# combining (Mc). We identify the ranges of marks we feel safe removing. +# References: +# https://en.wikipedia.org/wiki/Combining_character +# https://www.unicode.org/charts/PDF/U0300.pdf +# https://www.unicode.org/charts/PDF/U20D0.pdf +COMBINING_MARK_RANGES = ((0x0300, 0x0362), # Mn: Accents, IPA + (0x20dd, 0x20E0), # Me: Symbols + (0x20e2, 0x20e4),) # Me: Screen, keycap, triangle + + +def print_record(codepoint, letter): + if letter: + output = chr(codepoint) + "\t" + letter + else: + output = chr(codepoint) + + print(output) + + +class Codepoint: + def __init__(self, id, general_category, combining_ids): + self.id = id + self.general_category = general_category + self.combining_ids = combining_ids + + +def is_mark_to_remove(codepoint): + """Return true if this is a combining mark to remove.""" + if not is_mark(codepoint): + return False + + for begin, end in COMBINING_MARK_RANGES: + if codepoint.id >= begin and codepoint.id <= end: + return True + return False + + +def is_plain_letter(codepoint): + """Return true if codepoint represents a "plain letter".""" + for begin, end in PLAIN_LETTER_RANGES: + if codepoint.id >= begin and codepoint.id <= end: + return True + return False + + +def is_mark(codepoint): + """Returns true for diacritical marks (combining codepoints).""" + return codepoint.general_category in ("Mn", "Me", "Mc") + + +def is_letter_with_marks(codepoint, table): + """Returns true for letters combined with one or more marks.""" + # See https://www.unicode.org/reports/tr44/tr44-14.html#General_Category_Values + + # Letter may have no combining characters, in which case it has + # no marks. + if len(codepoint.combining_ids) == 1: + return False + + # A letter without diacritical marks has none of them. + if any(is_mark(table[i]) for i in codepoint.combining_ids[1:]) is False: + return False + + # Check if the base letter of this letter has marks. + codepoint_base = codepoint.combining_ids[0] + if is_plain_letter(table[codepoint_base]) is False and \ + is_letter_with_marks(table[codepoint_base], table) is False: + return False + + return True + + +def is_letter(codepoint, table): + """Return true for letter with or without diacritical marks.""" + return is_plain_letter(codepoint) or is_letter_with_marks(codepoint, table) + + +def get_plain_letter(codepoint, table): + """Return the base codepoint without marks. If this codepoint has more + than one combining character, do a recursive lookup on the table to + find out its plain base letter.""" + if is_letter_with_marks(codepoint, table): + if len(table[codepoint.combining_ids[0]].combining_ids) > 1: + return get_plain_letter(table[codepoint.combining_ids[0]], table) + elif is_plain_letter(table[codepoint.combining_ids[0]]): + return table[codepoint.combining_ids[0]] + + # Should not come here + assert False, 'Codepoint U+%0.2X' % codepoint.id + elif is_plain_letter(codepoint): + return codepoint + + # Should not come here + assert False, 'Codepoint U+%0.2X' % codepoint.id + + +def is_ligature(codepoint, table): + """Return true for letters combined with letters.""" + return all(is_letter(table[i], table) for i in codepoint.combining_ids) + + +def get_plain_letters(codepoint, table): + """Return a list of plain letters from a ligature.""" + assert(is_ligature(codepoint, table)) + return [get_plain_letter(table[id], table) for id in codepoint.combining_ids] + + +def parse_cldr_latin_ascii_transliterator(latinAsciiFilePath): + """Parse the XML file and return a set of tuples (src, trg), where "src" + is the original character and "trg" the substitute.""" + charactersSet = set() + + # RegEx to parse rules + rulePattern = re.compile(r'^(?:(.)|(\\u[0-9a-fA-F]{4})) \u2192 (?:\'(.+)\'|(.+)) ;') + + # construct tree from XML + transliterationTree = ET.parse(latinAsciiFilePath) + transliterationTreeRoot = transliterationTree.getroot() + + # Fetch all the transliteration rules. Since release 29 of Latin-ASCII.xml + # all the transliteration rules are located in a single tRule block with + # all rules separated into separate lines. + blockRules = transliterationTreeRoot.findall("./transforms/transform/tRule") + assert(len(blockRules) == 1) + + # Split the block of rules into one element per line. + rules = blockRules[0].text.splitlines() + + # And finish the processing of each individual rule. + for rule in rules: + matches = rulePattern.search(rule) + + # The regular expression capture four groups corresponding + # to the characters. + # + # Group 1: plain "src" char. Empty if group 2 is not. + # Group 2: unicode-escaped "src" char (e.g. "\u0110"). Empty if group 1 is not. + # + # Group 3: plain "trg" char. Empty if group 4 is not. + # Group 4: plain "trg" char between quotes. Empty if group 3 is not. + if matches is not None: + src = matches.group(1) if matches.group(1) is not None else bytes(matches.group(2), 'UTF-8').decode('unicode-escape') + trg = matches.group(3) if matches.group(3) is not None else matches.group(4) + + # "'" and """ are escaped + trg = trg.replace("\\'", "'").replace('\\"', '"') + + # the parser of unaccent only accepts non-whitespace characters + # for "src" and "trg" (see unaccent.c) + if not src.isspace() and not trg.isspace(): + charactersSet.add((ord(src), trg)) + + return charactersSet + + +def special_cases(): + """Returns the special cases which are not handled by other methods""" + charactersSet = set() + + # Cyrillic + charactersSet.add((0x0401, "\u0415")) # CYRILLIC CAPITAL LETTER IO + charactersSet.add((0x0451, "\u0435")) # CYRILLIC SMALL LETTER IO + + # Symbols of "Letterlike Symbols" Unicode Block (U+2100 to U+214F) + charactersSet.add((0x2103, "\xb0C")) # DEGREE CELSIUS + charactersSet.add((0x2109, "\xb0F")) # DEGREE FAHRENHEIT + + return charactersSet + + +def main(args): + # https://www.unicode.org/reports/tr44/tr44-14.html#Character_Decomposition_Mappings + decomposition_type_pattern = re.compile(" *<[^>]*> *") + + table = {} + all = [] + + # unordered set for ensure uniqueness + charactersSet = set() + + # read file UnicodeData.txt + with codecs.open( + args.unicodeDataFilePath, mode='r', encoding='UTF-8', + ) as unicodeDataFile: + # read everything we need into memory + for line in unicodeDataFile: + fields = line.split(";") + if len(fields) > 5: + # https://www.unicode.org/reports/tr44/tr44-14.html#UnicodeData.txt + general_category = fields[2] + decomposition = fields[5] + decomposition = re.sub(decomposition_type_pattern, ' ', decomposition) + id = int(fields[0], 16) + combining_ids = [int(s, 16) for s in decomposition.split(" ") if s != ""] + codepoint = Codepoint(id, general_category, combining_ids) + table[id] = codepoint + all.append(codepoint) + + # walk through all the codepoints looking for interesting mappings + for codepoint in all: + if codepoint.general_category.startswith('L') and \ + len(codepoint.combining_ids) > 1: + if is_letter_with_marks(codepoint, table): + charactersSet.add((codepoint.id, + chr(get_plain_letter(codepoint, table).id))) + elif args.noLigaturesExpansion is False and is_ligature(codepoint, table): + charactersSet.add((codepoint.id, + "".join(chr(combining_codepoint.id) + for combining_codepoint + in get_plain_letters(codepoint, table)))) + elif is_mark_to_remove(codepoint): + charactersSet.add((codepoint.id, None)) + + # add CLDR Latin-ASCII characters + if not args.noLigaturesExpansion: + charactersSet |= parse_cldr_latin_ascii_transliterator(args.latinAsciiFilePath) + charactersSet |= special_cases() + + # sort for more convenient display + charactersList = sorted(charactersSet, key=lambda characterPair: characterPair[0]) + + for characterPair in charactersList: + print_record(characterPair[0], characterPair[1]) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='This script builds unaccent.rules on standard output when given the contents of UnicodeData.txt and Latin-ASCII.xml given as arguments.') + parser.add_argument("--unicode-data-file", help="Path to formatted text file corresponding to UnicodeData.txt.", type=str, required=True, dest='unicodeDataFilePath') + parser.add_argument("--latin-ascii-file", help="Path to XML file from Unicode Common Locale Data Repository (CLDR) corresponding to Latin-ASCII transliterator (Latin-ASCII.xml).", type=str, dest='latinAsciiFilePath') + parser.add_argument("--no-ligatures-expansion", help="Do not expand ligatures and do not use Unicode CLDR Latin-ASCII transliterator. By default, this option is not enabled and \"--latin-ascii-file\" argument is required. If this option is enabled, \"--latin-ascii-file\" argument is optional and ignored.", action="store_true", dest='noLigaturesExpansion') + args = parser.parse_args() + + if args.noLigaturesExpansion is False and args.latinAsciiFilePath is None: + sys.stderr.write('You must specify the path to Latin-ASCII transliterator file with \"--latin-ascii-file\" option or use \"--no-ligatures-expansion\" option. Use \"-h\" option for help.') + sys.exit(1) + + main(args) diff --git a/contrib/unaccent/meson.build b/contrib/unaccent/meson.build new file mode 100644 index 0000000..bd629ec --- /dev/null +++ b/contrib/unaccent/meson.build @@ -0,0 +1,42 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +unaccent_sources = files( + 'unaccent.c', +) + +if host_system == 'windows' + unaccent_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'unaccent', + '--FILEDESC', 'unaccent - text search dictionary that removes accents',]) +endif + +unaccent = shared_module('unaccent', + unaccent_sources, + kwargs: contrib_mod_args, +) +contrib_targets += unaccent + +install_data( + 'unaccent--1.0--1.1.sql', + 'unaccent--1.1.sql', + 'unaccent.control', + kwargs: contrib_data_args, +) + +install_data( + 'unaccent.rules', + install_dir: dir_data / 'tsearch_data' +) + +# XXX: Implement downlo +tests += { + 'name': 'unaccent', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'unaccent', + ], + 'regress_args': ['--no-locale', '--encoding=UTF8'], + }, +} diff --git a/contrib/unaccent/sql/unaccent.sql b/contrib/unaccent/sql/unaccent.sql new file mode 100644 index 0000000..3fc0c70 --- /dev/null +++ b/contrib/unaccent/sql/unaccent.sql @@ -0,0 +1,34 @@ +CREATE EXTENSION unaccent; + +-- must have a UTF8 database +SELECT getdatabaseencoding(); + +SET client_encoding TO 'UTF8'; + +SELECT unaccent('foobar'); +SELECT unaccent('ёлка'); +SELECT unaccent('ЁЖИК'); +SELECT unaccent('˃˖˗˜'); +SELECT unaccent('À'); -- Remove combining diacritical 0x0300 +SELECT unaccent('℃℉'); -- degree signs +SELECT unaccent('℗'); -- sound recording copyright + +SELECT unaccent('unaccent', 'foobar'); +SELECT unaccent('unaccent', 'ёлка'); +SELECT unaccent('unaccent', 'ЁЖИК'); +SELECT unaccent('unaccent', '˃˖˗˜'); +SELECT unaccent('unaccent', 'À'); +SELECT unaccent('unaccent', '℃℉'); +SELECT unaccent('unaccent', '℗'); + +SELECT ts_lexize('unaccent', 'foobar'); +SELECT ts_lexize('unaccent', 'ёлка'); +SELECT ts_lexize('unaccent', 'ЁЖИК'); +SELECT ts_lexize('unaccent', '˃˖˗˜'); +SELECT ts_lexize('unaccent', 'À'); +SELECT ts_lexize('unaccent', '℃℉'); +SELECT ts_lexize('unaccent', '℗'); + +-- Controversial case. Black-Letter Capital H (U+210C) is translated by +-- Latin-ASCII.xml as 'x', but it should be 'H'. +SELECT unaccent('ℌ'); diff --git a/contrib/unaccent/unaccent--1.0--1.1.sql b/contrib/unaccent/unaccent--1.0--1.1.sql new file mode 100644 index 0000000..8efa0d0 --- /dev/null +++ b/contrib/unaccent/unaccent--1.0--1.1.sql @@ -0,0 +1,9 @@ +/* contrib/unaccent/unaccent--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION unaccent UPDATE TO '1.1'" to load this file. \quit + +ALTER FUNCTION unaccent(regdictionary, text) PARALLEL SAFE; +ALTER FUNCTION unaccent(text) PARALLEL SAFE; +ALTER FUNCTION unaccent_init(internal) PARALLEL SAFE; +ALTER FUNCTION unaccent_lexize(internal, internal, internal, internal) PARALLEL SAFE; diff --git a/contrib/unaccent/unaccent--1.1.sql b/contrib/unaccent/unaccent--1.1.sql new file mode 100644 index 0000000..ecc8651 --- /dev/null +++ b/contrib/unaccent/unaccent--1.1.sql @@ -0,0 +1,34 @@ +/* contrib/unaccent/unaccent--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION unaccent" to load this file. \quit + +CREATE FUNCTION unaccent(regdictionary, text) + RETURNS text + AS 'MODULE_PATHNAME', 'unaccent_dict' + LANGUAGE C STABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION unaccent(text) + RETURNS text + AS 'MODULE_PATHNAME', 'unaccent_dict' + LANGUAGE C STABLE STRICT PARALLEL SAFE; + +CREATE FUNCTION unaccent_init(internal) + RETURNS internal + AS 'MODULE_PATHNAME', 'unaccent_init' + LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION unaccent_lexize(internal,internal,internal,internal) + RETURNS internal + AS 'MODULE_PATHNAME', 'unaccent_lexize' + LANGUAGE C PARALLEL SAFE; + +CREATE TEXT SEARCH TEMPLATE unaccent ( + INIT = unaccent_init, + LEXIZE = unaccent_lexize +); + +CREATE TEXT SEARCH DICTIONARY unaccent ( + TEMPLATE = unaccent, + RULES = 'unaccent' +); diff --git a/contrib/unaccent/unaccent.c b/contrib/unaccent/unaccent.c new file mode 100644 index 0000000..64c879e --- /dev/null +++ b/contrib/unaccent/unaccent.c @@ -0,0 +1,434 @@ +/*------------------------------------------------------------------------- + * + * unaccent.c + * Text search unaccent dictionary + * + * Copyright (c) 2009-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * contrib/unaccent/unaccent.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "catalog/namespace.h" +#include "catalog/pg_ts_dict.h" +#include "commands/defrem.h" +#include "lib/stringinfo.h" +#include "tsearch/ts_cache.h" +#include "tsearch/ts_locale.h" +#include "tsearch/ts_public.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/regproc.h" +#include "utils/syscache.h" + +PG_MODULE_MAGIC; + +/* + * An unaccent dictionary uses a trie to find a string to replace. Each node + * of the trie is an array of 256 TrieChar structs; the N-th element of the + * array corresponds to next byte value N. That element can contain both a + * replacement string (to be used if the source string ends with this byte) + * and a link to another trie node (to be followed if there are more bytes). + * + * Note that the trie search logic pays no attention to multibyte character + * boundaries. This is OK as long as both the data entered into the trie and + * the data we're trying to look up are validly encoded; no partial-character + * matches will occur. + */ +typedef struct TrieChar +{ + struct TrieChar *nextChar; + char *replaceTo; + int replacelen; +} TrieChar; + +/* + * placeChar - put str into trie's structure, byte by byte. + * + * If node is NULL, we need to make a new node, which will be returned; + * otherwise the return value is the same as node. + */ +static TrieChar * +placeChar(TrieChar *node, const unsigned char *str, int lenstr, + const char *replaceTo, int replacelen) +{ + TrieChar *curnode; + + if (!node) + node = (TrieChar *) palloc0(sizeof(TrieChar) * 256); + + Assert(lenstr > 0); /* else str[0] doesn't exist */ + + curnode = node + *str; + + if (lenstr <= 1) + { + if (curnode->replaceTo) + ereport(WARNING, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("duplicate source strings, first one will be used"))); + else + { + curnode->replacelen = replacelen; + curnode->replaceTo = (char *) palloc(replacelen); + memcpy(curnode->replaceTo, replaceTo, replacelen); + } + } + else + { + curnode->nextChar = placeChar(curnode->nextChar, str + 1, lenstr - 1, + replaceTo, replacelen); + } + + return node; +} + +/* + * initTrie - create trie from file. + * + * Function converts UTF8-encoded file into current encoding. + */ +static TrieChar * +initTrie(const char *filename) +{ + TrieChar *volatile rootTrie = NULL; + MemoryContext ccxt = CurrentMemoryContext; + tsearch_readline_state trst; + volatile bool skip; + + filename = get_tsearch_config_filename(filename, "rules"); + if (!tsearch_readline_begin(&trst, filename)) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not open unaccent file \"%s\": %m", + filename))); + + do + { + /* + * pg_do_encoding_conversion() (called by tsearch_readline()) will + * emit exception if it finds untranslatable characters in current + * locale. We just skip such lines, continuing with the next. + */ + skip = true; + + PG_TRY(); + { + char *line; + + while ((line = tsearch_readline(&trst)) != NULL) + { + /*---------- + * The format of each line must be "src" or "src trg", where + * src and trg are sequences of one or more non-whitespace + * characters, separated by whitespace. Whitespace at start + * or end of line is ignored. If trg is omitted, an empty + * string is used as the replacement. + * + * We use a simple state machine, with states + * 0 initial (before src) + * 1 in src + * 2 in whitespace after src + * 3 in trg + * 4 in whitespace after trg + * -1 syntax error detected + *---------- + */ + int state; + char *ptr; + char *src = NULL; + char *trg = NULL; + int ptrlen; + int srclen = 0; + int trglen = 0; + + state = 0; + for (ptr = line; *ptr; ptr += ptrlen) + { + ptrlen = pg_mblen(ptr); + /* ignore whitespace, but end src or trg */ + if (t_isspace(ptr)) + { + if (state == 1) + state = 2; + else if (state == 3) + state = 4; + continue; + } + switch (state) + { + case 0: + /* start of src */ + src = ptr; + srclen = ptrlen; + state = 1; + break; + case 1: + /* continue src */ + srclen += ptrlen; + break; + case 2: + /* start of trg */ + trg = ptr; + trglen = ptrlen; + state = 3; + break; + case 3: + /* continue trg */ + trglen += ptrlen; + break; + default: + /* bogus line format */ + state = -1; + break; + } + } + + if (state == 1 || state == 2) + { + /* trg was omitted, so use "" */ + trg = ""; + trglen = 0; + } + + if (state > 0) + rootTrie = placeChar(rootTrie, + (unsigned char *) src, srclen, + trg, trglen); + else if (state < 0) + ereport(WARNING, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid syntax: more than two strings in unaccent rule"))); + + pfree(line); + } + skip = false; + } + PG_CATCH(); + { + ErrorData *errdata; + MemoryContext ecxt; + + ecxt = MemoryContextSwitchTo(ccxt); + errdata = CopyErrorData(); + if (errdata->sqlerrcode == ERRCODE_UNTRANSLATABLE_CHARACTER) + { + FlushErrorState(); + } + else + { + MemoryContextSwitchTo(ecxt); + PG_RE_THROW(); + } + } + PG_END_TRY(); + } + while (skip); + + tsearch_readline_end(&trst); + + return rootTrie; +} + +/* + * findReplaceTo - find longest possible match in trie + * + * On success, returns pointer to ending subnode, plus length of matched + * source string in *p_matchlen. On failure, returns NULL. + */ +static TrieChar * +findReplaceTo(TrieChar *node, const unsigned char *src, int srclen, + int *p_matchlen) +{ + TrieChar *result = NULL; + int matchlen = 0; + + *p_matchlen = 0; /* prevent uninitialized-variable warnings */ + + while (node && matchlen < srclen) + { + node = node + src[matchlen]; + matchlen++; + + if (node->replaceTo) + { + result = node; + *p_matchlen = matchlen; + } + + node = node->nextChar; + } + + return result; +} + +PG_FUNCTION_INFO_V1(unaccent_init); +Datum +unaccent_init(PG_FUNCTION_ARGS) +{ + List *dictoptions = (List *) PG_GETARG_POINTER(0); + TrieChar *rootTrie = NULL; + bool fileloaded = false; + ListCell *l; + + foreach(l, dictoptions) + { + DefElem *defel = (DefElem *) lfirst(l); + + if (strcmp(defel->defname, "rules") == 0) + { + if (fileloaded) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("multiple Rules parameters"))); + rootTrie = initTrie(defGetString(defel)); + fileloaded = true; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized Unaccent parameter: \"%s\"", + defel->defname))); + } + } + + if (!fileloaded) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("missing Rules parameter"))); + } + + PG_RETURN_POINTER(rootTrie); +} + +PG_FUNCTION_INFO_V1(unaccent_lexize); +Datum +unaccent_lexize(PG_FUNCTION_ARGS) +{ + TrieChar *rootTrie = (TrieChar *) PG_GETARG_POINTER(0); + char *srcchar = (char *) PG_GETARG_POINTER(1); + int32 len = PG_GETARG_INT32(2); + char *srcstart = srcchar; + TSLexeme *res; + StringInfoData buf; + + /* we allocate storage for the buffer only if needed */ + buf.data = NULL; + + while (len > 0) + { + TrieChar *node; + int matchlen; + + node = findReplaceTo(rootTrie, (unsigned char *) srcchar, len, + &matchlen); + if (node && node->replaceTo) + { + if (buf.data == NULL) + { + /* initialize buffer */ + initStringInfo(&buf); + /* insert any data we already skipped over */ + if (srcchar != srcstart) + appendBinaryStringInfo(&buf, srcstart, srcchar - srcstart); + } + appendBinaryStringInfo(&buf, node->replaceTo, node->replacelen); + } + else + { + matchlen = pg_mblen(srcchar); + if (buf.data != NULL) + appendBinaryStringInfo(&buf, srcchar, matchlen); + } + + srcchar += matchlen; + len -= matchlen; + } + + /* return a result only if we made at least one substitution */ + if (buf.data != NULL) + { + res = (TSLexeme *) palloc0(sizeof(TSLexeme) * 2); + res->lexeme = buf.data; + res->flags = TSL_FILTER; + } + else + res = NULL; + + PG_RETURN_POINTER(res); +} + +/* + * Function-like wrapper for dictionary + */ +PG_FUNCTION_INFO_V1(unaccent_dict); +Datum +unaccent_dict(PG_FUNCTION_ARGS) +{ + text *str; + int strArg; + Oid dictOid; + TSDictionaryCacheEntry *dict; + TSLexeme *res; + + if (PG_NARGS() == 1) + { + /* + * Use the "unaccent" dictionary that is in the same schema that this + * function is in. + */ + Oid procnspid = get_func_namespace(fcinfo->flinfo->fn_oid); + const char *dictname = "unaccent"; + + dictOid = GetSysCacheOid2(TSDICTNAMENSP, Anum_pg_ts_dict_oid, + PointerGetDatum(dictname), + ObjectIdGetDatum(procnspid)); + if (!OidIsValid(dictOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("text search dictionary \"%s.%s\" does not exist", + get_namespace_name(procnspid), dictname))); + strArg = 0; + } + else + { + dictOid = PG_GETARG_OID(0); + strArg = 1; + } + str = PG_GETARG_TEXT_PP(strArg); + + dict = lookup_ts_dictionary_cache(dictOid); + + res = (TSLexeme *) DatumGetPointer(FunctionCall4(&(dict->lexize), + PointerGetDatum(dict->dictData), + PointerGetDatum(VARDATA_ANY(str)), + Int32GetDatum(VARSIZE_ANY_EXHDR(str)), + PointerGetDatum(NULL))); + + PG_FREE_IF_COPY(str, strArg); + + if (res == NULL) + { + PG_RETURN_TEXT_P(PG_GETARG_TEXT_P_COPY(strArg)); + } + else if (res->lexeme == NULL) + { + pfree(res); + PG_RETURN_TEXT_P(PG_GETARG_TEXT_P_COPY(strArg)); + } + else + { + text *txt = cstring_to_text(res->lexeme); + + pfree(res->lexeme); + pfree(res); + + PG_RETURN_TEXT_P(txt); + } +} diff --git a/contrib/unaccent/unaccent.control b/contrib/unaccent/unaccent.control new file mode 100644 index 0000000..649cf68 --- /dev/null +++ b/contrib/unaccent/unaccent.control @@ -0,0 +1,6 @@ +# unaccent extension +comment = 'text search dictionary that removes accents' +default_version = '1.1' +module_pathname = '$libdir/unaccent' +relocatable = true +trusted = true diff --git a/contrib/unaccent/unaccent.rules b/contrib/unaccent/unaccent.rules new file mode 100644 index 0000000..3030166 --- /dev/null +++ b/contrib/unaccent/unaccent.rules @@ -0,0 +1,1650 @@ +¡ ! +© (C) +« << +­ - +® (R) +± +/- +» >> +¼ 1/4 +½ 1/2 +¾ 3/4 +¿ ? +À A +Á A + A +à A +Ä A +Å A +Æ AE +Ç C +È E +É E +Ê E +Ë E +Ì I +Í I +Î I +Ï I +Ð D +Ñ N +Ò O +Ó O +Ô O +Õ O +Ö O +× * +Ø O +Ù U +Ú U +Û U +Ü U +Ý Y +Þ TH +ß ss +à a +á a +â a +ã a +ä a +å a +æ ae +ç c +è e +é e +ê e +ë e +ì i +í i +î i +ï i +ð d +ñ n +ò o +ó o +ô o +õ o +ö o +÷ / +ø o +ù u +ú u +û u +ü u +ý y +þ th +ÿ y +Ā A +ā a +Ă A +ă a +Ą A +ą a +Ć C +ć c +Ĉ C +ĉ c +Ċ C +ċ c +Č C +č c +Ď D +ď d +Đ D +đ d +Ē E +ē e +Ĕ E +ĕ e +Ė E +ė e +Ę E +ę e +Ě E +ě e +Ĝ G +ĝ g +Ğ G +ğ g +Ġ G +ġ g +Ģ G +ģ g +Ĥ H +ĥ h +Ħ H +ħ h +Ĩ I +ĩ i +Ī I +ī i +Ĭ I +ĭ i +Į I +į i +İ I +ı i +IJ IJ +ij ij +Ĵ J +ĵ j +Ķ K +ķ k +ĸ q +Ĺ L +ĺ l +Ļ L +ļ l +Ľ L +ľ l +Ŀ L +ŀ l +Ł L +ł l +Ń N +ń n +Ņ N +ņ n +Ň N +ň n +ʼn 'n +Ŋ N +ŋ n +Ō O +ō o +Ŏ O +ŏ o +Ő O +ő o +Œ OE +œ oe +Ŕ R +ŕ r +Ŗ R +ŗ r +Ř R +ř r +Ś S +ś s +Ŝ S +ŝ s +Ş S +ş s +Š S +š s +Ţ T +ţ t +Ť T +ť t +Ŧ T +ŧ t +Ũ U +ũ u +Ū U +ū u +Ŭ U +ŭ u +Ů U +ů u +Ű U +ű u +Ų U +ų u +Ŵ W +ŵ w +Ŷ Y +ŷ y +Ÿ Y +Ź Z +ź z +Ż Z +ż z +Ž Z +ž z +ſ s +ƀ b +Ɓ B +Ƃ B +ƃ b +Ƈ C +ƈ c +Ɖ D +Ɗ D +Ƌ D +ƌ d +Ɛ E +Ƒ F +ƒ f +Ɠ G +ƕ hv +Ɩ I +Ɨ I +Ƙ K +ƙ k +ƚ l +Ɲ N +ƞ n +Ơ O +ơ o +Ƣ OI +ƣ oi +Ƥ P +ƥ p +ƫ t +Ƭ T +ƭ t +Ʈ T +Ư U +ư u +Ʋ V +Ƴ Y +ƴ y +Ƶ Z +ƶ z +DŽ DZ +Dž Dz +dž dz +LJ LJ +Lj Lj +lj lj +NJ NJ +Nj Nj +nj nj +Ǎ A +ǎ a +Ǐ I +ǐ i +Ǒ O +ǒ o +Ǔ U +ǔ u +Ǖ U +ǖ u +Ǘ U +ǘ u +Ǚ U +ǚ u +Ǜ U +ǜ u +Ǟ A +ǟ a +Ǡ A +ǡ a +Ǥ G +ǥ g +Ǧ G +ǧ g +Ǩ K +ǩ k +Ǫ O +ǫ o +Ǭ O +ǭ o +ǰ j +DZ DZ +Dz Dz +dz dz +Ǵ G +ǵ g +Ǹ N +ǹ n +Ǻ A +ǻ a +Ȁ A +ȁ a +Ȃ A +ȃ a +Ȅ E +ȅ e +Ȇ E +ȇ e +Ȉ I +ȉ i +Ȋ I +ȋ i +Ȍ O +ȍ o +Ȏ O +ȏ o +Ȑ R +ȑ r +Ȓ R +ȓ r +Ȕ U +ȕ u +Ȗ U +ȗ u +Ș S +ș s +Ț T +ț t +Ȟ H +ȟ h +ȡ d +Ȥ Z +ȥ z +Ȧ A +ȧ a +Ȩ E +ȩ e +Ȫ O +ȫ o +Ȭ O +ȭ o +Ȯ O +ȯ o +Ȱ O +ȱ o +Ȳ Y +ȳ y +ȴ l +ȵ n +ȶ t +ȷ j +ȸ db +ȹ qp +Ⱥ A +Ȼ C +ȼ c +Ƚ L +Ⱦ T +ȿ s +ɀ z +Ƀ B +Ʉ U +Ɇ E +ɇ e +Ɉ J +ɉ j +Ɍ R +ɍ r +Ɏ Y +ɏ y +ɓ b +ɕ c +ɖ d +ɗ d +ɛ e +ɟ j +ɠ g +ɡ g +ɢ G +ɦ h +ɧ h +ɨ i +ɪ I +ɫ l +ɬ l +ɭ l +ɱ m +ɲ n +ɳ n +ɴ N +ɶ OE +ɼ r +ɽ r +ɾ r +ʀ R +ʂ s +ʈ t +ʉ u +ʋ v +ʏ Y +ʐ z +ʑ z +ʙ B +ʛ G +ʜ H +ʝ j +ʟ L +ʠ q +ʣ dz +ʥ dz +ʦ ts +ʪ ls +ʫ lz +ʹ ' +ʺ " +ʻ ' +ʼ ' +ʽ ' +˂ < +˃ > +˄ ^ +ˆ ^ +ˈ ' +ˋ ` +ː : +˖ + +˗ - +˜ ~ +̀ +́ +̂ +̃ +̄ +̅ +̆ +̇ +̈ +̉ +̊ +̋ +̌ +̍ +̎ +̏ +̐ +̑ +̒ +̓ +̔ +̕ +̖ +̗ +̘ +̙ +̚ +̛ +̜ +̝ +̞ +̟ +̠ +̡ +̢ +̣ +̤ +̥ +̦ +̧ +̨ +̩ +̪ +̫ +̬ +̭ +̮ +̯ +̰ +̱ +̲ +̳ +̴ +̵ +̶ +̷ +̸ +̹ +̺ +̻ +̼ +̽ +̾ +̿ +̀ +́ +͂ +̓ +̈́ +ͅ +͆ +͇ +͈ +͉ +͊ +͋ +͌ +͍ +͎ +͏ +͐ +͑ +͒ +͓ +͔ +͕ +͖ +͗ +͘ +͙ +͚ +͛ +͜ +͝ +͞ +͟ +͠ +͡ +͢ +Ά Α +Έ Ε +Ή Η +Ί Ι +Ό Ο +Ύ Υ +Ώ Ω +ΐ ι +Ϊ Ι +Ϋ Υ +ά α +έ ε +ή η +ί ι +ΰ υ +ϊ ι +ϋ υ +ό ο +ύ υ +ώ ω +Ё Е +ё е +ᴀ A +ᴁ AE +ᴃ B +ᴄ C +ᴅ D +ᴆ D +ᴇ E +ᴊ J +ᴋ K +ᴌ L +ᴍ M +ᴏ O +ᴘ P +ᴛ T +ᴜ U +ᴠ V +ᴡ W +ᴢ Z +ᵫ ue +ᵬ b +ᵭ d +ᵮ f +ᵯ m +ᵰ n +ᵱ p +ᵲ r +ᵳ r +ᵴ s +ᵵ t +ᵶ z +ᵺ th +ᵻ I +ᵽ p +ᵾ U +ᶀ b +ᶁ d +ᶂ f +ᶃ g +ᶄ k +ᶅ l +ᶆ m +ᶇ n +ᶈ p +ᶉ r +ᶊ s +ᶌ v +ᶍ x +ᶎ z +ᶏ a +ᶑ d +ᶒ e +ᶓ e +ᶖ i +ᶙ u +Ḁ A +ḁ a +Ḃ B +ḃ b +Ḅ B +ḅ b +Ḇ B +ḇ b +Ḉ C +ḉ c +Ḋ D +ḋ d +Ḍ D +ḍ d +Ḏ D +ḏ d +Ḑ D +ḑ d +Ḓ D +ḓ d +Ḕ E +ḕ e +Ḗ E +ḗ e +Ḙ E +ḙ e +Ḛ E +ḛ e +Ḝ E +ḝ e +Ḟ F +ḟ f +Ḡ G +ḡ g +Ḣ H +ḣ h +Ḥ H +ḥ h +Ḧ H +ḧ h +Ḩ H +ḩ h +Ḫ H +ḫ h +Ḭ I +ḭ i +Ḯ I +ḯ i +Ḱ K +ḱ k +Ḳ K +ḳ k +Ḵ K +ḵ k +Ḷ L +ḷ l +Ḹ L +ḹ l +Ḻ L +ḻ l +Ḽ L +ḽ l +Ḿ M +ḿ m +Ṁ M +ṁ m +Ṃ M +ṃ m +Ṅ N +ṅ n +Ṇ N +ṇ n +Ṉ N +ṉ n +Ṋ N +ṋ n +Ṍ O +ṍ o +Ṏ O +ṏ o +Ṑ O +ṑ o +Ṓ O +ṓ o +Ṕ P +ṕ p +Ṗ P +ṗ p +Ṙ R +ṙ r +Ṛ R +ṛ r +Ṝ R +ṝ r +Ṟ R +ṟ r +Ṡ S +ṡ s +Ṣ S +ṣ s +Ṥ S +ṥ s +Ṧ S +ṧ s +Ṩ S +ṩ s +Ṫ T +ṫ t +Ṭ T +ṭ t +Ṯ T +ṯ t +Ṱ T +ṱ t +Ṳ U +ṳ u +Ṵ U +ṵ u +Ṷ U +ṷ u +Ṹ U +ṹ u +Ṻ U +ṻ u +Ṽ V +ṽ v +Ṿ V +ṿ v +Ẁ W +ẁ w +Ẃ W +ẃ w +Ẅ W +ẅ w +Ẇ W +ẇ w +Ẉ W +ẉ w +Ẋ X +ẋ x +Ẍ X +ẍ x +Ẏ Y +ẏ y +Ẑ Z +ẑ z +Ẓ Z +ẓ z +Ẕ Z +ẕ z +ẖ h +ẗ t +ẘ w +ẙ y +ẚ a +ẜ s +ẝ s +ẞ SS +Ạ A +ạ a +Ả A +ả a +Ấ A +ấ a +Ầ A +ầ a +Ẩ A +ẩ a +Ẫ A +ẫ a +Ậ A +ậ a +Ắ A +ắ a +Ằ A +ằ a +Ẳ A +ẳ a +Ẵ A +ẵ a +Ặ A +ặ a +Ẹ E +ẹ e +Ẻ E +ẻ e +Ẽ E +ẽ e +Ế E +ế e +Ề E +ề e +Ể E +ể e +Ễ E +ễ e +Ệ E +ệ e +Ỉ I +ỉ i +Ị I +ị i +Ọ O +ọ o +Ỏ O +ỏ o +Ố O +ố o +Ồ O +ồ o +Ổ O +ổ o +Ỗ O +ỗ o +Ộ O +ộ o +Ớ O +ớ o +Ờ O +ờ o +Ở O +ở o +Ỡ O +ỡ o +Ợ O +ợ o +Ụ U +ụ u +Ủ U +ủ u +Ứ U +ứ u +Ừ U +ừ u +Ử U +ử u +Ữ U +ữ u +Ự U +ự u +Ỳ Y +ỳ y +Ỵ Y +ỵ y +Ỷ Y +ỷ y +Ỹ Y +ỹ y +Ỻ LL +ỻ ll +Ỽ V +ỽ v +Ỿ Y +ỿ y +ἀ α +ἁ α +ἂ α +ἃ α +ἄ α +ἅ α +ἆ α +ἇ α +Ἀ Α +Ἁ Α +Ἂ Α +Ἃ Α +Ἄ Α +Ἅ Α +Ἆ Α +Ἇ Α +ἐ ε +ἑ ε +ἒ ε +ἓ ε +ἔ ε +ἕ ε +Ἐ Ε +Ἑ Ε +Ἒ Ε +Ἓ Ε +Ἔ Ε +Ἕ Ε +ἠ η +ἡ η +ἢ η +ἣ η +ἤ η +ἥ η +ἦ η +ἧ η +Ἠ Η +Ἡ Η +Ἢ Η +Ἣ Η +Ἤ Η +Ἥ Η +Ἦ Η +Ἧ Η +ἰ ι +ἱ ι +ἲ ι +ἳ ι +ἴ ι +ἵ ι +ἶ ι +ἷ ι +Ἰ Ι +Ἱ Ι +Ἲ Ι +Ἳ Ι +Ἴ Ι +Ἵ Ι +Ἶ Ι +Ἷ Ι +ὀ ο +ὁ ο +ὂ ο +ὃ ο +ὄ ο +ὅ ο +Ὀ Ο +Ὁ Ο +Ὂ Ο +Ὃ Ο +Ὄ Ο +Ὅ Ο +ὐ υ +ὑ υ +ὒ υ +ὓ υ +ὔ υ +ὕ υ +ὖ υ +ὗ υ +Ὑ Υ +Ὓ Υ +Ὕ Υ +Ὗ Υ +ὠ ω +ὡ ω +ὢ ω +ὣ ω +ὤ ω +ὥ ω +ὦ ω +ὧ ω +Ὠ Ω +Ὡ Ω +Ὢ Ω +Ὣ Ω +Ὤ Ω +Ὥ Ω +Ὦ Ω +Ὧ Ω +ὰ α +ὲ ε +ὴ η +ὶ ι +ὸ ο +ὺ υ +ὼ ω +ᾀ α +ᾁ α +ᾂ α +ᾃ α +ᾄ α +ᾅ α +ᾆ α +ᾇ α +ᾈ Α +ᾉ Α +ᾊ Α +ᾋ Α +ᾌ Α +ᾍ Α +ᾎ Α +ᾏ Α +ᾐ η +ᾑ η +ᾒ η +ᾓ η +ᾔ η +ᾕ η +ᾖ η +ᾗ η +ᾘ Η +ᾙ Η +ᾚ Η +ᾛ Η +ᾜ Η +ᾝ Η +ᾞ Η +ᾟ Η +ᾠ ω +ᾡ ω +ᾢ ω +ᾣ ω +ᾤ ω +ᾥ ω +ᾦ ω +ᾧ ω +ᾨ Ω +ᾩ Ω +ᾪ Ω +ᾫ Ω +ᾬ Ω +ᾭ Ω +ᾮ Ω +ᾯ Ω +ᾰ α +ᾱ α +ᾲ α +ᾳ α +ᾴ α +ᾶ α +ᾷ α +Ᾰ Α +Ᾱ Α +Ὰ Α +ᾼ Α +ῂ η +ῃ η +ῄ η +ῆ η +ῇ η +Ὲ Ε +Ὴ Η +ῌ Η +ῐ ι +ῑ ι +ῒ ι +ῖ ι +ῗ ι +Ῐ Ι +Ῑ Ι +Ὶ Ι +ῠ υ +ῡ υ +ῢ υ +ῤ ρ +ῥ ρ +ῦ υ +ῧ υ +Ῠ Υ +Ῡ Υ +Ὺ Υ +Ῥ Ρ +ῲ ω +ῳ ω +ῴ ω +ῶ ω +ῷ ω +Ὸ Ο +Ὼ Ω +ῼ Ω +‐ - +‑ - +‒ - +– - +— - +― - +‖ || +‘ ' +’ ' +‚ , +‛ ' +“ " +” " +„ ,, +‟ " +․ . +‥ .. +… ... +′ ' +″ " +‹ < +› > +‼ !! +⁄ / +⁅ [ +⁆ ] +⁇ ?? +⁈ ?! +⁉ !? +⁎ * +₠ CE +₢ Cr +₣ Fr. +₤ L. +₧ Pts +₹ Rs +₺ TL +⃝ +⃞ +⃟ +⃠ +⃢ +⃣ +⃤ +℀ a/c +℁ a/s +ℂ C +℃ °C +℅ c/o +℆ c/u +℉ °F +ℊ g +ℋ H +ℌ x +ℍ H +ℎ h +ℐ I +ℑ I +ℒ L +ℓ l +ℕ N +№ No +℗ (P) +℘ P +ℙ P +ℚ Q +ℛ R +ℜ R +ℝ R +℞ Rx +℡ TEL +ℤ Z +ℨ Z +ℬ B +ℭ C +ℯ e +ℰ E +ℱ F +ℳ M +ℴ o +ℹ i +℻ FAX +ⅅ D +ⅆ d +ⅇ e +ⅈ i +ⅉ j +⅐ 1/7 +⅑ 1/9 +⅒ 1/10 +⅓ 1/3 +⅔ 2/3 +⅕ 1/5 +⅖ 2/5 +⅗ 3/5 +⅘ 4/5 +⅙ 1/6 +⅚ 5/6 +⅛ 1/8 +⅜ 3/8 +⅝ 5/8 +⅞ 7/8 +⅟ 1/ +Ⅰ I +Ⅱ II +Ⅲ III +Ⅳ IV +Ⅴ V +Ⅵ VI +Ⅶ VII +Ⅷ VIII +Ⅸ IX +Ⅹ X +Ⅺ XI +Ⅻ XII +Ⅼ L +Ⅽ C +Ⅾ D +Ⅿ M +ⅰ i +ⅱ ii +ⅲ iii +ⅳ iv +ⅴ v +ⅵ vi +ⅶ vii +ⅷ viii +ⅸ ix +ⅹ x +ⅺ xi +ⅻ xii +ⅼ l +ⅽ c +ⅾ d +ⅿ m +↉ 0/3 +− - +∕ / +∖ \ +∣ | +∥ || +≪ << +≫ >> +⑴ (1) +⑵ (2) +⑶ (3) +⑷ (4) +⑸ (5) +⑹ (6) +⑺ (7) +⑻ (8) +⑼ (9) +⑽ (10) +⑾ (11) +⑿ (12) +⒀ (13) +⒁ (14) +⒂ (15) +⒃ (16) +⒄ (17) +⒅ (18) +⒆ (19) +⒇ (20) +⒈ 1. +⒉ 2. +⒊ 3. +⒋ 4. +⒌ 5. +⒍ 6. +⒎ 7. +⒏ 8. +⒐ 9. +⒑ 10. +⒒ 11. +⒓ 12. +⒔ 13. +⒕ 14. +⒖ 15. +⒗ 16. +⒘ 17. +⒙ 18. +⒚ 19. +⒛ 20. +⒜ (a) +⒝ (b) +⒞ (c) +⒟ (d) +⒠ (e) +⒡ (f) +⒢ (g) +⒣ (h) +⒤ (i) +⒥ (j) +⒦ (k) +⒧ (l) +⒨ (m) +⒩ (n) +⒪ (o) +⒫ (p) +⒬ (q) +⒭ (r) +⒮ (s) +⒯ (t) +⒰ (u) +⒱ (v) +⒲ (w) +⒳ (x) +⒴ (y) +⒵ (z) +⦅ (( +⦆ )) +⩴ ::= +⩵ == +⩶ === +Ⱡ L +ⱡ l +Ɫ L +Ᵽ P +Ɽ R +ⱥ a +ⱦ t +Ⱨ H +ⱨ h +Ⱪ K +ⱪ k +Ⱬ Z +ⱬ z +Ɱ M +ⱱ v +Ⱳ W +ⱳ w +ⱴ v +ⱸ e +ⱺ o +Ȿ S +Ɀ Z +、 , +。 . +〇 0 +〈 < +〉 > +《 << +》 >> +〔 [ +〕 ] +〘 [ +〙 ] +〚 [ +〛 ] +〝 " +〞 " +㍱ hPa +㍲ da +㍳ AU +㍴ bar +㍵ oV +㍶ pc +㍷ dm +㍺ IU +㎀ pA +㎁ nA +㎃ mA +㎄ kA +㎅ KB +㎆ MB +㎇ GB +㎈ cal +㎉ kcal +㎊ pF +㎋ nF +㎎ mg +㎏ kg +㎐ Hz +㎑ kHz +㎒ MHz +㎓ GHz +㎔ THz +㎙ fm +㎚ nm +㎜ mm +㎝ cm +㎞ km +㎧ m/s +㎩ Pa +㎪ kPa +㎫ MPa +㎬ GPa +㎭ rad +㎮ rad/s +㎰ ps +㎱ ns +㎳ ms +㎴ pV +㎵ nV +㎷ mV +㎸ kV +㎹ MV +㎺ pW +㎻ nW +㎽ mW +㎾ kW +㎿ MW +㏂ a.m. +㏃ Bq +㏄ cc +㏅ cd +㏆ C/kg +㏇ Co. +㏈ dB +㏉ Gy +㏊ ha +㏋ HP +㏌ in +㏍ KK +㏎ KM +㏏ kt +㏐ lm +㏑ ln +㏒ log +㏓ lx +㏔ mb +㏕ mil +㏖ mol +㏗ pH +㏘ p.m. +㏙ PPM +㏚ PR +㏛ sr +㏜ Sv +㏝ Wb +㏞ V/m +㏟ A/m +ꜰ F +ꜱ S +Ꜳ AA +ꜳ aa +Ꜵ AO +ꜵ ao +Ꜷ AU +ꜷ au +Ꜹ AV +ꜹ av +Ꜻ AV +ꜻ av +Ꜽ AY +ꜽ ay +Ꝁ K +ꝁ k +Ꝃ K +ꝃ k +Ꝅ K +ꝅ k +Ꝇ L +ꝇ l +Ꝉ L +ꝉ l +Ꝋ O +ꝋ o +Ꝍ O +ꝍ o +Ꝏ OO +ꝏ oo +Ꝑ P +ꝑ p +Ꝓ P +ꝓ p +Ꝕ P +ꝕ p +Ꝗ Q +ꝗ q +Ꝙ Q +ꝙ q +Ꝟ V +ꝟ v +Ꝡ VY +ꝡ vy +Ꝥ TH +ꝥ th +Ꝧ TH +ꝧ th +ꝱ d +ꝲ l +ꝳ m +ꝴ n +ꝵ r +ꝶ R +ꝷ t +Ꝺ D +ꝺ d +Ꝼ F +ꝼ f +Ꞇ T +ꞇ t +Ꞑ N +ꞑ n +Ꞓ C +ꞓ c +Ꞡ G +ꞡ g +Ꞣ K +ꞣ k +Ꞥ N +ꞥ n +Ꞧ R +ꞧ r +Ꞩ S +ꞩ s +Ɦ H +ff ff +fi fi +fl fl +ffi ffi +ffl ffl +ſt st +st st +︐ , +︑ , +︒ . +︓ : +︔ ; +︕ ! +︖ ? +︙ ... +︰ .. +︱ - +︲ - +︵ ( +︶ ) +︷ { +︸ } +︹ [ +︺ ] +︽ << +︾ >> +︿ < +﹀ > +﹇ [ +﹈ ] +﹐ , +﹑ , +﹒ . +﹔ ; +﹕ : +﹖ ? +﹗ ! +﹘ - +﹙ ( +﹚ ) +﹛ { +﹜ } +﹝ [ +﹞ ] +﹟ # +﹠ & +﹡ * +﹢ + +﹣ - +﹤ < +﹥ > +﹦ = +﹨ \ +﹩ $ +﹪ % +﹫ @ +! ! +" " +# # +$ $ +% % +& & +' ' +( ( +) ) +* * ++ + +, , +- - +. . +/ / +0 0 +1 1 +2 2 +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 +9 9 +: : +; ; +< < += = +> > +? ? +@ @ +A A +B B +C C +D D +E E +F F +G G +H H +I I +J J +K K +L L +M M +N N +O O +P P +Q Q +R R +S S +T T +U U +V V +W W +X X +Y Y +Z Z +[ [ +\ \ +] ] +^ ^ +_ _ +` ` +a a +b b +c c +d d +e e +f f +g g +h h +i i +j j +k k +l l +m m +n n +o o +p p +q q +r r +s s +t t +u u +v v +w w +x x +y y +z z +{ { +| | +} } +~ ~ +⦅ (( +⦆ )) +。 . +、 , +← <- +→ -> +🄀 0. +🄁 0, +🄂 1, +🄃 2, +🄄 3, +🄅 4, +🄆 5, +🄇 6, +🄈 7, +🄉 8, +🄊 9, +🄐 (A) +🄑 (B) +🄒 (C) +🄓 (D) +🄔 (E) +🄕 (F) +🄖 (G) +🄗 (H) +🄘 (I) +🄙 (J) +🄚 (K) +🄛 (L) +🄜 (M) +🄝 (N) +🄞 (O) +🄟 (P) +🄠 (Q) +🄡 (R) +🄢 (S) +🄣 (T) +🄤 (U) +🄥 (V) +🄦 (W) +🄧 (X) +🄨 (Y) +🄩 (Z) diff --git a/contrib/uuid-ossp/.gitignore b/contrib/uuid-ossp/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/uuid-ossp/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/uuid-ossp/Makefile b/contrib/uuid-ossp/Makefile new file mode 100644 index 0000000..e126ce1 --- /dev/null +++ b/contrib/uuid-ossp/Makefile @@ -0,0 +1,25 @@ +# contrib/uuid-ossp/Makefile + +MODULE_big = uuid-ossp +OBJS = \ + $(WIN32RES) \ + uuid-ossp.o + +EXTENSION = uuid-ossp +DATA = uuid-ossp--1.1.sql uuid-ossp--1.0--1.1.sql +PGFILEDESC = "uuid-ossp - UUID generation" + +REGRESS = uuid_ossp + +SHLIB_LINK += $(UUID_LIBS) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/uuid-ossp +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/uuid-ossp/expected/uuid_ossp.out b/contrib/uuid-ossp/expected/uuid_ossp.out new file mode 100644 index 0000000..409c885 --- /dev/null +++ b/contrib/uuid-ossp/expected/uuid_ossp.out @@ -0,0 +1,139 @@ +CREATE EXTENSION "uuid-ossp"; +SELECT uuid_nil(); + uuid_nil +-------------------------------------- + 00000000-0000-0000-0000-000000000000 +(1 row) + +SELECT uuid_ns_dns(); + uuid_ns_dns +-------------------------------------- + 6ba7b810-9dad-11d1-80b4-00c04fd430c8 +(1 row) + +SELECT uuid_ns_url(); + uuid_ns_url +-------------------------------------- + 6ba7b811-9dad-11d1-80b4-00c04fd430c8 +(1 row) + +SELECT uuid_ns_oid(); + uuid_ns_oid +-------------------------------------- + 6ba7b812-9dad-11d1-80b4-00c04fd430c8 +(1 row) + +SELECT uuid_ns_x500(); + uuid_ns_x500 +-------------------------------------- + 6ba7b814-9dad-11d1-80b4-00c04fd430c8 +(1 row) + +-- some quick and dirty field extraction functions +-- this is actually timestamp concatenated with clock sequence, per RFC 4122 +CREATE FUNCTION uuid_timestamp_bits(uuid) RETURNS varbit AS +$$ SELECT ('x' || substr($1::text, 15, 4) || substr($1::text, 10, 4) || + substr($1::text, 1, 8) || substr($1::text, 20, 4))::bit(80) + & x'0FFFFFFFFFFFFFFF3FFF' $$ +LANGUAGE SQL STRICT IMMUTABLE; +CREATE FUNCTION uuid_version_bits(uuid) RETURNS varbit AS +$$ SELECT ('x' || substr($1::text, 15, 2))::bit(8) & '11110000' $$ +LANGUAGE SQL STRICT IMMUTABLE; +CREATE FUNCTION uuid_reserved_bits(uuid) RETURNS varbit AS +$$ SELECT ('x' || substr($1::text, 20, 2))::bit(8) & '11000000' $$ +LANGUAGE SQL STRICT IMMUTABLE; +CREATE FUNCTION uuid_multicast_bit(uuid) RETURNS bool AS +$$ SELECT (('x' || substr($1::text, 25, 2))::bit(8) & '00000001') != '00000000' $$ +LANGUAGE SQL STRICT IMMUTABLE; +CREATE FUNCTION uuid_local_admin_bit(uuid) RETURNS bool AS +$$ SELECT (('x' || substr($1::text, 25, 2))::bit(8) & '00000010') != '00000000' $$ +LANGUAGE SQL STRICT IMMUTABLE; +CREATE FUNCTION uuid_node(uuid) RETURNS text AS +$$ SELECT substr($1::text, 25) $$ +LANGUAGE SQL STRICT IMMUTABLE; +-- Ideally, the multicast bit would never be set in V1 output, but the +-- UUID library may fall back to MC if it can't get the system MAC address. +-- Also, the local-admin bit might be set (if so, we're probably inside a VM). +-- So we can't test either bit here. +SELECT uuid_version_bits(uuid_generate_v1()), + uuid_reserved_bits(uuid_generate_v1()); + uuid_version_bits | uuid_reserved_bits +-------------------+-------------------- + 00010000 | 10000000 +(1 row) + +-- Although RFC 4122 only requires the multicast bit to be set in V1MC style +-- UUIDs, our implementation always sets the local-admin bit as well. +SELECT uuid_version_bits(uuid_generate_v1mc()), + uuid_reserved_bits(uuid_generate_v1mc()), + uuid_multicast_bit(uuid_generate_v1mc()), + uuid_local_admin_bit(uuid_generate_v1mc()); + uuid_version_bits | uuid_reserved_bits | uuid_multicast_bit | uuid_local_admin_bit +-------------------+--------------------+--------------------+---------------------- + 00010000 | 10000000 | t | t +(1 row) + +-- timestamp+clock sequence should be monotonic increasing in v1 +SELECT uuid_timestamp_bits(uuid_generate_v1()) < uuid_timestamp_bits(uuid_generate_v1()); + ?column? +---------- + t +(1 row) + +SELECT uuid_timestamp_bits(uuid_generate_v1mc()) < uuid_timestamp_bits(uuid_generate_v1mc()); + ?column? +---------- + t +(1 row) + +-- Ideally, the node value is stable in V1 addresses, but OSSP UUID +-- falls back to V1MC behavior if it can't get the system MAC address. +SELECT CASE WHEN uuid_multicast_bit(uuid_generate_v1()) AND + uuid_local_admin_bit(uuid_generate_v1()) THEN + true -- punt, no test + ELSE + uuid_node(uuid_generate_v1()) = uuid_node(uuid_generate_v1()) + END; + case +------ + t +(1 row) + +-- In any case, V1MC node addresses should be random. +SELECT uuid_node(uuid_generate_v1()) <> uuid_node(uuid_generate_v1mc()); + ?column? +---------- + t +(1 row) + +SELECT uuid_node(uuid_generate_v1mc()) <> uuid_node(uuid_generate_v1mc()); + ?column? +---------- + t +(1 row) + +SELECT uuid_generate_v3(uuid_ns_dns(), 'www.widgets.com'); + uuid_generate_v3 +-------------------------------------- + 3d813cbb-47fb-32ba-91df-831e1593ac29 +(1 row) + +SELECT uuid_generate_v5(uuid_ns_dns(), 'www.widgets.com'); + uuid_generate_v5 +-------------------------------------- + 21f7f8de-8051-5b89-8680-0195ef798b6a +(1 row) + +SELECT uuid_version_bits(uuid_generate_v4()), + uuid_reserved_bits(uuid_generate_v4()); + uuid_version_bits | uuid_reserved_bits +-------------------+-------------------- + 01000000 | 10000000 +(1 row) + +SELECT uuid_generate_v4() <> uuid_generate_v4(); + ?column? +---------- + t +(1 row) + diff --git a/contrib/uuid-ossp/meson.build b/contrib/uuid-ossp/meson.build new file mode 100644 index 0000000..b9fe603 --- /dev/null +++ b/contrib/uuid-ossp/meson.build @@ -0,0 +1,41 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not uuid.found() + subdir_done() +endif + +uuid_ossp_sources = files( + 'uuid-ossp.c', +) + +if host_system == 'windows' + uuid_ossp_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'uuid-ossp', + '--FILEDESC', 'uuid-ossp - UUID generation',]) +endif + +uuid_ossp = shared_module('uuid-ossp', + uuid_ossp_sources, + kwargs: contrib_mod_args + { + 'dependencies': [uuid, contrib_mod_args['dependencies']], + }, +) +contrib_targets += uuid_ossp + +install_data( + 'uuid-ossp--1.0--1.1.sql', + 'uuid-ossp--1.1.sql', + 'uuid-ossp.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'uuid-ossp', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'uuid_ossp', + ], + }, +} diff --git a/contrib/uuid-ossp/sql/uuid_ossp.sql b/contrib/uuid-ossp/sql/uuid_ossp.sql new file mode 100644 index 0000000..b4237df --- /dev/null +++ b/contrib/uuid-ossp/sql/uuid_ossp.sql @@ -0,0 +1,75 @@ +CREATE EXTENSION "uuid-ossp"; + +SELECT uuid_nil(); +SELECT uuid_ns_dns(); +SELECT uuid_ns_url(); +SELECT uuid_ns_oid(); +SELECT uuid_ns_x500(); + +-- some quick and dirty field extraction functions + +-- this is actually timestamp concatenated with clock sequence, per RFC 4122 +CREATE FUNCTION uuid_timestamp_bits(uuid) RETURNS varbit AS +$$ SELECT ('x' || substr($1::text, 15, 4) || substr($1::text, 10, 4) || + substr($1::text, 1, 8) || substr($1::text, 20, 4))::bit(80) + & x'0FFFFFFFFFFFFFFF3FFF' $$ +LANGUAGE SQL STRICT IMMUTABLE; + +CREATE FUNCTION uuid_version_bits(uuid) RETURNS varbit AS +$$ SELECT ('x' || substr($1::text, 15, 2))::bit(8) & '11110000' $$ +LANGUAGE SQL STRICT IMMUTABLE; + +CREATE FUNCTION uuid_reserved_bits(uuid) RETURNS varbit AS +$$ SELECT ('x' || substr($1::text, 20, 2))::bit(8) & '11000000' $$ +LANGUAGE SQL STRICT IMMUTABLE; + +CREATE FUNCTION uuid_multicast_bit(uuid) RETURNS bool AS +$$ SELECT (('x' || substr($1::text, 25, 2))::bit(8) & '00000001') != '00000000' $$ +LANGUAGE SQL STRICT IMMUTABLE; + +CREATE FUNCTION uuid_local_admin_bit(uuid) RETURNS bool AS +$$ SELECT (('x' || substr($1::text, 25, 2))::bit(8) & '00000010') != '00000000' $$ +LANGUAGE SQL STRICT IMMUTABLE; + +CREATE FUNCTION uuid_node(uuid) RETURNS text AS +$$ SELECT substr($1::text, 25) $$ +LANGUAGE SQL STRICT IMMUTABLE; + +-- Ideally, the multicast bit would never be set in V1 output, but the +-- UUID library may fall back to MC if it can't get the system MAC address. +-- Also, the local-admin bit might be set (if so, we're probably inside a VM). +-- So we can't test either bit here. +SELECT uuid_version_bits(uuid_generate_v1()), + uuid_reserved_bits(uuid_generate_v1()); + +-- Although RFC 4122 only requires the multicast bit to be set in V1MC style +-- UUIDs, our implementation always sets the local-admin bit as well. +SELECT uuid_version_bits(uuid_generate_v1mc()), + uuid_reserved_bits(uuid_generate_v1mc()), + uuid_multicast_bit(uuid_generate_v1mc()), + uuid_local_admin_bit(uuid_generate_v1mc()); + +-- timestamp+clock sequence should be monotonic increasing in v1 +SELECT uuid_timestamp_bits(uuid_generate_v1()) < uuid_timestamp_bits(uuid_generate_v1()); +SELECT uuid_timestamp_bits(uuid_generate_v1mc()) < uuid_timestamp_bits(uuid_generate_v1mc()); + +-- Ideally, the node value is stable in V1 addresses, but OSSP UUID +-- falls back to V1MC behavior if it can't get the system MAC address. +SELECT CASE WHEN uuid_multicast_bit(uuid_generate_v1()) AND + uuid_local_admin_bit(uuid_generate_v1()) THEN + true -- punt, no test + ELSE + uuid_node(uuid_generate_v1()) = uuid_node(uuid_generate_v1()) + END; + +-- In any case, V1MC node addresses should be random. +SELECT uuid_node(uuid_generate_v1()) <> uuid_node(uuid_generate_v1mc()); +SELECT uuid_node(uuid_generate_v1mc()) <> uuid_node(uuid_generate_v1mc()); + +SELECT uuid_generate_v3(uuid_ns_dns(), 'www.widgets.com'); +SELECT uuid_generate_v5(uuid_ns_dns(), 'www.widgets.com'); + +SELECT uuid_version_bits(uuid_generate_v4()), + uuid_reserved_bits(uuid_generate_v4()); + +SELECT uuid_generate_v4() <> uuid_generate_v4(); diff --git a/contrib/uuid-ossp/uuid-ossp--1.0--1.1.sql b/contrib/uuid-ossp/uuid-ossp--1.0--1.1.sql new file mode 100644 index 0000000..d6b82e6 --- /dev/null +++ b/contrib/uuid-ossp/uuid-ossp--1.0--1.1.sql @@ -0,0 +1,15 @@ +/* contrib/uuid-ossp/uuid-ossp--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION uuid-ossp UPDATE TO '1.1'" to load this file. \quit + +ALTER FUNCTION uuid_nil() PARALLEL SAFE; +ALTER FUNCTION uuid_ns_dns() PARALLEL SAFE; +ALTER FUNCTION uuid_ns_url() PARALLEL SAFE; +ALTER FUNCTION uuid_ns_oid() PARALLEL SAFE; +ALTER FUNCTION uuid_ns_x500() PARALLEL SAFE; +ALTER FUNCTION uuid_generate_v1() PARALLEL SAFE; +ALTER FUNCTION uuid_generate_v1mc() PARALLEL SAFE; +ALTER FUNCTION uuid_generate_v3(uuid, text) PARALLEL SAFE; +ALTER FUNCTION uuid_generate_v4() PARALLEL SAFE; +ALTER FUNCTION uuid_generate_v5(uuid, text) PARALLEL SAFE; diff --git a/contrib/uuid-ossp/uuid-ossp--1.1.sql b/contrib/uuid-ossp/uuid-ossp--1.1.sql new file mode 100644 index 0000000..c9cefd7 --- /dev/null +++ b/contrib/uuid-ossp/uuid-ossp--1.1.sql @@ -0,0 +1,54 @@ +/* contrib/uuid-ossp/uuid-ossp--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use '''CREATE EXTENSION "uuid-ossp"''' to load this file. \quit + +CREATE FUNCTION uuid_nil() +RETURNS uuid +AS 'MODULE_PATHNAME', 'uuid_nil' +IMMUTABLE STRICT LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION uuid_ns_dns() +RETURNS uuid +AS 'MODULE_PATHNAME', 'uuid_ns_dns' +IMMUTABLE STRICT LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION uuid_ns_url() +RETURNS uuid +AS 'MODULE_PATHNAME', 'uuid_ns_url' +IMMUTABLE STRICT LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION uuid_ns_oid() +RETURNS uuid +AS 'MODULE_PATHNAME', 'uuid_ns_oid' +IMMUTABLE STRICT LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION uuid_ns_x500() +RETURNS uuid +AS 'MODULE_PATHNAME', 'uuid_ns_x500' +IMMUTABLE STRICT LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION uuid_generate_v1() +RETURNS uuid +AS 'MODULE_PATHNAME', 'uuid_generate_v1' +VOLATILE STRICT LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION uuid_generate_v1mc() +RETURNS uuid +AS 'MODULE_PATHNAME', 'uuid_generate_v1mc' +VOLATILE STRICT LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION uuid_generate_v3(namespace uuid, name text) +RETURNS uuid +AS 'MODULE_PATHNAME', 'uuid_generate_v3' +IMMUTABLE STRICT LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION uuid_generate_v4() +RETURNS uuid +AS 'MODULE_PATHNAME', 'uuid_generate_v4' +VOLATILE STRICT LANGUAGE C PARALLEL SAFE; + +CREATE FUNCTION uuid_generate_v5(namespace uuid, name text) +RETURNS uuid +AS 'MODULE_PATHNAME', 'uuid_generate_v5' +IMMUTABLE STRICT LANGUAGE C PARALLEL SAFE; diff --git a/contrib/uuid-ossp/uuid-ossp.c b/contrib/uuid-ossp/uuid-ossp.c new file mode 100644 index 0000000..6399baf --- /dev/null +++ b/contrib/uuid-ossp/uuid-ossp.c @@ -0,0 +1,552 @@ +/*------------------------------------------------------------------------- + * + * UUID generation functions using the BSD, E2FS or OSSP UUID library + * + * Copyright (c) 2007-2023, PostgreSQL Global Development Group + * + * Portions Copyright (c) 2009 Andrew Gierth + * + * contrib/uuid-ossp/uuid-ossp.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "common/cryptohash.h" +#include "common/sha1.h" +#include "port/pg_bswap.h" +#include "utils/builtins.h" +#include "utils/uuid.h" +#include "varatt.h" + +/* + * It's possible that there's more than one uuid.h header file present. + * We expect configure to set the HAVE_ symbol for only the one we want. + * + * BSD includes a uuid_hash() function that conflicts with the one in + * builtins.h; we #define it out of the way. + */ +#define uuid_hash bsd_uuid_hash + +#if defined(HAVE_UUID_H) +#include +#elif defined(HAVE_OSSP_UUID_H) +#include +#elif defined(HAVE_UUID_UUID_H) +#include +#else +#error "please use configure's --with-uuid switch to select a UUID library" +#endif + +#undef uuid_hash + +/* Check our UUID length against OSSP's; better both be 16 */ +#if defined(HAVE_UUID_OSSP) && (UUID_LEN != UUID_LEN_BIN) +#error UUID length mismatch +#endif + +/* Define some constants like OSSP's, to make the code more readable */ +#ifndef HAVE_UUID_OSSP +#define UUID_MAKE_MC 0 +#define UUID_MAKE_V1 1 +#define UUID_MAKE_V2 2 +#define UUID_MAKE_V3 3 +#define UUID_MAKE_V4 4 +#define UUID_MAKE_V5 5 +#endif + +/* + * A DCE 1.1 compatible source representation of UUIDs, derived from + * the BSD implementation. BSD already has this; OSSP doesn't need it. + */ +#ifdef HAVE_UUID_E2FS +typedef struct +{ + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clock_seq_hi_and_reserved; + uint8_t clock_seq_low; + uint8_t node[6]; +} dce_uuid_t; +#else +#define dce_uuid_t uuid_t +#endif + +/* If not OSSP, we need some endianness-manipulation macros */ +#ifndef HAVE_UUID_OSSP + +#define UUID_TO_NETWORK(uu) \ +do { \ + uu.time_low = pg_hton32(uu.time_low); \ + uu.time_mid = pg_hton16(uu.time_mid); \ + uu.time_hi_and_version = pg_hton16(uu.time_hi_and_version); \ +} while (0) + +#define UUID_TO_LOCAL(uu) \ +do { \ + uu.time_low = pg_ntoh32(uu.time_low); \ + uu.time_mid = pg_ntoh16(uu.time_mid); \ + uu.time_hi_and_version = pg_ntoh16(uu.time_hi_and_version); \ +} while (0) + +#define UUID_V3_OR_V5(uu, v) \ +do { \ + uu.time_hi_and_version &= 0x0FFF; \ + uu.time_hi_and_version |= (v << 12); \ + uu.clock_seq_hi_and_reserved &= 0x3F; \ + uu.clock_seq_hi_and_reserved |= 0x80; \ +} while(0) + +#endif /* !HAVE_UUID_OSSP */ + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(uuid_nil); +PG_FUNCTION_INFO_V1(uuid_ns_dns); +PG_FUNCTION_INFO_V1(uuid_ns_url); +PG_FUNCTION_INFO_V1(uuid_ns_oid); +PG_FUNCTION_INFO_V1(uuid_ns_x500); + +PG_FUNCTION_INFO_V1(uuid_generate_v1); +PG_FUNCTION_INFO_V1(uuid_generate_v1mc); +PG_FUNCTION_INFO_V1(uuid_generate_v3); +PG_FUNCTION_INFO_V1(uuid_generate_v4); +PG_FUNCTION_INFO_V1(uuid_generate_v5); + +#ifdef HAVE_UUID_OSSP + +static void +pguuid_complain(uuid_rc_t rc) +{ + char *err = uuid_error(rc); + + if (err != NULL) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg("OSSP uuid library failure: %s", err))); + else + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg("OSSP uuid library failure: error code %d", rc))); +} + +/* + * We create a uuid_t object just once per session and re-use it for all + * operations in this module. OSSP UUID caches the system MAC address and + * other state in this object. Reusing the object has a number of benefits: + * saving the cycles needed to fetch the system MAC address over and over, + * reducing the amount of entropy we draw from /dev/urandom, and providing a + * positive guarantee that successive generated V1-style UUIDs don't collide. + * (On a machine fast enough to generate multiple UUIDs per microsecond, + * or whatever the system's wall-clock resolution is, we'd otherwise risk + * collisions whenever random initialization of the uuid_t's clock sequence + * value chanced to produce duplicates.) + * + * However: when we're doing V3 or V5 UUID creation, uuid_make needs two + * uuid_t objects, one holding the namespace UUID and one for the result. + * It's unspecified whether it's safe to use the same uuid_t for both cases, + * so let's cache a second uuid_t for use as the namespace holder object. + */ +static uuid_t * +get_cached_uuid_t(int which) +{ + static uuid_t *cached_uuid[2] = {NULL, NULL}; + + if (cached_uuid[which] == NULL) + { + uuid_rc_t rc; + + rc = uuid_create(&cached_uuid[which]); + if (rc != UUID_RC_OK) + { + cached_uuid[which] = NULL; + pguuid_complain(rc); + } + } + return cached_uuid[which]; +} + +static char * +uuid_to_string(const uuid_t *uuid) +{ + char *buf = palloc(UUID_LEN_STR + 1); + void *ptr = buf; + size_t len = UUID_LEN_STR + 1; + uuid_rc_t rc; + + rc = uuid_export(uuid, UUID_FMT_STR, &ptr, &len); + if (rc != UUID_RC_OK) + pguuid_complain(rc); + + return buf; +} + + +static void +string_to_uuid(const char *str, uuid_t *uuid) +{ + uuid_rc_t rc; + + rc = uuid_import(uuid, UUID_FMT_STR, str, UUID_LEN_STR + 1); + if (rc != UUID_RC_OK) + pguuid_complain(rc); +} + + +static Datum +special_uuid_value(const char *name) +{ + uuid_t *uuid = get_cached_uuid_t(0); + char *str; + uuid_rc_t rc; + + rc = uuid_load(uuid, name); + if (rc != UUID_RC_OK) + pguuid_complain(rc); + str = uuid_to_string(uuid); + + return DirectFunctionCall1(uuid_in, CStringGetDatum(str)); +} + +/* len is unused with OSSP, but we want to have the same number of args */ +static Datum +uuid_generate_internal(int mode, const uuid_t *ns, const char *name, int len) +{ + uuid_t *uuid = get_cached_uuid_t(0); + char *str; + uuid_rc_t rc; + + rc = uuid_make(uuid, mode, ns, name); + if (rc != UUID_RC_OK) + pguuid_complain(rc); + str = uuid_to_string(uuid); + + return DirectFunctionCall1(uuid_in, CStringGetDatum(str)); +} + + +static Datum +uuid_generate_v35_internal(int mode, pg_uuid_t *ns, text *name) +{ + uuid_t *ns_uuid = get_cached_uuid_t(1); + + string_to_uuid(DatumGetCString(DirectFunctionCall1(uuid_out, + UUIDPGetDatum(ns))), + ns_uuid); + + return uuid_generate_internal(mode, + ns_uuid, + text_to_cstring(name), + 0); +} + +#else /* !HAVE_UUID_OSSP */ + +static Datum +uuid_generate_internal(int v, unsigned char *ns, const char *ptr, int len) +{ + char strbuf[40]; + + switch (v) + { + case 0: /* constant-value uuids */ + strlcpy(strbuf, ptr, 37); + break; + + case 1: /* time/node-based uuids */ + { +#ifdef HAVE_UUID_E2FS + uuid_t uu; + + uuid_generate_time(uu); + uuid_unparse(uu, strbuf); + + /* + * PTR, if set, replaces the trailing characters of the uuid; + * this is to support v1mc, where a random multicast MAC is + * used instead of the physical one + */ + if (ptr && len <= 36) + strcpy(strbuf + (36 - len), ptr); +#else /* BSD */ + uuid_t uu; + uint32_t status = uuid_s_ok; + char *str = NULL; + + uuid_create(&uu, &status); + + if (status == uuid_s_ok) + { + uuid_to_string(&uu, &str, &status); + if (status == uuid_s_ok) + { + strlcpy(strbuf, str, 37); + + /* + * In recent NetBSD, uuid_create() has started + * producing v4 instead of v1 UUIDs. Check the + * version field and complain if it's not v1. + */ + if (strbuf[14] != '1') + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + /* translator: %c will be a hex digit */ + errmsg("uuid_create() produced a version %c UUID instead of the expected version 1", + strbuf[14]))); + + /* + * PTR, if set, replaces the trailing characters of + * the uuid; this is to support v1mc, where a random + * multicast MAC is used instead of the physical one + */ + if (ptr && len <= 36) + strcpy(strbuf + (36 - len), ptr); + } + free(str); + } + + if (status != uuid_s_ok) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg("uuid library failure: %d", + (int) status))); +#endif + break; + } + + case 3: /* namespace-based MD5 uuids */ + case 5: /* namespace-based SHA1 uuids */ + { + dce_uuid_t uu; +#ifdef HAVE_UUID_BSD + uint32_t status = uuid_s_ok; + char *str = NULL; +#endif + + if (v == 3) + { + pg_cryptohash_ctx *ctx = pg_cryptohash_create(PG_MD5); + + if (pg_cryptohash_init(ctx) < 0) + elog(ERROR, "could not initialize %s context: %s", "MD5", + pg_cryptohash_error(ctx)); + if (pg_cryptohash_update(ctx, ns, sizeof(uu)) < 0 || + pg_cryptohash_update(ctx, (unsigned char *) ptr, len) < 0) + elog(ERROR, "could not update %s context: %s", "MD5", + pg_cryptohash_error(ctx)); + /* we assume sizeof MD5 result is 16, same as UUID size */ + if (pg_cryptohash_final(ctx, (unsigned char *) &uu, + sizeof(uu)) < 0) + elog(ERROR, "could not finalize %s context: %s", "MD5", + pg_cryptohash_error(ctx)); + pg_cryptohash_free(ctx); + } + else + { + pg_cryptohash_ctx *ctx = pg_cryptohash_create(PG_SHA1); + unsigned char sha1result[SHA1_DIGEST_LENGTH]; + + if (pg_cryptohash_init(ctx) < 0) + elog(ERROR, "could not initialize %s context: %s", "SHA1", + pg_cryptohash_error(ctx)); + if (pg_cryptohash_update(ctx, ns, sizeof(uu)) < 0 || + pg_cryptohash_update(ctx, (unsigned char *) ptr, len) < 0) + elog(ERROR, "could not update %s context: %s", "SHA1", + pg_cryptohash_error(ctx)); + if (pg_cryptohash_final(ctx, sha1result, sizeof(sha1result)) < 0) + elog(ERROR, "could not finalize %s context: %s", "SHA1", + pg_cryptohash_error(ctx)); + pg_cryptohash_free(ctx); + + memcpy(&uu, sha1result, sizeof(uu)); + } + + /* the calculated hash is using local order */ + UUID_TO_NETWORK(uu); + UUID_V3_OR_V5(uu, v); + +#ifdef HAVE_UUID_E2FS + /* uuid_unparse expects local order */ + UUID_TO_LOCAL(uu); + uuid_unparse((unsigned char *) &uu, strbuf); +#else /* BSD */ + uuid_to_string(&uu, &str, &status); + + if (status == uuid_s_ok) + strlcpy(strbuf, str, 37); + + free(str); + + if (status != uuid_s_ok) + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg("uuid library failure: %d", + (int) status))); +#endif + break; + } + + case 4: /* random uuid */ + default: + { +#ifdef HAVE_UUID_E2FS + uuid_t uu; + + uuid_generate_random(uu); + uuid_unparse(uu, strbuf); +#else /* BSD */ + snprintf(strbuf, sizeof(strbuf), + "%08lx-%04x-%04x-%04x-%04x%08lx", + (unsigned long) arc4random(), + (unsigned) (arc4random() & 0xffff), + (unsigned) ((arc4random() & 0xfff) | 0x4000), + (unsigned) ((arc4random() & 0x3fff) | 0x8000), + (unsigned) (arc4random() & 0xffff), + (unsigned long) arc4random()); +#endif + break; + } + } + + return DirectFunctionCall1(uuid_in, CStringGetDatum(strbuf)); +} + +#endif /* HAVE_UUID_OSSP */ + + +Datum +uuid_nil(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_UUID_OSSP + return special_uuid_value("nil"); +#else + return uuid_generate_internal(0, NULL, + "00000000-0000-0000-0000-000000000000", 36); +#endif +} + + +Datum +uuid_ns_dns(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_UUID_OSSP + return special_uuid_value("ns:DNS"); +#else + return uuid_generate_internal(0, NULL, + "6ba7b810-9dad-11d1-80b4-00c04fd430c8", 36); +#endif +} + + +Datum +uuid_ns_url(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_UUID_OSSP + return special_uuid_value("ns:URL"); +#else + return uuid_generate_internal(0, NULL, + "6ba7b811-9dad-11d1-80b4-00c04fd430c8", 36); +#endif +} + + +Datum +uuid_ns_oid(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_UUID_OSSP + return special_uuid_value("ns:OID"); +#else + return uuid_generate_internal(0, NULL, + "6ba7b812-9dad-11d1-80b4-00c04fd430c8", 36); +#endif +} + + +Datum +uuid_ns_x500(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_UUID_OSSP + return special_uuid_value("ns:X500"); +#else + return uuid_generate_internal(0, NULL, + "6ba7b814-9dad-11d1-80b4-00c04fd430c8", 36); +#endif +} + + +Datum +uuid_generate_v1(PG_FUNCTION_ARGS) +{ + return uuid_generate_internal(UUID_MAKE_V1, NULL, NULL, 0); +} + + +Datum +uuid_generate_v1mc(PG_FUNCTION_ARGS) +{ +#ifdef HAVE_UUID_OSSP + char *buf = NULL; +#elif defined(HAVE_UUID_E2FS) + char strbuf[40]; + char *buf; + uuid_t uu; + + uuid_generate_random(uu); + + /* set IEEE802 multicast and local-admin bits */ + ((dce_uuid_t *) &uu)->node[0] |= 0x03; + + uuid_unparse(uu, strbuf); + buf = strbuf + 24; +#else /* BSD */ + char buf[16]; + + /* set IEEE802 multicast and local-admin bits */ + snprintf(buf, sizeof(buf), "-%04x%08lx", + (unsigned) ((arc4random() & 0xffff) | 0x0300), + (unsigned long) arc4random()); +#endif + + return uuid_generate_internal(UUID_MAKE_V1 | UUID_MAKE_MC, NULL, + buf, 13); +} + + +Datum +uuid_generate_v3(PG_FUNCTION_ARGS) +{ + pg_uuid_t *ns = PG_GETARG_UUID_P(0); + text *name = PG_GETARG_TEXT_PP(1); + +#ifdef HAVE_UUID_OSSP + return uuid_generate_v35_internal(UUID_MAKE_V3, ns, name); +#else + return uuid_generate_internal(UUID_MAKE_V3, (unsigned char *) ns, + VARDATA_ANY(name), VARSIZE_ANY_EXHDR(name)); +#endif +} + + +Datum +uuid_generate_v4(PG_FUNCTION_ARGS) +{ + return uuid_generate_internal(UUID_MAKE_V4, NULL, NULL, 0); +} + + +Datum +uuid_generate_v5(PG_FUNCTION_ARGS) +{ + pg_uuid_t *ns = PG_GETARG_UUID_P(0); + text *name = PG_GETARG_TEXT_PP(1); + +#ifdef HAVE_UUID_OSSP + return uuid_generate_v35_internal(UUID_MAKE_V5, ns, name); +#else + return uuid_generate_internal(UUID_MAKE_V5, (unsigned char *) ns, + VARDATA_ANY(name), VARSIZE_ANY_EXHDR(name)); +#endif +} diff --git a/contrib/uuid-ossp/uuid-ossp.control b/contrib/uuid-ossp/uuid-ossp.control new file mode 100644 index 0000000..142a99e --- /dev/null +++ b/contrib/uuid-ossp/uuid-ossp.control @@ -0,0 +1,6 @@ +# uuid-ossp extension +comment = 'generate universally unique identifiers (UUIDs)' +default_version = '1.1' +module_pathname = '$libdir/uuid-ossp' +relocatable = true +trusted = true diff --git a/contrib/vacuumlo/.gitignore b/contrib/vacuumlo/.gitignore new file mode 100644 index 0000000..f3f0ce3 --- /dev/null +++ b/contrib/vacuumlo/.gitignore @@ -0,0 +1,3 @@ +/vacuumlo + +/tmp_check/ diff --git a/contrib/vacuumlo/Makefile b/contrib/vacuumlo/Makefile new file mode 100644 index 0000000..6bc7b34 --- /dev/null +++ b/contrib/vacuumlo/Makefile @@ -0,0 +1,25 @@ +# contrib/vacuumlo/Makefile + +PGFILEDESC = "vacuumlo - removes orphaned large objects" +PGAPPICON = win32 + +PROGRAM = vacuumlo +OBJS = \ + $(WIN32RES) \ + vacuumlo.o + +TAP_TESTS = 1 + +PG_CPPFLAGS = -I$(libpq_srcdir) +PG_LIBS_INTERNAL = $(libpq_pgport) + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/vacuumlo +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/vacuumlo/meson.build b/contrib/vacuumlo/meson.build new file mode 100644 index 0000000..9fa7380 --- /dev/null +++ b/contrib/vacuumlo/meson.build @@ -0,0 +1,29 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +vacuumlo_sources = files( + 'vacuumlo.c', +) + +if host_system == 'windows' + vacuumlo_sources += rc_bin_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'vacuumlo', + '--FILEDESC', 'vacuumlo - removes orphaned large objects',]) +endif + +vacuumlo = executable('vacuumlo', + vacuumlo_sources, + dependencies: [frontend_code, libpq], + kwargs: default_bin_args, +) +contrib_targets += vacuumlo + +tests += { + 'name': 'vacuumlo', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'tap': { + 'tests': [ + 't/001_basic.pl', + ], + }, +} diff --git a/contrib/vacuumlo/t/001_basic.pl b/contrib/vacuumlo/t/001_basic.pl new file mode 100644 index 0000000..7506786 --- /dev/null +++ b/contrib/vacuumlo/t/001_basic.pl @@ -0,0 +1,14 @@ + +# Copyright (c) 2021-2023, PostgreSQL Global Development Group + +use strict; +use warnings; + +use PostgreSQL::Test::Utils; +use Test::More; + +program_help_ok('vacuumlo'); +program_version_ok('vacuumlo'); +program_options_handling_ok('vacuumlo'); + +done_testing(); diff --git a/contrib/vacuumlo/vacuumlo.c b/contrib/vacuumlo/vacuumlo.c new file mode 100644 index 0000000..8941262 --- /dev/null +++ b/contrib/vacuumlo/vacuumlo.c @@ -0,0 +1,544 @@ +/*------------------------------------------------------------------------- + * + * vacuumlo.c + * This removes orphaned large objects from a database. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * contrib/vacuumlo/vacuumlo.c + * + *------------------------------------------------------------------------- + */ +#include "postgres_fe.h" + +#include +#include +#include +#ifdef HAVE_TERMIOS_H +#include +#endif + +#include "catalog/pg_class_d.h" +#include "common/connect.h" +#include "common/logging.h" +#include "common/string.h" +#include "getopt_long.h" +#include "libpq-fe.h" +#include "pg_getopt.h" + +#define BUFSIZE 1024 + +enum trivalue +{ + TRI_DEFAULT, + TRI_NO, + TRI_YES +}; + +struct _param +{ + char *pg_user; + enum trivalue pg_prompt; + char *pg_port; + char *pg_host; + const char *progname; + int verbose; + int dry_run; + long transaction_limit; +}; + +static int vacuumlo(const char *database, const struct _param *param); +static void usage(const char *progname); + + + +/* + * This vacuums LOs of one database. It returns 0 on success, -1 on failure. + */ +static int +vacuumlo(const char *database, const struct _param *param) +{ + PGconn *conn; + PGresult *res, + *res2; + char buf[BUFSIZE]; + long matched; + long deleted; + int i; + bool new_pass; + bool success = true; + static char *password = NULL; + + /* Note: password can be carried over from a previous call */ + if (param->pg_prompt == TRI_YES && !password) + password = simple_prompt("Password: ", false); + + /* + * Start the connection. Loop until we have a password if requested by + * backend. + */ + do + { +#define PARAMS_ARRAY_SIZE 7 + + const char *keywords[PARAMS_ARRAY_SIZE]; + const char *values[PARAMS_ARRAY_SIZE]; + + keywords[0] = "host"; + values[0] = param->pg_host; + keywords[1] = "port"; + values[1] = param->pg_port; + keywords[2] = "user"; + values[2] = param->pg_user; + keywords[3] = "password"; + values[3] = password; + keywords[4] = "dbname"; + values[4] = database; + keywords[5] = "fallback_application_name"; + values[5] = param->progname; + keywords[6] = NULL; + values[6] = NULL; + + new_pass = false; + conn = PQconnectdbParams(keywords, values, true); + if (!conn) + { + pg_log_error("connection to database \"%s\" failed", database); + return -1; + } + + if (PQstatus(conn) == CONNECTION_BAD && + PQconnectionNeedsPassword(conn) && + !password && + param->pg_prompt != TRI_NO) + { + PQfinish(conn); + password = simple_prompt("Password: ", false); + new_pass = true; + } + } while (new_pass); + + /* check to see that the backend connection was successfully made */ + if (PQstatus(conn) == CONNECTION_BAD) + { + pg_log_error("%s", PQerrorMessage(conn)); + PQfinish(conn); + return -1; + } + + if (param->verbose) + { + fprintf(stdout, "Connected to database \"%s\"\n", database); + if (param->dry_run) + fprintf(stdout, "Test run: no large objects will be removed!\n"); + } + + res = PQexec(conn, ALWAYS_SECURE_SEARCH_PATH_SQL); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + pg_log_error("failed to set search_path: %s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + return -1; + } + PQclear(res); + + /* + * First we create and populate the LO temp table + */ + buf[0] = '\0'; + strcat(buf, "CREATE TEMP TABLE vacuum_l AS "); + if (PQserverVersion(conn) >= 90000) + strcat(buf, "SELECT oid AS lo FROM pg_largeobject_metadata"); + else + strcat(buf, "SELECT DISTINCT loid AS lo FROM pg_largeobject"); + res = PQexec(conn, buf); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + pg_log_error("failed to create temp table: %s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + return -1; + } + PQclear(res); + + /* + * Analyze the temp table so that planner will generate decent plans for + * the DELETEs below. + */ + buf[0] = '\0'; + strcat(buf, "ANALYZE vacuum_l"); + res = PQexec(conn, buf); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + pg_log_error("failed to vacuum temp table: %s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + return -1; + } + PQclear(res); + + /* + * Now find any candidate tables that have columns of type oid. + * + * NOTE: we ignore system tables and temp tables by the expedient of + * rejecting tables in schemas named 'pg_*'. In particular, the temp + * table formed above is ignored, and pg_largeobject will be too. If + * either of these were scanned, obviously we'd end up with nothing to + * delete... + */ + buf[0] = '\0'; + strcat(buf, "SELECT s.nspname, c.relname, a.attname "); + strcat(buf, "FROM pg_class c, pg_attribute a, pg_namespace s, pg_type t "); + strcat(buf, "WHERE a.attnum > 0 AND NOT a.attisdropped "); + strcat(buf, " AND a.attrelid = c.oid "); + strcat(buf, " AND a.atttypid = t.oid "); + strcat(buf, " AND c.relnamespace = s.oid "); + strcat(buf, " AND t.typname in ('oid', 'lo') "); + strcat(buf, " AND c.relkind in (" CppAsString2(RELKIND_RELATION) ", " CppAsString2(RELKIND_MATVIEW) ")"); + strcat(buf, " AND s.nspname !~ '^pg_'"); + res = PQexec(conn, buf); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + pg_log_error("failed to find OID columns: %s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + return -1; + } + + for (i = 0; i < PQntuples(res); i++) + { + char *schema, + *table, + *field; + + schema = PQgetvalue(res, i, 0); + table = PQgetvalue(res, i, 1); + field = PQgetvalue(res, i, 2); + + if (param->verbose) + fprintf(stdout, "Checking %s in %s.%s\n", field, schema, table); + + schema = PQescapeIdentifier(conn, schema, strlen(schema)); + table = PQescapeIdentifier(conn, table, strlen(table)); + field = PQescapeIdentifier(conn, field, strlen(field)); + + if (!schema || !table || !field) + { + pg_log_error("%s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + PQfreemem(schema); + PQfreemem(table); + PQfreemem(field); + return -1; + } + + snprintf(buf, BUFSIZE, + "DELETE FROM vacuum_l " + "WHERE lo IN (SELECT %s FROM %s.%s)", + field, schema, table); + res2 = PQexec(conn, buf); + if (PQresultStatus(res2) != PGRES_COMMAND_OK) + { + pg_log_error("failed to check %s in table %s.%s: %s", + field, schema, table, PQerrorMessage(conn)); + PQclear(res2); + PQclear(res); + PQfinish(conn); + PQfreemem(schema); + PQfreemem(table); + PQfreemem(field); + return -1; + } + PQclear(res2); + + PQfreemem(schema); + PQfreemem(table); + PQfreemem(field); + } + PQclear(res); + + /* + * Now, those entries remaining in vacuum_l are orphans. Delete 'em. + * + * We don't want to run each delete as an individual transaction, because + * the commit overhead would be high. However, since 9.0 the backend will + * acquire a lock per deleted LO, so deleting too many LOs per transaction + * risks running out of room in the shared-memory lock table. Accordingly, + * we delete up to transaction_limit LOs per transaction. + */ + res = PQexec(conn, "begin"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + pg_log_error("failed to start transaction: %s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + return -1; + } + PQclear(res); + + buf[0] = '\0'; + strcat(buf, + "DECLARE myportal CURSOR WITH HOLD FOR SELECT lo FROM vacuum_l"); + res = PQexec(conn, buf); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + pg_log_error("DECLARE CURSOR failed: %s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + return -1; + } + PQclear(res); + + snprintf(buf, BUFSIZE, "FETCH FORWARD %ld IN myportal", + param->transaction_limit > 0 ? param->transaction_limit : 1000L); + + deleted = 0; + + do + { + res = PQexec(conn, buf); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + pg_log_error("FETCH FORWARD failed: %s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + return -1; + } + + matched = PQntuples(res); + if (matched <= 0) + { + /* at end of resultset */ + PQclear(res); + break; + } + + for (i = 0; i < matched; i++) + { + Oid lo = atooid(PQgetvalue(res, i, 0)); + + if (param->verbose) + { + fprintf(stdout, "\rRemoving lo %6u ", lo); + fflush(stdout); + } + + if (param->dry_run == 0) + { + if (lo_unlink(conn, lo) < 0) + { + pg_log_error("failed to remove lo %u: %s", lo, + PQerrorMessage(conn)); + if (PQtransactionStatus(conn) == PQTRANS_INERROR) + { + success = false; + break; /* out of inner for-loop */ + } + } + else + deleted++; + } + else + deleted++; + + if (param->transaction_limit > 0 && + (deleted % param->transaction_limit) == 0) + { + res2 = PQexec(conn, "commit"); + if (PQresultStatus(res2) != PGRES_COMMAND_OK) + { + pg_log_error("failed to commit transaction: %s", + PQerrorMessage(conn)); + PQclear(res2); + PQclear(res); + PQfinish(conn); + return -1; + } + PQclear(res2); + res2 = PQexec(conn, "begin"); + if (PQresultStatus(res2) != PGRES_COMMAND_OK) + { + pg_log_error("failed to start transaction: %s", + PQerrorMessage(conn)); + PQclear(res2); + PQclear(res); + PQfinish(conn); + return -1; + } + PQclear(res2); + } + } + + PQclear(res); + } while (success); + + /* + * That's all folks! + */ + res = PQexec(conn, "commit"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + pg_log_error("failed to commit transaction: %s", + PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + return -1; + } + PQclear(res); + + PQfinish(conn); + + if (param->verbose) + { + if (param->dry_run) + fprintf(stdout, "\rWould remove %ld large objects from database \"%s\".\n", + deleted, database); + else if (success) + fprintf(stdout, + "\rSuccessfully removed %ld large objects from database \"%s\".\n", + deleted, database); + else + fprintf(stdout, "\rRemoval from database \"%s\" failed at object %ld of %ld.\n", + database, deleted, matched); + } + + return ((param->dry_run || success) ? 0 : -1); +} + +static void +usage(const char *progname) +{ + printf("%s removes unreferenced large objects from databases.\n\n", progname); + printf("Usage:\n %s [OPTION]... DBNAME...\n\n", progname); + printf("Options:\n"); + printf(" -l, --limit=LIMIT commit after removing each LIMIT large objects\n"); + printf(" -n, --dry-run don't remove large objects, just show what would be done\n"); + printf(" -v, --verbose write a lot of progress messages\n"); + printf(" -V, --version output version information, then exit\n"); + printf(" -?, --help show this help, then exit\n"); + printf("\nConnection options:\n"); + printf(" -h, --host=HOSTNAME database server host or socket directory\n"); + printf(" -p, --port=PORT database server port\n"); + printf(" -U, --username=USERNAME user name to connect as\n"); + printf(" -w, --no-password never prompt for password\n"); + printf(" -W, --password force password prompt\n"); + printf("\n"); + printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT); + printf("%s home page: <%s>\n", PACKAGE_NAME, PACKAGE_URL); +} + + +int +main(int argc, char **argv) +{ + static struct option long_options[] = { + {"host", required_argument, NULL, 'h'}, + {"limit", required_argument, NULL, 'l'}, + {"dry-run", no_argument, NULL, 'n'}, + {"port", required_argument, NULL, 'p'}, + {"username", required_argument, NULL, 'U'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"no-password", no_argument, NULL, 'w'}, + {"password", no_argument, NULL, 'W'}, + {"help", no_argument, NULL, '?'}, + {NULL, 0, NULL, 0} + }; + + int rc = 0; + struct _param param; + int c; + int port; + const char *progname; + int optindex; + + pg_logging_init(argv[0]); + progname = get_progname(argv[0]); + + /* Set default parameter values */ + param.pg_user = NULL; + param.pg_prompt = TRI_DEFAULT; + param.pg_host = NULL; + param.pg_port = NULL; + param.progname = progname; + param.verbose = 0; + param.dry_run = 0; + param.transaction_limit = 1000; + + /* Process command-line arguments */ + if (argc > 1) + { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) + { + usage(progname); + exit(0); + } + if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) + { + puts("vacuumlo (PostgreSQL) " PG_VERSION); + exit(0); + } + } + + while ((c = getopt_long(argc, argv, "h:l:np:U:vwW", long_options, &optindex)) != -1) + { + switch (c) + { + case 'h': + param.pg_host = pg_strdup(optarg); + break; + case 'l': + param.transaction_limit = strtol(optarg, NULL, 10); + if (param.transaction_limit < 0) + pg_fatal("transaction limit must not be negative (0 disables)"); + break; + case 'n': + param.dry_run = 1; + param.verbose = 1; + break; + case 'p': + port = strtol(optarg, NULL, 10); + if ((port < 1) || (port > 65535)) + pg_fatal("invalid port number: %s", optarg); + param.pg_port = pg_strdup(optarg); + break; + case 'U': + param.pg_user = pg_strdup(optarg); + break; + case 'v': + param.verbose = 1; + break; + case 'w': + param.pg_prompt = TRI_NO; + break; + case 'W': + param.pg_prompt = TRI_YES; + break; + default: + /* getopt_long already emitted a complaint */ + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit(1); + } + } + + /* No database given? Show usage */ + if (optind >= argc) + { + pg_log_error("missing required argument: database name"); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit(1); + } + + for (c = optind; c < argc; c++) + { + /* Work on selected database */ + rc += (vacuumlo(argv[c], ¶m) != 0); + } + + return rc; +} diff --git a/contrib/xml2/.gitignore b/contrib/xml2/.gitignore new file mode 100644 index 0000000..5dcb3ff --- /dev/null +++ b/contrib/xml2/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/contrib/xml2/Makefile b/contrib/xml2/Makefile new file mode 100644 index 0000000..0d703fe --- /dev/null +++ b/contrib/xml2/Makefile @@ -0,0 +1,26 @@ +# contrib/xml2/Makefile + +MODULE_big = pgxml +OBJS = \ + $(WIN32RES) \ + xpath.o \ + xslt_proc.o + +EXTENSION = xml2 +DATA = xml2--1.1.sql xml2--1.0--1.1.sql +PGFILEDESC = "xml2 - XPath querying and XSLT" + +REGRESS = xml2 + +SHLIB_LINK += $(filter -lxslt, $(LIBS)) -lxml2 + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/xml2 +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/xml2/expected/xml2.out b/contrib/xml2/expected/xml2.out new file mode 100644 index 0000000..eba6ae6 --- /dev/null +++ b/contrib/xml2/expected/xml2.out @@ -0,0 +1,224 @@ +CREATE EXTENSION xml2; +select query_to_xml('select 1 as x',true,false,''); + query_to_xml +--------------------------------------------------------------- + + + + + + + 1 + + + + + +
+ + +(1 row) + +select xslt_process( query_to_xml('select x from generate_series(1,5) as +x',true,false,'')::text, +$$ + + + + + + + + + + + +$$::text); + xslt_process +--------------------------------------------------------------- + + + + + + + + + 1 + + + + + + + + 2 + + + + + + + + 3 + + + + + + + + 4 + + + + + + + + 5 + + + + + +
+ + +(1 row) + +CREATE TABLE xpath_test (id integer NOT NULL, t xml); +INSERT INTO xpath_test VALUES (1, '1'); +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4); + id +---- +(0 rows) + +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4, doc int4); + id | doc +----+----- + 1 | 1 +(1 row) + +DROP TABLE xpath_test; +CREATE TABLE xpath_test (id integer NOT NULL, t text); +INSERT INTO xpath_test VALUES (1, '1'); +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4); + id +---- +(0 rows) + +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4, doc int4); + id | doc +----+----- + 1 | 1 +(1 row) + +create table articles (article_id integer, article_xml xml, date_entered date); +insert into articles (article_id, article_xml, date_entered) +values (2, '
test37
', now()); +SELECT * FROM +xpath_table('article_id', + 'article_xml', + 'articles', + '/article/author|/article/pages|/article/title', + 'date_entered > ''2003-01-01'' ') +AS t(article_id integer, author text, page_count integer, title text); + article_id | author | page_count | title +------------+--------+------------+------- + 2 | test | 37 | +(1 row) + +-- this used to fail when invoked a second time +select xslt_process('',$$ + + + + + +$$)::xml; + xslt_process +-------------- + + + +(1 row) + +select xslt_process('',$$ + + + + + +$$)::xml; + xslt_process +-------------- + + + +(1 row) + +create table t1 (id integer, xml_data xml); +insert into t1 (id, xml_data) +values +(1, 'Some +Value'); +create index idx_xpath on t1 ( xpath_string +('/attributes/attribute[@name="attr_1"]/text()', xml_data::text)); +SELECT xslt_process('cim30400'::text, $$ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +$$::text, 'n1="v1",n2="v2",n3="v3",n4="v4",n5="v5",n6="v6",n7="v7",n8="v8",n9="v9",n10="v10",n11="v11",n12="v12"'::text); + xslt_process +------------------------ + + + v1 + + v2 + + v3 + + v4 + + v5 + + v6 + + v7 + + v8 + + v9 + + v10+ + v11+ + v12+ + + + +(1 row) + +-- possible security exploit +SELECT xslt_process('Hello from XML', +$$ + + + + + + + +$$); +ERROR: failed to apply stylesheet diff --git a/contrib/xml2/expected/xml2_1.out b/contrib/xml2/expected/xml2_1.out new file mode 100644 index 0000000..bac90e5 --- /dev/null +++ b/contrib/xml2/expected/xml2_1.out @@ -0,0 +1,168 @@ +CREATE EXTENSION xml2; +select query_to_xml('select 1 as x',true,false,''); + query_to_xml +--------------------------------------------------------------- + + + + + + + 1 + + + + + +
+ + +(1 row) + +select xslt_process( query_to_xml('select x from generate_series(1,5) as +x',true,false,'')::text, +$$ + + + + + + + + + + + +$$::text); +ERROR: xslt_process() is not available without libxslt +CREATE TABLE xpath_test (id integer NOT NULL, t xml); +INSERT INTO xpath_test VALUES (1, '1'); +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4); + id +---- +(0 rows) + +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4, doc int4); + id | doc +----+----- + 1 | 1 +(1 row) + +DROP TABLE xpath_test; +CREATE TABLE xpath_test (id integer NOT NULL, t text); +INSERT INTO xpath_test VALUES (1, '1'); +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4); + id +---- +(0 rows) + +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4, doc int4); + id | doc +----+----- + 1 | 1 +(1 row) + +create table articles (article_id integer, article_xml xml, date_entered date); +insert into articles (article_id, article_xml, date_entered) +values (2, '
test37
', now()); +SELECT * FROM +xpath_table('article_id', + 'article_xml', + 'articles', + '/article/author|/article/pages|/article/title', + 'date_entered > ''2003-01-01'' ') +AS t(article_id integer, author text, page_count integer, title text); + article_id | author | page_count | title +------------+--------+------------+------- + 2 | test | 37 | +(1 row) + +-- this used to fail when invoked a second time +select xslt_process('',$$ + + + + + +$$)::xml; +ERROR: xslt_process() is not available without libxslt +select xslt_process('',$$ + + + + + +$$)::xml; +ERROR: xslt_process() is not available without libxslt +create table t1 (id integer, xml_data xml); +insert into t1 (id, xml_data) +values +(1, 'Some +Value'); +create index idx_xpath on t1 ( xpath_string +('/attributes/attribute[@name="attr_1"]/text()', xml_data::text)); +SELECT xslt_process('cim30400'::text, $$ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +$$::text, 'n1="v1",n2="v2",n3="v3",n4="v4",n5="v5",n6="v6",n7="v7",n8="v8",n9="v9",n10="v10",n11="v11",n12="v12"'::text); +ERROR: xslt_process() is not available without libxslt +-- possible security exploit +SELECT xslt_process('Hello from XML', +$$ + + + + + + + +$$); +ERROR: xslt_process() is not available without libxslt diff --git a/contrib/xml2/meson.build b/contrib/xml2/meson.build new file mode 100644 index 0000000..4989567 --- /dev/null +++ b/contrib/xml2/meson.build @@ -0,0 +1,43 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not libxml.found() + subdir_done() +endif + +xml2_sources = files( + 'xpath.c', + 'xslt_proc.c', +) + +if host_system == 'windows' + xml2_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pgxml', + '--FILEDESC', 'xml2 - XPath querying and XSLT',]) +endif + +xml2 = shared_module('pgxml', + xml2_sources, + c_pch: pch_postgres_h, + kwargs: contrib_mod_args + { + 'dependencies': [libxml, libxslt, contrib_mod_args['dependencies']], + }, +) +contrib_targets += xml2 + +install_data( + 'xml2--1.0--1.1.sql', + 'xml2--1.1.sql', + 'xml2.control', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'xml2', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'xml2', + ], + }, +} diff --git a/contrib/xml2/sql/xml2.sql b/contrib/xml2/sql/xml2.sql new file mode 100644 index 0000000..ac49cfa --- /dev/null +++ b/contrib/xml2/sql/xml2.sql @@ -0,0 +1,139 @@ +CREATE EXTENSION xml2; + +select query_to_xml('select 1 as x',true,false,''); + +select xslt_process( query_to_xml('select x from generate_series(1,5) as +x',true,false,'')::text, +$$ + + + + + + + + + + + +$$::text); + +CREATE TABLE xpath_test (id integer NOT NULL, t xml); +INSERT INTO xpath_test VALUES (1, '1'); +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4); +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4, doc int4); + +DROP TABLE xpath_test; +CREATE TABLE xpath_test (id integer NOT NULL, t text); +INSERT INTO xpath_test VALUES (1, '1'); +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4); +SELECT * FROM xpath_table('id', 't', 'xpath_test', '/doc/int', 'true') +as t(id int4, doc int4); + +create table articles (article_id integer, article_xml xml, date_entered date); +insert into articles (article_id, article_xml, date_entered) +values (2, '
test37
', now()); +SELECT * FROM +xpath_table('article_id', + 'article_xml', + 'articles', + '/article/author|/article/pages|/article/title', + 'date_entered > ''2003-01-01'' ') +AS t(article_id integer, author text, page_count integer, title text); + +-- this used to fail when invoked a second time +select xslt_process('',$$ + + + + + +$$)::xml; + +select xslt_process('',$$ + + + + + +$$)::xml; + +create table t1 (id integer, xml_data xml); +insert into t1 (id, xml_data) +values +(1, 'Some +Value'); + +create index idx_xpath on t1 ( xpath_string +('/attributes/attribute[@name="attr_1"]/text()', xml_data::text)); + +SELECT xslt_process('cim30400'::text, $$ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +$$::text, 'n1="v1",n2="v2",n3="v3",n4="v4",n5="v5",n6="v6",n7="v7",n8="v8",n9="v9",n10="v10",n11="v11",n12="v12"'::text); + +-- possible security exploit +SELECT xslt_process('Hello from XML', +$$ + + + + + + + +$$); diff --git a/contrib/xml2/xml2--1.0--1.1.sql b/contrib/xml2/xml2--1.0--1.1.sql new file mode 100644 index 0000000..350afb0 --- /dev/null +++ b/contrib/xml2/xml2--1.0--1.1.sql @@ -0,0 +1,18 @@ +/* contrib/xml2/xml2--1.0--1.1.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION xml2 UPDATE TO '1.1'" to load this file. \quit + +ALTER FUNCTION xml_valid(text) PARALLEL SAFE; +ALTER FUNCTION xml_encode_special_chars(text) PARALLEL SAFE; +ALTER FUNCTION xpath_string(text, text) PARALLEL SAFE; +ALTER FUNCTION xpath_nodeset(text, text, text, text) PARALLEL SAFE; +ALTER FUNCTION xpath_number(text, text) PARALLEL SAFE; +ALTER FUNCTION xpath_bool(text, text) PARALLEL SAFE; +ALTER FUNCTION xpath_list(text, text, text) PARALLEL SAFE; +ALTER FUNCTION xpath_list(text, text) PARALLEL SAFE; +ALTER FUNCTION xpath_nodeset(text, text) PARALLEL SAFE; +ALTER FUNCTION xpath_nodeset(text, text, text) PARALLEL SAFE; +ALTER FUNCTION xpath_table(text, text, text, text, text) PARALLEL SAFE; +ALTER FUNCTION xslt_process(text, text, text) PARALLEL SAFE; +ALTER FUNCTION xslt_process(text, text) PARALLEL SAFE; diff --git a/contrib/xml2/xml2--1.1.sql b/contrib/xml2/xml2--1.1.sql new file mode 100644 index 0000000..671372c --- /dev/null +++ b/contrib/xml2/xml2--1.1.sql @@ -0,0 +1,73 @@ +/* contrib/xml2/xml2--1.1.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION xml2" to load this file. \quit + +--SQL for XML parser + +-- deprecated old name for xml_is_well_formed +CREATE FUNCTION xml_valid(text) RETURNS bool +AS 'xml_is_well_formed' +LANGUAGE INTERNAL STRICT STABLE PARALLEL SAFE; + +CREATE FUNCTION xml_encode_special_chars(text) RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION xpath_string(text,text) RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION xpath_nodeset(text,text,text,text) RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION xpath_number(text,text) RETURNS float4 +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION xpath_bool(text,text) RETURNS boolean +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +-- List function + +CREATE FUNCTION xpath_list(text,text,text) RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION xpath_list(text,text) RETURNS text +AS 'SELECT xpath_list($1,$2,'','')' +LANGUAGE SQL STRICT IMMUTABLE PARALLEL SAFE; + +-- Wrapper functions for nodeset where no tags needed + +CREATE FUNCTION xpath_nodeset(text,text) +RETURNS text +AS 'SELECT xpath_nodeset($1,$2,'''','''')' +LANGUAGE SQL STRICT IMMUTABLE PARALLEL SAFE; + +CREATE FUNCTION xpath_nodeset(text,text,text) +RETURNS text +AS 'SELECT xpath_nodeset($1,$2,'''',$3)' +LANGUAGE SQL STRICT IMMUTABLE PARALLEL SAFE; + +-- Table function + +CREATE FUNCTION xpath_table(text,text,text,text,text) +RETURNS setof record +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT STABLE PARALLEL SAFE; + +-- XSLT functions + +CREATE FUNCTION xslt_process(text,text,text) +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL SAFE; + +-- the function checks for the correct argument count +CREATE FUNCTION xslt_process(text,text) +RETURNS text +AS 'MODULE_PATHNAME' +LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; diff --git a/contrib/xml2/xml2.control b/contrib/xml2/xml2.control new file mode 100644 index 0000000..ba2c059 --- /dev/null +++ b/contrib/xml2/xml2.control @@ -0,0 +1,6 @@ +# xml2 extension +comment = 'XPath querying and XSLT' +default_version = '1.1' +module_pathname = '$libdir/pgxml' +# XXX do we still need this to be non-relocatable? +relocatable = false diff --git a/contrib/xml2/xpath.c b/contrib/xml2/xpath.c new file mode 100644 index 0000000..9464193 --- /dev/null +++ b/contrib/xml2/xpath.c @@ -0,0 +1,745 @@ +/* + * contrib/xml2/xpath.c + * + * Parser interface for DOM-based parser (libxml) rather than + * stream-based SAX-type parser + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "executor/spi.h" +#include "fmgr.h" +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/xml.h" + +/* libxml includes */ + +#include +#include +#include +#include +#include + +PG_MODULE_MAGIC; + +/* exported for use by xslt_proc.c */ + +PgXmlErrorContext *pgxml_parser_init(PgXmlStrictness strictness); + +/* workspace for pgxml_xpath() */ + +typedef struct +{ + xmlDocPtr doctree; + xmlXPathContextPtr ctxt; + xmlXPathObjectPtr res; +} xpath_workspace; + +/* local declarations */ + +static xmlChar *pgxmlNodeSetToText(xmlNodeSetPtr nodeset, + xmlChar *toptagname, xmlChar *septagname, + xmlChar *plainsep); + +static text *pgxml_result_to_text(xmlXPathObjectPtr res, xmlChar *toptag, + xmlChar *septag, xmlChar *plainsep); + +static xmlChar *pgxml_texttoxmlchar(text *textstring); + +static xmlXPathObjectPtr pgxml_xpath(text *document, xmlChar *xpath, + xpath_workspace *workspace); + +static void cleanup_workspace(xpath_workspace *workspace); + + +/* + * Initialize for xml parsing. + * + * As with the underlying pg_xml_init function, calls to this MUST be followed + * by a PG_TRY block that guarantees that pg_xml_done is called. + */ +PgXmlErrorContext * +pgxml_parser_init(PgXmlStrictness strictness) +{ + PgXmlErrorContext *xmlerrcxt; + + /* Set up error handling (we share the core's error handler) */ + xmlerrcxt = pg_xml_init(strictness); + + /* Note: we're assuming an elog cannot be thrown by the following calls */ + + /* Initialize libxml */ + xmlInitParser(); + + xmlSubstituteEntitiesDefault(1); + + return xmlerrcxt; +} + + +/* Encodes special characters (<, >, &, " and \r) as XML entities */ + +PG_FUNCTION_INFO_V1(xml_encode_special_chars); + +Datum +xml_encode_special_chars(PG_FUNCTION_ARGS) +{ + text *tin = PG_GETARG_TEXT_PP(0); + text *tout; + xmlChar *ts, + *tt; + + ts = pgxml_texttoxmlchar(tin); + + tt = xmlEncodeSpecialChars(NULL, ts); + + pfree(ts); + + tout = cstring_to_text((char *) tt); + + xmlFree(tt); + + PG_RETURN_TEXT_P(tout); +} + +/* + * Function translates a nodeset into a text representation + * + * iterates over each node in the set and calls xmlNodeDump to write it to + * an xmlBuffer -from which an xmlChar * string is returned. + * + * each representation is surrounded by ... + * + * plainsep is an ordinary (not tag) separator - if used, then nodes are + * cast to string as output method + */ +static xmlChar * +pgxmlNodeSetToText(xmlNodeSetPtr nodeset, + xmlChar *toptagname, + xmlChar *septagname, + xmlChar *plainsep) +{ + xmlBufferPtr buf; + xmlChar *result; + int i; + + buf = xmlBufferCreate(); + + if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0)) + { + xmlBufferWriteChar(buf, "<"); + xmlBufferWriteCHAR(buf, toptagname); + xmlBufferWriteChar(buf, ">"); + } + if (nodeset != NULL) + { + for (i = 0; i < nodeset->nodeNr; i++) + { + if (plainsep != NULL) + { + xmlBufferWriteCHAR(buf, + xmlXPathCastNodeToString(nodeset->nodeTab[i])); + + /* If this isn't the last entry, write the plain sep. */ + if (i < (nodeset->nodeNr) - 1) + xmlBufferWriteChar(buf, (char *) plainsep); + } + else + { + if ((septagname != NULL) && (xmlStrlen(septagname) > 0)) + { + xmlBufferWriteChar(buf, "<"); + xmlBufferWriteCHAR(buf, septagname); + xmlBufferWriteChar(buf, ">"); + } + xmlNodeDump(buf, + nodeset->nodeTab[i]->doc, + nodeset->nodeTab[i], + 1, 0); + + if ((septagname != NULL) && (xmlStrlen(septagname) > 0)) + { + xmlBufferWriteChar(buf, ""); + } + } + } + } + + if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0)) + { + xmlBufferWriteChar(buf, ""); + } + result = xmlStrdup(buf->content); + xmlBufferFree(buf); + return result; +} + + +/* Translate a PostgreSQL "varlena" -i.e. a variable length parameter + * into the libxml2 representation + */ +static xmlChar * +pgxml_texttoxmlchar(text *textstring) +{ + return (xmlChar *) text_to_cstring(textstring); +} + +/* Publicly visible XPath functions */ + +/* + * This is a "raw" xpath function. Check that it returns child elements + * properly + */ +PG_FUNCTION_INFO_V1(xpath_nodeset); + +Datum +xpath_nodeset(PG_FUNCTION_ARGS) +{ + text *document = PG_GETARG_TEXT_PP(0); + text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */ + xmlChar *toptag = pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(2)); + xmlChar *septag = pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(3)); + xmlChar *xpath; + text *xpres; + xmlXPathObjectPtr res; + xpath_workspace workspace; + + xpath = pgxml_texttoxmlchar(xpathsupp); + + res = pgxml_xpath(document, xpath, &workspace); + + xpres = pgxml_result_to_text(res, toptag, septag, NULL); + + cleanup_workspace(&workspace); + + pfree(xpath); + + if (xpres == NULL) + PG_RETURN_NULL(); + PG_RETURN_TEXT_P(xpres); +} + +/* + * The following function is almost identical, but returns the elements in + * a list. + */ +PG_FUNCTION_INFO_V1(xpath_list); + +Datum +xpath_list(PG_FUNCTION_ARGS) +{ + text *document = PG_GETARG_TEXT_PP(0); + text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */ + xmlChar *plainsep = pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(2)); + xmlChar *xpath; + text *xpres; + xmlXPathObjectPtr res; + xpath_workspace workspace; + + xpath = pgxml_texttoxmlchar(xpathsupp); + + res = pgxml_xpath(document, xpath, &workspace); + + xpres = pgxml_result_to_text(res, NULL, NULL, plainsep); + + cleanup_workspace(&workspace); + + pfree(xpath); + + if (xpres == NULL) + PG_RETURN_NULL(); + PG_RETURN_TEXT_P(xpres); +} + + +PG_FUNCTION_INFO_V1(xpath_string); + +Datum +xpath_string(PG_FUNCTION_ARGS) +{ + text *document = PG_GETARG_TEXT_PP(0); + text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */ + xmlChar *xpath; + int32 pathsize; + text *xpres; + xmlXPathObjectPtr res; + xpath_workspace workspace; + + pathsize = VARSIZE_ANY_EXHDR(xpathsupp); + + /* + * We encapsulate the supplied path with "string()" = 8 chars + 1 for NUL + * at end + */ + /* We could try casting to string using the libxml function? */ + + xpath = (xmlChar *) palloc(pathsize + 9); + memcpy((char *) xpath, "string(", 7); + memcpy((char *) (xpath + 7), VARDATA_ANY(xpathsupp), pathsize); + xpath[pathsize + 7] = ')'; + xpath[pathsize + 8] = '\0'; + + res = pgxml_xpath(document, xpath, &workspace); + + xpres = pgxml_result_to_text(res, NULL, NULL, NULL); + + cleanup_workspace(&workspace); + + pfree(xpath); + + if (xpres == NULL) + PG_RETURN_NULL(); + PG_RETURN_TEXT_P(xpres); +} + + +PG_FUNCTION_INFO_V1(xpath_number); + +Datum +xpath_number(PG_FUNCTION_ARGS) +{ + text *document = PG_GETARG_TEXT_PP(0); + text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */ + xmlChar *xpath; + float4 fRes; + xmlXPathObjectPtr res; + xpath_workspace workspace; + + xpath = pgxml_texttoxmlchar(xpathsupp); + + res = pgxml_xpath(document, xpath, &workspace); + + pfree(xpath); + + if (res == NULL) + PG_RETURN_NULL(); + + fRes = xmlXPathCastToNumber(res); + + cleanup_workspace(&workspace); + + if (xmlXPathIsNaN(fRes)) + PG_RETURN_NULL(); + + PG_RETURN_FLOAT4(fRes); +} + + +PG_FUNCTION_INFO_V1(xpath_bool); + +Datum +xpath_bool(PG_FUNCTION_ARGS) +{ + text *document = PG_GETARG_TEXT_PP(0); + text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */ + xmlChar *xpath; + int bRes; + xmlXPathObjectPtr res; + xpath_workspace workspace; + + xpath = pgxml_texttoxmlchar(xpathsupp); + + res = pgxml_xpath(document, xpath, &workspace); + + pfree(xpath); + + if (res == NULL) + PG_RETURN_BOOL(false); + + bRes = xmlXPathCastToBoolean(res); + + cleanup_workspace(&workspace); + + PG_RETURN_BOOL(bRes); +} + + + +/* Core function to evaluate XPath query */ + +static xmlXPathObjectPtr +pgxml_xpath(text *document, xmlChar *xpath, xpath_workspace *workspace) +{ + int32 docsize = VARSIZE_ANY_EXHDR(document); + PgXmlErrorContext *xmlerrcxt; + xmlXPathCompExprPtr comppath; + + workspace->doctree = NULL; + workspace->ctxt = NULL; + workspace->res = NULL; + + xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY); + + PG_TRY(); + { + workspace->doctree = xmlParseMemory((char *) VARDATA_ANY(document), + docsize); + if (workspace->doctree != NULL) + { + workspace->ctxt = xmlXPathNewContext(workspace->doctree); + workspace->ctxt->node = xmlDocGetRootElement(workspace->doctree); + + /* compile the path */ + comppath = xmlXPathCompile(xpath); + if (comppath == NULL) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, + "XPath Syntax Error"); + + /* Now evaluate the path expression. */ + workspace->res = xmlXPathCompiledEval(comppath, workspace->ctxt); + + xmlXPathFreeCompExpr(comppath); + } + } + PG_CATCH(); + { + cleanup_workspace(workspace); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + } + PG_END_TRY(); + + if (workspace->res == NULL) + cleanup_workspace(workspace); + + pg_xml_done(xmlerrcxt, false); + + return workspace->res; +} + +/* Clean up after processing the result of pgxml_xpath() */ +static void +cleanup_workspace(xpath_workspace *workspace) +{ + if (workspace->res) + xmlXPathFreeObject(workspace->res); + workspace->res = NULL; + if (workspace->ctxt) + xmlXPathFreeContext(workspace->ctxt); + workspace->ctxt = NULL; + if (workspace->doctree) + xmlFreeDoc(workspace->doctree); + workspace->doctree = NULL; +} + +static text * +pgxml_result_to_text(xmlXPathObjectPtr res, + xmlChar *toptag, + xmlChar *septag, + xmlChar *plainsep) +{ + xmlChar *xpresstr; + text *xpres; + + if (res == NULL) + return NULL; + + switch (res->type) + { + case XPATH_NODESET: + xpresstr = pgxmlNodeSetToText(res->nodesetval, + toptag, + septag, plainsep); + break; + + case XPATH_STRING: + xpresstr = xmlStrdup(res->stringval); + break; + + default: + elog(NOTICE, "unsupported XQuery result: %d", res->type); + xpresstr = xmlStrdup((const xmlChar *) ""); + } + + /* Now convert this result back to text */ + xpres = cstring_to_text((char *) xpresstr); + + /* Free various storage */ + xmlFree(xpresstr); + + return xpres; +} + +/* + * xpath_table is a table function. It needs some tidying (as do the + * other functions here! + */ +PG_FUNCTION_INFO_V1(xpath_table); + +Datum +xpath_table(PG_FUNCTION_ARGS) +{ + /* Function parameters */ + char *pkeyfield = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *xmlfield = text_to_cstring(PG_GETARG_TEXT_PP(1)); + char *relname = text_to_cstring(PG_GETARG_TEXT_PP(2)); + char *xpathset = text_to_cstring(PG_GETARG_TEXT_PP(3)); + char *condition = text_to_cstring(PG_GETARG_TEXT_PP(4)); + + /* SPI (input tuple) support */ + SPITupleTable *tuptable; + HeapTuple spi_tuple; + TupleDesc spi_tupdesc; + + + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + AttInMetadata *attinmeta; + + char **values; + xmlChar **xpaths; + char *pos; + const char *pathsep = "|"; + + int numpaths; + int ret; + uint64 proc; + int j; + int rownr; /* For issuing multiple rows from one original + * document */ + bool had_values; /* To determine end of nodeset results */ + StringInfoData query_buf; + PgXmlErrorContext *xmlerrcxt; + volatile xmlDocPtr doctree = NULL; + + InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC); + + /* must have at least one output column (for the pkey) */ + if (rsinfo->setDesc->natts < 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("xpath_table must have at least one output column"))); + + /* + * At the moment we assume that the returned attributes make sense for the + * XPath specified (i.e. we trust the caller). It's not fatal if they get + * it wrong - the input function for the column type will raise an error + * if the path result can't be converted into the correct binary + * representation. + */ + + attinmeta = TupleDescGetAttInMetadata(rsinfo->setDesc); + + values = (char **) palloc(rsinfo->setDesc->natts * sizeof(char *)); + xpaths = (xmlChar **) palloc(rsinfo->setDesc->natts * sizeof(xmlChar *)); + + /* + * Split XPaths. xpathset is a writable CString. + * + * Note that we stop splitting once we've done all needed for tupdesc + */ + numpaths = 0; + pos = xpathset; + while (numpaths < (rsinfo->setDesc->natts - 1)) + { + xpaths[numpaths++] = (xmlChar *) pos; + pos = strstr(pos, pathsep); + if (pos != NULL) + { + *pos = '\0'; + pos++; + } + else + break; + } + + /* Now build query */ + initStringInfo(&query_buf); + + /* Build initial sql statement */ + appendStringInfo(&query_buf, "SELECT %s, %s FROM %s WHERE %s", + pkeyfield, + xmlfield, + relname, + condition); + + if ((ret = SPI_connect()) < 0) + elog(ERROR, "xpath_table: SPI_connect returned %d", ret); + + if ((ret = SPI_exec(query_buf.data, 0)) != SPI_OK_SELECT) + elog(ERROR, "xpath_table: SPI execution failed for query %s", + query_buf.data); + + proc = SPI_processed; + tuptable = SPI_tuptable; + spi_tupdesc = tuptable->tupdesc; + + /* + * Check that SPI returned correct result. If you put a comma into one of + * the function parameters, this will catch it when the SPI query returns + * e.g. 3 columns. + */ + if (spi_tupdesc->natts != 2) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("expression returning multiple columns is not valid in parameter list"), + errdetail("Expected two columns in SPI result, got %d.", spi_tupdesc->natts))); + } + + /* + * Setup the parser. This should happen after we are done evaluating the + * query, in case it calls functions that set up libxml differently. + */ + xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY); + + PG_TRY(); + { + /* For each row i.e. document returned from SPI */ + uint64 i; + + for (i = 0; i < proc; i++) + { + char *pkey; + char *xmldoc; + xmlXPathContextPtr ctxt; + xmlXPathObjectPtr res; + xmlChar *resstr; + xmlXPathCompExprPtr comppath; + HeapTuple ret_tuple; + + /* Extract the row data as C Strings */ + spi_tuple = tuptable->vals[i]; + pkey = SPI_getvalue(spi_tuple, spi_tupdesc, 1); + xmldoc = SPI_getvalue(spi_tuple, spi_tupdesc, 2); + + /* + * Clear the values array, so that not-well-formed documents + * return NULL in all columns. Note that this also means that + * spare columns will be NULL. + */ + for (j = 0; j < rsinfo->setDesc->natts; j++) + values[j] = NULL; + + /* Insert primary key */ + values[0] = pkey; + + /* Parse the document */ + if (xmldoc) + doctree = xmlParseMemory(xmldoc, strlen(xmldoc)); + else /* treat NULL as not well-formed */ + doctree = NULL; + + if (doctree == NULL) + { + /* not well-formed, so output all-NULL tuple */ + ret_tuple = BuildTupleFromCStrings(attinmeta, values); + tuplestore_puttuple(rsinfo->setResult, ret_tuple); + heap_freetuple(ret_tuple); + } + else + { + /* New loop here - we have to deal with nodeset results */ + rownr = 0; + + do + { + /* Now evaluate the set of xpaths. */ + had_values = false; + for (j = 0; j < numpaths; j++) + { + ctxt = xmlXPathNewContext(doctree); + ctxt->node = xmlDocGetRootElement(doctree); + + /* compile the path */ + comppath = xmlXPathCompile(xpaths[j]); + if (comppath == NULL) + xml_ereport(xmlerrcxt, ERROR, + ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, + "XPath Syntax Error"); + + /* Now evaluate the path expression. */ + res = xmlXPathCompiledEval(comppath, ctxt); + xmlXPathFreeCompExpr(comppath); + + if (res != NULL) + { + switch (res->type) + { + case XPATH_NODESET: + /* We see if this nodeset has enough nodes */ + if (res->nodesetval != NULL && + rownr < res->nodesetval->nodeNr) + { + resstr = xmlXPathCastNodeToString(res->nodesetval->nodeTab[rownr]); + had_values = true; + } + else + resstr = NULL; + + break; + + case XPATH_STRING: + resstr = xmlStrdup(res->stringval); + break; + + default: + elog(NOTICE, "unsupported XQuery result: %d", res->type); + resstr = xmlStrdup((const xmlChar *) ""); + } + + /* + * Insert this into the appropriate column in the + * result tuple. + */ + values[j + 1] = (char *) resstr; + } + xmlXPathFreeContext(ctxt); + } + + /* Now add the tuple to the output, if there is one. */ + if (had_values) + { + ret_tuple = BuildTupleFromCStrings(attinmeta, values); + tuplestore_puttuple(rsinfo->setResult, ret_tuple); + heap_freetuple(ret_tuple); + } + + rownr++; + } while (had_values); + } + + if (doctree != NULL) + xmlFreeDoc(doctree); + doctree = NULL; + + if (pkey) + pfree(pkey); + if (xmldoc) + pfree(xmldoc); + } + } + PG_CATCH(); + { + if (doctree != NULL) + xmlFreeDoc(doctree); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + } + PG_END_TRY(); + + if (doctree != NULL) + xmlFreeDoc(doctree); + + pg_xml_done(xmlerrcxt, false); + + SPI_finish(); + + /* + * SFRM_Materialize mode expects us to return a NULL Datum. The actual + * tuples are in our tuplestore and passed back through rsinfo->setResult. + * rsinfo->setDesc is set to the tuple description that we actually used + * to build our tuples with, so the caller can verify we did what it was + * expecting. + */ + return (Datum) 0; +} diff --git a/contrib/xml2/xslt_proc.c b/contrib/xml2/xslt_proc.c new file mode 100644 index 0000000..2189bca --- /dev/null +++ b/contrib/xml2/xslt_proc.c @@ -0,0 +1,256 @@ +/* + * contrib/xml2/xslt_proc.c + * + * XSLT processing functions (requiring libxslt) + * + * John Gray, for Torchbox 2003-04-01 + */ +#include "postgres.h" + +#include "executor/spi.h" +#include "fmgr.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/xml.h" + +#ifdef USE_LIBXSLT + +/* libxml includes */ + +#include +#include +#include + +/* libxslt includes */ + +#include +#include +#include +#include +#include +#endif /* USE_LIBXSLT */ + + +#ifdef USE_LIBXSLT + +/* declarations to come from xpath.c */ +extern PgXmlErrorContext *pgxml_parser_init(PgXmlStrictness strictness); + +/* local defs */ +static const char **parse_params(text *paramstr); +#endif /* USE_LIBXSLT */ + + +PG_FUNCTION_INFO_V1(xslt_process); + +Datum +xslt_process(PG_FUNCTION_ARGS) +{ +#ifdef USE_LIBXSLT + + text *doct = PG_GETARG_TEXT_PP(0); + text *ssheet = PG_GETARG_TEXT_PP(1); + text *result; + text *paramstr; + const char **params; + PgXmlErrorContext *xmlerrcxt; + volatile xsltStylesheetPtr stylesheet = NULL; + volatile xmlDocPtr doctree = NULL; + volatile xmlDocPtr restree = NULL; + volatile xsltSecurityPrefsPtr xslt_sec_prefs = NULL; + volatile xsltTransformContextPtr xslt_ctxt = NULL; + volatile int resstat = -1; + xmlChar *resstr = NULL; + int reslen = 0; + + if (fcinfo->nargs == 3) + { + paramstr = PG_GETARG_TEXT_PP(2); + params = parse_params(paramstr); + } + else + { + /* No parameters */ + params = (const char **) palloc(sizeof(char *)); + params[0] = NULL; + } + + /* Setup parser */ + xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY); + + PG_TRY(); + { + xmlDocPtr ssdoc; + bool xslt_sec_prefs_error; + + /* Parse document */ + doctree = xmlParseMemory((char *) VARDATA_ANY(doct), + VARSIZE_ANY_EXHDR(doct)); + + if (doctree == NULL) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, + "error parsing XML document"); + + /* Same for stylesheet */ + ssdoc = xmlParseMemory((char *) VARDATA_ANY(ssheet), + VARSIZE_ANY_EXHDR(ssheet)); + + if (ssdoc == NULL) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, + "error parsing stylesheet as XML document"); + + /* After this call we need not free ssdoc separately */ + stylesheet = xsltParseStylesheetDoc(ssdoc); + + if (stylesheet == NULL) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, + "failed to parse stylesheet"); + + xslt_ctxt = xsltNewTransformContext(stylesheet, doctree); + + xslt_sec_prefs_error = false; + if ((xslt_sec_prefs = xsltNewSecurityPrefs()) == NULL) + xslt_sec_prefs_error = true; + + if (xsltSetSecurityPrefs(xslt_sec_prefs, XSLT_SECPREF_READ_FILE, + xsltSecurityForbid) != 0) + xslt_sec_prefs_error = true; + if (xsltSetSecurityPrefs(xslt_sec_prefs, XSLT_SECPREF_WRITE_FILE, + xsltSecurityForbid) != 0) + xslt_sec_prefs_error = true; + if (xsltSetSecurityPrefs(xslt_sec_prefs, XSLT_SECPREF_CREATE_DIRECTORY, + xsltSecurityForbid) != 0) + xslt_sec_prefs_error = true; + if (xsltSetSecurityPrefs(xslt_sec_prefs, XSLT_SECPREF_READ_NETWORK, + xsltSecurityForbid) != 0) + xslt_sec_prefs_error = true; + if (xsltSetSecurityPrefs(xslt_sec_prefs, XSLT_SECPREF_WRITE_NETWORK, + xsltSecurityForbid) != 0) + xslt_sec_prefs_error = true; + if (xsltSetCtxtSecurityPrefs(xslt_sec_prefs, xslt_ctxt) != 0) + xslt_sec_prefs_error = true; + + if (xslt_sec_prefs_error) + ereport(ERROR, + (errmsg("could not set libxslt security preferences"))); + + restree = xsltApplyStylesheetUser(stylesheet, doctree, params, + NULL, NULL, xslt_ctxt); + + if (restree == NULL) + xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, + "failed to apply stylesheet"); + + resstat = xsltSaveResultToString(&resstr, &reslen, restree, stylesheet); + } + PG_CATCH(); + { + if (restree != NULL) + xmlFreeDoc(restree); + if (xslt_ctxt != NULL) + xsltFreeTransformContext(xslt_ctxt); + if (xslt_sec_prefs != NULL) + xsltFreeSecurityPrefs(xslt_sec_prefs); + if (stylesheet != NULL) + xsltFreeStylesheet(stylesheet); + if (doctree != NULL) + xmlFreeDoc(doctree); + xsltCleanupGlobals(); + + pg_xml_done(xmlerrcxt, true); + + PG_RE_THROW(); + } + PG_END_TRY(); + + xmlFreeDoc(restree); + xsltFreeTransformContext(xslt_ctxt); + xsltFreeSecurityPrefs(xslt_sec_prefs); + xsltFreeStylesheet(stylesheet); + xmlFreeDoc(doctree); + xsltCleanupGlobals(); + + pg_xml_done(xmlerrcxt, false); + + /* XXX this is pretty dubious, really ought to throw error instead */ + if (resstat < 0) + PG_RETURN_NULL(); + + result = cstring_to_text_with_len((char *) resstr, reslen); + + if (resstr) + xmlFree(resstr); + + PG_RETURN_TEXT_P(result); +#else /* !USE_LIBXSLT */ + + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("xslt_process() is not available without libxslt"))); + PG_RETURN_NULL(); +#endif /* USE_LIBXSLT */ +} + +#ifdef USE_LIBXSLT + +static const char ** +parse_params(text *paramstr) +{ + char *pos; + char *pstr; + char *nvsep = "="; + char *itsep = ","; + const char **params; + int max_params; + int nparams; + + pstr = text_to_cstring(paramstr); + + max_params = 20; /* must be even! */ + params = (const char **) palloc((max_params + 1) * sizeof(char *)); + nparams = 0; + + pos = pstr; + + while (*pos != '\0') + { + if (nparams >= max_params) + { + max_params *= 2; + params = (const char **) repalloc(params, + (max_params + 1) * sizeof(char *)); + } + params[nparams++] = pos; + pos = strstr(pos, nvsep); + if (pos != NULL) + { + *pos = '\0'; + pos++; + } + else + { + /* No equal sign, so ignore this "parameter" */ + nparams--; + break; + } + + /* since max_params is even, we still have nparams < max_params */ + params[nparams++] = pos; + pos = strstr(pos, itsep); + if (pos != NULL) + { + *pos = '\0'; + pos++; + } + else + break; + } + + /* Add the terminator marker; we left room for it in the palloc's */ + params[nparams] = NULL; + + return params; +} + +#endif /* USE_LIBXSLT */ -- cgit v1.2.3